From 2dab801d3176e14d4a0bd217186369ef8efb3cba Mon Sep 17 00:00:00 2001 From: Charles Sprayberry Date: Sat, 14 Dec 2024 11:30:00 -0500 Subject: [PATCH] Prioritized Profiles and ContainerFactory improvements (#401) * Prioritized Profiles and ContainerFactory improvements - Allow for prioritized profile services and injects - Share code when possible for common ContainerFactory functionality * Remove readonly class, not allowed in 8.2 * Add new phpunit vendor lib * Remove override attribute not available in 8.2 --- .github/workflows/php.yml | 2 +- README.md | 15 +- known-issues.xml | 313 +++++------------- src/Bootstrap/Bootstrap.php | 1 + src/Cli/Command/ValidateCommand.php | 2 +- .../AbstractContainerFactory.php | 181 ++++++---- .../AliasDefinitionResolver.php | 2 + .../AliasResolution/AliasResolutionReason.php | 1 + .../StandardAliasDefinitionResolver.php | 31 +- .../AurynContainerFactory.php | 190 ++++++----- .../AurynContainerFactoryState.php | 84 ----- .../ContainerFactoryState.php | 7 - src/ContainerFactory/HasMethodInjectState.php | 42 --- .../HasServicePrepareState.php | 27 -- .../IlluminateContainerFactory.php | 262 +++++---------- .../IlluminateContainerFactoryState.php | 142 -------- src/ContainerFactory/ParameterStore.php | 2 +- .../PhpDiContainerFactory.php | 177 +++++----- .../PhpDiContainerFactoryState.php | 150 --------- .../State/ContainerFactoryState.php | 234 +++++++++++++ .../{ => State}/ContainerReference.php | 2 +- .../State/InjectParameterValue.php | 19 ++ .../{ => State}/ServiceCollectorReference.php | 10 +- .../State/ValueFetchedFromParameterStore.php | 25 ++ src/Definition/ClassMethod.php | 17 + src/Definition/ClassMethodParameter.php | 26 ++ src/Definition/DefinitionFactory.php | 161 ++++++--- src/Definition/InjectDefinition.php | 25 +- .../XmlContainerDefinitionSerializer.php | 16 +- src/Definition/ServiceDelegateDefinition.php | 21 +- src/Definition/ServicePrepareDefinition.php | 7 +- ...ReflectionParameterForInjectDefinition.php | 10 + .../MultipleInjectOnSameParameter.php | 17 + src/Exception/UnknownReflectionType.php | 10 + src/Function/types.php | 1 + src/Internal/ServiceFromFunctionalApi.php | 8 +- .../Check/DuplicateServiceDelegate.php | 4 +- .../Check/DuplicateServicePrepare.php | 4 +- .../MultiplePrimaryForAbstractService.php | 4 +- .../Check/NonPublicServiceDelegate.php | 6 +- .../Check/NonPublicServicePrepare.php | 4 +- src/Profiles.php | 21 +- src/Reflection/TypeFactory.php | 56 +++- src/Reflection/TypeUnion.php | 2 +- ...tatedTargetContainerDefinitionAnalyzer.php | 8 +- .../ContainerDefinitionAnalysisOptions.php | 2 +- .../FooService.php | 14 + .../ServiceFactory.php | 17 + .../ServiceInterface.php | 10 + test/Fixture/Fixtures.php | 12 + .../PrioritizedProfile/BarImplementation.php | 10 + .../BazQuxImplementation.php | 10 + .../DefaultImplementation.php | 10 + .../PrioritizedProfile/FooImplementation.php | 10 + .../PrioritizedProfile/FooInterface.php | 10 + test/Fixture/PrioritizedProfileFixture.php | 37 +++ .../PrioritizedProfileInject/Injector.php | 18 + .../PrioritizedProfileInjectFixture.php | 18 + .../Injector.php | 24 ++ ...PrioritizedProfileInjectPrepareFixture.php | 18 + ...mePackagesComposerJsonPathProviderTest.php | 1 + .../ContainerDefinitionAssertionsTrait.php | 2 +- .../ContainerFactoryTestCase.php | 89 +++++ .../StandardAliasDefinitionResolverTest.php | 53 ++- .../Unit/Definition/DefinitionFactoryTest.php | 125 +++---- test/Unit/Helper/HasMockDefinitions.php | 33 +- .../Check/DuplicateServiceDelegateTest.php | 6 +- .../InjectService.php | 17 + .../InjectServicePrepare.php | 19 ++ test/Unit/ProfilesTest.php | 19 ++ test/Unit/Reflection/TypeFactoryTest.php | 11 + .../XmlContainerDefinitionSerializerTest.php | 40 +-- .../AssertExpectedInjectDefinition.php | 8 +- ...HasServiceDelegateDefinitionTestsTrait.php | 6 +- .../HasServicePrepareDefinitionTestsTrait.php | 2 +- test/Unit/ThirdPartyFunctionsTest.php | 22 +- 76 files changed, 1640 insertions(+), 1382 deletions(-) delete mode 100644 src/ContainerFactory/AurynContainerFactoryState.php delete mode 100644 src/ContainerFactory/ContainerFactoryState.php delete mode 100644 src/ContainerFactory/HasMethodInjectState.php delete mode 100644 src/ContainerFactory/HasServicePrepareState.php delete mode 100644 src/ContainerFactory/IlluminateContainerFactoryState.php delete mode 100644 src/ContainerFactory/PhpDiContainerFactoryState.php create mode 100644 src/ContainerFactory/State/ContainerFactoryState.php rename src/ContainerFactory/{ => State}/ContainerReference.php (84%) create mode 100644 src/ContainerFactory/State/InjectParameterValue.php rename src/ContainerFactory/{ => State}/ServiceCollectorReference.php (56%) create mode 100644 src/ContainerFactory/State/ValueFetchedFromParameterStore.php create mode 100644 src/Definition/ClassMethod.php create mode 100644 src/Definition/ClassMethodParameter.php create mode 100644 src/Exception/InvalidReflectionParameterForInjectDefinition.php create mode 100644 src/Exception/MultipleInjectOnSameParameter.php create mode 100644 src/Exception/UnknownReflectionType.php create mode 100644 test/Fixture/DelegatedServiceWithInjectedParameter/FooService.php create mode 100644 test/Fixture/DelegatedServiceWithInjectedParameter/ServiceFactory.php create mode 100644 test/Fixture/DelegatedServiceWithInjectedParameter/ServiceInterface.php create mode 100644 test/Fixture/PrioritizedProfile/BarImplementation.php create mode 100644 test/Fixture/PrioritizedProfile/BazQuxImplementation.php create mode 100644 test/Fixture/PrioritizedProfile/DefaultImplementation.php create mode 100644 test/Fixture/PrioritizedProfile/FooImplementation.php create mode 100644 test/Fixture/PrioritizedProfile/FooInterface.php create mode 100644 test/Fixture/PrioritizedProfileFixture.php create mode 100644 test/Fixture/PrioritizedProfileInject/Injector.php create mode 100644 test/Fixture/PrioritizedProfileInjectFixture.php create mode 100644 test/Fixture/PrioritizedProfileInjectPrepare/Injector.php create mode 100644 test/Fixture/PrioritizedProfileInjectPrepareFixture.php create mode 100644 test/Unit/LogicalErrorApps/MultipleInjectDefinition/InjectService.php create mode 100644 test/Unit/LogicalErrorApps/MultipleInjectPrepare/InjectServicePrepare.php diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 1c64ca3e..1611b8bd 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -64,7 +64,7 @@ jobs: run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" - name: Composer (Annotated Container) run: composer install - - name: Composer (Static Analysis Tools) + - name: Composer (Code Linting) run: cd tools/labrador-cs && composer install - name: Code Linting run: phpcs -q --standard=./tools/labrador-cs/vendor/cspray/labrador-coding-standard/ruleset.xml --exclude=Generic.Files.LineLength --report=checkstyle src test | cs2pr \ No newline at end of file diff --git a/README.md b/README.md index 1830ecd0..55a30764 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A Dependency Injection framework for creating an autowired, feature-rich, [PSR-1 - Use Profiles to easily use different services in different runtimes - Create type-safe, highly flexible configuration objects - Easily include third-party services that cannot be easily annotated -- Bring Your Own Container! +- Support for a variety of different Dependency Injection containers ## Quick Start @@ -70,18 +70,18 @@ Be sure to review the generated configuration! A "normal" Composer setup might r ```xml - + src tests - .annotated-container-cache ``` -Now, bootstrap your Container in your app. +Now, bootstrap your Container in your app. In this example, we're going to assume that you +have `php-di/php-di` installed. ```php bootstrapContainer( - Profiles::fromList(['default']) -); +$container = Bootstrap::fromAnnotatedContainerConventions( + new PhpDiContainerFactory($emitter), $emitter +)->bootstrapContainer(Profiles::fromList(['default'])); $storage = $container->get(BlobStorage::class); // instanceof FilesystemStorage ``` diff --git a/known-issues.xml b/known-issues.xml index 119a3900..f92060db 100644 --- a/known-issues.xml +++ b/known-issues.xml @@ -27,100 +27,95 @@ - - - - - - - - + valueType->name()]]> + + name]]]> + + + |object]]> + + + listOf->toCollection($values)]]> + + + + + $this->serviceCollectorReferenceToListOfServices($containerBuilder, $state, $definition, $value)]]> + + + + + make($definition->type()->name())]]> + execute( + [$serviceDelegateDefinition->classMethod()->class()->name(), $serviceDelegateDefinition->classMethod()->methodName()], + $this->parametersForServiceDelegateToArray($injector, $state, $serviceDelegateDefinition), + )]]> - - - valueType->name()]]> - - - - abstractService()->name()]]> - class()->name()]]> - concreteService()->name()]]> - delegateType()->name()]]> - delegateType()->name()]]> - delegateType()->name()]]> - service()->name()]]> - serviceType()->name()]]> - serviceType()->name()]]> - type()->name()]]> - type()->name()]]> - type()->name()]]> - valueType->name()]]> - + + $containerBuilder->get($value->name)]]> + $this->serviceCollectorReferenceToListOfServices($containerBuilder, $state, $definition, $value)]]> + $closure()]]> + $closure()]]> + - name()]]> + - name()]]]> - + + + + + + call( + [$target, $serviceDelegateDefinition->classMethod()->methodName()], + array_map(static fn(Closure $closure) => $closure(), $this->parametersForServiceDelegateToArray($container, $state, $serviceDelegateDefinition)), + )]]> + get($definition->type()->name())]]> + get($definition->type()->name())]]> - - - - - - - - class($this->type)]]> - - - - - - abstractService()->name()]]> - class()->name()]]> - service()->name()]]> - - - + + + + @@ -129,41 +124,50 @@ name()]]]> + + + + + + + call( + [$serviceDelegateDefinition->classMethod()->class()->name(), $serviceDelegateDefinition->classMethod()->methodName()], + $this->parametersForServiceDelegateToArray($container, $state, $serviceDelegateDefinition), + )]]> + get($definition->type()->name())]]> + + + + - + - valueType->name()]]> + + + + + + + - name()]]> name()]]> - - - - - - - - - - typeFactory->class($reflection->getDeclaringClass()->getName())]]> - - delegateMethod]]> + method]]> method]]> parameter]]> @@ -171,20 +175,13 @@ - - - - - - - @@ -193,25 +190,6 @@ types()->class($domElement->nodeValue)]]> types()->fromName($domElement->nodeValue)]]> - - types()->class($domElement->nodeValue), - iterator_to_array($xpath->query('cd:valueType/cd:typeIntersect/*', $injectDefinition)) - )]]> - types()->class($domElement->nodeValue), - iterator_to_array($xpath->query('cd:valueType/cd:typeIntersect/*', $injectDefinition)) - )]]> - types()->class($domElement->nodeValue), - iterator_to_array($xpath->query('cd:valueType/cd:typeIntersect/*', $injectDefinition)) - )]]> - class($abstract)]]> - class($concrete)]]> - class($delegateType)]]> - class($service)]]> - class($serviceType)]]> - @@ -237,137 +215,7 @@ - - - - - - - - - - - - - - - - - - name]]> - profiles]]> - - - - - - - - - ($attribute instanceof ServicePrepareFromFunctionalApi ? 'Call to servicePrepare() in DefinitionProvider' : 'Attributed with ' . $attribute::class) . PHP_EOL]]> - - - - - type()->name()]]> - - - - - - - $this->array(), - 'bool' => $this->bool(), - 'float' => $this->float(), - 'int' => $this->int(), - 'mixed' => $this->mixed(), - 'never' => $this->never(), - 'null' => $this->null(), - 'object' => $this->object(), - 'self' => $this->self(), - 'static' => $this->static(), - 'string' => $this->string(), - 'void' => $this->void(), - default => $this->class($name), - }]]> - - - - - - name]]> - name]]> - types]]> - types]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - createType('array')]]> - createType('array')]]> - createType('bool')]]> - createType('bool')]]> - createType('float')]]> - createType('float')]]> - createType('int')]]> - createType('int')]]> - createType('mixed')]]> - createType('mixed')]]> - createType('never')]]> - createType('never')]]> - createType('null')]]> - createType('null')]]> - createType('object')]]> - createType('object')]]> - createType('self')]]> - createType('self')]]> - createType('static')]]> - createType('static')]]> - createType('string')]]> - createType('string')]]> - createType('void')]]> - createType('void')]]> - - - - ]]> - - - @@ -376,22 +224,21 @@ - - - - - getName()]]> - - - - - - - $attributeType->value, AttributeType::cases())]]> + + + + + + + directories]]> + + + + diff --git a/src/Bootstrap/Bootstrap.php b/src/Bootstrap/Bootstrap.php index beffab3b..3d2666e8 100644 --- a/src/Bootstrap/Bootstrap.php +++ b/src/Bootstrap/Bootstrap.php @@ -5,6 +5,7 @@ use Cspray\AnnotatedContainer\AnnotatedContainer; use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactory; use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactoryOptionsBuilder; +use Cspray\AnnotatedContainer\ContainerFactory\PhpDiContainerFactory; use Cspray\AnnotatedContainer\Definition\ContainerDefinition; use Cspray\AnnotatedContainer\Event\Emitter; use Cspray\AnnotatedContainer\Filesystem\Filesystem; diff --git a/src/Cli/Command/ValidateCommand.php b/src/Cli/Command/ValidateCommand.php index 5f79b27b..97261ea9 100644 --- a/src/Cli/Command/ValidateCommand.php +++ b/src/Cli/Command/ValidateCommand.php @@ -170,7 +170,7 @@ public function handle(Input $input, TerminalOutput $output) : int { } private function profiles(Input $input) : Profiles { - $inputProfiles = $input->option('profiles') ?? ['default']; + $inputProfiles = $input->option('profiles') ?? [Profiles::DEFAULT_PROFILE]; if (is_bool($inputProfiles)) { throw ProfileNotString::fromNotString(); } diff --git a/src/ContainerFactory/AbstractContainerFactory.php b/src/ContainerFactory/AbstractContainerFactory.php index 4092761b..7075d1d3 100644 --- a/src/ContainerFactory/AbstractContainerFactory.php +++ b/src/ContainerFactory/AbstractContainerFactory.php @@ -3,9 +3,11 @@ namespace Cspray\AnnotatedContainer\ContainerFactory; use Cspray\AnnotatedContainer\AnnotatedContainer; -use Cspray\AnnotatedContainer\ContainerFactory\AliasResolution\AliasDefinitionResolution; use Cspray\AnnotatedContainer\ContainerFactory\AliasResolution\AliasDefinitionResolver; use Cspray\AnnotatedContainer\ContainerFactory\AliasResolution\StandardAliasDefinitionResolver; +use Cspray\AnnotatedContainer\ContainerFactory\State\ContainerFactoryState; +use Cspray\AnnotatedContainer\ContainerFactory\State\InjectParameterValue; +use Cspray\AnnotatedContainer\ContainerFactory\State\ServiceCollectorReference; use Cspray\AnnotatedContainer\Definition\ContainerDefinition; use Cspray\AnnotatedContainer\Definition\InjectDefinition; use Cspray\AnnotatedContainer\Definition\ProfilesAwareContainerDefinition; @@ -13,17 +15,18 @@ use Cspray\AnnotatedContainer\Definition\ServiceDelegateDefinition; use Cspray\AnnotatedContainer\Definition\ServicePrepareDefinition; use Cspray\AnnotatedContainer\Event\ContainerFactoryEmitter; -use Cspray\AnnotatedContainer\Exception\ParameterStoreNotFound; use Cspray\AnnotatedContainer\Profiles; -use Cspray\Typiphy\ObjectType; -use UnitEnum; +/** + * @template ContainerBuilder of object + * @template IntermediaryContainer of object + */ abstract class AbstractContainerFactory implements ContainerFactory { private readonly ?ContainerFactoryEmitter $emitter; /** - * @var ParameterStore[] + * @var array */ private array $parameterStores = []; @@ -39,46 +42,110 @@ public function __construct( } final public function createContainer(ContainerDefinition $containerDefinition, ContainerFactoryOptions $containerFactoryOptions = null) : AnnotatedContainer { - $activeProfiles = $containerFactoryOptions?->profiles() ?? Profiles::fromList(['default']); + $activeProfiles = $containerFactoryOptions?->profiles() ?? Profiles::defaultOnly(); $this->emitter?->emitBeforeContainerCreation($activeProfiles, $containerDefinition); - $container = $this->createAnnotatedContainer( - $this->createContainerState($containerDefinition, $activeProfiles), - $activeProfiles + $state = new ContainerFactoryState( + new ProfilesAwareContainerDefinition($containerDefinition, $activeProfiles), + $activeProfiles, + $this->aliasDefinitionResolver, + $this->parameterStores, ); + $container = $this->createAnnotatedContainer($state); $this->emitter?->emitAfterContainerCreation($activeProfiles, $containerDefinition, $container); return $container; } - private function createContainerState(ContainerDefinition $containerDefinition, Profiles $activeProfiles) : ContainerFactoryState { - $definition = new ProfilesAwareContainerDefinition($containerDefinition, $activeProfiles); - $state = $this->containerFactoryState($definition); + /** + * @param ContainerBuilder $containerBuilder + * @return array + */ + final protected function parametersForServiceConstructorToArray( + object $containerBuilder, + ContainerFactoryState $state, + ServiceDefinition $serviceDefinition + ) : array { + return $this->listOfInjectDefinitionsToArray( + $containerBuilder, + $state, + $state->constructorInjectDefinitionsForServiceDefinition($serviceDefinition) + ); + } - foreach ($definition->serviceDefinitions() as $serviceDefinition) { - $this->handleServiceDefinition($state, $serviceDefinition); - } + /** + * @param ContainerBuilder $containerBuilder + * @return array + */ + final protected function parametersForServicePrepareToArray( + object $containerBuilder, + ContainerFactoryState $state, + ServicePrepareDefinition $definition, + ) : array { + return $this->listOfInjectDefinitionsToArray( + $containerBuilder, + $state, + $state->injectDefinitionsForServicePrepareDefinition($definition) + ); + } - foreach ($definition->serviceDelegateDefinitions() as $serviceDelegateDefinition) { - $this->handleServiceDelegateDefinition($state, $serviceDelegateDefinition); - } + /** + * @param ContainerBuilder $containerBuilder + * @return array + */ + final protected function parametersForServiceDelegateToArray( + object $containerBuilder, + ContainerFactoryState $state, + ServiceDelegateDefinition $definition, + ) : array { + return $this->listOfInjectDefinitionsToArray( + $containerBuilder, + $state, + $state->injectDefinitionsForServiceDelegateDefinition($definition) + ); + } - foreach ($definition->servicePrepareDefinitions() as $servicePrepareDefinition) { - $this->handleServicePrepareDefinition($state, $servicePrepareDefinition); - } + /** + * @param IntermediaryContainer $container + * @param ServiceCollectorReference $reference + * @return list|object + */ + final protected function serviceCollectorReferenceToListOfServices( + object $container, + ContainerFactoryState $state, + InjectDefinition $definition, + ServiceCollectorReference $reference + ) : array|object { + $values = []; + foreach ($state->serviceDefinitions() as $serviceDefinition) { + if ($serviceDefinition->isAbstract() || + $serviceDefinition->type()->equals($definition->service()) || + !is_a($serviceDefinition->type()->name(), $reference->valueType->name(), true) + ) { + continue; + } - foreach ($definition->aliasDefinitions() as $aliasDefinition) { - $resolution = $this->aliasDefinitionResolver->resolveAlias($definition, $aliasDefinition->abstractService()); - $this->handleAliasDefinition($state, $resolution); + $values[] = $this->retrieveServiceFromIntermediaryContainer($container, $serviceDefinition); } - foreach ($definition->injectDefinitions() as $injectDefinition) { - $this->handleInjectDefinition($state, $injectDefinition); + return $reference->listOf->toCollection($values); + } + + /** + * @param ContainerBuilder $containerBuilder + * @param list $definitions + * @return array + */ + private function listOfInjectDefinitionsToArray(object $containerBuilder, ContainerFactoryState $state, array $definitions) : array { + $params = []; + foreach ($definitions as $injectDefinition) { + $injectParameterValue = $this->resolveParameterForInjectDefinition($containerBuilder, $state, $injectDefinition); + $params[$injectParameterValue->name] = $injectParameterValue->value; } - return $state; + return $params; } /** @@ -92,50 +159,22 @@ final public function addParameterStore(ParameterStore $parameterStore): void { $this->parameterStores[$parameterStore->name()] = $parameterStore; } - final protected function parameterStore(string $storeName) : ?ParameterStore { - return $this->parameterStores[$storeName] ?? null; - } - - final protected function injectDefinitionValue(InjectDefinition $definition) : mixed { - $value = $definition->value(); - $store = $definition->storeName(); - if ($store !== null) { - $parameterStore = $this->parameterStore($store); - if ($parameterStore === null) { - throw ParameterStoreNotFound::fromParameterStoreNotAddedToContainerFactory($store); - } - assert(is_string($value) && $value !== ''); - - /** @var mixed $value */ - $value = $parameterStore->fetch($definition->type(), $value); - } - - $type = $definition->type(); - if ($value instanceof ListOf) { - $value = new ServiceCollectorReference( - $value, - $value->type(), - $type - ); - } elseif ((class_exists($definition->type()->name()) || interface_exists($definition->type()->name())) && !is_a($definition->type()->name(), UnitEnum::class, true)) { - assert(is_string($value) && $value !== ''); - $value = new ContainerReference($value, $type); - } - - return $value; - } - - abstract protected function containerFactoryState(ContainerDefinition $containerDefinition) : ContainerFactoryState; - - abstract protected function handleServiceDefinition(ContainerFactoryState $state, ServiceDefinition $definition) : void; - - abstract protected function handleAliasDefinition(ContainerFactoryState $state, AliasDefinitionResolution $resolution) : void; - - abstract protected function handleServiceDelegateDefinition(ContainerFactoryState $state, ServiceDelegateDefinition $definition) : void; + abstract protected function createAnnotatedContainer(ContainerFactoryState $state) : AnnotatedContainer; - abstract protected function handleServicePrepareDefinition(ContainerFactoryState $state, ServicePrepareDefinition $definition) : void; - - abstract protected function handleInjectDefinition(ContainerFactoryState $state, InjectDefinition $definition) : void; + /** + * @param ContainerBuilder $containerBuilder + */ + abstract protected function resolveParameterForInjectDefinition( + object $containerBuilder, + ContainerFactoryState $state, + InjectDefinition $definition, + ) : InjectParameterValue; - abstract protected function createAnnotatedContainer(ContainerFactoryState $state, Profiles $activeProfiles) : AnnotatedContainer; + /** + * @param IntermediaryContainer $container + */ + abstract protected function retrieveServiceFromIntermediaryContainer( + object $container, + ServiceDefinition $definition + ) : object; } diff --git a/src/ContainerFactory/AliasResolution/AliasDefinitionResolver.php b/src/ContainerFactory/AliasResolution/AliasDefinitionResolver.php index 5596231a..1e8af79d 100644 --- a/src/ContainerFactory/AliasResolution/AliasDefinitionResolver.php +++ b/src/ContainerFactory/AliasResolution/AliasDefinitionResolver.php @@ -3,12 +3,14 @@ namespace Cspray\AnnotatedContainer\ContainerFactory\AliasResolution; use Cspray\AnnotatedContainer\Definition\ContainerDefinition; +use Cspray\AnnotatedContainer\Profiles; use Cspray\AnnotatedContainer\Reflection\Type; interface AliasDefinitionResolver { public function resolveAlias( ContainerDefinition $containerDefinition, + Profiles $profiles, Type $abstractService ) : AliasDefinitionResolution; } diff --git a/src/ContainerFactory/AliasResolution/AliasResolutionReason.php b/src/ContainerFactory/AliasResolution/AliasResolutionReason.php index 2702bfd1..6e96eacd 100644 --- a/src/ContainerFactory/AliasResolution/AliasResolutionReason.php +++ b/src/ContainerFactory/AliasResolution/AliasResolutionReason.php @@ -7,6 +7,7 @@ enum AliasResolutionReason { case SingleConcreteService; case MultipleConcreteService; case ConcreteServiceIsPrimary; + case ConcreteServiceHasPrioritizedProfile; case ServiceIsDelegated; case MultiplePrimaryService; } diff --git a/src/ContainerFactory/AliasResolution/StandardAliasDefinitionResolver.php b/src/ContainerFactory/AliasResolution/StandardAliasDefinitionResolver.php index b7fc400e..21660951 100644 --- a/src/ContainerFactory/AliasResolution/StandardAliasDefinitionResolver.php +++ b/src/ContainerFactory/AliasResolution/StandardAliasDefinitionResolver.php @@ -4,12 +4,14 @@ use Cspray\AnnotatedContainer\Definition\AliasDefinition; use Cspray\AnnotatedContainer\Definition\ContainerDefinition; +use Cspray\AnnotatedContainer\Profiles; use Cspray\AnnotatedContainer\Reflection\Type; final class StandardAliasDefinitionResolver implements AliasDefinitionResolver { public function resolveAlias( ContainerDefinition $containerDefinition, + Profiles $profiles, Type $abstractService ) : AliasDefinitionResolution { if ($this->isServiceDelegate($containerDefinition, $abstractService)) { @@ -40,7 +42,22 @@ public function resolveAlias( $definition = $primaryAliases[0]; $reason = AliasResolutionReason::ConcreteServiceIsPrimary; } elseif (count($primaryAliases) === 0) { - $reason = AliasResolutionReason::MultipleConcreteService; + $prioritizedServices = []; + foreach ($aliases as $alias) { + $priorityScore = $profiles->priorityScore( + $this->profilesForServiceDefinition($containerDefinition, $alias->concreteService()) + ); + $prioritizedServices[$priorityScore] ??= []; + $prioritizedServices[$priorityScore][] = $alias; + } + $highestScore = max(array_keys($prioritizedServices)); + + if (count($prioritizedServices[$highestScore]) === 1) { + $reason = AliasResolutionReason::ConcreteServiceHasPrioritizedProfile; + $definition = $prioritizedServices[$highestScore][0]; + } else { + $reason = AliasResolutionReason::MultipleConcreteService; + } } else { $reason = AliasResolutionReason::MultiplePrimaryService; } @@ -70,7 +87,7 @@ public function aliasDefinition() : ?AliasDefinition { private function isServiceDelegate(ContainerDefinition $containerDefinition, Type $service) : bool { foreach ($containerDefinition->serviceDelegateDefinitions() as $serviceDelegateDefinition) { - if ($serviceDelegateDefinition->serviceType()->name() === $service->name()) { + if ($serviceDelegateDefinition->service()->equals($service)) { return true; } } @@ -90,4 +107,14 @@ private function primaryServiceNames(ContainerDefinition $containerDefinition) : } return $names; } + + private function profilesForServiceDefinition(ContainerDefinition $containerDefinition, Type $service) : array { + foreach ($containerDefinition->serviceDefinitions() as $serviceDefinition) { + if ($serviceDefinition->type()->equals($service)) { + return $serviceDefinition->profiles(); + } + } + + return []; + } } diff --git a/src/ContainerFactory/AurynContainerFactory.php b/src/ContainerFactory/AurynContainerFactory.php index 4e9957d9..84d61042 100644 --- a/src/ContainerFactory/AurynContainerFactory.php +++ b/src/ContainerFactory/AurynContainerFactory.php @@ -9,17 +9,16 @@ use Cspray\AnnotatedContainer\Autowire\AutowireableInvoker; use Cspray\AnnotatedContainer\Autowire\AutowireableParameter; use Cspray\AnnotatedContainer\Autowire\AutowireableParameterSet; -use Cspray\AnnotatedContainer\ContainerFactory\AliasResolution\AliasDefinitionResolution; -use Cspray\AnnotatedContainer\Definition\ContainerDefinition; +use Cspray\AnnotatedContainer\ContainerFactory\State\ContainerFactoryState; +use Cspray\AnnotatedContainer\ContainerFactory\State\ContainerReference; +use Cspray\AnnotatedContainer\ContainerFactory\State\InjectParameterValue; +use Cspray\AnnotatedContainer\ContainerFactory\State\ServiceCollectorReference; +use Cspray\AnnotatedContainer\ContainerFactory\State\ValueFetchedFromParameterStore; use Cspray\AnnotatedContainer\Definition\InjectDefinition; use Cspray\AnnotatedContainer\Definition\ServiceDefinition; -use Cspray\AnnotatedContainer\Definition\ServiceDelegateDefinition; -use Cspray\AnnotatedContainer\Definition\ServicePrepareDefinition; use Cspray\AnnotatedContainer\Exception\ContainerException; use Cspray\AnnotatedContainer\Exception\ServiceNotFound; use Cspray\AnnotatedContainer\Profiles; -use Cspray\Typiphy\ObjectType; -use function Cspray\Typiphy\objectType; // @codeCoverageIgnoreStart // phpcs:disable @@ -31,93 +30,26 @@ /** * A ContainerFactory that utilizes the rdlowrey/auryn Container as its backing implementation. + * + * @extends AbstractContainerFactory */ final class AurynContainerFactory extends AbstractContainerFactory implements ContainerFactory { - protected function containerFactoryState(ContainerDefinition $containerDefinition) : AurynContainerFactoryState { - return new AurynContainerFactoryState($containerDefinition); - } - - protected function handleServiceDefinition(ContainerFactoryState $state, ServiceDefinition $definition) : void { - assert($state instanceof AurynContainerFactoryState); - $state->injector->share($definition->type()->name()); - $name = $definition->name(); - if ($name !== null) { - $state->addNameType($name, $definition->type()); - } - } + protected function createAnnotatedContainer(ContainerFactoryState $state) : AnnotatedContainer { + $injector = new Injector(); - protected function handleAliasDefinition(ContainerFactoryState $state, AliasDefinitionResolution $resolution) : void { - assert($state instanceof AurynContainerFactoryState); - $alias = $resolution->aliasDefinition(); - if ($alias !== null) { - $state->injector->alias( - $alias->abstractService()->name(), - $alias->concreteService()->name() - ); - } - } - - protected function handleServiceDelegateDefinition(ContainerFactoryState $state, ServiceDelegateDefinition $definition) : void { - assert($state instanceof AurynContainerFactoryState); - $delegateType = $definition->delegateType()->name(); - $delegateMethod = $definition->delegateMethod(); - - $parameters = $state->parametersForMethod($delegateType, $delegateMethod); - $state->injector->delegate( - $definition->serviceType()->name(), - static fn() : mixed => $state->injector->execute([$delegateType, $delegateMethod], $parameters) - ); - } - - protected function handleServicePrepareDefinition(ContainerFactoryState $state, ServicePrepareDefinition $definition) : void { - assert($state instanceof AurynContainerFactoryState); - $serviceType = $definition->service()->name(); - - $state->addServicePrepare($serviceType, $definition->methodName()); - } - - protected function handleInjectDefinition(ContainerFactoryState $state, InjectDefinition $definition) : void { - assert($state instanceof AurynContainerFactoryState); - $injectTargetType = $definition->class()->name(); - $method = $definition->methodName(); - $parameterName = $definition->parameterName(); - /** @var mixed $value */ - $value = $this->injectDefinitionValue($definition); - - $state->addMethodInject($injectTargetType, $method, $parameterName, $value); - } - - protected function createAnnotatedContainer(ContainerFactoryState $state, Profiles $activeProfiles) : AnnotatedContainer { - assert($state instanceof AurynContainerFactoryState); - - foreach ($state->methodInject() as $service => $methods) { - if (array_key_exists('__construct', $methods)) { - $state->injector->define($service, $methods['__construct']); - } - } - - foreach ($state->servicePrepares() as $serviceType => $methods) { - $state->injector->prepare( - $serviceType, - static function(object $object) use($state, $methods) : void { - foreach ($methods as $method) { - $params = $state->parametersForMethod($object::class, $method); - $state->injector->execute([$object, $method], $params); - } - } - ); - } + $this->addServiceDefinitionsToInjector($state, $injector); + $this->addServiceDelegateDefinitionsToInjector($state, $injector); - return new class($state, $activeProfiles) implements AnnotatedContainer { + return new class($injector, $state) implements AnnotatedContainer { public function __construct( - private readonly AurynContainerFactoryState $state, - Profiles $activeProfiles + private readonly Injector $injector, + private readonly ContainerFactoryState $state, ) { - $state->injector->delegate(AutowireableFactory::class, fn() => $this); - $state->injector->delegate(AutowireableInvoker::class, fn() => $this); - $state->injector->delegate(Profiles::class, fn() => $activeProfiles); + $this->injector->delegate(AutowireableFactory::class, fn() => $this); + $this->injector->delegate(AutowireableInvoker::class, fn() => $this); + $this->injector->delegate(Profiles::class, fn() => $this->state->activeProfiles()); } /** @@ -133,13 +65,13 @@ public function get(string $id) { assert($id !== ''); - $namedType = $this->state->typeForName($id); + $namedType = $this->state->typeForServiceName($id) ?? null; if ($namedType !== null) { $id = $namedType->name(); } /** @var T|mixed $value */ - $value = $this->state->injector->make($id); + $value = $this->injector->make($id); return $value; } catch (InjectionException $injectionException) { throw ContainerException::fromCaughtThrowable($injectionException); @@ -149,13 +81,13 @@ public function get(string $id) { public function has(string $id): bool { assert($id !== ''); - $namedType = $this->state->typeForName($id); + $namedType = $this->state->typeForServiceName($id) ?? null; if ($namedType !== null) { return true; } $anyDefined = 0; - foreach ($this->state->injector->inspect($id) as $definitions) { + foreach ($this->injector->inspect($id) as $definitions) { $anyDefined += count($definitions); } return $anyDefined > 0; @@ -168,7 +100,7 @@ public function has(string $id): bool { */ public function make(string $classType, AutowireableParameterSet $parameters = null) : object { /** @var T $object */ - $object = $this->state->injector->make( + $object = $this->injector->make( $classType, $this->convertAutowireableParameterSet($parameters) ); @@ -177,11 +109,11 @@ public function make(string $classType, AutowireableParameterSet $parameters = n } public function backingContainer() : Injector { - return $this->state->injector; + return $this->injector; } public function invoke(callable $callable, AutowireableParameterSet $parameters = null) : mixed { - return $this->state->injector->execute( + return $this->injector->execute( $callable, $this->convertAutowireableParameterSet($parameters) ); @@ -212,4 +144,78 @@ private function convertAutowireableParameterSet(AutowireableParameterSet $param } }; } + + private function addServiceDefinitionsToInjector(ContainerFactoryState $state, Injector $injector) : void { + foreach ($state->serviceDefinitions() as $serviceDefinition) { + $injector->share($serviceDefinition->type()->name()); + + if ($serviceDefinition->isAbstract()) { + $aliasedType = $state->resolveAliasDefinitionForAbstractService($serviceDefinition); + if ($aliasedType !== null) { + $injector->alias($serviceDefinition->type()->name(), $aliasedType->name()); + } + } + + $constructorParams = $this->parametersForServiceConstructorToArray($injector, $state, $serviceDefinition); + if ($constructorParams !== []) { + $injector->define($serviceDefinition->type()->name(), $constructorParams); + } + + $servicePrepares = $state->servicePrepareDefinitionsForServiceDefinition($serviceDefinition); + if ($servicePrepares !== []) { + $injector->prepare( + $serviceDefinition->type()->name(), + function(object $object) use($state, $injector, $servicePrepares) : void { + foreach ($servicePrepares as $servicePrepareDefinition) { + $injector->execute( + [$object, $servicePrepareDefinition->classMethod()->methodName()], + $this->parametersForServicePrepareToArray($injector, $state, $servicePrepareDefinition) + ); + } + } + ); + } + } + } + + private function addServiceDelegateDefinitionsToInjector(ContainerFactoryState $state, Injector $injector) : void { + foreach ($state->serviceDelegateDefinitions() as $serviceDelegateDefinition) { + $injector->delegate( + $serviceDelegateDefinition->service()->name(), + function() use($injector, $state, $serviceDelegateDefinition) : object { + return $injector->execute( + [$serviceDelegateDefinition->classMethod()->class()->name(), $serviceDelegateDefinition->classMethod()->methodName()], + $this->parametersForServiceDelegateToArray($injector, $state, $serviceDelegateDefinition), + ); + } + ); + } + } + + protected function resolveParameterForInjectDefinition( + object $containerBuilder, + ContainerFactoryState $state, + InjectDefinition $definition, + ) : InjectParameterValue { + $key = $definition->classMethodParameter()->parameterName(); + $value = $definition->value(); + if ($value instanceof ContainerReference) { + $nameType = $state->typeForServiceName($value->name); + $value = $nameType === null ? $value->name : $nameType->name(); + } elseif ($value instanceof ServiceCollectorReference) { + $key = '+' . $key; + $value = fn() => $this->serviceCollectorReferenceToListOfServices($containerBuilder, $state, $definition, $value); + } elseif ($value instanceof ValueFetchedFromParameterStore) { + $key = '+' . $key; + $value = static fn() : mixed => $value->get(); + } else { + $key = ':' . $key; + } + + return new InjectParameterValue($key, $value); + } + + protected function retrieveServiceFromIntermediaryContainer(object $container, ServiceDefinition $definition) : object { + return $container->make($definition->type()->name()); + } } diff --git a/src/ContainerFactory/AurynContainerFactoryState.php b/src/ContainerFactory/AurynContainerFactoryState.php deleted file mode 100644 index a4c8f8b0..00000000 --- a/src/ContainerFactory/AurynContainerFactoryState.php +++ /dev/null @@ -1,84 +0,0 @@ - - */ - private array $nameTypeMap = []; - - - public function __construct( - private readonly ContainerDefinition $containerDefinition - ) { - $this->injector = new Injector(); - } - - - /** - * @template T - * @param class-string $class - * @param non-empty-string $method - * @param non-empty-string $param - */ - public function addMethodInject(string $class, string $method, string $param, mixed $value) : void { - if ($value instanceof ContainerReference) { - $key = $param; - $nameType = $this->typeForName($value->name); - if ($nameType !== null) { - $value = $nameType->name(); - } else { - $value = $value->name; - } - } elseif ($value instanceof ServiceCollectorReference) { - $key = '+' . $param; - $values = []; - foreach ($this->containerDefinition->serviceDefinitions() as $serviceDefinition) { - if ($serviceDefinition->isAbstract() || $serviceDefinition->type()->name() === $class) { - continue; - } - - if (is_a($serviceDefinition->type()->name(), $value->valueType->name(), true)) { - /** @var T $objectValue */ - $objectValue = $this->injector->make($serviceDefinition->type()->name()); - $values[] = $objectValue; - } - } - - $value = static fn() : mixed => $value->listOf->toCollection($values); - } else { - $key = ':' . $param; - } - - $this->addResolvedMethodInject($class, $method, $key, $value); - } - - /** - * @param non-empty-string $name - * @param Type $type - * @return void - */ - public function addNameType(string $name, Type $type) : void { - $this->nameTypeMap[$name] = $type; - } - - /** - * @param non-empty-string $name - * @return Type|null - */ - public function typeForName(string $name) : ?Type { - return $this->nameTypeMap[$name] ?? null; - } -} diff --git a/src/ContainerFactory/ContainerFactoryState.php b/src/ContainerFactory/ContainerFactoryState.php deleted file mode 100644 index 6ad660e1..00000000 --- a/src/ContainerFactory/ContainerFactoryState.php +++ /dev/null @@ -1,7 +0,0 @@ ->> - */ - private array $methodInject = []; - - /** - * @param class-string $class - * @param non-empty-string $method - * @param non-empty-string $param - * @return void - */ - public function addMethodInject(string $class, string $method, string $param, mixed $value) : void { - $this->methodInject[$class] ??= []; - $this->methodInject[$class][$method] ??= []; - $this->methodInject[$class][$method][$param] = $value; - } - - /** - * @param class-string $class - * @param non-empty-string $method - * @return array - */ - public function parametersForMethod(string $class, string $method) : array { - return $this->methodInject[$class][$method] ?? []; - } - - /** - * @return array>> - */ - public function methodInject() : array { - return $this->methodInject; - } -} diff --git a/src/ContainerFactory/HasServicePrepareState.php b/src/ContainerFactory/HasServicePrepareState.php deleted file mode 100644 index 7bbde3c7..00000000 --- a/src/ContainerFactory/HasServicePrepareState.php +++ /dev/null @@ -1,27 +0,0 @@ -> - */ - private array $servicePrepares = []; - - /** - * @param class-string $class - * @param non-empty-string $method - */ - public function addServicePrepare(string $class, string $method) : void { - $this->servicePrepares[$class] ??= []; - $this->servicePrepares[$class][] = $method; - } - - /** - * @return array> - */ - public function servicePrepares() : array { - return $this->servicePrepares; - } -} diff --git a/src/ContainerFactory/IlluminateContainerFactory.php b/src/ContainerFactory/IlluminateContainerFactory.php index ae2ae903..e1b44f90 100644 --- a/src/ContainerFactory/IlluminateContainerFactory.php +++ b/src/ContainerFactory/IlluminateContainerFactory.php @@ -2,27 +2,23 @@ namespace Cspray\AnnotatedContainer\ContainerFactory; +use Closure; use Cspray\AnnotatedContainer\AnnotatedContainer; use Cspray\AnnotatedContainer\Autowire\AutowireableFactory; use Cspray\AnnotatedContainer\Autowire\AutowireableInvoker; use Cspray\AnnotatedContainer\Autowire\AutowireableParameterSet; -use Cspray\AnnotatedContainer\ContainerFactory\AliasResolution\AliasDefinitionResolution; -use Cspray\AnnotatedContainer\ContainerFactory\AliasResolution\AliasDefinitionResolver; -use Cspray\AnnotatedContainer\ContainerFactory\AliasResolution\StandardAliasDefinitionResolver; -use Cspray\AnnotatedContainer\Definition\ConfigurationDefinition; -use Cspray\AnnotatedContainer\Definition\ContainerDefinition; +use Cspray\AnnotatedContainer\ContainerFactory\State\ContainerFactoryState; +use Cspray\AnnotatedContainer\ContainerFactory\State\ContainerReference; +use Cspray\AnnotatedContainer\ContainerFactory\State\InjectParameterValue; +use Cspray\AnnotatedContainer\ContainerFactory\State\ServiceCollectorReference; +use Cspray\AnnotatedContainer\ContainerFactory\State\ValueFetchedFromParameterStore; use Cspray\AnnotatedContainer\Definition\InjectDefinition; use Cspray\AnnotatedContainer\Definition\ServiceDefinition; -use Cspray\AnnotatedContainer\Definition\ServiceDelegateDefinition; -use Cspray\AnnotatedContainer\Definition\ServicePrepareDefinition; -use Cspray\AnnotatedContainer\Event\ContainerFactoryEmitter; use Cspray\AnnotatedContainer\Exception\ServiceNotFound; use Cspray\AnnotatedContainer\Profiles; -use Cspray\Typiphy\ObjectType; +use Cspray\AnnotatedContainer\Reflection\Type; use Illuminate\Contracts\Container\Container; use function Cspray\AnnotatedContainer\Reflection\types; -use function Cspray\Typiphy\arrayType; -use function Cspray\Typiphy\objectType; // @codeCoverageIgnoreStart // phpcs:disable @@ -32,196 +28,87 @@ // phpcs:enable // @codeCoverageIgnoreEnd +/** + * @extends AbstractContainerFactory + */ final class IlluminateContainerFactory extends AbstractContainerFactory { - public function __construct( - ContainerFactoryEmitter $emitter, - private readonly Container $container = new \Illuminate\Container\Container(), - AliasDefinitionResolver $aliasDefinitionResolver = new StandardAliasDefinitionResolver(), + protected function createAnnotatedContainer(ContainerFactoryState $state) : AnnotatedContainer { + $container = new \Illuminate\Container\Container(); - ) { - parent::__construct($emitter, $aliasDefinitionResolver); - } - - protected function containerFactoryState(ContainerDefinition $containerDefinition) : ContainerFactoryState { - return new IlluminateContainerFactoryState($this->container, $containerDefinition); - } - - protected function handleServiceDefinition(ContainerFactoryState $state, ServiceDefinition $definition) : void { - assert($state instanceof IlluminateContainerFactoryState); - if ($definition->isConcrete()) { - $state->addConcreteService($definition->type()->name()); - } else { - $state->addAbstractService($definition->type()->name()); - } - $name = $definition->name(); - if ($name !== null) { - $state->addNamedService($definition->type()->name(), $name); - } - } - - protected function handleAliasDefinition(ContainerFactoryState $state, AliasDefinitionResolution $resolution) : void { - assert($state instanceof IlluminateContainerFactoryState); - $definition = $resolution->aliasDefinition(); - if ($definition !== null) { - $state->addAlias($definition->abstractService()->name(), $definition->concreteService()->name()); - } - } - - protected function handleServiceDelegateDefinition(ContainerFactoryState $state, ServiceDelegateDefinition $definition) : void { - assert($state instanceof IlluminateContainerFactoryState); - - $reflectionMethod = new \ReflectionMethod($definition->delegateType()->name(), $definition->delegateMethod()); - if ($reflectionMethod->isStatic()) { - $state->addStaticDelegate( - $definition->serviceType()->name(), - $definition->delegateType()->name(), - $definition->delegateMethod() - ); - } else { - $state->addInstanceDelegate( - $definition->serviceType()->name(), - $definition->delegateType()->name(), - $definition->delegateMethod() - ); - } - } - - protected function handleServicePrepareDefinition(ContainerFactoryState $state, ServicePrepareDefinition $definition) : void { - assert($state instanceof IlluminateContainerFactoryState); - $state->addServicePrepare($definition->service()->name(), $definition->methodName()); - } - - protected function handleInjectDefinition(ContainerFactoryState $state, InjectDefinition $definition) : void { - assert($state instanceof IlluminateContainerFactoryState); - $state->addMethodInject( - $definition->class()->name(), - $definition->methodName(), - $definition->parameterName(), - $this->injectDefinitionValue($definition) - ); - } - - protected function createAnnotatedContainer(ContainerFactoryState $state, Profiles $activeProfiles) : AnnotatedContainer { - assert($state instanceof IlluminateContainerFactoryState); - $container = $state->container; - - - foreach ($state->aliases() as $abstract => $concrete) { - $container->singleton($abstract, $concrete); - } - - foreach ($state->delegates() as $service => $delegateInfo) { - if ($delegateInfo['isStatic']) { - $target = $delegateInfo['delegateType']; + foreach ($state->serviceDefinitions() as $serviceDefinition) { + if ($serviceDefinition->isAbstract()) { + $aliasedType = $state->resolveAliasDefinitionForAbstractService($serviceDefinition); + if ($aliasedType !== null) { + $container->singleton($serviceDefinition->type()->name(), $aliasedType->name()); + } } else { - $target = $container->get($delegateInfo['delegateType']); + $container->singleton($serviceDefinition->type()->name()); } - $container->singleton( - $service, - static function(Container $container) use($target, $delegateInfo) : object { - $object = $container->call([$target, $delegateInfo['delegateMethod']]); - assert(is_object($object)); - return $object; - } - ); - } - foreach ($state->namedServices() as $service => $name) { - $container->alias($service, $name); - } + $name = $serviceDefinition->name(); + if ($name !== null) { + $container->alias($serviceDefinition->type()->name(), $name); + } - foreach ($state->concreteServices() as $service) { - $container->singleton($service); - } + foreach ($this->parametersForServiceConstructorToArray($container, $state, $serviceDefinition) as $key => $value) { + $container->when($serviceDefinition->type()->name())->needs($key)->give($value); + } - foreach ($state->servicePrepares() as $service => $methods) { - $container->afterResolving($service, static function (object $created, Container $container) use($state, $service, $methods) { - foreach ($methods as $method) { - /** @var array $params */ - $params = []; - /** - * @var mixed $value - */ - foreach ($state->parametersForMethod($service, $method) as $param => $value) { - /** @var mixed $resolvedValue */ - $resolvedValue = $value instanceof ContainerReference ? $container->get($value->name) : $value; - $params[$param] = $resolvedValue; + $servicePrepares = $state->servicePrepareDefinitionsForServiceDefinition($serviceDefinition); + if ($servicePrepares !== []) { + $container->afterResolving($serviceDefinition->type()->name(), function(object $object) use($state, $servicePrepares, $container) : void { + foreach ($servicePrepares as $servicePrepare) { + $container->call( + [$object, $servicePrepare->classMethod()->methodName()], + array_map(static fn(Closure $closure) => $closure(), $this->parametersForServicePrepareToArray($container, $state, $servicePrepare)), + ); } - $container->call([$created, $method], $params); - } - }); + }); + } } - - foreach ($state->methodInject() as $service => $methods) { - foreach ($methods as $method => $params) { - if ($method === '__construct') { - /** @var mixed $value */ - foreach ($params as $param => $value) { - if ($value instanceof ContainerReference) { - $container->when($service) - ->needs($value->type->name()) - ->give($value->name); - } elseif ($value instanceof ServiceCollectorReference) { - if ($value->collectionType === types()->array()) { - $paramIdentifier = sprintf('$%s', $param); - } else { - $paramIdentifier = $value->collectionType->name(); - } - - $container->when($service) - ->needs($paramIdentifier) - ->give(function() use($state, $container, $value, $service): mixed { - $values = []; - foreach ($state->containerDefinition->serviceDefinitions() as $serviceDefinition) { - if ($serviceDefinition->isAbstract() || $serviceDefinition->type()->name() === $service) { - continue; - } - - if (is_a($serviceDefinition->type()->name(), $value->valueType->name(), true)) { - $values[] = $container->get($serviceDefinition->type()->name()); - } - } - return $value->listOf->toCollection($values); - }); - } else { - $container->when($service) - ->needs(sprintf('$%s', $param)) - ->give(static fn() : mixed => $value); - } + foreach ($state->serviceDelegateDefinitions() as $serviceDelegateDefinition) { + $container->singleton( + $serviceDelegateDefinition->service()->name(), + function (Container $container) use($serviceDelegateDefinition, $state) : object { + if ($serviceDelegateDefinition->classMethod()->isStatic()) { + $target = $serviceDelegateDefinition->classMethod()->class()->name(); + } else { + $target = $container->get($serviceDelegateDefinition->classMethod()->class()->name()); } - } - } - } - foreach ($state->abstractServices() as $abstractService) { - $container->singletonIf($abstractService); + return $container->call( + [$target, $serviceDelegateDefinition->classMethod()->methodName()], + array_map(static fn(Closure $closure) => $closure(), $this->parametersForServiceDelegateToArray($container, $state, $serviceDelegateDefinition)), + ); + } + ); } - $container->instance(Profiles::class, $activeProfiles); + $container->instance(Profiles::class, $state->activeProfiles()); - return new class($state) implements AnnotatedContainer { + return new class($container) implements AnnotatedContainer { public function __construct( - private readonly IlluminateContainerFactoryState $state, + private readonly Container $container, ) { - $this->state->container->instance(AutowireableFactory::class, $this); - $this->state->container->instance(AutowireableInvoker::class, $this); + $this->container->instance(AutowireableFactory::class, $this); + $this->container->instance(AutowireableInvoker::class, $this); } public function backingContainer() : Container { - return $this->state->container; + return $this->container; } public function make(string $classType, AutowireableParameterSet $parameters = null) : object { - $object = $this->state->container->make($classType, $this->resolvedParameters($parameters)); + $object = $this->container->make($classType, $this->resolvedParameters($parameters)); assert($object instanceof $classType); return $object; } public function invoke(callable $callable, AutowireableParameterSet $parameters = null) : mixed { - return $this->state->container->call($callable, $this->resolvedParameters($parameters)); + return $this->container->call($callable, $this->resolvedParameters($parameters)); } /** @@ -234,10 +121,10 @@ private function resolvedParameters(?AutowireableParameterSet $parameters) : arr foreach ($parameters as $parameter) { if ($parameter->isServiceIdentifier()) { $parameterValue = $parameter->value(); - assert($parameterValue instanceof ObjectType); + assert($parameterValue instanceof Type); /** @psalm-var mixed $value */ - $value = $this->state->container->get($parameterValue->name()); + $value = $this->container->get($parameterValue->name()); } else { /** @psalm-var mixed $value */ $value = $parameter->value(); @@ -261,13 +148,38 @@ public function get(string $id) { } /** @var T|mixed $object */ - $object = $this->state->container->get($id); + $object = $this->container->get($id); return $object; } public function has(string $id) : bool { - return $this->state->container->has($id); + return $this->container->has($id); } }; } + + protected function resolveParameterForInjectDefinition(object $containerBuilder, ContainerFactoryState $state, InjectDefinition $definition,) : InjectParameterValue { + $key = sprintf('%s', $definition->classMethodParameter()->parameterName()); + if ($definition->classMethodParameter()->methodName() === '__construct') { + $key = '$' . $key; + } + $value = $definition->value(); + if ($value instanceof ContainerReference) { + $key = $definition->classMethodParameter()->type()->name(); + $value = fn() => $containerBuilder->get($value->name); + } elseif ($value instanceof ServiceCollectorReference) { + if (!$value->collectionType->equals(types()->array())) { + $key = $value->collectionType->name(); + } + $value = fn() => $this->serviceCollectorReferenceToListOfServices($containerBuilder, $state, $definition, $value); + } else { + $value = fn() : mixed => $value instanceof ValueFetchedFromParameterStore ? $value->get() : $value; + } + + return new InjectParameterValue($key, $value); + } + + protected function retrieveServiceFromIntermediaryContainer(object $container, ServiceDefinition $definition) : object { + return $container->get($definition->type()->name()); + } } diff --git a/src/ContainerFactory/IlluminateContainerFactoryState.php b/src/ContainerFactory/IlluminateContainerFactoryState.php deleted file mode 100644 index c8aaa243..00000000 --- a/src/ContainerFactory/IlluminateContainerFactoryState.php +++ /dev/null @@ -1,142 +0,0 @@ - - */ - private array $delegates = []; - - /** - * @var list - */ - private array $concreteServices = []; - - /** - * @var list - */ - private array $abstractServices = []; - - /** - * @var array - */ - private array $aliases = []; - - /** - * @var array - */ - private array $namedServices = []; - - public function __construct( - public readonly Container $container, - public readonly ContainerDefinition $containerDefinition - ) { - } - - /** - * @param class-string $service - * @param class-string $delegate - * @param non-empty-string $method - * @return void - */ - public function addStaticDelegate(string $service, string $delegate, string $method) : void { - $this->delegates[$service] = [ - 'delegateType' => $delegate, - 'delegateMethod' => $method, - 'isStatic' => true - ]; - } - - /** - * @param class-string $service - * @param class-string $delegate - * @param non-empty-string $method - * @return void - */ - public function addInstanceDelegate(string $service, string $delegate, string $method) : void { - $this->delegates[$service] = [ - 'delegateType' => $delegate, - 'delegateMethod' => $method, - 'isStatic' => false - ]; - } - - /** - * @param class-string $service - * @return void - */ - public function addAbstractService(string $service) : void { - $this->abstractServices[] = $service; - } - - /** - * @param class-string $service - * @return void - */ - public function addConcreteService(string $service) : void { - $this->concreteServices[] = $service; - } - - /** - * @param class-string $service - * @param non-empty-string $name - * @return void - */ - public function addNamedService(string $service, string $name) : void { - $this->namedServices[$service] = $name; - } - - /** - * @param class-string $abstract - * @param class-string $concrete - * @return void - */ - public function addAlias(string $abstract, string $concrete) : void { - $this->aliases[$abstract] = $concrete; - } - - /** - * @return list - */ - public function abstractServices() : array { - return $this->abstractServices; - } - - /** - * @return list - */ - public function concreteServices() : array { - return $this->concreteServices; - } - - /** - * @return array - */ - public function aliases() : array { - return $this->aliases; - } - - /** - * @return array - */ - public function delegates() : array { - return $this->delegates; - } - - /** - * @return array - */ - public function namedServices() : array { - return $this->namedServices; - } -} diff --git a/src/ContainerFactory/ParameterStore.php b/src/ContainerFactory/ParameterStore.php index 833bde61..de565ce7 100644 --- a/src/ContainerFactory/ParameterStore.php +++ b/src/ContainerFactory/ParameterStore.php @@ -14,7 +14,7 @@ interface ParameterStore { /** * The name of the store; Inject definitions that use this string in their from argument will use this ParameterStore. * - * @return string + * @return non-empty-string */ public function name() : string; diff --git a/src/ContainerFactory/PhpDiContainerFactory.php b/src/ContainerFactory/PhpDiContainerFactory.php index 4fa1c576..e342a4fc 100644 --- a/src/ContainerFactory/PhpDiContainerFactory.php +++ b/src/ContainerFactory/PhpDiContainerFactory.php @@ -5,23 +5,22 @@ use Cspray\AnnotatedContainer\AnnotatedContainer; use Cspray\AnnotatedContainer\Autowire\AutowireableFactory; use Cspray\AnnotatedContainer\Autowire\AutowireableInvoker; -use Cspray\AnnotatedContainer\Autowire\AutowireableParameter; use Cspray\AnnotatedContainer\Autowire\AutowireableParameterSet; -use Cspray\AnnotatedContainer\ContainerFactory\AliasResolution\AliasDefinitionResolution; -use Cspray\AnnotatedContainer\Definition\ContainerDefinition; +use Cspray\AnnotatedContainer\ContainerFactory\State\ContainerReference; +use Cspray\AnnotatedContainer\ContainerFactory\State\InjectParameterValue; +use Cspray\AnnotatedContainer\ContainerFactory\State\ServiceCollectorReference; +use Cspray\AnnotatedContainer\ContainerFactory\State\ValueFetchedFromParameterStore; use Cspray\AnnotatedContainer\Definition\InjectDefinition; +use Cspray\AnnotatedContainer\ContainerFactory\State\ContainerFactoryState; use Cspray\AnnotatedContainer\Definition\ServiceDefinition; -use Cspray\AnnotatedContainer\Definition\ServiceDelegateDefinition; -use Cspray\AnnotatedContainer\Definition\ServicePrepareDefinition; use Cspray\AnnotatedContainer\Profiles; use DI\Container; use Cspray\AnnotatedContainer\Exception\ServiceNotFound; -use Cspray\Typiphy\ObjectType; use DI\ContainerBuilder; -use DI\Definition\Helper\AutowireDefinitionHelper; use DI\Definition\Reference; -use function Cspray\Typiphy\objectType; +use function DI\autowire; use function DI\decorate; +use function DI\factory; use function DI\get; // @codeCoverageIgnoreStart @@ -35,111 +34,82 @@ /** * A ContainerFactory that utilizes the php-di/php-di library. + * + * @extends AbstractContainerFactory */ final class PhpDiContainerFactory extends AbstractContainerFactory implements ContainerFactory { - protected function containerFactoryState(ContainerDefinition $containerDefinition) : ContainerFactoryState { - return new PhpDiContainerFactoryState($containerDefinition); - } - - protected function handleServiceDefinition(ContainerFactoryState $state, ServiceDefinition $definition) : void { - assert($state instanceof PhpDiContainerFactoryState); - $serviceType = $definition->type()->name(); - $state->addService($serviceType); - $state->autowireService($serviceType); - $key = $serviceType; - $name = $definition->name(); - if ($name !== null) { - $state->addService($name); - $state->referenceService($name, $definition->type()->name()); - $key = $name; - } - $state->setServiceKey($serviceType, $key); - } - - protected function handleAliasDefinition(ContainerFactoryState $state, AliasDefinitionResolution $resolution) : void { - assert($state instanceof PhpDiContainerFactoryState); - $aliasDefinition = $resolution->aliasDefinition(); - if ($aliasDefinition !== null) { - // We know that $abstractService can't be null here because AliasDefinitions are inferred from those types - // that are available for resolution after all definitions have been provided. The AliasDefinition couldn't - // exist if there wasn't a corresponding abstract service. - $abstractService = $state->serviceKey($aliasDefinition->abstractService()->name()); - assert($abstractService !== null); - $state->referenceService( - $abstractService, - $aliasDefinition->concreteService()->name() - ); - } - } - - public function handleServiceDelegateDefinition(ContainerFactoryState $state, ServiceDelegateDefinition $definition) : void { - assert($state instanceof PhpDiContainerFactoryState); - $serviceName = $definition->serviceType()->name(); - $state->factoryService($serviceName, static fn(Container $container) : mixed => $container->call( - [$definition->delegateType()->name(), $definition->delegateMethod()] - )); - } - - public function handleServicePrepareDefinition(ContainerFactoryState $state, ServicePrepareDefinition $definition) : void { - assert($state instanceof PhpDiContainerFactoryState); + protected function createAnnotatedContainer(ContainerFactoryState $state) : AnnotatedContainer { + $containerBuilder = new ContainerBuilder(); - $state->addServicePrepare($definition->service()->name(), $definition->methodName()); - } + $definitions = []; + $servicePrepareDefinitions = []; + foreach ($state->serviceDefinitions() as $serviceDefinition) { + $serviceDelegateDefinition = $state->serviceDelegateDefinitionForServiceDefinition($serviceDefinition); - public function handleInjectDefinition(ContainerFactoryState $state, InjectDefinition $definition) : void { - assert($state instanceof PhpDiContainerFactoryState); - $state->addMethodInject( - $definition->class()->name(), - $definition->methodName(), - $definition->parameterName(), - $this->injectDefinitionValue($definition) - ); - } + if ($serviceDelegateDefinition === null) { + $definitions[$serviceDefinition->type()->name()] = autowire(); - protected function createAnnotatedContainer(ContainerFactoryState $state, Profiles $activeProfiles) : AnnotatedContainer { - assert($state instanceof PhpDiContainerFactoryState); - $containerBuilder = new ContainerBuilder(); + foreach ($this->parametersForServiceConstructorToArray($containerBuilder, $state, $serviceDefinition) as $param => $value) { + $definitions[$serviceDefinition->type()->name()]->constructorParameter($param, $value); + } + } else { + $definitions[$serviceDefinition->type()->name()] = function (Container $container) use($state, $serviceDelegateDefinition) : object { + return $container->call( + [$serviceDelegateDefinition->classMethod()->class()->name(), $serviceDelegateDefinition->classMethod()->methodName()], + $this->parametersForServiceDelegateToArray($container, $state, $serviceDelegateDefinition), + ); + }; + } - $definitions = $state->definitions(); + $name = $serviceDefinition->name(); + if ($name !== null) { + $definitions[$name] = get($serviceDefinition->type()->name()); + } - foreach ($state->methodInject() as $service => $methods) { - $serviceDefinition = $definitions[$service]; - assert($serviceDefinition instanceof AutowireDefinitionHelper); - foreach ($methods as $method => $params) { - if ($method === '__construct') { - /** @var mixed $value */ - foreach ($params as $param => $value) { - $serviceDefinition->constructorParameter($param, $value); - } + if ($serviceDefinition->isAbstract()) { + $alias = $state->resolveAliasDefinitionForAbstractService($serviceDefinition); + if ($alias !== null) { + $definitions[$serviceDefinition->type()->name()] = get($alias->name()); } } - } - $servicePrepareDefinitions = []; - foreach ($state->servicePrepares() as $service => $methods) { - $servicePrepareDefinitions[$service] = decorate(static function (object $service, Container $container) use($state, $methods) { - foreach ($methods as $method) { - $params = $state->parametersForMethod($service::class, $method); - $container->call([$service, $method], $params); - } - return $service; - }); + $servicePrepares = $state->servicePrepareDefinitionsForServiceDefinition($serviceDefinition); + if ($servicePrepares !== []) { + $servicePrepareDefinitions[$serviceDefinition->type()->name()] = decorate(function (object $object, Container $container) use($servicePrepares, $state) : mixed { + foreach ($servicePrepares as $servicePrepare) { + $container->call( + [$object, $servicePrepare->classMethod()->methodName()], + $this->parametersForServicePrepareToArray($container, $state, $servicePrepare), + ); + } + + return $object; + }); + } } $containerBuilder->addDefinitions($definitions); $containerBuilder->addDefinitions($servicePrepareDefinitions); - return new class($containerBuilder->build(), $state->services(), $activeProfiles) implements AnnotatedContainer { + return new class($containerBuilder->build(), $state, array_values(array_keys($definitions))) implements AnnotatedContainer { + + private readonly array $knownServices; public function __construct( private readonly Container $container, - private readonly array $serviceTypes, - Profiles $activeProfiles + ContainerFactoryState $state, + array $knownServices ) { $this->container->set(AutowireableFactory::class, $this); $this->container->set(AutowireableInvoker::class, $this); - $this->container->set(Profiles::class, $activeProfiles); + $this->container->set(Profiles::class, $state->activeProfiles()); + + $knownServices[] = AutowireableFactory::class; + $knownServices[] = AutowireableInvoker::class; + $knownServices[] = Profiles::class; + + $this->knownServices = $knownServices; } public function make(string $classType, AutowireableParameterSet $parameters = null) : object { @@ -168,7 +138,7 @@ public function get(string $id) { } public function has(string $id) : bool { - return in_array($id, $this->serviceTypes); + return in_array($id, $this->knownServices, true); } public function backingContainer() : Container { @@ -204,4 +174,27 @@ private function convertAutowireableParameterSet(AutowireableParameterSet $param } }; } + + protected function resolveParameterForInjectDefinition(object $containerBuilder, ContainerFactoryState $state, InjectDefinition $definition,) : InjectParameterValue { + $value = $definition->value(); + + if ($value instanceof ContainerReference) { + $value = get($value->name); + } elseif ($value instanceof ServiceCollectorReference) { + $value = factory(fn(Container $container) : mixed => $this->serviceCollectorReferenceToListOfServices( + $container, + $state, + $definition, + $value + )); + } elseif ($value instanceof ValueFetchedFromParameterStore) { + $value = factory($value->get(...)); + } + + return new InjectParameterValue($definition->classMethodParameter()->parameterName(), $value); + } + + protected function retrieveServiceFromIntermediaryContainer(object $container, ServiceDefinition $definition) : object { + return $container->get($definition->type()->name()); + } } diff --git a/src/ContainerFactory/PhpDiContainerFactoryState.php b/src/ContainerFactory/PhpDiContainerFactoryState.php deleted file mode 100644 index 887d9df3..00000000 --- a/src/ContainerFactory/PhpDiContainerFactoryState.php +++ /dev/null @@ -1,150 +0,0 @@ - - */ - private array $services = []; - - /** - * @var array - */ - private array $definitions = []; - - /** - * @var array - */ - private array $serviceKeys = []; - - public function __construct( - private readonly ContainerDefinition $containerDefinition - ) { - $this->services[] = AutowireableFactory::class; - $this->services[] = AutowireableInvoker::class; - $this->services[] = Profiles::class; - } - - /** - * @param class-string $class - * @param non-empty-string $method - * @param non-empty-string $param - * @return void - */ - public function addMethodInject(string $class, string $method, string $param, mixed $value) : void { - if ($value instanceof ContainerReference) { - $value = get($value->name); - } - - if ($value instanceof ServiceCollectorReference) { - $values = []; - foreach ($this->containerDefinition->serviceDefinitions() as $serviceDefinition) { - if ($serviceDefinition->isAbstract() || $serviceDefinition->type()->name() === $class) { - continue; - } - - if (is_a($serviceDefinition->type()->name(), $value->valueType->name(), true)) { - $values[] = get($serviceDefinition->type()->name()); - } - } - - $value = factory(function(Container $container) use ($values, $value) : mixed { - /** @var list $resolvedValues */ - $resolvedValues = []; - /** @var Reference $val */ - foreach ($values as $val) { - $resolvedVal = $val->resolve($container); - assert(is_object($resolvedVal)); - $resolvedValues[] = $resolvedVal; - } - return $value->listOf->toCollection($resolvedValues); - }); - } - - $this->addResolvedMethodInject($class, $method, $param, $value); - } - - /** - * @return array - */ - public function definitions() : array { - return $this->definitions; - } - - /** - * @return list - */ - public function services() : array { - return $this->services; - } - - /** - * @param non-empty-string $service - * @return void - */ - public function addService(string $service) : void { - $this->services[] = $service; - } - - /** - * @param class-string $service - * @return void - */ - public function autowireService(string $service) : void { - $this->definitions[$service] = autowire(); - } - - /** - * @param non-empty-string $name - * @param non-empty-string $service - * @return void - */ - public function referenceService(string $name, string $service) : void { - $this->definitions[$name] = get($service); - } - - /** - * @param non-empty-string $name - * @param Closure $closure - * @return void - */ - public function factoryService(string $name, Closure $closure) : void { - $this->definitions[$name] = $closure; - } - - /** - * @param class-string $serviceType - * @param non-empty-string $key - * @return void - */ - public function setServiceKey(string $serviceType, string $key) : void { - $this->serviceKeys[$serviceType] = $key; - } - - /** - * @param class-string $serviceType - * @return non-empty-string|null - */ - public function serviceKey(string $serviceType) : ?string { - return $this->serviceKeys[$serviceType] ?? null; - } -} diff --git a/src/ContainerFactory/State/ContainerFactoryState.php b/src/ContainerFactory/State/ContainerFactoryState.php new file mode 100644 index 00000000..9a5f8bd0 --- /dev/null +++ b/src/ContainerFactory/State/ContainerFactoryState.php @@ -0,0 +1,234 @@ + + */ + private readonly array $injectDefinitions; + + public function __construct( + private readonly ContainerDefinition $containerDefinition, + private readonly Profiles $profiles, + private readonly AliasDefinitionResolver $aliasDefinitionResolver, + /** + * @var array + */ + private readonly array $parameterStores, + ) { + $resolvedInjects = []; + foreach ($this->containerDefinition->injectDefinitions() as $injectDefinition) { + $resolvedInjects[] = $this->injectDefinitionWithResolvableValue($injectDefinition); + } + $this->injectDefinitions = $resolvedInjects; + } + + private function injectDefinitionWithResolvableValue(InjectDefinition $injectDefinition) : InjectDefinition { + $value = $injectDefinition->value(); + $store = $injectDefinition->storeName(); + $type = $injectDefinition->classMethodParameter()->type(); + if ($store !== null) { + $parameterStore = $this->parameterStores[$store] ?? null; + if ($parameterStore === null) { + throw ParameterStoreNotFound::fromParameterStoreNotAddedToContainerFactory($store); + } + assert(is_string($value) && $value !== ''); + + /** @var mixed $value */ + $value = new ValueFetchedFromParameterStore($parameterStore, $type, $value); + } + + if ($value instanceof ListOf) { + $value = new ServiceCollectorReference($value, $value->type(), $type); + } elseif ((class_exists($type->name()) || interface_exists($type->name())) && !is_a($type->name(), UnitEnum::class, true)) { + assert(is_string($value) && $value !== ''); + $value = new ContainerReference($value, $type); + } + + return new class($injectDefinition, $value) implements InjectDefinition { + + public function __construct( + private readonly InjectDefinition $injectDefinition, + private readonly mixed $value, + ) { + } + + public function value() : mixed { + return $this->value; + } + + public function profiles() : array { + return $this->injectDefinition->profiles(); + } + + public function storeName() : ?string { + return $this->injectDefinition->storeName(); + } + + public function attribute() : InjectAttribute { + return $this->injectDefinition->attribute(); + } + + public function service() : Type { + return $this->injectDefinition->service(); + } + + public function classMethodParameter() : ClassMethodParameter { + return $this->injectDefinition->classMethodParameter(); + } + }; + } + + public function activeProfiles() : Profiles { + return $this->profiles; + } + + /** + * @return list + */ + public function serviceDefinitions() : array { + return $this->containerDefinition->serviceDefinitions(); + } + + /** + * @return list + */ + public function servicePrepareDefinitions() : array { + return $this->containerDefinition->servicePrepareDefinitions(); + } + + /** + * @return list + */ + public function serviceDelegateDefinitions() : array { + return $this->containerDefinition->serviceDelegateDefinitions(); + } + + public function resolveAliasDefinitionForAbstractService(ServiceDefinition $serviceDefinition) : ?Type { + return $this->aliasDefinitionResolver->resolveAlias( + $this->containerDefinition, + $this->profiles, + $serviceDefinition->type() + )->aliasDefinition()?->concreteService(); + } + + /** + * @param ServiceDefinition $serviceDefinition + * @return list + */ + public function constructorInjectDefinitionsForServiceDefinition(ServiceDefinition $serviceDefinition) : array { + return $this->prioritizeInjectDefinitions($serviceDefinition->type(), '__construct'); + } + + /** + * @param ServicePrepareDefinition $servicePrepareDefinition + * @return list + */ + public function injectDefinitionsForServicePrepareDefinition(ServicePrepareDefinition $servicePrepareDefinition) : array { + return $this->prioritizeInjectDefinitions($servicePrepareDefinition->service(), $servicePrepareDefinition->classMethod()->methodName()); + } + + /** + * @param ServiceDelegateDefinition $serviceDelegateDefinition + * @return list + */ + public function injectDefinitionsForServiceDelegateDefinition(ServiceDelegateDefinition $serviceDelegateDefinition) : array { + return array_values(array_filter( + $this->injectDefinitions, + static fn(InjectDefinition $i) => $i->service()->equals($serviceDelegateDefinition->classMethod()->class()) && + $i->classMethodParameter()->methodName() === $serviceDelegateDefinition->classMethod()->methodName() + )); + } + + /** + * @param Type $type + * @param non-empty-string $method + * @return list + */ + private function prioritizeInjectDefinitions(Type $type, string $method) : array { + /** + * @var array> $prioritizedParams + */ + $prioritizedParams = []; + foreach ($this->injectDefinitions as $injectDefinition) { + if (!$injectDefinition->service()->equals($type) || $injectDefinition->classMethodParameter()->methodName() !== $method) { + continue; + } + + $parameter = $injectDefinition->classMethodParameter()->parameterName(); + $prioritizedParams[$parameter] ??= []; + + $score = $this->profiles->priorityScore($injectDefinition->profiles()); + $prioritizedParams[$parameter][$score] ??= []; + $prioritizedParams[$parameter][$score][] = $injectDefinition; + } + + $params = []; + foreach ($prioritizedParams as $param => $scoredInjectDefinitions) { + $paramScore = max(array_keys($scoredInjectDefinitions)); + if (count($scoredInjectDefinitions[$paramScore]) > 1) { + throw MultipleInjectOnSameParameter::fromClassMethodParamHasMultipleInject( + $type->name(), + $method, + $param + ); + } + + $params[] = $scoredInjectDefinitions[$paramScore][0]; + } + + return $params; + } + + /** + * @param ServiceDefinition $serviceDefinition + * @return list + */ + public function servicePrepareDefinitionsForServiceDefinition(ServiceDefinition $serviceDefinition) : array { + return array_values(array_filter( + $this->servicePrepareDefinitions(), + static fn(ServicePrepareDefinition $sp) => $sp->service()->equals($serviceDefinition->type()) + )); + } + + public function serviceDelegateDefinitionForServiceDefinition(ServiceDefinition $serviceDefinition) : ?ServiceDelegateDefinition { + foreach ($this->containerDefinition->serviceDelegateDefinitions() as $serviceDelegateDefinition) { + if ($serviceDelegateDefinition->service()->equals($serviceDefinition->type())) { + return $serviceDelegateDefinition; + } + } + + return null; + } + + public function typeForServiceName(string $name) : ?Type { + foreach ($this->containerDefinition->serviceDefinitions() as $serviceDefinition) { + if ($serviceDefinition->name() === $name) { + return $serviceDefinition->type(); + } + } + + return null; + } +} diff --git a/src/ContainerFactory/ContainerReference.php b/src/ContainerFactory/State/ContainerReference.php similarity index 84% rename from src/ContainerFactory/ContainerReference.php rename to src/ContainerFactory/State/ContainerReference.php index 0bc96896..274366eb 100644 --- a/src/ContainerFactory/ContainerReference.php +++ b/src/ContainerFactory/State/ContainerReference.php @@ -1,6 +1,6 @@ $listOf + * @param Type $valueType + * @param Type|TypeUnion|TypeIntersect $collectionType + */ public function __construct( public readonly ListOf $listOf, public readonly Type $valueType, diff --git a/src/ContainerFactory/State/ValueFetchedFromParameterStore.php b/src/ContainerFactory/State/ValueFetchedFromParameterStore.php new file mode 100644 index 00000000..e352df5d --- /dev/null +++ b/src/ContainerFactory/State/ValueFetchedFromParameterStore.php @@ -0,0 +1,25 @@ +parameterStore->fetch($this->type, $this->value); + } +} diff --git a/src/Definition/ClassMethod.php b/src/Definition/ClassMethod.php new file mode 100644 index 00000000..874fa6ef --- /dev/null +++ b/src/Definition/ClassMethod.php @@ -0,0 +1,17 @@ +attribute->profiles(); if ($profiles === []) { - $profiles = ['default']; + $profiles = [Profiles::DEFAULT_PROFILE]; } return $profiles; @@ -145,15 +147,10 @@ public function servicePrepareDefinitionFromClassMethodAndAttribute( string $method, ServicePrepareAttribute $attribute, ) : ServicePrepareDefinition { - return new class($objectType, $method, $attribute) implements ServicePrepareDefinition { - /** - * @param Type $service - * @param non-empty-string $method - * @param ServicePrepareAttribute $attribute - */ + return new class($objectType, $this->classMethod($objectType, $method, false), $attribute) implements ServicePrepareDefinition { public function __construct( private readonly Type $service, - private readonly string $method, + private readonly ClassMethod $classMethod, private readonly ServicePrepareAttribute $attribute, ) { } @@ -162,11 +159,8 @@ public function service() : Type { return $this->service; } - /** - * @return non-empty-string - */ - public function methodName() : string { - return $this->method; + public function classMethod() : ClassMethod { + return $this->classMethod; } public function attribute() : ServicePrepareAttribute { @@ -205,7 +199,9 @@ public function serviceDelegateDefinitionFromClassMethodAndAttribute( string $delegateMethod, ServiceDelegateAttribute $attribute, ) : ServiceDelegateDefinition { - $reflection = new ReflectionMethod($delegateType->name(), $delegateMethod); + $name = $delegateType->name(); + assert(class_exists($name)); + $reflection = new ReflectionMethod($name, $delegateMethod); return $this->serviceDelegateDefinitionFromReflectionMethodAndAttribute($reflection, $attribute); } @@ -242,36 +238,30 @@ private function serviceDelegateDefinitionFromReflectionMethodAndAttribute( $serviceType = $this->typeFactory->class($returnTypeName); return new class( - $delegateType, - $delegateMethod, $serviceType, + $this->classMethod($delegateType, $delegateMethod, $reflection->isStatic()), $attribute ) implements ServiceDelegateDefinition { public function __construct( - private readonly Type $delegateType, - private readonly string $delegateMethod, private readonly Type $serviceType, + private readonly ClassMethod $classMethod, private readonly ServiceDelegateAttribute $attribute, ) { } - public function delegateType() : Type { - return $this->delegateType; - } - - public function delegateMethod() : string { - return $this->delegateMethod; + public function service() : Type { + return $this->serviceType; } - public function serviceType() : Type { - return $this->serviceType; + public function classMethod() : ClassMethod { + return $this->classMethod; } public function profiles() : array { $profiles = $this->attribute->profiles(); if ($profiles === []) { - $profiles = ['default']; + $profiles = [Profiles::DEFAULT_PROFILE]; } return $profiles; @@ -301,7 +291,11 @@ public function injectDefinitionFromReflectionParameterAndAttribute( ReflectionParameter $reflection, InjectAttribute $attribute, ) : InjectDefinition { - $class = $this->typeFactory->class($reflection->getDeclaringClass()->getName()); + $declaringClass = $reflection->getDeclaringClass(); + if ($declaringClass === null) { + throw InvalidReflectionParameterForInjectDefinition::fromReflectionParameterHasNoDeclaringClass(); + } + $class = $this->typeFactory->class($declaringClass->getName()); $method = $reflection->getDeclaringFunction()->getName(); $type = $this->typeFactory->fromReflection($reflection->getType()); $parameter = $reflection->getName(); @@ -309,6 +303,14 @@ public function injectDefinitionFromReflectionParameterAndAttribute( return $this->injectDefinitionFromManualSetup($class, $method, $type, $parameter, $attribute); } + /** + * @param Type $service + * @param non-empty-string $method + * @param Type|TypeUnion|TypeIntersect $type + * @param non-empty-string $parameterName + * @param InjectAttribute $injectAttribute + * @return InjectDefinition + */ public function injectDefinitionFromManualSetup( Type $service, string $method, @@ -318,35 +320,29 @@ public function injectDefinitionFromManualSetup( ) : InjectDefinition { return new class( $service, - $method, - $type, - $parameterName, + $this->classMethodParameter( + $service, + $method, + $type, + $parameterName, + false + ), $injectAttribute, ) implements InjectDefinition { public function __construct( - private readonly Type $class, - private readonly string $method, - private readonly Type|TypeUnion|TypeIntersect $type, - private readonly string $parameter, + private readonly Type $service, + private readonly ClassMethodParameter $classMethodParameter, private readonly InjectAttribute $attribute, ) { } - public function class() : Type { - return $this->class; - } - - public function methodName() : string { - return $this->method; - } - - public function type() : Type|TypeUnion|TypeIntersect { - return $this->type; + public function service() : Type { + return $this->service; } - public function parameterName() : string { - return $this->parameter; + public function classMethodParameter() : ClassMethodParameter { + return $this->classMethodParameter; } public function value() : mixed { @@ -355,7 +351,7 @@ public function value() : mixed { public function profiles() : array { $profiles = $this->attribute->profiles(); - return $profiles === [] ? ['default'] : $profiles; + return $profiles === [] ? [Profiles::DEFAULT_PROFILE] : $profiles; } public function storeName() : ?string { @@ -385,4 +381,73 @@ public function concreteService() : Type { } }; } + + /** + * @param Type $class + * @param non-empty-string $method + * @return ClassMethod + */ + private function classMethod(Type $class, string $method, bool $isStatic) : ClassMethod { + return new class($class, $method, $isStatic) implements ClassMethod { + + public function __construct( + private readonly Type $class, + private readonly string $method, + private readonly bool $isStatic, + ) { + } + + public function class() : Type { + return $this->class; + } + + public function methodName() : string { + return $this->method; + } + + public function isStatic() : bool { + return $this->isStatic; + } + }; + } + + private function classMethodParameter( + Type $class, + string $method, + Type|TypeUnion|TypeIntersect $type, + string $parameter, + bool $isStatic + ) : ClassMethodParameter { + return new class($class, $method, $type, $parameter, $isStatic) implements ClassMethodParameter { + + public function __construct( + private readonly Type $class, + private readonly string $method, + private readonly Type|TypeUnion|TypeIntersect $type, + private readonly string $parameter, + private readonly bool $isStatic, + ) { + } + + public function class() : Type { + return $this->class; + } + + public function methodName() : string { + return $this->method; + } + + public function type() : Type|TypeUnion|TypeIntersect { + return $this->type; + } + + public function parameterName() : string { + return $this->parameter; + } + + public function isStatic() : bool { + return $this->isStatic; + } + }; + } } diff --git a/src/Definition/InjectDefinition.php b/src/Definition/InjectDefinition.php index e0688a77..db3a33d5 100644 --- a/src/Definition/InjectDefinition.php +++ b/src/Definition/InjectDefinition.php @@ -14,11 +14,11 @@ interface InjectDefinition { /** - * Returns the type of the method parameter or property that is being injected into. - * - * @return Type|TypeUnion|TypeIntersect + * The class that has the method being injected into. */ - public function type() : Type|TypeUnion|TypeIntersect; + public function service() : Type; + + public function classMethodParameter() : ClassMethodParameter; /** * The value that should be injected or passed to a ParameterStore defined by getStoreName() to derive the value @@ -43,21 +43,4 @@ public function profiles() : array; public function storeName() : ?string; public function attribute() : InjectAttribute; - - /** - * The class that has the method being injected into. - */ - public function class() : Type; - - /** - * @return non-empty-string - */ - public function methodName() : string; - - /** - * The name of the parameter or property that should have a value injected. - * - * @return non-empty-string - */ - public function parameterName() : string; } diff --git a/src/Definition/Serializer/XmlContainerDefinitionSerializer.php b/src/Definition/Serializer/XmlContainerDefinitionSerializer.php index 9baa015f..0d261b06 100644 --- a/src/Definition/Serializer/XmlContainerDefinitionSerializer.php +++ b/src/Definition/Serializer/XmlContainerDefinitionSerializer.php @@ -122,7 +122,7 @@ private function addServicePrepareDefinitionsToDom(DOMElement $root, ContainerDe $dom->createElementNS(self::XML_SCHEMA, 'type', $servicePrepareDefinition->service()->name()) ); $servicePrepareDefinitionNode->appendChild( - $dom->createElementNS(self::XML_SCHEMA, 'method', $servicePrepareDefinition->methodName()) + $dom->createElementNS(self::XML_SCHEMA, 'method', $servicePrepareDefinition->classMethod()->methodName()) ); $servicePrepareDefinitionNode->appendChild( @@ -144,13 +144,13 @@ private function addServiceDelegateDefinitionsToDom(DOMElement $root, ContainerD ); $serviceDelegateDefinitionNode->appendChild( - $dom->createElementNS(self::XML_SCHEMA, 'service', $serviceDelegateDefinition->serviceType()->name()) + $dom->createElementNS(self::XML_SCHEMA, 'service', $serviceDelegateDefinition->service()->name()) ); $serviceDelegateDefinitionNode->appendChild( - $dom->createElementNS(self::XML_SCHEMA, 'delegateType', $serviceDelegateDefinition->delegateType()->name()) + $dom->createElementNS(self::XML_SCHEMA, 'delegateType', $serviceDelegateDefinition->classMethod()->class()->name()) ); $serviceDelegateDefinitionNode->appendChild( - $dom->createElementNS(self::XML_SCHEMA, 'delegateMethod', $serviceDelegateDefinition->delegateMethod()) + $dom->createElementNS(self::XML_SCHEMA, 'delegateMethod', $serviceDelegateDefinition->classMethod()->methodName()) ); $serviceDelegateDefinitionNode->appendChild( $dom->createElementNS(self::XML_SCHEMA, 'attribute', base64_encode(serialize($serviceDelegateDefinition->attribute()))) @@ -191,7 +191,7 @@ private function addInjectDefinitionsToDom(DOMElement $root, ContainerDefinition } private function createAppropriateElementForValueType(DOMDocument $dom, InjectDefinition $injectDefinition) : DOMElement { - $valueType = $injectDefinition->type(); + $valueType = $injectDefinition->classMethodParameter()->type(); if ($valueType instanceof Type) { $valueTypeElement = $this->createTypeElement($dom, $valueType); } elseif ($valueType instanceof TypeUnion) { @@ -243,16 +243,16 @@ private function addMethodParameterInjectDefinitionToDom(DOMElement $root, Injec $dom = $root->ownerDocument; $root->appendChild( - $dom->createElementNS(self::XML_SCHEMA, 'class', $injectDefinition->class()->name()) + $dom->createElementNS(self::XML_SCHEMA, 'class', $injectDefinition->classMethodParameter()->class()->name()) ); - $methodName = $injectDefinition->methodName(); + $methodName = $injectDefinition->classMethodParameter()->methodName(); $root->appendChild( $dom->createElementNS(self::XML_SCHEMA, 'method', $methodName) ); $root->appendChild( - $dom->createElementNS(self::XML_SCHEMA, 'parameter', $injectDefinition->parameterName()) + $dom->createElementNS(self::XML_SCHEMA, 'parameter', $injectDefinition->classMethodParameter()->parameterName()) ); } diff --git a/src/Definition/ServiceDelegateDefinition.php b/src/Definition/ServiceDelegateDefinition.php index 2dbaa5a8..d9895a85 100644 --- a/src/Definition/ServiceDelegateDefinition.php +++ b/src/Definition/ServiceDelegateDefinition.php @@ -4,32 +4,15 @@ use Cspray\AnnotatedContainer\Attribute\ServiceDelegateAttribute; use Cspray\AnnotatedContainer\Reflection\Type; -use Cspray\Typiphy\ObjectType; /** * Defines a factory method for creating a specific type of Service. */ interface ServiceDelegateDefinition { - /** - * Return the FQCN for the factory class that should create this Service. - * - * Please note that you can specify other Services in the Container and have them injected into the constructor - * of this factory class. - */ - public function delegateType() : Type; - - /** - * Return the method on the delegateType that should be invoked to create the Service. - * - * The method can accept Services or otherwise inject values from the Container. The Container will be used to - * execute the factory method. - * - * @return non-empty-string - */ - public function delegateMethod() : string; + public function service() : Type; - public function serviceType() : Type; + public function classMethod() : ClassMethod; /** * @return list diff --git a/src/Definition/ServicePrepareDefinition.php b/src/Definition/ServicePrepareDefinition.php index 3bb29d7c..ca29166f 100644 --- a/src/Definition/ServicePrepareDefinition.php +++ b/src/Definition/ServicePrepareDefinition.php @@ -19,12 +19,7 @@ interface ServicePrepareDefinition { */ public function service() : Type; - /** - * The method that should be invoked on the Service. - * - * @return non-empty-string - */ - public function methodName() : string; + public function classMethod() : ClassMethod; public function attribute() : ServicePrepareAttribute; } diff --git a/src/Exception/InvalidReflectionParameterForInjectDefinition.php b/src/Exception/InvalidReflectionParameterForInjectDefinition.php new file mode 100644 index 00000000..336903fc --- /dev/null +++ b/src/Exception/InvalidReflectionParameterForInjectDefinition.php @@ -0,0 +1,10 @@ + + */ private readonly array $profiles, private readonly bool $isPrimary, + /** + * @var ?non-empty-string + */ private readonly ?string $name, ) { } diff --git a/src/LogicalConstraint/Check/DuplicateServiceDelegate.php b/src/LogicalConstraint/Check/DuplicateServiceDelegate.php index a0baa081..7c2b3cd4 100644 --- a/src/LogicalConstraint/Check/DuplicateServiceDelegate.php +++ b/src/LogicalConstraint/Check/DuplicateServiceDelegate.php @@ -22,8 +22,8 @@ public function constraintViolations(ContainerDefinition $containerDefinition, P $delegateMap = []; foreach ($containerDefinition->serviceDelegateDefinitions() as $definition) { - $service = $definition->serviceType()->name(); - $method = sprintf('%s::%s', $definition->delegateType()->name(), $definition->delegateMethod()); + $service = $definition->service()->name(); + $method = sprintf('%s::%s', $definition->classMethod()->class()->name(), $definition->classMethod()->methodName()); $delegateMap[$service] ??= []; $attribute = $definition->attribute(); if ($attribute instanceof ServiceDelegateFromFunctionalApi) { diff --git a/src/LogicalConstraint/Check/DuplicateServicePrepare.php b/src/LogicalConstraint/Check/DuplicateServicePrepare.php index 2912e5d0..f268f136 100644 --- a/src/LogicalConstraint/Check/DuplicateServicePrepare.php +++ b/src/LogicalConstraint/Check/DuplicateServicePrepare.php @@ -15,14 +15,14 @@ final class DuplicateServicePrepare implements LogicalConstraint { public function constraintViolations(ContainerDefinition $containerDefinition, Profiles $profiles) : LogicalConstraintViolationCollection { $violations = new LogicalConstraintViolationCollection(); - /** @var array> $servicePrepareMap */ + /** @var array> $servicePrepareMap */ $servicePrepareMap = []; foreach ($containerDefinition->servicePrepareDefinitions() as $prepareDefinition) { $classMethod = sprintf( '%s::%s', $prepareDefinition->service()->name(), - $prepareDefinition->methodName() + $prepareDefinition->classMethod()->methodName() ); $servicePrepareMap[$classMethod] ??= []; diff --git a/src/LogicalConstraint/Check/MultiplePrimaryForAbstractService.php b/src/LogicalConstraint/Check/MultiplePrimaryForAbstractService.php index eec5f01c..76c1cb98 100644 --- a/src/LogicalConstraint/Check/MultiplePrimaryForAbstractService.php +++ b/src/LogicalConstraint/Check/MultiplePrimaryForAbstractService.php @@ -67,7 +67,9 @@ private function getAbstractServices(ContainerDefinition $containerDefinition) : private function getConcreteServicesInstanceOf(ContainerDefinition $containerDefinition, ServiceDefinition $serviceDefinition) : Generator { foreach ($containerDefinition->serviceDefinitions() as $service) { if ($service->isConcrete()) { - if (is_subclass_of($service->type()->name(), $serviceDefinition->type()->name())) { + $serviceDefinitionType = $serviceDefinition->type()->name(); + assert(class_exists($serviceDefinitionType)); + if (is_subclass_of($service->type()->name(), $serviceDefinitionType)) { yield $service; } } diff --git a/src/LogicalConstraint/Check/NonPublicServiceDelegate.php b/src/LogicalConstraint/Check/NonPublicServiceDelegate.php index 22751d12..97772622 100644 --- a/src/LogicalConstraint/Check/NonPublicServiceDelegate.php +++ b/src/LogicalConstraint/Check/NonPublicServiceDelegate.php @@ -15,7 +15,7 @@ public function constraintViolations(ContainerDefinition $containerDefinition, P $violations = new LogicalConstraintViolationCollection(); foreach ($containerDefinition->serviceDelegateDefinitions() as $delegateDefinition) { - $reflection = new \ReflectionMethod(sprintf('%s::%s', $delegateDefinition->delegateType()->name(), $delegateDefinition->delegateMethod())); + $reflection = new \ReflectionMethod(sprintf('%s::%s', $delegateDefinition->classMethod()->class()->name(), $delegateDefinition->classMethod()->methodName())); if ($reflection->isProtected() || $reflection->isPrivate()) { $protectedOrPrivate = $reflection->isProtected() ? 'protected' : 'private'; $violations->add( @@ -23,8 +23,8 @@ public function constraintViolations(ContainerDefinition $containerDefinition, P sprintf( 'A %s method, %s::%s, is marked as a service delegate. Service delegates MUST be marked public.', $protectedOrPrivate, - $delegateDefinition->delegateType()->name(), - $delegateDefinition->delegateMethod() + $delegateDefinition->classMethod()->class()->name(), + $delegateDefinition->classMethod()->methodName() ) ) ); diff --git a/src/LogicalConstraint/Check/NonPublicServicePrepare.php b/src/LogicalConstraint/Check/NonPublicServicePrepare.php index 17eb23e0..2dc95a98 100644 --- a/src/LogicalConstraint/Check/NonPublicServicePrepare.php +++ b/src/LogicalConstraint/Check/NonPublicServicePrepare.php @@ -15,7 +15,7 @@ public function constraintViolations(ContainerDefinition $containerDefinition, P $violations = new LogicalConstraintViolationCollection(); foreach ($containerDefinition->servicePrepareDefinitions() as $prepareDefinition) { - $reflection = new \ReflectionMethod(sprintf('%s::%s', $prepareDefinition->service()->name(), $prepareDefinition->methodName())); + $reflection = new \ReflectionMethod(sprintf('%s::%s', $prepareDefinition->service()->name(), $prepareDefinition->classMethod()->methodName())); if ($reflection->isPrivate() || $reflection->isProtected()) { $protectedOrPrivate = $reflection->isProtected() ? 'protected' : 'private'; $violations->add( @@ -24,7 +24,7 @@ public function constraintViolations(ContainerDefinition $containerDefinition, P 'A %s method, %s::%s, is marked as a service prepare. Service prepare methods MUST be marked public.', $protectedOrPrivate, $prepareDefinition->service()->name(), - $prepareDefinition->methodName() + $prepareDefinition->classMethod()->methodName() ) ) ); diff --git a/src/Profiles.php b/src/Profiles.php index 4bed40a2..52f978df 100644 --- a/src/Profiles.php +++ b/src/Profiles.php @@ -6,6 +6,8 @@ final class Profiles { + public const DEFAULT_PROFILE = 'default'; + /** * @var non-empty-list $profiles */ @@ -31,7 +33,7 @@ private function __construct(array $profiles) { } public static function defaultOnly() : self { - return new self(['default']); + return new self([self::DEFAULT_PROFILE]); } /** @@ -76,6 +78,23 @@ public function isAnyActive(array $profiles) : bool { return count(array_intersect($this->profiles, $profiles)) >= 1; } + public function priorityScore(array $profiles) : int { + if ($profiles === []) { + return -1; + } + + if ($profiles === [self::DEFAULT_PROFILE]) { + return 0; + } + + // we don't want to count the default profile if it is present + // only non-default active profiles increase the score + $active = array_diff($this->profiles, [self::DEFAULT_PROFILE]); + $profiles = array_diff($profiles, [self::DEFAULT_PROFILE]); + + return count(array_intersect($active, $profiles)); + } + /** * @return non-empty-list */ diff --git a/src/Reflection/TypeFactory.php b/src/Reflection/TypeFactory.php index a9df8c91..13510b73 100644 --- a/src/Reflection/TypeFactory.php +++ b/src/Reflection/TypeFactory.php @@ -2,6 +2,7 @@ namespace Cspray\AnnotatedContainer\Reflection; +use Cspray\AnnotatedContainer\Exception\UnknownReflectionType; use ReflectionNamedType; use ReflectionType; @@ -11,8 +12,7 @@ public function fromReflection(?ReflectionType $reflectionType) : Type|TypeUnion if ($reflectionType === null) { return $this->mixed(); } elseif ($reflectionType instanceof ReflectionNamedType) { - // if there is no name for the type it is implicitly mixed - $name = $reflectionType->getName() ?? 'mixed'; + $name = $reflectionType->getName(); $type = $this->fromName($name); if ($reflectionType->allowsNull() && $type !== $this->mixed() && $type !== $this->null()) { @@ -33,13 +33,15 @@ public function fromReflection(?ReflectionType $reflectionType) : Type|TypeUnion $intersectTypes[] = $this->fromReflection($rt); } $type = $this->intersect(...$intersectTypes); + } else { + throw UnknownReflectionType::fromReflectionTypeInvalid(); } return $type; } public function fromName(string $name) : Type { - return match ($name) { + $type = match ($name) { 'array' => $this->array(), 'bool' => $this->bool(), 'float' => $this->float(), @@ -52,75 +54,94 @@ public function fromName(string $name) : Type { 'static' => $this->static(), 'string' => $this->string(), 'void' => $this->void(), - default => $this->class($name), + default => null, }; + if ($type === null) { + assert(class_exists($name)); + $type = $this->class($name); + } + + return $type; } public function array() : Type { + /** @var ?Type $type */ static $type; return $type ??= $this->createType('array'); } public function bool() : Type { + /** @var ?Type $type */ static $type; return $type ??= $this->createType('bool'); } /** * @param class-string $class - * @return self + * @return Type */ public function class(string $class) : Type { + /** @var array $types */ static $types = []; return $types[$class] ??= $this->createType($class); } public function float() : Type { + /** @var ?Type $type */ static $type; return $type ??= $this->createType('float'); } public function int() : Type { + /** @var ?Type $type */ static $type; return $type ??= $this->createType('int'); } public function mixed() : Type { + /** @var ?Type $type */ static $type; return $type ??= $this->createType('mixed'); } public function never() : Type { + /** @var ?Type $type */ static $type; return $type ??= $this->createType('never'); } public function null() : Type { + /** @var ?Type $type */ static $type; return $type ??= $this->createType('null'); } public function object() : Type { + /** @var ?Type $type */ static $type; return $type ??= $this->createType('object'); } public function self() : Type { + /** @var ?Type $type */ static $type; return $type ??= $this->createType('self'); } public function static() : Type { + /** @var ?Type $type */ static $type; return $type ??= $this->createType('static'); } public function string() : Type { + /** @var ?Type $type */ static $type; return $type ??= $this->createType('string'); } public function void() : Type { + /** @var ?Type $type */ static $type; return $type ??= $this->createType('void'); } @@ -148,9 +169,16 @@ public function intersect(Type $first, Type $second, Type...$additional) : TypeI ); } + /** + * @param non-empty-string $typeName + * @return Type + */ private function createType(string $typeName) : Type { return new class($typeName) implements Type { public function __construct( + /** + * @var non-empty-string + */ private readonly string $name ) { } @@ -170,9 +198,15 @@ public function equals(TypeUnion|TypeIntersect|Type $type) : bool { } private function createTypeUnion(Type|TypeIntersect $one, Type|TypeIntersect $two, Type|TypeIntersect...$additional) : TypeUnion { - return new class([$one, $two, ...$additional]) implements TypeUnion { + return new class(array_values([$one, $two, ...$additional])) implements TypeUnion { + /** + * @var non-empty-string + */ private readonly string $name; public function __construct( + /** + * @var non-empty-list + */ private readonly array $types ) { $typeName = static fn(Type|TypeIntersect $type): string => @@ -185,7 +219,7 @@ public function name() : string { } /** - * @return list + * @return non-empty-list */ public function types() : array { return $this->types; @@ -208,8 +242,11 @@ public function equals(TypeUnion|TypeIntersect|Type $type) : bool { } private function createTypeIntersect(Type $one, Type $two, Type...$additional) : TypeIntersect { - return new class([$one, $two, ...$additional]) implements TypeIntersect { + return new class(array_values([$one, $two, ...$additional])) implements TypeIntersect { public function __construct( + /** + * @var non-empty-list + */ private readonly array $types ) { } @@ -221,6 +258,9 @@ public function name() : string { ); } + /** + * @return non-empty-list + */ public function types() : array { return $this->types; } diff --git a/src/Reflection/TypeUnion.php b/src/Reflection/TypeUnion.php index cc66df0b..de7d7742 100644 --- a/src/Reflection/TypeUnion.php +++ b/src/Reflection/TypeUnion.php @@ -10,7 +10,7 @@ interface TypeUnion extends TypeEqualityComparator { public function name() : string; /** - * @return list + * @return non-empty-list */ public function types() : array; } diff --git a/src/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalyzer.php b/src/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalyzer.php index 677637e9..746ad2f9 100644 --- a/src/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalyzer.php +++ b/src/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalyzer.php @@ -149,7 +149,7 @@ private function addAnnotatedDefinitions( } foreach ($consumer['serviceDelegateDefinitions'] as $serviceDelegateDefinition) { - $serviceDef = $this->serviceDefinition($containerDefinitionBuilder, $serviceDelegateDefinition->serviceType()); + $serviceDef = $this->serviceDefinition($containerDefinitionBuilder, $serviceDelegateDefinition->service()); // We need to handle the scenario where a user is using Annotated Container with limited or no Attributes // In that use case the user is providing many ServiceDelegate attributes, we should not require manually @@ -157,7 +157,7 @@ private function addAnnotatedDefinitions( // to properly represent the state of the Container for tooling and analysis. if ($serviceDef === null) { $impliedThroughDelegationServiceDefinition = definitionFactory()->serviceDefinitionFromObjectTypeAndAttribute( - $serviceDelegateDefinition->serviceType(), + $serviceDelegateDefinition->service(), new Service() ); $containerDefinitionBuilder = $containerDefinitionBuilder->withServiceDefinition( @@ -170,7 +170,7 @@ private function addAnnotatedDefinitions( $concretePrepareDefinitions = array_filter($consumer['servicePrepareDefinitions'], function (ServicePrepareDefinition $prepareDef) use ($containerDefinitionBuilder) { $serviceDef = $this->serviceDefinition($containerDefinitionBuilder, $prepareDef->service()); if (is_null($serviceDef)) { - $exception = InvalidServicePrepare::fromClassNotService($prepareDef->service()->name(), $prepareDef->methodName()); + $exception = InvalidServicePrepare::fromClassNotService($prepareDef->service()->name(), $prepareDef->classMethod()->methodName()); throw $exception; } return $serviceDef->isConcrete(); @@ -189,6 +189,7 @@ private function addAnnotatedDefinitions( foreach ($abstractPrepareDefinitions as $abstractPrepareDefinition) { $concreteServiceName = $concretePrepareDefinition->service()->name(); $abstractServiceName = $abstractPrepareDefinition->service()->name(); + assert(class_exists($abstractServiceName)); if (is_subclass_of($concreteServiceName, $abstractServiceName)) { $hasAbstractPrepare = true; break; @@ -278,6 +279,7 @@ private function addAliasDefinitions(ContainerDefinitionBuilder $containerDefini foreach ($abstractTypes as $abstractType) { foreach ($concreteTypes as $concreteType) { $abstractTypeString = $abstractType->name(); + assert(class_exists($abstractTypeString)); if (is_subclass_of($concreteType->name(), $abstractTypeString)) { $aliasDefinition = definitionFactory()->aliasDefinition($abstractType, $concreteType); $containerDefinitionBuilder = $containerDefinitionBuilder->withAliasDefinition($aliasDefinition); diff --git a/src/StaticAnalysis/ContainerDefinitionAnalysisOptions.php b/src/StaticAnalysis/ContainerDefinitionAnalysisOptions.php index 17b64b03..297f9b54 100644 --- a/src/StaticAnalysis/ContainerDefinitionAnalysisOptions.php +++ b/src/StaticAnalysis/ContainerDefinitionAnalysisOptions.php @@ -12,7 +12,7 @@ interface ContainerDefinitionAnalysisOptions { /** * Return a list of directories to scan for annotated services. * - * @return list + * @return list */ public function scanDirectories() : array; diff --git a/test/Fixture/DelegatedServiceWithInjectedParameter/FooService.php b/test/Fixture/DelegatedServiceWithInjectedParameter/FooService.php new file mode 100644 index 00000000..bff407a6 --- /dev/null +++ b/test/Fixture/DelegatedServiceWithInjectedParameter/FooService.php @@ -0,0 +1,14 @@ +class(FooInterface::class); + } + + public function defaultImplementation() : Type { + return types()->class(DefaultImplementation::class); + } + + public function fooImplementation() : Type { + return types()->class(FooImplementation::class); + } + + public function barImplementation() : Type { + return types()->class(BarImplementation::class); + } + + public function bazQuxImplementation() : Type { + return types()->class(BazQuxImplementation::class); + } +} diff --git a/test/Fixture/PrioritizedProfileInject/Injector.php b/test/Fixture/PrioritizedProfileInject/Injector.php new file mode 100644 index 00000000..eacc1606 --- /dev/null +++ b/test/Fixture/PrioritizedProfileInject/Injector.php @@ -0,0 +1,18 @@ +class(Injector::class); + } +} diff --git a/test/Fixture/PrioritizedProfileInjectPrepare/Injector.php b/test/Fixture/PrioritizedProfileInjectPrepare/Injector.php new file mode 100644 index 00000000..28ef2437 --- /dev/null +++ b/test/Fixture/PrioritizedProfileInjectPrepare/Injector.php @@ -0,0 +1,24 @@ +value = $value; + } +} diff --git a/test/Fixture/PrioritizedProfileInjectPrepareFixture.php b/test/Fixture/PrioritizedProfileInjectPrepareFixture.php new file mode 100644 index 00000000..ddcfaf94 --- /dev/null +++ b/test/Fixture/PrioritizedProfileInjectPrepareFixture.php @@ -0,0 +1,18 @@ +class(Injector::class); + } +} diff --git a/test/Unit/Bootstrap/ComposerRuntimePackagesComposerJsonPathProviderTest.php b/test/Unit/Bootstrap/ComposerRuntimePackagesComposerJsonPathProviderTest.php index 6f95c21e..c24e0e43 100644 --- a/test/Unit/Bootstrap/ComposerRuntimePackagesComposerJsonPathProviderTest.php +++ b/test/Unit/Bootstrap/ComposerRuntimePackagesComposerJsonPathProviderTest.php @@ -55,6 +55,7 @@ public function testCorrectPathsAreReturnedBasedOnInstalledPackages() : void { $phpunitToolsDir . '/vendor/sebastian/recursion-context/composer.json', $phpunitToolsDir . '/vendor/sebastian/type/composer.json', $phpunitToolsDir . '/vendor/sebastian/version/composer.json', + $phpunitToolsDir . '/vendor/staabm/side-effects-detector/composer.json', $phpunitToolsDir . '/vendor/theseer/tokenizer/composer.json', ]; diff --git a/test/Unit/ContainerDefinitionAssertionsTrait.php b/test/Unit/ContainerDefinitionAssertionsTrait.php index 3c8b2aaf..e1995c43 100644 --- a/test/Unit/ContainerDefinitionAssertionsTrait.php +++ b/test/Unit/ContainerDefinitionAssertionsTrait.php @@ -68,7 +68,7 @@ protected function assertServicePrepareTypes(array $expectedServicePrepare, arra foreach ($servicePrepareDefinitions as $servicePrepareDefinition) { $this->assertInstanceOf(ServicePrepareDefinition::class, $servicePrepareDefinition); $key = $servicePrepareDefinition->service()->name(); - $actualMap[] = [$key, $servicePrepareDefinition->methodName()]; + $actualMap[] = [$key, $servicePrepareDefinition->classMethod()->methodName()]; } array_multisort($actualMap); diff --git a/test/Unit/ContainerFactory/ContainerFactoryTestCase.php b/test/Unit/ContainerFactory/ContainerFactoryTestCase.php index 93d59e84..edbd772a 100644 --- a/test/Unit/ContainerFactory/ContainerFactoryTestCase.php +++ b/test/Unit/ContainerFactory/ContainerFactoryTestCase.php @@ -13,6 +13,7 @@ use Cspray\AnnotatedContainer\Definition\Serializer\XmlContainerDefinitionSerializer; use Cspray\AnnotatedContainer\Event\Emitter; use Cspray\AnnotatedContainer\Exception\InvalidAlias; +use Cspray\AnnotatedContainer\Exception\MultipleInjectOnSameParameter; use Cspray\AnnotatedContainer\Exception\ParameterStoreNotFound; use Cspray\AnnotatedContainer\Profiles; use Cspray\AnnotatedContainer\Reflection\Type; @@ -26,6 +27,8 @@ use Cspray\AnnotatedContainer\Unit\Helper\StubParameterStore; use Cspray\AnnotatedContainer\Fixture\Fixture; use Cspray\AnnotatedContainer\Fixture\Fixtures; +use Cspray\AnnotatedContainer\Unit\LogicalErrorApps\MultipleInjectDefinition\InjectService; +use Cspray\AnnotatedContainer\Unit\LogicalErrorApps\MultipleInjectPrepare\InjectServicePrepare; use Cspray\AnnotatedTarget\PhpParserAnnotatedTargetParser; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; @@ -543,4 +546,90 @@ public function testCreatingServiceWithProfileAwareServiceDelegate(array $profil self::assertSame($expected, $service->get()); } + + public function testCreatingPrioritizedProfileWithOnlyDefaultHasCorrectServiceCreated() : void { + $container = $this->getContainer(Fixtures::prioritizedProfile()->getPath(), Profiles::defaultOnly()); + + $service = $container->get(Fixtures::prioritizedProfile()->fooInterface()->name()); + + self::assertInstanceOf( + Fixtures::prioritizedProfile()->defaultImplementation()->name(), + $service + ); + } + + public function testCreatingPrioritizedProfileWithSingleProfileHasCorrectServiceCreated() : void { + $container = $this->getContainer(Fixtures::prioritizedProfile()->getPath(), Profiles::fromList(['default', 'foo'])); + + $service = $container->get(Fixtures::prioritizedProfile()->fooInterface()->name()); + + self::assertInstanceOf( + Fixtures::prioritizedProfile()->fooImplementation()->name(), + $service + ); + } + + public function testCreatingPrioritizedProfileWithMultipleProfilesHasCorrectServiceCreated() : void { + $container = $this->getContainer(Fixtures::prioritizedProfile()->getPath(), Profiles::fromList(['default', 'baz', 'qux'])); + + $service = $container->get(Fixtures::prioritizedProfile()->fooInterface()->name()); + + self::assertInstanceOf( + Fixtures::prioritizedProfile()->bazQuxImplementation()->name(), + $service + ); + } + + public static function prioritizedProfileInjectProvider() : array { + return [ + 'default' => [Profiles::defaultOnly(), 'default'], + 'foo' => [Profiles::fromList(['default', 'foo']), 'foo'], + 'baz-qux' => [Profiles::fromList(['default', 'foo', 'baz', 'qux']), 'baz-qux'], + ]; + } + + #[DataProvider('prioritizedProfileInjectProvider')] + public function testCreatingServiceWithPrioritizedProfileInject(Profiles $profiles, string $expected) : void { + $container = $this->getContainer(Fixtures::prioritizedProfileInject()->getPath(), $profiles); + + $service = $container->get(Fixtures::prioritizedProfileInject()->injector()->name()); + + self::assertSame($expected, $service->value); + } + + #[DataProvider('prioritizedProfileInjectProvider')] + public function testCreatingServiceWithPrioritizedProfileInjectPrepare(Profiles $profiles, string $expected) : void { + $container = $this->getContainer(Fixtures::prioritizedProfileInjectPrepare()->getPath(), $profiles); + + $service = $container->get(Fixtures::prioritizedProfileInjectPrepare()->injector()->name()); + + self::assertSame($expected, $service->value); + } + + public function testCreatingServiceWithMultipleInjectOnConstructThrowsException() : void { + $this->expectException(MultipleInjectOnSameParameter::class); + $this->expectExceptionMessage('Multiple InjectDefinitions were found for ' . InjectService::class . '::__construct($value).'); + + $this->getContainer(__DIR__ . '/../LogicalErrorApps/MultipleInjectDefinition'); + } + + public function testCreatingServiceWithMultipleInjectOnServicePrepareThrowsException() : void { + $this->expectException(MultipleInjectOnSameParameter::class); + $this->expectExceptionMessage('Multiple InjectDefinitions were found for ' . InjectServicePrepare::class . '::setValue($value).'); + + $container = $this->getContainer(__DIR__ . '/../LogicalErrorApps/MultipleInjectPrepare'); + $container->get(InjectServicePrepare::class); + } + + public function testCreatingDelegatedServiceWithInstancedFactoryWithInjectDefinition() : void { + $container = $this->getContainer(__DIR__ . '/../../Fixture/DelegatedServiceWithInjectedParameter'); + + $service = $container->get(\Cspray\AnnotatedContainer\Fixture\DelegatedServiceWithInjectedParameter\ServiceInterface::class); + + self::assertInstanceOf( + \Cspray\AnnotatedContainer\Fixture\DelegatedServiceWithInjectedParameter\FooService::class, + $service + ); + self::assertSame('my injected value', $service->value); + } } diff --git a/test/Unit/ContainerFactory/StandardAliasDefinitionResolverTest.php b/test/Unit/ContainerFactory/StandardAliasDefinitionResolverTest.php index da0a68c0..df5dffd4 100644 --- a/test/Unit/ContainerFactory/StandardAliasDefinitionResolverTest.php +++ b/test/Unit/ContainerFactory/StandardAliasDefinitionResolverTest.php @@ -5,6 +5,7 @@ use Cspray\AnnotatedContainer\ContainerFactory\AliasResolution\AliasResolutionReason; use Cspray\AnnotatedContainer\ContainerFactory\AliasResolution\StandardAliasDefinitionResolver; use Cspray\AnnotatedContainer\Fixture\Fixtures; +use Cspray\AnnotatedContainer\Profiles; use Cspray\AnnotatedContainer\Unit\Helper\HasMockDefinitions; use PHPUnit\Framework\TestCase; @@ -20,7 +21,7 @@ public function testPassAbstractServiceDefinitionWithNoConcreteDefinitionReturns ); $subject = new StandardAliasDefinitionResolver(); - $resolution = $subject->resolveAlias($containerDefinition, Fixtures::ambiguousAliasedServices()->fooInterface()); + $resolution = $subject->resolveAlias($containerDefinition, Profiles::defaultOnly(), Fixtures::ambiguousAliasedServices()->fooInterface()); self::assertSame(AliasResolutionReason::NoConcreteService, $resolution->aliasResolutionReason()); self::assertNull($resolution->aliasDefinition()); @@ -42,7 +43,7 @@ public function testPassAbstractServiceDefinitionWithSingleConcreteDefinitionRet ); $subject = new StandardAliasDefinitionResolver(); - $resolution = $subject->resolveAlias($containerDefinition, Fixtures::ambiguousAliasedServices()->fooInterface()); + $resolution = $subject->resolveAlias($containerDefinition, Profiles::defaultOnly(), Fixtures::ambiguousAliasedServices()->fooInterface()); self::assertSame(AliasResolutionReason::SingleConcreteService, $resolution->aliasResolutionReason()); self::assertSame($aliasDefinition, $resolution->aliasDefinition()); @@ -68,7 +69,7 @@ public function testPassAbstractServiceDefinitionWithMultipleConcreteDefinitionR ); $subject = new StandardAliasDefinitionResolver(); - $resolution = $subject->resolveAlias($containerDefinition, Fixtures::ambiguousAliasedServices()->fooInterface()); + $resolution = $subject->resolveAlias($containerDefinition, Profiles::defaultOnly(), Fixtures::ambiguousAliasedServices()->fooInterface()); self::assertSame(AliasResolutionReason::MultipleConcreteService, $resolution->aliasResolutionReason()); self::assertNull($resolution->aliasDefinition()); @@ -88,7 +89,7 @@ public function testPassAbstractServiceDefinitionWithMultipleConcreteDefinitionW ); $subject = new StandardAliasDefinitionResolver(); - $resolution = $subject->resolveAlias($containerDefinition, Fixtures::ambiguousAliasedServices()->fooInterface()); + $resolution = $subject->resolveAlias($containerDefinition, Profiles::defaultOnly(), Fixtures::ambiguousAliasedServices()->fooInterface()); self::assertSame(AliasResolutionReason::ConcreteServiceIsPrimary, $resolution->aliasResolutionReason()); self::assertSame($aliasDefinition, $resolution->aliasDefinition()); @@ -113,7 +114,7 @@ public function testDelegatedAbstractServiceHasNoAlias() : void { ); $subject = new StandardAliasDefinitionResolver(); - $resolution = $subject->resolveAlias($containerDefinition, Fixtures::delegatedService()->serviceInterface()); + $resolution = $subject->resolveAlias($containerDefinition, Profiles::defaultOnly(), Fixtures::delegatedService()->serviceInterface()); self::assertSame(AliasResolutionReason::ServiceIsDelegated, $resolution->aliasResolutionReason()); self::assertNull($resolution->aliasDefinition()); @@ -133,9 +134,49 @@ public function testMultiplePrimaryServiceIsNull() : void { ); $subject = new StandardAliasDefinitionResolver(); - $resolution = $subject->resolveAlias($containerDefinition, Fixtures::ambiguousAliasedServices()->fooInterface()); + $resolution = $subject->resolveAlias($containerDefinition, Profiles::defaultOnly(), Fixtures::ambiguousAliasedServices()->fooInterface()); self::assertSame(AliasResolutionReason::MultiplePrimaryService, $resolution->aliasResolutionReason()); self::assertNull($resolution->aliasDefinition()); } + + public function testConcreteServiceChosenAsAliasIfProfileHasHighestPriority() : void { + $containerDefinition = $this->containerDefinition( + serviceDefinitions: [ + $this->abstractServiceDefinition(Fixtures::injectServiceConstructorServices()->fooInterface()), + $this->concreteServiceDefinition(Fixtures::injectServiceConstructorServices()->barImplementation()), + $this->concreteServiceDefinition(Fixtures::injectServiceConstructorServices()->fooImplementation(), ['foo']) + ], + aliasDefinitions: [ + $this->aliasDefinition( + Fixtures::injectServiceConstructorServices()->fooInterface(), + Fixtures::injectServiceConstructorServices()->barImplementation(), + ), + $this->aliasDefinition( + Fixtures::injectServiceConstructorServices()->fooInterface(), + Fixtures::injectServiceConstructorServices()->fooImplementation(), + ) + ] + ); + + $subject = new StandardAliasDefinitionResolver(); + $reason = $subject->resolveAlias( + $containerDefinition, + Profiles::fromList(['default', 'foo']), + Fixtures::injectServiceConstructorServices()->fooInterface() + ); + + self::assertSame( + AliasResolutionReason::ConcreteServiceHasPrioritizedProfile, + $reason->aliasResolutionReason(), + ); + self::assertSame( + Fixtures::injectServiceConstructorServices()->fooInterface(), + $reason->aliasDefinition()->abstractService(), + ); + self::assertSame( + Fixtures::injectServiceConstructorServices()->fooImplementation(), + $reason->aliasDefinition()->concreteService(), + ); + } } diff --git a/test/Unit/Definition/DefinitionFactoryTest.php b/test/Unit/Definition/DefinitionFactoryTest.php index 567d310e..beeddab2 100644 --- a/test/Unit/Definition/DefinitionFactoryTest.php +++ b/test/Unit/Definition/DefinitionFactoryTest.php @@ -3,6 +3,7 @@ namespace Cspray\AnnotatedContainer\Unit\Definition; use Closure; +use Cspray\AnnotatedContainer\Attribute\Inject; use Cspray\AnnotatedContainer\Attribute\InjectAttribute; use Cspray\AnnotatedContainer\Attribute\ServiceAttribute; use Cspray\AnnotatedContainer\Attribute\ServiceDelegateAttribute; @@ -13,6 +14,7 @@ use Cspray\AnnotatedContainer\Definition\ServiceDelegateDefinition; use Cspray\AnnotatedContainer\Definition\ServicePrepareDefinition; use Cspray\AnnotatedContainer\Exception\InjectAttributeRequired; +use Cspray\AnnotatedContainer\Exception\InvalidReflectionParameterForInjectDefinition; use Cspray\AnnotatedContainer\Exception\ServiceAttributeRequired; use Cspray\AnnotatedContainer\Exception\ServiceDelegateAttributeRequired; use Cspray\AnnotatedContainer\Exception\ServiceDelegateReturnsIntersectionType; @@ -406,14 +408,11 @@ public function testServicePrepareDefinitionFromAnnotatedTargetWithValidParamete $attribute = $this->createMock(ServicePrepareAttribute::class); $definition = ($definitionCreator->bindTo($this, $this))($attribute); - self::assertSame( - types()->class(LoggerAwareInterface::class), - $definition->service() - ); - self::assertSame( - 'setLogger', - $definition->methodName() - ); + self::assertInstanceOf(ServicePrepareDefinition::class, $definition); + self::assertSame(types()->class(LoggerAwareInterface::class), $definition->service()); + self::assertSame(types()->class(LoggerAwareInterface::class), $definition->classMethod()->class()); + self::assertSame('setLogger', $definition->classMethod()->methodName()); + self::assertFalse($definition->classMethod()->isStatic()); self::assertSame($attribute, $definition->attribute()); } @@ -638,22 +637,12 @@ public function testServiceDelegateDefinitionFromAnnotatedTargetWithValidParamet $definition = ($definitionCreator->bindTo($this, $this))($attribute); - self::assertSame( - Fixtures::delegatedService()->serviceInterface(), - $definition->serviceType() - ); - self::assertSame( - Fixtures::delegatedService()->serviceFactory(), - $definition->delegateType() - ); - self::assertSame( - 'createService', - $definition->delegateMethod(), - ); - self::assertSame( - ['default'], - $definition->profiles(), - ); + self::assertInstanceOf(ServiceDelegateDefinition::class, $definition); + self::assertSame(Fixtures::delegatedService()->serviceInterface(), $definition->service()); + self::assertSame(Fixtures::delegatedService()->serviceFactory(), $definition->classMethod()->class()); + self::assertSame('createService', $definition->classMethod()->methodName()); + self::assertFalse($definition->classMethod()->isStatic()); + self::assertSame(['default'], $definition->profiles()); self::assertSame($attribute, $definition->attribute()); } @@ -668,22 +657,11 @@ public function testServiceDelegateDefinitionFromStaticFactoryReturnsSelfCreates $attribute ); - self::assertSame( - Fixtures::thirdPartyKitchenSink()->nonAnnotatedService(), - $definition->serviceType() - ); - self::assertSame( - Fixtures::thirdPartyKitchenSink()->nonAnnotatedService(), - $definition->delegateType() - ); - self::assertSame( - 'create', - $definition->delegateMethod(), - ); - self::assertSame( - ['default'], - $definition->profiles(), - ); + self::assertSame(Fixtures::thirdPartyKitchenSink()->nonAnnotatedService(), $definition->service()); + self::assertSame(Fixtures::thirdPartyKitchenSink()->nonAnnotatedService(), $definition->classMethod()->class()); + self::assertSame('create', $definition->classMethod()->methodName()); + self::assertTrue($definition->classMethod()->isStatic()); + self::assertSame(['default'], $definition->profiles()); self::assertSame($attribute, $definition->attribute()); } @@ -698,22 +676,12 @@ public function testServiceDelegateWithExplicitProfilesRespected(Closure $defini $definition = ($definitionCreator->bindTo($this, $this))($attribute); - self::assertSame( - Fixtures::delegatedService()->serviceInterface(), - $definition->serviceType() - ); - self::assertSame( - Fixtures::delegatedService()->serviceFactory(), - $definition->delegateType() - ); - self::assertSame( - 'createService', - $definition->delegateMethod(), - ); - self::assertSame( - ['drip', 'hippopotamus', 'chameleon'], - $definition->profiles(), - ); + self::assertInstanceOf(ServiceDelegateDefinition::class, $definition); + self::assertSame(Fixtures::delegatedService()->serviceInterface(), $definition->service()); + self::assertSame(Fixtures::delegatedService()->serviceFactory(), $definition->classMethod()->class()); + self::assertSame('createService', $definition->classMethod()->methodName()); + self::assertFalse($definition->classMethod()->isStatic()); + self::assertSame(['drip', 'hippopotamus', 'chameleon'], $definition->profiles()); self::assertSame($attribute, $definition->attribute()); } @@ -1259,15 +1227,17 @@ public function testInjectDefinitionFromAnnotatedTargetWithTypeHasCorrectInforma $definition = ($definitionCreator->bindTo($this, $this))($attribute); - self::assertSame($service, $definition->class()); - self::assertSame('__construct', $definition->methodName()); - self::assertSame($parameterName, $definition->parameterName()); + self::assertInstanceOf(InjectDefinition::class, $definition); + self::assertSame($service, $definition->service()); + self::assertSame($service, $definition->classMethodParameter()->class()); + self::assertSame('__construct', $definition->classMethodParameter()->methodName()); + self::assertSame($parameterName, $definition->classMethodParameter()->parameterName()); if ($type instanceof Type) { - self::assertSame($type, $definition->type()); + self::assertSame($type, $definition->classMethodParameter()->type()); } else { - self::assertSame($type->types(), $definition->type()->types()); + self::assertSame($type->types(), $definition->classMethodParameter()->type()->types()); } - + self::assertFalse($definition->classMethodParameter()->isStatic()); self::assertSame($value, $definition->value()); self::assertSame(['default'], $definition->profiles()); self::assertNull($definition->storeName()); @@ -1312,10 +1282,13 @@ public function testInjectDefinitionFromAnnotatedTargetWithExplicitProfiles(Clos $definition = ($definitionCreator->bindTo($this, $this))($attribute); - self::assertSame($service, $definition->class()); - self::assertSame('__construct', $definition->methodName()); - self::assertSame('values', $definition->parameterName()); - self::assertSame(types()->array(), $definition->type()); + self::assertInstanceOf(InjectDefinition::class, $definition); + self::assertSame($service, $definition->service()); + self::assertSame($service, $definition->classMethodParameter()->class()); + self::assertSame('__construct', $definition->classMethodParameter()->methodName()); + self::assertSame('values', $definition->classMethodParameter()->parameterName()); + self::assertSame(types()->array(), $definition->classMethodParameter()->type()); + self::assertFalse($definition->classMethodParameter()->isStatic()); self::assertSame(['foo', 'bar'], $definition->value()); self::assertSame(['test'], $definition->profiles()); self::assertNull($definition->storeName()); @@ -1337,16 +1310,28 @@ public function testInjectDefinitionWithExplicitWithDefinedStoreName(Closure $de $definition = ($definitionCreator->bindTo($this, $this))($attribute); - self::assertSame($service, $definition->class()); - self::assertSame('__construct', $definition->methodName()); - self::assertSame('values', $definition->parameterName()); - self::assertSame(types()->array(), $definition->type()); + self::assertInstanceOf(InjectDefinition::class, $definition); + self::assertSame($service, $definition->service()); + self::assertSame($service, $definition->classMethodParameter()->class()); + self::assertSame('__construct', $definition->classMethodParameter()->methodName()); + self::assertSame('values', $definition->classMethodParameter()->parameterName()); + self::assertSame(types()->array(), $definition->classMethodParameter()->type()); + self::assertFalse($definition->classMethodParameter()->isStatic()); self::assertSame(['foo', 'bar'], $definition->value()); self::assertSame(['default'], $definition->profiles()); self::assertSame('some store name', $definition->storeName()); self::assertSame($attribute, $definition->attribute()); } + public function testInjectDefinitionFromReflectionParameterWithNoDeclaringClassThrowsException() : void { + $reflectionParameter = (new \ReflectionFunction('strlen'))->getParameters()[0]; + + $this->expectException(InvalidReflectionParameterForInjectDefinition::class); + $this->expectExceptionMessage('A ReflectionParameter used to create an InjectDefinition MUST contain a declaring class.'); + + $this->subject->injectDefinitionFromReflectionParameterAndAttribute($reflectionParameter, new Inject('value')); + } + public function testAliasDefinition() : void { $definition = $this->subject->aliasDefinition( Fixtures::ambiguousAliasedServices()->fooInterface(), diff --git a/test/Unit/Helper/HasMockDefinitions.php b/test/Unit/Helper/HasMockDefinitions.php index 066060f9..f53f8803 100644 --- a/test/Unit/Helper/HasMockDefinitions.php +++ b/test/Unit/Helper/HasMockDefinitions.php @@ -7,6 +7,8 @@ use Cspray\AnnotatedContainer\Attribute\ServiceDelegateAttribute; use Cspray\AnnotatedContainer\Attribute\ServicePrepareAttribute; use Cspray\AnnotatedContainer\Definition\AliasDefinition; +use Cspray\AnnotatedContainer\Definition\ClassMethod; +use Cspray\AnnotatedContainer\Definition\ClassMethodParameter; use Cspray\AnnotatedContainer\Definition\ContainerDefinition; use Cspray\AnnotatedContainer\Definition\InjectDefinition; use Cspray\AnnotatedContainer\Definition\ServiceDefinition; @@ -93,7 +95,12 @@ private function servicePrepareDefinition( ) : ServicePrepareDefinition { $mock = $this->createMock(ServicePrepareDefinition::class); $mock->method('service')->willReturn($service); - $mock->method('methodName')->willReturn($method); + $classMethod = $this->createMock(ClassMethod::class); + $classMethod->method('class')->willReturn($service); + $classMethod->method('methodName')->willReturn($method); + $classMethod->method('isStatic')->willReturn(false); + + $mock->method('classMethod')->willReturn($classMethod); $attribute = $this->createMock(ServicePrepareAttribute::class); $mock->method('attribute')->willReturn($attribute); @@ -107,10 +114,15 @@ private function serviceDelegateDefinition( array $profiles = [], ) : ServiceDelegateDefinition { $mock = $this->createMock(ServiceDelegateDefinition::class); - $mock->method('delegateType')->willReturn($factory); - $mock->method('delegateMethod')->willReturn($method); - $mock->method('serviceType')->willReturn($service); + $classMethod = $this->createMock(ClassMethod::class); + $classMethod->method('class')->willReturn($factory); + $classMethod->method('methodName')->willReturn($method); + $classMethod->method('isStatic')->willReturn(false); + + $mock->method('service')->willReturn($service); $mock->method('profiles')->willReturn($profiles); + $mock->method('classMethod')->willReturn($classMethod); + $attribute = $this->createMock(ServiceDelegateAttribute::class); $attribute->method('profiles')->willReturn([]); $attribute->method('service')->willReturn(null); @@ -137,10 +149,15 @@ private function injectDefinition( ?string $store = null ) { $mock = $this->createMock(InjectDefinition::class); - $mock->method('class')->willReturn($service); - $mock->method('methodName')->willReturn($method); - $mock->method('parameterName')->willReturn($parameter); - $mock->method('type')->willReturn($type); + $mock->method('service')->willReturn($service); + $classMethod = $this->createMock(ClassMethodParameter::class); + $classMethod->method('class')->willReturn($service); + $classMethod->method('methodName')->willReturn($method); + $classMethod->method('parameterName')->willReturn($parameter); + $classMethod->method('type')->willReturn($type); + $classMethod->method('isStatic')->willReturn(false); + + $mock->method('classMethodParameter')->willReturn($classMethod); $mock->method('value')->willReturn($value); $mock->method('profiles')->willReturn($profiles); $mock->method('storeName')->willReturn($store); diff --git a/test/Unit/LogicalConstraint/Check/DuplicateServiceDelegateTest.php b/test/Unit/LogicalConstraint/Check/DuplicateServiceDelegateTest.php index 49528094..c51df6ac 100644 --- a/test/Unit/LogicalConstraint/Check/DuplicateServiceDelegateTest.php +++ b/test/Unit/LogicalConstraint/Check/DuplicateServiceDelegateTest.php @@ -32,7 +32,7 @@ public function testNoDuplicateDelegateHasNoViolations() : void { )->build() ); - $violations = $this->subject->constraintViolations($definition, Profiles::fromList(['default'])); + $violations = $this->subject->constraintViolations($definition, Profiles::defaultOnly()); self::assertCount(0, $violations); } @@ -44,7 +44,7 @@ public function testDuplicateDelegateAttributeForSameServiceHasCorrectViolation( )->build() ); - $violations = $this->subject->constraintViolations($definition, Profiles::fromList(['default'])); + $violations = $this->subject->constraintViolations($definition, Profiles::defaultOnly()); self::assertCount(1, $violations); @@ -86,7 +86,7 @@ public function consume(DefinitionProviderContext $context) : void { )->build() ); - $violations = $this->subject->constraintViolations($definition, Profiles::fromList(['default'])); + $violations = $this->subject->constraintViolations($definition, Profiles::defaultOnly()); self::assertCount(1, $violations); diff --git a/test/Unit/LogicalErrorApps/MultipleInjectDefinition/InjectService.php b/test/Unit/LogicalErrorApps/MultipleInjectDefinition/InjectService.php new file mode 100644 index 00000000..6784723e --- /dev/null +++ b/test/Unit/LogicalErrorApps/MultipleInjectDefinition/InjectService.php @@ -0,0 +1,17 @@ +toArray()); } + + public static function priorityScoreProvider() : array { + return [ + 'defaultOnly empty list' => [Profiles::defaultOnly(), [], -1], + 'defaultOnly with default' => [Profiles::defaultOnly(), ['default'], 0], + 'multiple profiles with just default' => [Profiles::fromList(['default', 'foo', 'bar']), ['default'], 0], + 'multiple profiles with single non-default active' => [Profiles::fromList(['default', 'foo', 'bar']), ['default', 'foo'], 1], + 'multiple profiles with multiple non-default active' => [Profiles::fromList(['default', 'foo', 'bar', 'baz']), ['default', 'foo', 'baz'], 2], + ]; + } + + #[DataProvider('priorityScoreProvider')] + public function testProfilesToScoreEmptyReturnsNegativeScore( + Profiles $profiles, + array $toScore, + int $expectedScore + ) : void { + self::assertSame($expectedScore, $profiles->priorityScore($toScore)); + } } diff --git a/test/Unit/Reflection/TypeFactoryTest.php b/test/Unit/Reflection/TypeFactoryTest.php index 1ce5b1b0..68db5d1e 100644 --- a/test/Unit/Reflection/TypeFactoryTest.php +++ b/test/Unit/Reflection/TypeFactoryTest.php @@ -3,6 +3,7 @@ namespace Cspray\AnnotatedContainer\Unit\Reflection; use Closure; +use Cspray\AnnotatedContainer\Exception\UnknownReflectionType; use Cspray\AnnotatedContainer\Reflection\Type; use Cspray\AnnotatedContainer\Reflection\TypeEqualityComparator; use Cspray\AnnotatedContainer\Reflection\TypeFactory; @@ -375,6 +376,16 @@ public function testTypeEqualityComparison( self::assertSame($expected, $a->equals($b)); } + public function testTypeFactoryFromReflectionNotCorrectTypeThrowsException() : void { + $typeFactory = new TypeFactory(); + $reflectionType = $this->createMock(\ReflectionType::class); + + $this->expectException(UnknownReflectionType::class); + $this->expectExceptionMessage('An unknown ReflectionType encountered, only ReflectionNamedType, ReflectionUnionType, and ReflectionIntersectionType are supported.'); + + $typeFactory->fromReflection($reflectionType); + } + private function reflectionTypeClass(): object { return new class { public function array() : array { diff --git a/test/Unit/Serializer/XmlContainerDefinitionSerializerTest.php b/test/Unit/Serializer/XmlContainerDefinitionSerializerTest.php index 425a3fc5..a85525d7 100644 --- a/test/Unit/Serializer/XmlContainerDefinitionSerializerTest.php +++ b/test/Unit/Serializer/XmlContainerDefinitionSerializerTest.php @@ -927,7 +927,7 @@ public function testDeserializeServicePrepareDefinitions() : void { self::assertCount(1, $actual->servicePrepareDefinitions()); $prepareDefinition = $actual->servicePrepareDefinitions()[0]; self::assertSame(Fixtures::interfacePrepareServices()->fooInterface(), $prepareDefinition->service()); - self::assertSame('setBar', $prepareDefinition->methodName()); + self::assertSame('setBar', $prepareDefinition->classMethod()->methodName()); self::assertEquals($attribute, $prepareDefinition->attribute()); } @@ -963,9 +963,9 @@ public function testDeserializeServiceDelegateDefinitions() : void { self::assertCount(1, $actual->serviceDelegateDefinitions()); $delegateDefinition = $actual->serviceDelegateDefinitions()[0]; - self::assertSame(Fixtures::delegatedService()->serviceInterface(), $delegateDefinition->serviceType()); - self::assertSame(Fixtures::delegatedService()->serviceFactory(), $delegateDefinition->delegateType()); - self::assertSame('createService', $delegateDefinition->delegateMethod()); + self::assertSame(Fixtures::delegatedService()->serviceInterface(), $delegateDefinition->service()); + self::assertSame(Fixtures::delegatedService()->serviceFactory(), $delegateDefinition->classMethod()->class()); + self::assertSame('createService', $delegateDefinition->classMethod()->methodName()); self::assertEquals($attribute, $delegateDefinition->attribute()); } @@ -1006,12 +1006,12 @@ public function testDeserializeInjectMethodParameter() : void { self::assertSame( Fixtures::injectConstructorServices()->injectStringService(), - $injectDefinition->class() + $injectDefinition->service() ); self::assertEquals($attribute, $injectDefinition->attribute()); - self::assertSame('__construct', $injectDefinition->methodName()); - self::assertSame('val', $injectDefinition->parameterName()); - self::assertSame(types()->string(), $injectDefinition->type()); + self::assertSame('__construct', $injectDefinition->classMethodParameter()->methodName()); + self::assertSame('val', $injectDefinition->classMethodParameter()->parameterName()); + self::assertSame(types()->string(), $injectDefinition->classMethodParameter()->type()); self::assertSame('foobar', $injectDefinition->value()); self::assertSame(['default'], $injectDefinition->profiles()); self::assertNull($injectDefinition->storeName()); @@ -1054,12 +1054,12 @@ public function testDeserializeInjectDefinitionUnitEnumValueMethodParameter() : self::assertSame( Fixtures::injectEnumConstructorServices()->enumInjector(), - $injectDefinition->class() + $injectDefinition->service() ); - self::assertSame('__construct', $injectDefinition->methodName()); + self::assertSame('__construct', $injectDefinition->classMethodParameter()->methodName()); self::assertEquals($attribute, $injectDefinition->attribute()); - self::assertSame('directions', $injectDefinition->parameterName()); - self::assertSame(types()->class(CardinalDirections::class), $injectDefinition->type()); + self::assertSame('directions', $injectDefinition->classMethodParameter()->parameterName()); + self::assertSame(types()->class(CardinalDirections::class), $injectDefinition->classMethodParameter()->type()); self::assertSame(CardinalDirections::West, $injectDefinition->value()); self::assertSame(['default'], $injectDefinition->profiles()); self::assertNull($injectDefinition->storeName()); @@ -1102,12 +1102,12 @@ public function testDeserializeInjectDefinitionMethodParameterWithStore() : void self::assertSame( Fixtures::injectCustomStoreServices()->scalarInjector(), - $injectDefinition->class() + $injectDefinition->service() ); - self::assertSame('__construct', $injectDefinition->methodName()); + self::assertSame('__construct', $injectDefinition->classMethodParameter()->methodName()); self::assertEquals($attribute, $injectDefinition->attribute()); - self::assertSame('key', $injectDefinition->parameterName()); - self::assertSame(types()->string(), $injectDefinition->type()); + self::assertSame('key', $injectDefinition->classMethodParameter()->parameterName()); + self::assertSame(types()->string(), $injectDefinition->classMethodParameter()->type()); self::assertSame('key', $injectDefinition->value()); self::assertSame(['default'], $injectDefinition->profiles()); self::assertSame('test-store', $injectDefinition->storeName()); @@ -1150,12 +1150,12 @@ public function testDeserializeInjectMethodWithProfiles() : void { self::assertSame( Fixtures::injectConstructorServices()->injectStringService(), - $injectDefinition->class() + $injectDefinition->service() ); self::assertEquals($attribute, $injectDefinition->attribute()); - self::assertSame('__construct', $injectDefinition->methodName()); - self::assertSame('val', $injectDefinition->parameterName()); - self::assertSame(types()->string(), $injectDefinition->type()); + self::assertSame('__construct', $injectDefinition->classMethodParameter()->methodName()); + self::assertSame('val', $injectDefinition->classMethodParameter()->parameterName()); + self::assertSame(types()->string(), $injectDefinition->classMethodParameter()->type()); self::assertSame('annotated container', $injectDefinition->value()); self::assertSame(['foo', 'baz'], $injectDefinition->profiles()); self::assertNull($injectDefinition->storeName()); diff --git a/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalysisTests/HasTestsTrait/AssertExpectedInjectDefinition.php b/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalysisTests/HasTestsTrait/AssertExpectedInjectDefinition.php index cea33b49..36c159e9 100644 --- a/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalysisTests/HasTestsTrait/AssertExpectedInjectDefinition.php +++ b/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalysisTests/HasTestsTrait/AssertExpectedInjectDefinition.php @@ -29,7 +29,7 @@ public function assert(ExpectedInject $expectedInject, ContainerDefinition $cont } private function getDefinitionsForService(ExpectedInject $expectedInject, ContainerDefinition $containerDefinition) : array { - $definitionsForService = array_filter($containerDefinition->injectDefinitions(), fn($injectDefinition) => $injectDefinition->class() === $expectedInject->service); + $definitionsForService = array_filter($containerDefinition->injectDefinitions(), fn($injectDefinition) => $injectDefinition->service() === $expectedInject->service); if (empty($definitionsForService)) { Assert::fail(sprintf( 'Could not find an InjectDefinition for %s in the provided ContainerDefinition.', @@ -40,7 +40,7 @@ private function getDefinitionsForService(ExpectedInject $expectedInject, Contai } private function filterMethodName(ExpectedInject $expectedInject, array $injectDefinitions) : array { - $definitionsForInjectTarget = array_filter($injectDefinitions, fn($injectDefinition) => $injectDefinition->methodName() === $expectedInject->methodName); + $definitionsForInjectTarget = array_filter($injectDefinitions, fn($injectDefinition) => $injectDefinition->classMethodParameter()->methodName() === $expectedInject->methodName); if (empty($definitionsForInjectTarget)) { Assert::fail(sprintf( 'Could not find an InjectDefinition for method %s::%s.', @@ -52,7 +52,7 @@ private function filterMethodName(ExpectedInject $expectedInject, array $injectD } private function filterMethodParameter(ExpectedInject $expectedInject, array $injectDefinitions) : array { - $definitionsForParam = array_filter($injectDefinitions, fn($injectDefinition) => $injectDefinition->parameterName() === $expectedInject->tarname); + $definitionsForParam = array_filter($injectDefinitions, fn($injectDefinition) => $injectDefinition->classMethodParameter()->parameterName() === $expectedInject->tarname); if (empty($definitionsForParam)) { Assert::fail(sprintf( 'Could not find an InjectDefinition for parameter \'%s\' on method %s::%s.', @@ -65,7 +65,7 @@ private function filterMethodParameter(ExpectedInject $expectedInject, array $in } private function validateMethodType(ExpectedInject $expectedInject, array $injectDefinitions) : void { - $definitionsWithTypes = array_filter($injectDefinitions, static fn(InjectDefinition $injectDefinition): bool => $injectDefinition->type()->equals($expectedInject->type)); + $definitionsWithTypes = array_filter($injectDefinitions, static fn(InjectDefinition $injectDefinition): bool => $injectDefinition->classMethodParameter()->type()->equals($expectedInject->type)); if (empty($definitionsWithTypes)) { Assert::fail(sprintf( 'Could not find an InjectDefinition for parameter \'%s\' on method %s::%s with type \'%s\'.', diff --git a/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalysisTests/HasTestsTrait/HasServiceDelegateDefinitionTestsTrait.php b/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalysisTests/HasTestsTrait/HasServiceDelegateDefinitionTestsTrait.php index 134d72fd..9f15114b 100644 --- a/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalysisTests/HasTestsTrait/HasServiceDelegateDefinitionTestsTrait.php +++ b/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalysisTests/HasTestsTrait/HasServiceDelegateDefinitionTestsTrait.php @@ -16,13 +16,13 @@ abstract public static function serviceDelegateProvider() : array; final public function testServiceDelegateDefinition(ExpectedServiceDelegate $expectedServiceDelegate) : void { $definition = null; foreach ($this->getSubject()->serviceDelegateDefinitions() as $delegateDefinition) { - if ($delegateDefinition->serviceType() === $expectedServiceDelegate->service) { + if ($delegateDefinition->service() === $expectedServiceDelegate->service) { $definition = $delegateDefinition; break; } } - $this->assertSame($expectedServiceDelegate->factory, $definition?->delegateType()); - $this->assertSame($expectedServiceDelegate->method, $definition?->delegateMethod()); + $this->assertSame($expectedServiceDelegate->factory, $definition?->classMethod()->class()); + $this->assertSame($expectedServiceDelegate->method, $definition?->classMethod()->methodName()); } } diff --git a/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalysisTests/HasTestsTrait/HasServicePrepareDefinitionTestsTrait.php b/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalysisTests/HasTestsTrait/HasServicePrepareDefinitionTestsTrait.php index 911f70d0..caff1c21 100644 --- a/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalysisTests/HasTestsTrait/HasServicePrepareDefinitionTestsTrait.php +++ b/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalysisTests/HasTestsTrait/HasServicePrepareDefinitionTestsTrait.php @@ -26,7 +26,7 @@ final public function testServicePrepareDefinitionMethod(ExpectedServicePrepare fn(ServicePrepareDefinition $servicePrepareDefinition) => $servicePrepareDefinition->service() === $expectedServicePrepare->type ); $prepareMethods = array_map( - fn(ServicePrepareDefinition $servicePrepareDefinition) => $servicePrepareDefinition->methodName(), + fn(ServicePrepareDefinition $servicePrepareDefinition) => $servicePrepareDefinition->classMethod()->methodName(), $preparesForService ); diff --git a/test/Unit/ThirdPartyFunctionsTest.php b/test/Unit/ThirdPartyFunctionsTest.php index 94ec22e1..0fa44c98 100644 --- a/test/Unit/ThirdPartyFunctionsTest.php +++ b/test/Unit/ThirdPartyFunctionsTest.php @@ -57,18 +57,18 @@ public function testSingleConcreteServiceIsPrimary() { public function testServiceDelegateDefinition() { $serviceDelegateDefinition = serviceDelegate(Fixtures::delegatedService()->serviceFactory(), 'createService'); - $this->assertSame(Fixtures::delegatedService()->serviceInterface()->name(), $serviceDelegateDefinition->serviceType()->name()); - $this->assertSame(Fixtures::delegatedService()->serviceFactory()->name(), $serviceDelegateDefinition->delegateType()->name()); - $this->assertSame('createService', $serviceDelegateDefinition->delegateMethod()); + $this->assertSame(Fixtures::delegatedService()->serviceInterface()->name(), $serviceDelegateDefinition->service()->name()); + $this->assertSame(Fixtures::delegatedService()->serviceFactory()->name(), $serviceDelegateDefinition->classMethod()->class()->name()); + $this->assertSame('createService', $serviceDelegateDefinition->classMethod()->methodName()); $this->assertSame(['default'], $serviceDelegateDefinition->profiles()); } public function testServiceDelegateDefinitionWithExplicitProfiles() : void { $serviceDelegateDefinition = serviceDelegate(Fixtures::delegatedService()->serviceFactory(), 'createService', ['the', 'love', 'plug']); - $this->assertSame(Fixtures::delegatedService()->serviceInterface()->name(), $serviceDelegateDefinition->serviceType()->name()); - $this->assertSame(Fixtures::delegatedService()->serviceFactory()->name(), $serviceDelegateDefinition->delegateType()->name()); - $this->assertSame('createService', $serviceDelegateDefinition->delegateMethod()); + $this->assertSame(Fixtures::delegatedService()->serviceInterface()->name(), $serviceDelegateDefinition->service()->name()); + $this->assertSame(Fixtures::delegatedService()->serviceFactory()->name(), $serviceDelegateDefinition->classMethod()->class()->name()); + $this->assertSame('createService', $serviceDelegateDefinition->classMethod()->methodName()); $this->assertSame(['the', 'love', 'plug'], $serviceDelegateDefinition->profiles()); } @@ -89,10 +89,12 @@ public function testInjectMethodParam() { 42 ); - $this->assertSame(Fixtures::injectConstructorServices()->injectFloatService(), $inject->class()); - $this->assertSame('__construct', $inject->methodName()); - $this->assertSame('dessert', $inject->parameterName()); - $this->assertSame(types()->int(), $inject->type()); + $this->assertSame(Fixtures::injectConstructorServices()->injectFloatService(), $inject->service()); + $this->assertSame(Fixtures::injectConstructorServices()->injectFloatService(), $inject->classMethodParameter()->class()); + $this->assertSame('__construct', $inject->classMethodParameter()->methodName()); + $this->assertSame('dessert', $inject->classMethodParameter()->parameterName()); + $this->assertSame(types()->int(), $inject->classMethodParameter()->type()); + $this->assertFalse($inject->classMethodParameter()->isStatic()); $this->assertSame(42, $inject->value()); $this->assertSame(['default'], $inject->profiles()); $this->assertNull($inject->storeName());