Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .git-blame-ignore-revs
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ e224e927dabcd3046455985118ed424c43aba054
e85fae4f7290329ac9cfae159e19c1bb55062e4b
10daa978ac4beebdbc8e9dcc6fa07657001fd91e
7bb064715cf4fb35fd1c9add945addb3959405c4
5bd6cc77c40f19059dbdda4704d2f380099b29e3
5bd6cc77c40f19059dbdda4704d2f380099b29e3eb7e6e7d13e3fe753ae5219fb67452e15cd90c65
7e6e995f8e32353104b95c1b4394faf1bf030dbb
5 changes: 3 additions & 2 deletions .idea/php.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions .idea/stdlib.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

142 changes: 142 additions & 0 deletions src/Objects/Support/Traits/Macroable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<?php

/*
* Copyright (c) 2025. Encore Digital Group.
* All Rights Reserved.
*/

namespace EncoreDigitalGroup\StdLib\Objects\Support\Traits;

use BadMethodCallException;
use Closure;
use Illuminate\Database\Eloquent\Model;
use ReflectionClass;
use ReflectionMethod;

/** @experimental */
trait Macroable
{
protected static array $macros = [];

public static function macro(string $name, object|callable $macro): void
{
static::$macros[$name] = $macro;
}

public static function mixin(object $mixin, bool $replace = true): void
{
$methods = (new ReflectionClass($mixin))->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));
}
}