diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index d931db0..9271f1b 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -44,4 +44,5 @@ e224e927dabcd3046455985118ed424c43aba054 e85fae4f7290329ac9cfae159e19c1bb55062e4b 10daa978ac4beebdbc8e9dcc6fa07657001fd91e 7bb064715cf4fb35fd1c9add945addb3959405c4 -5bd6cc77c40f19059dbdda4704d2f380099b29e3 \ No newline at end of file +5bd6cc77c40f19059dbdda4704d2f380099b29e3eb7e6e7d13e3fe753ae5219fb67452e15cd90c65 +7e6e995f8e32353104b95c1b4394faf1bf030dbb diff --git a/.idea/php.xml b/.idea/php.xml index 2aa006e..4da245d 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -44,7 +44,6 @@ - @@ -58,7 +57,6 @@ - @@ -115,6 +113,9 @@ + + + diff --git a/.idea/stdlib.iml b/.idea/stdlib.iml index b687b1e..101fa13 100644 --- a/.idea/stdlib.iml +++ b/.idea/stdlib.iml @@ -58,8 +58,6 @@ - - @@ -92,6 +90,9 @@ + + + diff --git a/src/Objects/Support/Traits/Macroable.php b/src/Objects/Support/Traits/Macroable.php new file mode 100644 index 0000000..fc958c8 --- /dev/null +++ b/src/Objects/Support/Traits/Macroable.php @@ -0,0 +1,142 @@ +getMethods( + ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED + ); + + foreach ($methods as $method) { + if ($replace || !static::hasMacro($method->name)) { + static::macro($method->name, $method->invoke($mixin)); + } + } + } + + public static function hasMacro(string $name): bool + { + return isset(static::$macros[$name]); + } + + public static function flushMacros(): void + { + static::$macros = []; + } + + protected static function canDeferToLaravel(): bool + { + return class_exists(Model::class) && + is_subclass_of(static::class, Model::class); + } + + /** + * Defer static method call to the parent class's __callStatic for Eloquent models. + * + * @return mixed + */ + protected static function deferToLaravel(string $method, array $parameters, bool $static = false) + { + if (static::canDeferToLaravel()) { + // Defer to the parent class's __callStatic (e.g., Model::__callStatic) + $parentClass = get_parent_class(static::class); + if ($parentClass && (new ReflectionClass($parentClass))->hasMethod("__callStatic")) { + return parent::__callStatic($method, $parameters); + } + } + + throw new BadMethodCallException(sprintf("Method %s::%s does not exist.", static::class, $method)); + } + + /** + * Defer instance method call to the parent class's __call for Eloquent models. + */ + protected function deferToEloquentInstance(string $method, array $parameters): mixed + { + if (static::canDeferToLaravel()) { + // Defer to the parent class's __call (e.g., Model::__call) + $parentClass = get_parent_class($this); + if ($parentClass && (new ReflectionClass($parentClass))->hasMethod("__call")) { + return parent::__call($method, $parameters); + } + } + + throw new BadMethodCallException(sprintf("Method %s::%s does not exist.", static::class, $method)); + } + + public static function __callStatic(string $method, array $parameters): mixed + { + // Check if a macro exists for the method + if (static::hasMacro($method)) { + $macro = static::$macros[$method]; + + if ($macro instanceof Closure) { + $macro = $macro->bindTo(null, static::class); + } + + return $macro(...$parameters); + } + + // Defer to the parent class's __callStatic if it exists + $parentClass = get_parent_class(static::class); + if ($parentClass && (new ReflectionClass($parentClass))->hasMethod("__callStatic")) { + return parent::__callStatic($method, $parameters); + } + + // If the class is an Eloquent model, defer to Laravel's handling + if (static::canDeferToLaravel()) { + return static::deferToLaravel($method, $parameters, true); + } + + throw new BadMethodCallException(sprintf("Method %s::%s does not exist.", static::class, $method)); + } + + public function __call(string $method, array $parameters): mixed + { + // Check if a macro exists for the method + if (static::hasMacro($method)) { + $macro = static::$macros[$method]; + + if ($macro instanceof Closure) { + $macro = $macro->bindTo($this, static::class); + } + + return $macro(...$parameters); + } + + // Defer to the parent class's __call if it exists + $parentClass = get_parent_class($this); + if ($parentClass && (new ReflectionClass($parentClass))->hasMethod("__call")) { + return parent::__call($method, $parameters); + } + + // If the class is an Eloquent model, defer to Laravel's handling + if (static::canDeferToLaravel()) { + return $this->deferToEloquentInstance($method, $parameters); + } + + throw new BadMethodCallException(sprintf("Method %s::%s does not exist.", static::class, $method)); + } +} \ No newline at end of file