diff --git a/src/Attributes.php b/src/Attributes.php index a67023c3..f466d3b0 100644 --- a/src/Attributes.php +++ b/src/Attributes.php @@ -4,8 +4,11 @@ use ArrayAccess; use ArrayIterator; +use Closure; use InvalidArgumentException; use IteratorAggregate; +use ReflectionException; +use ReflectionFunction; use Traversable; use function ipl\Stdlib\get_php_type; @@ -509,12 +512,77 @@ public function getIterator(): Traversable return new ArrayIterator($this->attributes); } + /** + * Rebind all {@see self::$callbacks} and {@see self::$setterCallbacks} that point to `$oldThis` to `$newThis`. + * + * @param object $oldThis + * @param object $newThis + * + * @return void + * @throws ReflectionException + */ + public function rebind(object $oldThis, object $newThis): void + { + $this->rebindCallback($this->callbacks, true, $oldThis, $newThis); + $this->rebindCallback($this->setterCallbacks, false, $oldThis, $newThis); + } + + /** + * Helper Method for {@see self::rebind()} + * + * Loops over all `$callbacks`, binds them to `$newThis` where `$oldThis` matches and + * overwrites the existing callback either in {@see self::$callbacks} or {@see self::$setterCallbacks}, depending + * on `$isGetter`. + * + * @param callable[] $callbacks + * @param bool $isGetter + * @param object $oldThis + * @param object $newThis + * + * @return void + * @throws ReflectionException + */ + private function rebindCallback(array $callbacks, bool $isGetter, object $oldThis, object $newThis): void + { + foreach ($callbacks as $key => $callback) { + if (! $callback instanceof Closure) { + if (is_array($callback) && ! is_string($callback[0])) { + if (spl_object_id($callback[0]) == spl_object_id($oldThis)) { + $this->registerAttributeCallback( + $key, + $isGetter ? Closure::fromCallable($callback)->bindTo($newThis) : null, + ! $isGetter ? Closure::fromCallable($callback)->bindTo($newThis) : null + ); + } + } + + continue; + } + + $closureThis = (new ReflectionFunction($callback)) + ->getClosureThis(); + + // Closure is most likely static + if ($closureThis === null) { + continue; + } + + $closureThisHash = spl_object_id($closureThis); + + if ($closureThisHash == spl_object_id($oldThis)) { + $this->registerAttributeCallback( + $key, + $isGetter ? Closure::fromCallable($callback)->bindTo($newThis) : null, + ! $isGetter ? Closure::fromCallable($callback)->bindTo($newThis) : null + ); + } + } + } + public function __clone() { foreach ($this->attributes as &$attribute) { $attribute = clone $attribute; } - - // TODO(lippserd): Rebind callbacks. However that should work then :). } }