Skip to content

Commit e6747dc

Browse files
onairmarcEncoreBot
andauthored
Create Experimental Macroable Trait (#111)
Co-authored-by: EncoreBot <[email protected]>
1 parent 0a71af5 commit e6747dc

File tree

4 files changed

+150
-5
lines changed

4 files changed

+150
-5
lines changed

.git-blame-ignore-revs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,5 @@ e224e927dabcd3046455985118ed424c43aba054
4444
e85fae4f7290329ac9cfae159e19c1bb55062e4b
4545
10daa978ac4beebdbc8e9dcc6fa07657001fd91e
4646
7bb064715cf4fb35fd1c9add945addb3959405c4
47-
5bd6cc77c40f19059dbdda4704d2f380099b29e3
47+
5bd6cc77c40f19059dbdda4704d2f380099b29e3eb7e6e7d13e3fe753ae5219fb67452e15cd90c65
48+
7e6e995f8e32353104b95c1b4394faf1bf030dbb

.idea/php.xml

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/stdlib.iml

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?php
2+
3+
/*
4+
* Copyright (c) 2025. Encore Digital Group.
5+
* All Rights Reserved.
6+
*/
7+
8+
namespace EncoreDigitalGroup\StdLib\Objects\Support\Traits;
9+
10+
use BadMethodCallException;
11+
use Closure;
12+
use Illuminate\Database\Eloquent\Model;
13+
use ReflectionClass;
14+
use ReflectionMethod;
15+
16+
/** @experimental */
17+
trait Macroable
18+
{
19+
protected static array $macros = [];
20+
21+
public static function macro(string $name, object|callable $macro): void
22+
{
23+
static::$macros[$name] = $macro;
24+
}
25+
26+
public static function mixin(object $mixin, bool $replace = true): void
27+
{
28+
$methods = (new ReflectionClass($mixin))->getMethods(
29+
ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
30+
);
31+
32+
foreach ($methods as $method) {
33+
if ($replace || !static::hasMacro($method->name)) {
34+
static::macro($method->name, $method->invoke($mixin));
35+
}
36+
}
37+
}
38+
39+
public static function hasMacro(string $name): bool
40+
{
41+
return isset(static::$macros[$name]);
42+
}
43+
44+
public static function flushMacros(): void
45+
{
46+
static::$macros = [];
47+
}
48+
49+
protected static function canDeferToLaravel(): bool
50+
{
51+
return class_exists(Model::class) &&
52+
is_subclass_of(static::class, Model::class);
53+
}
54+
55+
/**
56+
* Defer static method call to the parent class's __callStatic for Eloquent models.
57+
*
58+
* @return mixed
59+
*/
60+
protected static function deferToLaravel(string $method, array $parameters, bool $static = false)
61+
{
62+
if (static::canDeferToLaravel()) {
63+
// Defer to the parent class's __callStatic (e.g., Model::__callStatic)
64+
$parentClass = get_parent_class(static::class);
65+
if ($parentClass && (new ReflectionClass($parentClass))->hasMethod("__callStatic")) {
66+
return parent::__callStatic($method, $parameters);
67+
}
68+
}
69+
70+
throw new BadMethodCallException(sprintf("Method %s::%s does not exist.", static::class, $method));
71+
}
72+
73+
/**
74+
* Defer instance method call to the parent class's __call for Eloquent models.
75+
*/
76+
protected function deferToEloquentInstance(string $method, array $parameters): mixed
77+
{
78+
if (static::canDeferToLaravel()) {
79+
// Defer to the parent class's __call (e.g., Model::__call)
80+
$parentClass = get_parent_class($this);
81+
if ($parentClass && (new ReflectionClass($parentClass))->hasMethod("__call")) {
82+
return parent::__call($method, $parameters);
83+
}
84+
}
85+
86+
throw new BadMethodCallException(sprintf("Method %s::%s does not exist.", static::class, $method));
87+
}
88+
89+
public static function __callStatic(string $method, array $parameters): mixed
90+
{
91+
// Check if a macro exists for the method
92+
if (static::hasMacro($method)) {
93+
$macro = static::$macros[$method];
94+
95+
if ($macro instanceof Closure) {
96+
$macro = $macro->bindTo(null, static::class);
97+
}
98+
99+
return $macro(...$parameters);
100+
}
101+
102+
// Defer to the parent class's __callStatic if it exists
103+
$parentClass = get_parent_class(static::class);
104+
if ($parentClass && (new ReflectionClass($parentClass))->hasMethod("__callStatic")) {
105+
return parent::__callStatic($method, $parameters);
106+
}
107+
108+
// If the class is an Eloquent model, defer to Laravel's handling
109+
if (static::canDeferToLaravel()) {
110+
return static::deferToLaravel($method, $parameters, true);
111+
}
112+
113+
throw new BadMethodCallException(sprintf("Method %s::%s does not exist.", static::class, $method));
114+
}
115+
116+
public function __call(string $method, array $parameters): mixed
117+
{
118+
// Check if a macro exists for the method
119+
if (static::hasMacro($method)) {
120+
$macro = static::$macros[$method];
121+
122+
if ($macro instanceof Closure) {
123+
$macro = $macro->bindTo($this, static::class);
124+
}
125+
126+
return $macro(...$parameters);
127+
}
128+
129+
// Defer to the parent class's __call if it exists
130+
$parentClass = get_parent_class($this);
131+
if ($parentClass && (new ReflectionClass($parentClass))->hasMethod("__call")) {
132+
return parent::__call($method, $parameters);
133+
}
134+
135+
// If the class is an Eloquent model, defer to Laravel's handling
136+
if (static::canDeferToLaravel()) {
137+
return $this->deferToEloquentInstance($method, $parameters);
138+
}
139+
140+
throw new BadMethodCallException(sprintf("Method %s::%s does not exist.", static::class, $method));
141+
}
142+
}

0 commit comments

Comments
 (0)