diff --git a/README.md b/README.md index 7a4437e..de34f5c 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ composer require gzhegow/di; use Gzhegow\Di\Demo\MyClassTwo; use Gzhegow\Di\Demo\MyClassFour; +use Gzhegow\Di\Demo\MyClassFive; use Gzhegow\Di\Demo\MyClassThree; use Gzhegow\Di\Demo\MyClassOneOne; use Gzhegow\Di\Reflector\Reflector; @@ -32,11 +33,11 @@ use function Gzhegow\Di\_di_bind; use function Gzhegow\Di\_di_make; use function Gzhegow\Di\_di_call; use function Gzhegow\Di\_di_extend; +use function Gzhegow\Di\_php_throw; use function Gzhegow\Di\_di_autowire; use function Gzhegow\Di\_di_get_lazy; use function Gzhegow\Di\_di_make_lazy; use function Gzhegow\Di\_di_bind_singleton; -use function Gzhegow\Di\_php_throw; require_once __DIR__ . '/vendor/autoload.php'; @@ -59,11 +60,10 @@ set_error_handler(static function ($severity, $err, $file, $line) { throw new \ErrorException($err, -1, $severity, $file, $line); } }); -set_exception_handler('dd'); -// set_exception_handler(static function ($e) { -// var_dump($e); -// die(); -// }); +set_exception_handler(static function ($e) { + var_dump($e); + die(); +}); // >>> Создаем контейнер @@ -74,6 +74,11 @@ $di = _di(); // $di = $di::getInstance(); // $di::setInstance(new Di()); +// >>> Ставим режим работы контейнера +$di->setSettings([ + 'injectorResolveUseTake' => true, // > будет использовать take(), то есть при незарегистированных зависимостях создаст новые экземпляры и передаст их в конструктор (test/staging) + // 'injectorResolveUseTake' => false, // > будет использовать get(), а значит при незарегистированных зависимостях выбросит исключение (production) +]); // >>> Настраиваем кеш для рефлексии функций и конструкторов $cacheDir = __DIR__ . '/var/cache'; @@ -108,15 +113,12 @@ $di->setCacheSettings([ // >>> Так можно очистить кеш принудительно (обычно для этого делают консольный скрипт и запускают вручную или кроном, но если использовать symfony/cache можно и просто установить TTL - время устаревания) +print_r('Clearing cache...' . PHP_EOL); $di->clearCache(); +print_r('Cleared.' . PHP_EOL); +print_r(PHP_EOL); -// >>> Можно зарегистрировать класс в контейнере (смысл только в том, что метод get() не будет выбрасывать исключение) -// _di_bind(MyClassOneOne::class); - -// >>> Можно привязать на интерфейс (а значит объект сможет пройти проверки зависимостей на входе конструктора) -// _di_bind(MyClassOneInterface::class, MyClassOneOne::class); - // >>> А тут при создании класса будет использоваться фабричный метод $fnNewMyClassOne = static function () { $object = _di_make(MyClassOneOne::class, [ 123 ]); @@ -126,13 +128,20 @@ $fnNewMyClassOne = static function () { // _di_bind(MyClassOneInterface::class, $fnNewMyClassOne); // >>> И его результат будет сохранен как одиночка, то есть при втором вызове get()/ask() вернется тот же экземпляр -_di_bind_singleton(MyClassOneInterface::class, $fnNewMyClassOne); +// >>> А также зарегистрируем алиас на наш интерфейс по имени (это позволяет нам использовать сервис-локатор не создавая интерфейсы под каждую настройку зависимости) + +// >>> Можно зарегистрировать класс в контейнере (смысл только в том, что метод get() не будет выбрасывать исключение) +// _di_bind(MyClassOneOne::class); +// >>> Можно привязать на интерфейс (а значит объект сможет пройти проверки зависимостей на входе конструктора) +// _di_bind(MyClassOneInterface::class, MyClassOneOne::class, $isSingleton = false); +// >>> Можно сразу указать, что созданный экземпляр будет одиночкой +// _di_bind_singleton(MyClassOneInterface::class, MyClassOneOne::class); -// >>> Зарегистрируем алиас на наш интерфейс по имени (если мы задаем конфигурацию в виде строк, а не в виде Class::class, мы избегаем подгрузки классов через autoloader, точнее откладываем её) -_di_bind('one', '\Gzhegow\Di\Demo\MyClassOneInterface'); +_di_bind_singleton(MyClassOneInterface::class, $fnNewMyClassOne); +_di_bind('one', '\Gzhegow\Di\Demo\MyClassOneInterface'); // > Если мы задаем конфигурацию в виде строк, а не в виде Class::class, мы избегаем подгрузки классов через autoloader, точнее откладываем её // >>> Мы знаем, что сервис MyClassTwo долго выполняет __construct(), например, соединяется по сети, и нам нужно отложить его запуск до первого вызова. Регистриуем как обычно, а дальше запросим через _di_get_lazy() -_di_bind(MyClassTwoInterface::class, MyClassTwo::class); +_di_bind_singleton(MyClassTwoInterface::class, MyClassTwo::class); _di_bind('two', '\Gzhegow\Di\Demo\MyClassTwoInterface'); // >>> Зарегистрируем класс как синглтон (первый вызов создаст объект, второй - вернет созданный) @@ -141,72 +150,109 @@ _di_bind('three', '\Gzhegow\Di\Demo\MyClassThree'); // >>> MyClassThree требует сервисов One и Two, а чтобы не фиксировать сигнатуру конструктора, мы добавим их с помощью Интерфейсов и Трейтов _di_extend(MyClassOneAwareInterface::class, static function (MyClassOneAwareInterface $aware) { - $one = _di_get(MyClassOneInterface::class); - - $aware->setOne($one); - - return $aware; + $aware->setOne($one = _di_get(MyClassOneInterface::class)); }); _di_extend(MyClassTwoAwareInterface::class, static function (MyClassTwoAwareInterface $aware) { - $two = _di_get_lazy('two'); - - $aware->setTwo($two); - - return $aware; + $aware->setTwo($two = _di_get_lazy('two')); }); // >>> Пример. "Дай сервис c заполненными зависимостями" +print_r('Case1:' . PHP_EOL); $three = _di_get(MyClassThree::class); var_dump(get_class($three)); // string(28) "Gzhegow\Di\Demo\MyClassThree" _assert_true(get_class($three) === 'Gzhegow\Di\Demo\MyClassThree'); - - +// // >>> Если класс помечен как сиглтон, запросы его вернут один и тот же экземпляр $three1 = _di_get(MyClassThree::class); $three2 = _di_get(MyClassThree::class); $threeByAlias = _di_get('three'); _assert_true($three1 === $three2); _assert_true($three1 === $threeByAlias); - +// // >>> Еще можно использовать синтаксис указывая выходной тип, чтобы PHPStorm корректно работал с подсказками ("генерики") // $two = _di_get(MyClassTwoInterface::class, MyClassTwo::class); // > без параметров, бросит исключение, если не зарегистрировано в контейнере // $two = _di_ask(MyClassTwoInterface::class, MyClassTwo::class); // > get() если зарегистрировано, NULL если не зарегистрировано // $two = _di_make(MyClassTwoInterface::class, [], MyClassTwo::class); // > всегда новый экземпляр с параметрами // $two = _di_take(MyClassTwoInterface::class, [], MyClassTwo::class); // > get() если зарегистрировано, make() если не зарегистрировано +print_r(PHP_EOL); // >>> Ранее мы говорили, что этот сервис слишком долго выполняет конструктор. Запросим его как ленивый. При этом подстановка в аргументы конструктора конечно будет невозможна, но как сервис-локатор - удобная вещь! // >>> В PHP к сожалению нет возможности создать анонимный класс, который расширяет ("extend") имя класса, который лежит в переменной. Поэтому, к сожалению, только такие LazyService... +print_r('Case2:' . PHP_EOL); +// // $two = _di_get_lazy(MyClassTwoInterface::class, MyClassTwo::class); // $two = _di_make_lazy(MyClassTwoInterface::class, [], MyClassTwo::class); -$two = _di_make_lazy(MyClassTwoInterface::class, [ 'hello' => 'User' ], MyClassTwo::class); -var_dump(get_class($two)); // string(27) "Gzhegow\Di\Lazy\LazyService" +// +$two = _di_get_lazy(MyClassTwoInterface::class, MyClassTwo::class, [ 'hello' => 'User' ]); +$two2 = _di_get_lazy(MyClassTwoInterface::class, MyClassTwo::class); +$two3 = _di_make_lazy(MyClassTwoInterface::class, [ 'hello' => 'User2' ], MyClassTwo::class); +var_dump(get_class($two)); // string(27) "Gzhegow\Di\Lazy\LazyService" +var_dump(get_class($two2)); // string(27) "Gzhegow\Di\Lazy\LazyService" _assert_true(get_class($two) === 'Gzhegow\Di\Lazy\LazyService'); - +_assert_true(get_class($two2) === 'Gzhegow\Di\Lazy\LazyService'); +// // >>> При вызове первого метода объект внутри LazyService будет создан с аргументами, что указали в __configure() или без них (только зависимости), если не указали -echo 'MyClassB загружается (3 секунды)...' . PHP_EOL; // MyClassB загружается (3 секунды)... -$two->do(); // Hello, [ User ] ! - - -// >>> Еще пример. "Дозаполним аргументы уже существующего объекта, который мы не регистрировали" - вызовет функцию на уже существующем объекте +echo 'MyClassB загружается (3 секунды)...' . PHP_EOL; // MyClassB загружается (3 секунды)... +$two->do(); // Hello, [ User ] ! +$two2->do(); // Hello, [ User2 ] ! +_assert_true($two !== $two2); +_assert_true($two->instance === $two2->instance); +echo 'MyClassB загружается (3 секунды)...' . PHP_EOL; // MyClassB загружается (3 секунды)... +$two3->do(); // Hello, [ User2 ] ! +_assert_true($two !== $two3); +_assert_true($two->instance !== $two3->instance); +print_r(PHP_EOL); + + +// >>> Еще пример. "Дозаполним аргументы уже существующего объекта, который мы не регистрировали" +print_r('Case3:' . PHP_EOL); $four = new MyClassFour(); +// +// > вызовет функцию на уже существующем объекте // _di_autowire($four, $customArgs = [], $customMethod = '__myCustomAutowire'); // > поддерживает несколько дополнительных аргументов _di_autowire($four); -var_dump(get_class($four)); // string(27) "Gzhegow\Di\Demo\MyClassFour" -var_dump(get_class($four->one)); // string(29) "Gzhegow\Di\Demo\MyClassOneOne" +// +var_dump(get_class($four)); // string(27) "Gzhegow\Di\Demo\MyClassFour" +var_dump(get_class($four->one)); // string(29) "Gzhegow\Di\Demo\MyClassOneOne" _assert_true(get_class($four) === 'Gzhegow\Di\Demo\MyClassFour'); _assert_true(get_class($four->one) === 'Gzhegow\Di\Demo\MyClassOneOne'); +$four2 = new MyClassFour(); +_di_autowire($four2); +_assert_true($four->one === $four2->one); // > зависимость по интерфейсу, зарегистрированная как одиночка, будет равна в двух разных экземплярах +print_r(PHP_EOL); + + +// >>> Еще пример. "Дозаполним аргументы уже существующего объекта, который мы не регистрировали, и который имеет зависимости, которые мы тоже не регистрировали" +print_r('Case4:' . PHP_EOL); +$five = new MyClassFive(); +// +// _di_autowire($four, $customArgs = [], $customMethod = '__myCustomAutowire'); // > поддерживает несколько дополнительных аргументов +_di_autowire($five); +// +var_dump(get_class($five)); // string(27) "Gzhegow\Di\Demo\MyClassFive" +var_dump(get_class($five->four)); // string(27) "Gzhegow\Di\Demo\MyClassFour" +_assert_true(get_class($five) === 'Gzhegow\Di\Demo\MyClassFive'); +_assert_true(get_class($five->four) === 'Gzhegow\Di\Demo\MyClassFour'); +$five2 = new MyClassFive(); +_di_autowire($five2); +_assert_true($five->four !== $five2->four); // > зависимость по классу, ранее не зарегистрированная, получит два разных экземпляра +print_r(PHP_EOL); // >>> Еще пример. "Вызовем функцию, подбросив в неё зависимости" +print_r('Case5:' . PHP_EOL); $result = _di_call(static function (MyClassThree $three) { return get_class($three); }); var_dump($result); // string(28) "Gzhegow\Di\Demo\MyClassThree" _assert_true($result === 'Gzhegow\Di\Demo\MyClassThree'); +print_r(PHP_EOL); // >>> Теперь сохраним кеш сделанной за скрипт рефлексии для следующего раза +print_r('Saving cache...' . PHP_EOL); $di->flushCache(); +print_r('Saved.'); ``` \ No newline at end of file diff --git a/composer.json b/composer.json index e72da64..efa1409 100644 --- a/composer.json +++ b/composer.json @@ -35,4 +35,4 @@ "sort-packages": false } -} \ No newline at end of file +} diff --git a/src/Di.php b/src/Di.php index 52c2d0d..5fff96f 100644 --- a/src/Di.php +++ b/src/Di.php @@ -347,6 +347,18 @@ public function autowire(object $instance, array $methodArgs = null, string $met } + /** + * @param callable $fn + * + * @return mixed + */ + public function callUserFunc($fn, ...$args) // : mixed + { + $result = $this->injector->autowireUserFunc($fn, ...$args); + + return $result; + } + /** * @param callable $fn * diff --git a/src/DiInterface.php b/src/DiInterface.php index d52e538..186d784 100644 --- a/src/DiInterface.php +++ b/src/DiInterface.php @@ -143,6 +143,13 @@ public function makeLazy($id, array $parameters = null, string $contractT = null public function autowire(object $instance, array $methodArgs = null, string $methodName = null); + /** + * @param callable $fn + * + * @return mixed + */ + public function callUserFunc($fn, ...$args); + /** * @param callable $fn * diff --git a/src/Injector/Injector.php b/src/Injector/Injector.php index 7c54405..3982608 100644 --- a/src/Injector/Injector.php +++ b/src/Injector/Injector.php @@ -33,7 +33,7 @@ class Injector implements InjectorInterface /** * @var array */ - protected $bindList = []; + protected $bindToTypeList = []; /** * @var array @@ -61,6 +61,10 @@ class Injector implements InjectorInterface */ protected $extendList = []; + /** + * @var array + */ + protected $singletonList = []; /** * @var array */ @@ -110,7 +114,7 @@ public function merge($di) // : static ); } - foreach ( $di->bindList as $_bindId => $bindType ) { + foreach ( $di->bindToTypeList as $_bindId => $bindType ) { $bindId = Id::from($_bindId); $bindProperty = "{$bindType}List"; $bindObject = $di->{$bindProperty}[ $_bindId ]; @@ -144,7 +148,7 @@ public function has($id, Id &$result = null) : bool $_id = $id->getValue(); - if (isset($this->bindList[ $_id ])) { + if (isset($this->bindToTypeList[ $_id ])) { $result = $id; return true; @@ -173,7 +177,7 @@ public function bindItemAlias(Id $id, Id $aliasId, bool $isSingleton = false) // ); } - $this->bindList[ $_id ] = static::BIND_TYPE_ALIAS; + $this->bindToTypeList[ $_id ] = static::BIND_TYPE_ALIAS; $this->aliasList[ $_id ] = $_aliasId; $_id = $id->getValue(); @@ -210,7 +214,7 @@ public function bindItemClass(Id $id, Id $classId, bool $isSingleton = false) // ); } - $this->bindList[ $_id ] = static::BIND_TYPE_CLASS; + $this->bindToTypeList[ $_id ] = static::BIND_TYPE_CLASS; $this->classList[ $_id ] = $_classId; $_id = $id->getValue(); @@ -232,7 +236,7 @@ public function bindItemFactory(Id $id, callable $fnFactory, bool $isSingleton = $_id = $id->getValue(); - $this->bindList[ $_id ] = static::BIND_TYPE_FACTORY; + $this->bindToTypeList[ $_id ] = static::BIND_TYPE_FACTORY; $this->factoryList[ $_id ] = $fnFactory; $_id = $id->getValue(); @@ -254,7 +258,7 @@ public function bindItemInstance(Id $id, object $instance, bool $isSingleton = f $_id = $id->getValue(); - $this->bindList[ $_id ] = static::BIND_TYPE_INSTANCE; + $this->bindToTypeList[ $_id ] = static::BIND_TYPE_INSTANCE; $this->instanceList[ $_id ] = $instance; $_id = $id->getValue(); @@ -340,7 +344,7 @@ public function extendItem(Id $id, callable $fnExtend) // : static /** * @template-covariant T * - * @param class-string|null $contractT + * @param class-string $contractT * * @return T|null */ @@ -368,7 +372,7 @@ public function askItem(Id $id, string $contractT = '', bool $forceInstanceOf = /** * @template-covariant T * - * @param class-string|null $contractT + * @param class-string $contractT * * @return T * @@ -384,34 +388,64 @@ public function getItem(Id $id, string $contractT = '', bool $forceInstanceOf = $_id = $id->getValue(); - if (isset($this->instanceList[ $_id ])) { + if (isset($this->singletonList[ $_id ])) { + $instance = $this->singletonList[ $_id ]; + + } elseif (isset($this->instanceList[ $_id ])) { $instance = $this->instanceList[ $_id ]; + if ($forceInstanceOf && ! is_a($instance, $contractT)) { + throw new RuntimeException( + 'Returned object should be instance of: ' + . $contractT + . ' / ' . _php_dump($instance) + ); + } + + if (isset($this->isSingletonIndex[ $_id ])) { + $this->singletonList[ $_id ] = $instance; + } + + return $instance; + } else { - [ $_resolvedId ] = $this->resolveBoundId($id); + $resolvedPath = $this->resolveItemPath($id); - if (isset($this->instanceList[ $_resolvedId ])) { - $instance = $this->instanceList[ $_resolvedId ]; + $instance = null; + foreach ( $resolvedPath as $_resolvedId => $resolvedType ) { + $isSingleton = false; + $isInstance = false; - } else { - $resolvedId = Id::from($_resolvedId); + (false) + || ($isSingleton = isset($this->singletonList[ $_resolvedId ])) + || ($isInstance = isset($this->instanceList[ $_resolvedId ])); - $instance = $this->makeItem($resolvedId, $parametersWhenNew); + if ($isSingleton || $isInstance) { + $instance = null + ?? $this->singletonList[ $_resolvedId ] + ?? $this->instanceList[ $_resolvedId ]; + + break; + } } - } - if (isset($this->isSingletonIndex[ $_id ]) - && ! isset($this->instanceList[ $_id ]) - ) { - $this->instanceList[ $_id ] = $instance; - } + if (null === $instance) { + $instance = $this->resolveItem($resolvedPath, $id, $parametersWhenNew); + } - if ($forceInstanceOf && ! is_a($instance, $contractT)) { - throw new RuntimeException( - 'Returned object should be instance of: ' - . $contractT - . ' / ' . _php_dump($instance) - ); + if ($forceInstanceOf && ! is_a($instance, $contractT)) { + throw new RuntimeException( + 'Returned object should be instance of: ' + . $contractT + . ' / ' . _php_dump($instance) + ); + } + + foreach ( $resolvedPath as $_resolvedId => $resolvedType ) { + if (isset($this->isSingletonIndex[ $_resolvedId ])) { + $this->singletonList[ $_resolvedId ] = $instance; + } + } } return $instance; @@ -420,34 +454,15 @@ public function getItem(Id $id, string $contractT = '', bool $forceInstanceOf = /** * @template-covariant T * - * @param class-string|null $contractT + * @param class-string $contractT * * @return T */ public function makeItem(Id $id, array $parameters = [], string $contractT = '', bool $forceInstanceOf = false) : object { - $id = Id::from($id); - - $_id = $id->getValue(); - - [ $bound, , $boundType ] = $resolved = $this->resolveItem($id); + $resolvedPath = $this->resolveItemPath($id); - if (static::BIND_TYPE_INSTANCE === $boundType) { - $instance = clone $bound; - - } elseif (static::BIND_TYPE_CLASS === $boundType) { - $instance = $this->autowireConstructorArray($bound, $parameters); - - } elseif (static::BIND_TYPE_FACTORY === $boundType) { - $instance = $this->autowireUserFuncArray($bound, $parameters); - - } else { - throw new RuntimeException( - 'Unknown `boundType` while making: ' - . $boundType - . ' / ' . $_id - ); - } + $instance = $this->resolveItem($resolvedPath, $id, $parameters); if ($forceInstanceOf && ! is_a($instance, $contractT)) { throw new RuntimeException( @@ -457,35 +472,9 @@ public function makeItem(Id $id, array $parameters = [], string $contractT = '', ); } - if (isset($this->isSingletonIndex[ $_id ]) - && ! isset($this->instanceList[ $_id ]) - ) { - $this->instanceList[ $_id ] = $instance; - } - - $classmap = [ $_id => $_id ]; - - if ($id->isContract()) { - $classmap += class_parents($_id); - $classmap += class_implements($_id); - } - - $classmap += class_parents($instance); - $classmap += class_implements($instance); - - $intersect = array_intersect_key($this->extendList, $classmap); - - if ($intersect) { - $callablesOrdered = []; - - foreach ( $intersect as $extendClass => $callables ) { - $callablesOrdered += $callables; - } - - ksort($callablesOrdered); - - foreach ( $callablesOrdered as $callable ) { - $this->autowireUserFuncArray($callable, [ $instance ]); + foreach ( $resolvedPath as $_resolvedId => $resolvedType ) { + if (isset($this->isSingletonIndex[ $_resolvedId ])) { + $this->singletonList[ $_resolvedId ] = $instance; } } @@ -495,7 +484,7 @@ public function makeItem(Id $id, array $parameters = [], string $contractT = '', /** * @template-covariant T * - * @param class-string|null $contractT + * @param class-string $contractT * * @return T */ @@ -536,7 +525,18 @@ public function autowireItem(object $instance, array $methodArgs = [], string $m } - public function autowireUserFuncArray(callable $fn, array $args = []) + public function autowireUserFunc(callable $fn, ...$args) // : mixed + { + $reflectResult = $this->reflector->reflectArgumentsCallable($fn); + + $_args = $this->resolveArguments($reflectResult, $fn, $args); + + $result = call_user_func($fn, ...$_args); + + return $result; + } + + public function autowireUserFuncArray(callable $fn, array $args = []) // : mixed { $reflectResult = $this->reflector->reflectArgumentsCallable($fn); @@ -616,100 +616,104 @@ protected function resolveBind(Id $id, $mixed) : array } - /** - * @return array{ - * 0: mixed, - * 1: string, - * 2: string, - * 3: array - * } - */ - protected function resolveItem(Id $id) : array + protected function resolveItem(array $resolvedPath, Id $id, array $parameters = []) : object { - [ $itemId, $itemType, $itemFullpath ] = $this->resolveItemId($id); + $_id = $id->getValue(); + + $lastResolvedType = end($resolvedPath); + $lastResolvedId = key($resolvedPath); + + if (static::BIND_TYPE_INSTANCE === $lastResolvedType) { + $resolvedInstance = $this->instanceList[ $lastResolvedId ]; + + $instance = clone $resolvedInstance; + + } elseif (static::BIND_TYPE_CLASS === $lastResolvedType) { + $resolvedClass = $this->classList[ $lastResolvedId ] ?? $lastResolvedId; + + $instance = $this->autowireConstructorArray($resolvedClass, $parameters); - $itemProperty = "{$itemType}List"; - $itemPropertyValue = $this->{$itemProperty}[ $itemId ] ?? null; + } elseif (static::BIND_TYPE_FACTORY === $lastResolvedType) { + $resolvedFnFactory = $this->factoryList[ $lastResolvedId ]; - if ($isClass = ($itemType === static::BIND_TYPE_CLASS)) { - $itemPropertyValue = $itemId; + $instance = $this->autowireUserFuncArray($resolvedFnFactory, $parameters); + + } else { + // } elseif (static::BIND_TYPE_ALIAS === $lastResolvedType) { + + throw new RuntimeException( + 'Unknown `boundType` while making: ' + . $lastResolvedType + . ' / ' . $_id + ); } - $result = [ $itemPropertyValue, $itemId, $itemType, $itemFullpath ]; + $extendIdList = []; + $extendContractList = []; - return $result; - } + $id->isContract() + ? ($extendContractList[ $_id ] = true) + : ($extendIdList[ $_id ] = true); - /** - * @return array{ - * 0: mixed, - * 1: string, - * 2: string, - * 3: array - * } - */ - protected function resolveBound(Id $id) : array - { - [ $itemId, $itemType, $itemFullpath ] = $this->resolveBoundId($id); + foreach ( $resolvedPath as $_resolvedId => $resolvedType ) { + $resolvedId = Id::from($_resolvedId); - $itemProperty = "{$itemType}List"; - $itemPropertyValue = $this->{$itemProperty}[ $itemId ] ?? null; + $resolvedId->isContract() + ? ($extendContractList[ $_resolvedId ] = true) + : ($extendIdList[ $_resolvedId ] = true); + } - $result = [ $itemPropertyValue, $itemId, $itemType, $itemFullpath ]; + $extendContractList[ get_class($instance) ] = true; - return $result; - } + $extendList = []; + $extendList += $extendIdList; + $extendList += $extendContractList; + foreach ( $extendContractList as $contract => $bool ) { + $extendList += class_implements($contract); + $extendList += class_parents($contract); + } - /** - * @return array{ - * 0: mixed, - * 1: string, - * 2: string, - * 3: array - * } - */ - protected function resolveClass(Id $id) : array - { - [ $itemId, $itemType, $itemFullpath ] = $this->resolveClassId($id); + $intersect = array_intersect_key($this->extendList, $extendList); - $itemPropertyValue = $itemId; + if ($intersect) { + $callablesOrdered = []; - $result = [ $itemPropertyValue, $itemId, $itemType, $itemFullpath ]; + foreach ( $intersect as $extendClass => $callables ) { + $callablesOrdered += $callables; + } - return $result; - } + ksort($callablesOrdered); + + foreach ( $callablesOrdered as $callable ) { + $this->autowireUserFuncArray($callable, [ $instance ]); + } + } + return $instance; + } /** - * @return array{ - * 0: string, - * 1: string, - * 2: array - * } + * @return array */ - protected function resolveItemId(Id $id) : array + protected function resolveItemPath(Id $id) : array { $_id = $id->getValue(); - $result = isset($this->bindList[ $_id ]) - ? $this->resolveBoundId($id) - : $this->resolveClassId($id); + $result = isset($this->bindToTypeList[ $_id ]) + ? $this->resolveBoundPath($id) + : $this->resolveUnboundPath($id); return $result; } /** - * @return array{ - * 0: string, - * 1: string, - * 2: array - * } + * @return array */ - protected function resolveBoundId(Id $id) : array + protected function resolveBoundPath(Id $id) : array { $_id = $id->getValue(); - if (! isset($this->bindList[ $_id ])) { + if (! isset($this->bindToTypeList[ $_id ])) { throw new RuntimeException( 'Unable to ' . __FUNCTION__ . '. ' . 'Missing `id`: ' . $_id @@ -717,19 +721,15 @@ protected function resolveBoundId(Id $id) : array } $boundId = $_id; - $boundType = $this->bindList[ $boundId ]; - $boundPath = []; - $boundFullpath = [ $boundId => $boundType ]; + $boundType = $this->bindToTypeList[ $_id ]; $queue = []; - $queue[] = [ $boundId, $boundType, $boundPath ]; + $queue[] = [ $boundId, $boundType, [] ]; - while ( $queue ) { - [ $boundId, $boundType, $boundPath ] = array_shift($queue); + $boundFullpath = []; - if (static::BIND_TYPE_ALIAS !== $boundType) { - break; - } + do { + [ $boundId, $boundType, $boundPath ] = array_shift($queue); if (isset($boundPath[ $boundId ])) { throw new RuntimeException( @@ -742,44 +742,41 @@ protected function resolveBoundId(Id $id) : array $boundFullpath = $boundPath; $boundFullpath[ $boundId ] = $boundType; - $boundId = $this->aliasList[ $boundId ] ?? null; - $boundType = $this->bindList[ $boundId ] ?? null; + if (static::BIND_TYPE_ALIAS === $boundType) { + $boundIdChild = $this->aliasList[ $boundId ] ?? null; + $boundTypeChild = $this->bindToTypeList[ $boundIdChild ] ?? null; + $boundPathChild = $boundFullpath; - if (null === $boundType) { - $boundIdObject = Id::from($boundId); + if (null === $boundTypeChild) { + if (class_exists($boundIdChild)) { + $boundTypeChild = static::BIND_TYPE_CLASS; - if ($boundIdObject->isClass()) { - $boundType = static::BIND_TYPE_CLASS; - - } else { - throw new RuntimeException( - 'Unable to ' . __FUNCTION__ . '. ' - . 'Missing `boundId` while making: ' - . '[ ' . implode(' -> ', array_keys($boundFullpath)) . ' ]' - ); + } else { + throw new RuntimeException( + 'Unable to ' . __FUNCTION__ . '. ' + . 'Missing `boundId` while making: ' + . '[ ' . implode(' -> ', array_keys($boundPathChild)) . ' ]' + ); + } } - } - $queue[] = [ $boundId, $boundType, $boundFullpath ]; - } + $queue[] = [ $boundIdChild, $boundTypeChild, $boundPathChild ]; + } + } while ( $queue ); - $result = [ $boundId, $boundType, $boundFullpath ]; + $result = $boundFullpath; return $result; } /** - * @return array{ - * 0: string, - * 1: string, - * 2: array - * } + * @return array */ - protected function resolveClassId(Id $id) : array + protected function resolveUnboundPath(Id $id) : array { $_id = $id->getValue(); - if (isset($this->bindList[ $_id ])) { + if (isset($this->bindToTypeList[ $_id ])) { throw new RuntimeException( 'Unable to ' . __FUNCTION__ . '. ' . 'Bind exists, so this `id` it is not a class: ' . $_id @@ -793,11 +790,10 @@ protected function resolveClassId(Id $id) : array ); } - $itemId = $_id; - $itemType = static::BIND_TYPE_CLASS; - $itemFullpath = [ $itemId => $itemType ]; + $classId = $_id; + $classType = static::BIND_TYPE_CLASS; - $result = [ $itemId, $itemType, $itemFullpath ]; + $result = [ $classId => $classType ]; return $result; } diff --git a/src/Injector/InjectorInterface.php b/src/Injector/InjectorInterface.php index ec80bac..2ada07e 100644 --- a/src/Injector/InjectorInterface.php +++ b/src/Injector/InjectorInterface.php @@ -45,7 +45,7 @@ public function extendItem(Id $id, callable $fnExtend); /** * @template-covariant T * - * @param class-string|null $contractT + * @param class-string $contractT * * @return T|null */ @@ -54,7 +54,7 @@ public function askItem(Id $id, string $contractT = '', bool $forceInstanceOf = /** * @template-covariant T * - * @param class-string|null $contractT + * @param class-string $contractT * * @return T * @@ -65,7 +65,7 @@ public function getItem(Id $id, string $contractT = '', bool $forceInstanceOf = /** * @template-covariant T * - * @param class-string|null $contractT + * @param class-string $contractT * * @return T */ @@ -74,7 +74,7 @@ public function makeItem(Id $id, array $parameters = [], string $contractT = '', /** * @template-covariant T * - * @param class-string|null $contractT + * @param class-string $contractT * * @return T */ @@ -91,8 +91,11 @@ public function takeItem(Id $id, array $parametersWhenNew = [], string $contract public function autowireItem(object $instance, array $methodArgs = [], string $methodName = '') : object; + public function autowireUserFunc(callable $fn, ...$args); + public function autowireUserFuncArray(callable $fn, array $args = []); + /** * @template-covariant T *