From c0f9009b484175f5cd60ada694135b26084873da Mon Sep 17 00:00:00 2001 From: Charles Sprayberry Date: Tue, 24 Jun 2025 10:35:49 -0400 Subject: [PATCH 1/6] Work on supporting Laravel framework --- CHANGELOG.md | 60 ----- README.md | 5 +- docs/how-to/01-add-third-party-services.md | 68 +++++- docs/how-to/02-bootstrap-your-container.md | 216 +++++++++++++----- known-issues.xml | 108 +++++---- phpunit.xml | 2 - src/Bootstrap/Bootstrap.php | 8 +- .../XmlBootstrappingConfiguration.php | 6 +- .../AbstractContainerFactory.php | 104 --------- .../{ => Auryn}/AurynContainerFactory.php | 88 ++++--- .../ContainerFactoryOptions.php | 14 +- .../ContainerFactoryOptionsBuilder.php | 32 --- .../Illuminate/IlluminateContainerBinder.php | 91 ++++++++ .../IlluminateContainerFactory.php | 108 +++------ .../{ => PhpDi}/PhpDiContainerFactory.php | 61 ++--- .../State/ContainerFactoryState.php | 25 ++ .../State/InjectParameterValueProvider.php | 17 ++ .../State/ParameterResolver.php | 74 ++++++ src/Function/definitions.php | 7 +- .../InjectDefinitionFromFunctionalApi.php | 38 +++ test/Unit/Bootstrap/BootstrapTest.php | 4 +- .../AurynContainerFactoryTest.php | 2 +- .../ContainerFactoryOptionsBuilderTest.php | 5 +- .../ContainerFactoryTestCase.php | 5 +- .../IlluminateContainerFactoryTest.php | 2 +- .../PhpDiContainerFactoryTest.php | 2 +- test/Unit/ThirdPartyFunctionsTest.php | 92 ++++++-- 27 files changed, 744 insertions(+), 500 deletions(-) rename src/ContainerFactory/{ => Auryn}/AurynContainerFactory.php (72%) delete mode 100644 src/ContainerFactory/ContainerFactoryOptionsBuilder.php create mode 100644 src/ContainerFactory/Illuminate/IlluminateContainerBinder.php rename src/ContainerFactory/{ => Illuminate}/IlluminateContainerFactory.php (51%) rename src/ContainerFactory/{ => PhpDi}/PhpDiContainerFactory.php (77%) create mode 100644 src/ContainerFactory/State/InjectParameterValueProvider.php create mode 100644 src/ContainerFactory/State/ParameterResolver.php create mode 100644 src/Internal/InjectDefinitionFromFunctionalApi.php diff --git a/CHANGELOG.md b/CHANGELOG.md index e78d5903..94cf687f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,67 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [v3.0.0](https://github.com/cspray/annotated-container/tree/v3.0.0) - 2024-12-15 -The v3.0 release represents a substantial improvement in several areas of the project, but also includes several backwards compatability breaks; particularly if you were using container caching, providing Definitions via a DefinitionProvider, or utilizing the Bootstrap observer system. - -### Added - -- Version attribute to the `annotated-container.xml` XML configuration. In future iterations work may be done to ensure versions for your configuration are compatible with the version of the library installed. -- A more complete event system with informative events being emitted throughout the entire Annotated Container lifecycle. Previously, this functionality was much more limited and provided by Bootstrap observers. -- A Definition factory that allows creating all definitions, except for a `ContainerDefinition`, in a more complete, concise manner. Previously, this functionality was provided by Definition Builders using a Fluent API. -- More feature-complete caching system for a `ContainerDefinition`. Previously, this was powered by direct calls to PHP's filesystem functions. Now a purpose-built interface, `Cspray\AnnotatedContainer\Definition\Cache\ContainerDefinitionCache`, allows whatever caching strategy is most appropriate. -- A `Cspray\AnnotatedContainer\Filesystem\Filesystem` interface and implementation that allows for executing common tasks on a file. Previously, the native PHP functions for accessing a filesystem were being used. This change was primarily implemented to improve testability without requiring for a heavyweight abstraction from a third-party library. -- `Cspray\AnnotatedContainer\Profiles` value object that indicates what profiles are active when a container is created. -- Normalized the conventions around setting up the Annotated Container CLI tool. You can now use `Cspray\AnnotatedContainer\Cli\AnnotatedContainerCliRunner` in your own scripts, allowing you to easily customize caching, among other things. -- A bootstrapping utility class, `Cspray\AnnotatedContainer\Bootstrap\ContainerDefinitionAnalysisOptionsFromBootstrappingConfiguration` that allows consistently creating the `ContainerDefinitionAnalysisOptions` to be used. -- A discrete interface, `Cspray\AnnotatedContainer\Bootstrap\PackagesComposerJsonPathProvider`, to determine what path should be used when scanning a `composer.json` for third-party dependencies. This is utilized by the CLI tool to generate the appropriate configuration file. -- A discrete `DefaultDefinitionProviderFactory` implementation, previously this functionality was inlined into the bootstrap. - -### Changed - -- **Updated the required PHP version to 8.2!** -- Updated the version of `nikic/php-parser` used to 5.3. -- All interactions with the `Bootstrap` object are available through named, static methods. The `Bootstrap` constructor is now private and the `new` construct cannot be used. Generally speaking, you should make use of the new `Bootstrap::fromAnnotatedContainerConventions` method. -- Container factory to use is no longer implicit and must be explicitly provided during your bootstrapping. -- `AnnotatedContainer::getBackingContainer` was renamed to `backingContainer`. -- All Definitions that had a nullable `attribute` method were changed to require a non-null Attribute value. Please see the ADR "Require Definitions To Provide Attribute" for more information. -- Improved the `XmlContainerDefinitionSerializer` and corresponding XSD to remove redundant information being stored in the Attribute. -- The `AnnotatedContainerVersion` class was updated to use the `composer/runtime` dependency directly, instead of using `ocramius/package-versions`. -- Added template parameters to the `AutowireableFactory::make` method, allowing more type information to be conveyed. -- Several implementations had their `get` prefix removed, as it was redundant and not necessary. -- Moved test code that was in `fixture_src` under `test/Fixture`. -- When utilizing the `composer.json` configuration for configuring third-party initializers, more checks are made to ensure that a valid data structure has been passed. Previously, this had undefined behavior when invalid data types or values were present. - -### Fixed - -- Several places where `declare(strict_types=1)` was not properly set. -- Fixed an error where the interface `AutowireableParameterSet` was implementing a template instead of extending it. - -### Removed - -- All code associated with Bootstrap observers, including but not limited to: - - `Cspray\AnnotatedContainer\Bootstrap\ObserverFactory` - - `Cspray\AnnotatedContainer\Bootstrap\ContainerAnalyticsObserver` - - `Cspray\AnnotatedContainer\Bootstrap\ContainerCreatedObserver` - - `Cspray\AnnotatedContainer\Bootstrap\PostAnalysisObserver` - - `Cspray\AnnotatedContainer\Bootstrap\PreAnalysisObserver` - - `Cspray\AnnotatedContainer\Bootstrap\ServiceWiringObserver` (See `ServiceWiringListener`) - - `Cspray\AnnotatedContainer\Bootstrap\ThirdPartyInitializer::getObserverClasses` -- All Definition builder objects, except for the `ContainerDefinitionBuilder`. See the new `DefinitionFactory` for equivalent functionality. -- The `ActiveProfiles` value object. See the new `Profiles` value object for a replacement. -- All code associated with Configuration, including but not limited to: - - `Cspray\AnnotatedContainer\Definition\ConfigurationDefinition` - - `Cspray\AnnotatedContainer\Definition\ConfigurationDefinitionBuilder` - - `Cspray\AnnotatedContainer\Attribute\ConfigurationAttribute` - - `Cspray\AnnotatedContainer\Attribute\Configuration` -- Removed built-in file caching used in bootstrapping. This functionality is replaced by implementing your own `ContainerDefinitionCache` implementation, or explicitly using the `FileBackedContainerDefinitionCache` provided out-of-the-box. -- Removed built-in logging used throughout the library. This functionality will be replaced at a future date with a set of listeners that will log the same information. These listeners will require installing a separate repo and explicitly opting in. -- Removed the following Composer dependencies: - - brick/varexporter - - cspray/typiphy - - ocramius/package-versions - - psr/log ## [v2.4.0](https://github.com/cspray/annotated-container/tree/v2.4.0) - 2024-06-08 diff --git a/README.md b/README.md index 4685b6dc..45c093f4 100644 --- a/README.md +++ b/README.md @@ -89,10 +89,7 @@ have `php-di/php-di` installed. // app bootstrap in __DIR__ . '/app.php' require __DIR__ . '/vendor/autoload.php'; -use Cspray\AnnotatedContainer\Bootstrap\Bootstrap; -use Cspray\AnnotatedContainer\Event\Emitter; -use Cspray\AnnotatedContainer\Profiles; -use Cspray\AnnotatedContainer\ContainerFactory\PhpDiContainerFactory; +use Cspray\AnnotatedContainer\Bootstrap\Bootstrap;use Cspray\AnnotatedContainer\ContainerFactory\PhpDi\PhpDiContainerFactory;use Cspray\AnnotatedContainer\Event\Emitter;use Cspray\AnnotatedContainer\Profiles; $emitter = new Emitter(); diff --git a/docs/how-to/01-add-third-party-services.md b/docs/how-to/01-add-third-party-services.md index ac65328b..e1852ff9 100644 --- a/docs/how-to/01-add-third-party-services.md +++ b/docs/how-to/01-add-third-party-services.md @@ -2,15 +2,53 @@ It is very likely that you'll need to add some service to the Container that can't be annotated. AnnotatedContainer offers a set of functions to easily add third-party services with all the feature-parity and functionality available to annotated code. This guide goes through a step-by-step guide on how to integrate the popular [Monolog](https://github.com/Seldaek/monolog) library with [PSR-3](https://www.php-fig.org/psr/psr-3/) services. +Starting with Annotated Container 2.3.0, new functionality was added that allows much easier, implicit setup of third-party services. The "Implicit Setup", detailed below, is the preferred method of adding third-party services to your container. The "Explicit Setup" details what was the documented approach for versions prior to 2.3. It also uses an approach that does not rely on Attributes of any kind. If you're using Annotated Container without Attributes, this is the preferred approach for your use case. + > This guide assumes a basic understanding on how to interact with this library. If you're unsure of something we discuss here it is recommended you checkout the rest of the /docs/tutorials section. + +## Implicit Setup + +## Step 1 - Install PSR-3 and Monolog + +```shell +composer require monolog/monolog psr/log +``` + +## Step 2 - Create Factory and Assign ServiceDelegate + +```php +pushHandler(new StreamHandler('php://stdout')); + + return $log; + } + +} +``` + +This is all that's required for the implicit setup. When we encounter a `#[ServiceDelegate]` for a type that has not been defined as a service, we add the service implicitly and assign the appropriate factory method for creating that service. + +## Explicit Setup + ## Step 1 - Install PSR-3 and Monolog ```shell composer require monolog/monolog psr/log ``` -## Step 2 - Create a Service Factory +## Step 2 - Create Factory ```php addServiceDefinition(service($loggerType = objectType(LoggerInterface::class))); + $context->addServiceDefinition( + service(types()->class(LoggerInterface::class)) + ); $context->addServiceDelegateDefinition( - serviceDelegate(objectType(MonologLoggerFactory::class), 'createLogger') + serviceDelegate( + types()->class(MonologLoggerFactory::class), 'createLogger' + ) ); $context->addServicePrepareDefinition( servicePrepare( - objectType(LoggerAwareInterface::class), - 'setLogger' + types()->class(LoggerAwareInterface::class), 'setLogger' ) ); } @@ -66,7 +107,9 @@ class ThirdPartyServicesProvider implements DefinitionProvider { ```xml - + src @@ -88,7 +131,10 @@ use Psr\Log\LoggerInterface; use Cspray\AnnotatedContainer\Bootstrap\Bootstrap; use Cspray\AnnotatedContainer\Event\Emitter; -$container = Bootstrap::from(new Emitter())->bootstrapContainer(); +$container = Bootstrap::fromAnnotatedContainerConventions( + new YourContainerFactory(), + new Emitter() +)->bootstrapContainer(); ``` Now, your PSR Logger will be created through a factory. Any services can inject a `LoggerInterface` directly in the constructor, preferred, or implement the `LoggerAwareInterface` to have it injected automatically after construction. \ No newline at end of file diff --git a/docs/how-to/02-bootstrap-your-container.md b/docs/how-to/02-bootstrap-your-container.md index a6fd2892..78b54169 100644 --- a/docs/how-to/02-bootstrap-your-container.md +++ b/docs/how-to/02-bootstrap-your-container.md @@ -1,20 +1,22 @@ # Bootstrap Your Container -As Annotated Container has added more and more functionality the bootstrapping it requires has necessarily grown. It is possible to get up and running without using the provided tooling, but we highly recommend using the CLI tool and corresponding `Cspray\AnnotatedContainer\Bootstrap` to create your Container. This document details how to take advantage of Annotated Container's functionality using this tooling. +As Annotated Container has added more and more functionality, the bootstrapping it requires has necessarily grown. It is possible to get up and running without using the provided tooling, but we highly recommend using the CLI tool and corresponding `Cspray\AnnotatedContainer\Bootstrap\Bootstrap` to create your Container. This document details how to take advantage of Annotated Container's functionality using this tooling. ## Step 1 - Init Your Configuration -The first step is to create a configuration file that details how Annotated Container should bootstrap itself. As long as you have a `composer.json` in your project's root directory, and it defines at least one directory with a PSR-4 or PSR-0 namespace then the tooling can figure out which directories to scan. Run the following shell command: +The first step is to create a configuration file that details how Annotated Container should bootstrap itself. As long as you have a `composer.json` in your project's root directory, and it defines at least one directory with a PSR-4 or PSR-0 namespace, then the tooling can figure out which directories to scan. Run the following shell command: ```shell ./vendor/bin/annotated-container init ``` -If successful you'll get a configuration file named `annotated-container.xml` in the root of your project. In most setups it'll look something like this: +If successful, you'll get a configuration file named `annotated-container.xml` in the root of your project. In most setups it'll look something like this: ```xml - + src @@ -24,13 +26,13 @@ If successful you'll get a configuration file named `annotated-container.xml` in ``` -The most important, and the only thing that's actually required, is to define at least 1 source directory to scan. It should be noted that **all** directories autoloaded in your `composer.json` will be scanned, including any `autoload-dev` entries. If this is not desired, be sure to remove these directories after the configuration is generated. +The only required configuration is to define at least 1 source directory to scan. It should be noted that **all** directories autoloaded in your `composer.json` will be scanned, including any `autoload-dev` entries. If this is not desired, be sure to remove these directories after the configuration is generated. -The rest of this guide will add new elements to this configuration. The steps below are optional, if you don't require any "bells & whistles" skip to Step 4. +The rest of this guide will add new elements to this configuration. The steps below are optional, if you don't require any "bells & whistles" skip to Step 5. ## Step 2 - Setup Third Party Services (optional) -To define services that can't be annotated you can make use of a `Cspray\AnnotatedContainer\StaticAnalysis\DefinitionProvider` implementation. Implementing this interface allows you to use the [functional API](../references/03-functional-api.md) to augment your ContainerDefinition without using Attributes. Out-of-the-box, it is expected `DefinitionProvider` implementations will have a zero-argument constructor. Later on in this document I will discuss ways that you can override construction if your implementation has dependencies. Primarily this should be used to integrate third-party libraries that can't have Attributes assigned to them. +To define services that can't be annotated, you can make use of a `Cspray\AnnotatedContainer\StaticAnalysis\DefinitionProvider` implementation. Implementing this interface allows you to use the [functional API](../references/03-functional-api.md) to augment your ContainerDefinition without using Attributes. Out-of-the-box, it is expected `DefinitionProvider` implementations will have a zero-argument constructor. Later on in this document I will discuss ways that you can override construction if your implementation has dependencies. Primarily this should be used to integrate third-party libraries that can't have Attributes assigned to them. Somewhere in your source code: @@ -55,7 +57,9 @@ Now, upgrade the configuration to let bootstrapping know which class to use. ```xml - + src @@ -70,7 +74,7 @@ Now, upgrade the configuration to let bootstrapping know which class to use. ### Step 3 - Provide your custom ParameterStore (optional) -In any sufficiently large enough application you'll probably want to take advantage of parameter stores to have complete programmatic control over what non-service values get injected. You can define a list of ParameterStore implementations that should be added during bootstrapping. Out-of-the-box, it is expected that these implementations will have a no argument constructor. Later in this document I'll go over how to override construction of these implementations if they require dependencies. +In any sufficiently large enough application you'll probably want to take advantage of parameter stores to have complete programmatic control over what non-service values get injected. You can define a list of ParameterStore implementations that should be added during bootstrapping. Out-of-the-box, it is expected that these implementations will have a no argument constructor. Later in this document, I'll go over how to override construction of these implementations if they require dependencies. Somewhere in your source code: @@ -83,7 +87,6 @@ use Cspray\Annotatedcontainer\ContainerFactory\ParameterStore; final class MyCustomParameterStore implements ParameterStore { - public function getName() : string { return 'my-store'; } @@ -97,10 +100,11 @@ final class MyCustomParameterStore implements ParameterStore { Next, update your configuration. - ```xml - + src @@ -113,34 +117,76 @@ Next, update your configuration. ``` -### Step 4 - Create Your Container -Before completing this step go put some Attributes on the services in your codebase! +### Step 4 - Register your Listeners (optional) + +Starting with Annotated Container v3, the previously used Observer system was removed and replaced with a more complete, advanced Event system. Pretty much everything that Annotated Container does is covered by an Event and corresponding Listener. For a complete list of all available Listeners, check out the interfaces available in `Cspray\AnnotatedContainer\Event\Listener`. You might also be interested in extending the `Cspray\AnnotatedContainer\Bootstrap\Listener\ServiceWiringListener`. + +Listeners allow you to log information, do complex dependency wiring, and programmatically respond when things happen within the lifecycle of Annotated Container. You can define a list of these Listeners in the configuration, that will automatically be registered with the Emitter. Out-of-the-box, it is expected these implementations will have a no argument constructor. Later in this document, I'll go over ways to customize the construction of your Listeners. -Now that the configuration file has been modified, and you've attributed your codebase, to fit your needs you can create your container! If you're using only out-of-the-box functionality this can be done with the following code snippet. +In our example, we're going to implement the `Cspray\AnnotatedContainer\Event\Listener\Bootstrap\BeforeBoostrap` Listener. Somewhere in your source code: ```php bootstrapContainer(); +```xml + + + + + src + tests + + + + Acme\Demo\DemoListener + + ``` -It is important in this code that you add whatever Listeners might be appropriate to the `$emitter` using `Emitter::addListener`. This includes any Listeners that you might be using from third-party libraries. There is currently no plans to allow Listeners to be defined through configuration and MUST be added as part of your bootstrapping. The Emitter is one of the few pieces of Bootstrapping that is ALWAYS required. It cannot be stressed enough how important setting up your Listeners are in this section of your code. +### Step 5 - Create Your Container + +Before completing this step go put some Attributes on the services in your codebase! + +Now that the configuration file has been modified, and you've attributed your codebase, you can create your container! If you're using only out-of-the-box functionality this can be done with the following code snippet. + +```php +bootstrapContainer(); +``` -You have the ability to control specific aspects of the Bootstrapping process by providing different arguments to the `bootstrapContainer` method, using different static constructor methods, or adjust the optional parameters to `Bootstrap::fromMinimalSetup`. +You have the ability to control specific aspects of the Bootstrapping process by providing different arguments to the `bootstrapContainer` method, using different static constructor methods, or adjust the optional parameters to `Bootstrap::fromAnnotatedContainerConventions`. #### Specifying Profiles -The first argument, `$profiles`, passed to `bootstrapContainer` should be an instance of `Cspray\AnnotatedContainer\Profiles`. This value object has a variety of static constructor methods on it that allow creating an instance with the appropriate values for your use case. If you don't provide any instance of this value object the active profiles will be `['default']`. If you specify your own `Profiles` instance it is HIGHLY RECOMMENDED you included the `default` profile. Otherwise, it is highly expected that your Container will not be wired correctly. +The only argument, `$profiles`, passed to `bootstrapContainer` should be an instance of `Cspray\AnnotatedContainer\Profiles`. This value object has a variety of static constructor methods on it that allow creating an instance with the appropriate values for your use case. If you don't provide any instance of this value object the active profiles will be `['default']`. If you specify your own `Profiles` instance it is HIGHLY RECOMMENDED you included the `default` profile. Otherwise, it is highly expected that your Container will not be wired correctly. ```php bootstrapContainer(Profiles::fromList(['default', 'prod'])); +$container = Bootstrap::fromAnnotatedContainerConventions( + new YourContainerFactory(), + new Emitter() +)->bootstrapContainer(Profiles::fromList(['default', 'prod'])); ``` -#### Changing the Configuration File +#### Providing Your Own Configuration + +It is possible for you to provide your own `BootstrappingConfiguration`, in case you don't want to use the XML config that comes out-of-the-box. Or, you may have moved the XML config file to a new location, outside your root directory. In these situations, you'll want to provide your own configuration object. -Perhaps you didn't name your configuration file the default, it is recommended you do so but perhaps there's good reasons to change it. You can pass the second argument, `$configurationFile`, to `bootstrapContainer` that defines the name of the configuration file. If you don't pass any arguments the default value `annotated-container.xml` will be used. +If you provide your own Configuration, you can no longer make use of Annotated Container conventions. You'll need to use the `Bootstrap::fromCompleteSetup` method, which also requires you to provide a `BootstrappingDirectoryResolver`. ```php bootstrapContainer(bootstrappingConfigurationProvider: new XmlBootstrappingConfigurationProvider('my-container.xml')); +$container = Bootstrap::fromCompleteSetup( + new YourBootstrappingConfiguration(), + new YourContainerFactory(), + new Emitter(), + new VendorPresenceBasedBootstrappingDirectoryResolver() +)->bootstrapContainer(); ``` -If you require a configuration that is not the default XML files, you can implement your own `Cspray\AnnotatedContainer\Bootstrap\BootstrappingConfigurationProvider` instead. - #### Constructing DefinitionProvider -There might be dependencies you need to determine what third-party services should be included in your `DefinitionProvider` implementations. If so, there's a `Cspray\AnnotatedContainer\Bootstrap\DefinitionProviderFactory` interface that you can implement and then pass that instance to the `Bootstrap::minimalSetup()` method. +There might be dependencies you need to determine what third-party services should be included in your `DefinitionProvider` implementations. If so, there's a `Cspray\AnnotatedContainer\Bootstrap\DefinitionProviderFactory` interface that you can implement and then pass that instance to the `Bootstrap::fromAnnotatedContainerConventions()` method. ```php bootstrapContainer(); +)->bootstrapContainer(); ``` #### Constructing ParameterStore -The custom `ParameterStore` implementations you use might require some dependency to gather the appropriate values. In this case, the `Cspray\AnnotatedContainer\Bootstrap\ParameterStoreFactory` interface can be implemented and passed to the `$parameterStoreFactory` construct argument. +The custom `ParameterStore` implementations you use might require some dependency to gather the appropriate values. In this case, the `Cspray\AnnotatedContainer\Bootstrap\ParameterStoreFactory` interface can be implemented and passed to the `Bootstrap::fromAnnotatedContainerConventions()` method. ```php bootstrapContainer(); +)->bootstrapContainer(); +``` + +#### Constructing Listeners + +The Listeners you register in your configuration might require some dependency. In this case, the `Cspray\AnnotatedContainer\Bootstrap\ListenerFactory` interface can be implemented and passed to the `Bootstrap::fromAnnotatedContainerConventions()` method. + + +```php +bootstrapContainer(); ``` #### Changing Resolved Paths -By default, boostrapping expects all the path fragments in your configuration to be in the root of your project. You can have explicit control over which absolute path is used by implementing a `Cspray\AnnotatedContainer\Bootstrap\BootstrappingDirectoryResolver`. You'll need to use the `Bootstrap::fromCompleteSetup` and be prepared to provide more dependencies as well. In our code example we use the default, provided implementations, but you can use whatever implementation is appropriate. +By default, bootstrapping expects all the path fragments in your configuration to be in the root of your project. You can have explicit control over which absolute path is used by implementing a `Cspray\AnnotatedContainer\Bootstrap\BootstrappingDirectoryResolver`. You'll need to use the `Bootstrap::fromCompleteSetup` and be prepared to provide more dependencies as well. In our code example we use the default, provided implementations, but you can use whatever implementation is appropriate. ```php bootstrapContainer(); ``` @@ -245,25 +330,42 @@ $container = Bootstrap::fromCompleteSetup( The static analysis portion of Annotated Container can, like most static analysis tools, be relatively time-consuming. In PHP applications that act as long-running processes, the type this maintainer tends to develop using Annotated Container, this cost is negligible. It happens just 1 time and is just a small part of the initial startup costs. However, in traditional PHP applications that only live for the length of the request this can be costly. In this situation, it is recommended you configure your bootstrap to cache the ContainerDefinition. -Setting up caching is something that you must explicitly opt into during your bootstrapping. In the 2.x series it was possible to configure a directory to use as a cache. This was removed in 3.0 in favor of a much more robust caching mechanism. The below snippet of code is how to effectively setup your 3.0 Annotated Container to cache similarly to 2.0. +Setting up caching is something that you must explicitly opt into during your bootstrapping. In the 2.x series it was possible to configure a directory to use as a cache. This was removed in 3.0, in favor of a much more robust caching mechanism. The below snippet of code is how to effectively setup your 3.0 Annotated Container to cache similarly to 2.0. ```php bootstrapContainer( - bootstrappingConfigurationProvider: new CacheAwareBootstrappingConfigurationProvider( - new XmlBootstrappingConfigurationProvider(), - new FileBackedContainerDefinitionCache( - new XmlContainerDefinitionSerializer(), - __DIR__ . '/.annotated-container-cache' - ) +$container = Bootstrap::fromCompleteSetup( + new CacheAwareBootstrappingConfiguration( + new XmlBootstrappingConfiguration( + new PhpFunctionsFilesystem(), + 'annotated-container.xml', + ), + new FileBackedContainerDefinitionCache( + new XmlContainerDefinitionSerializer(), + __DIR__ . '/.annotated-container-cache' ) - ); + ), + new YourContainerFactory(), + new Emitter(), + new VendorPresenceBasedBootstrappingDirectoryResolver() +)->bootstrapContainer(); ``` If the cache implementations provided by Annotated Container are not sufficient, you can create your own `Cspray\AnnotatedContainer\Definition\Cache\ContainerDefinitionCache` appropriate for your use case. diff --git a/known-issues.xml b/known-issues.xml index 46c2a7c3..a24d93cf 100644 --- a/known-issues.xml +++ b/known-issues.xml @@ -1,5 +1,5 @@ - + @@ -31,27 +31,16 @@ }]]> - - - valueType->name()]]> - - - name]]]> - - - |object]]> - - - listOf->toCollection($values)]]> - - - + - $this->serviceCollectorReferenceToListOfServices($containerBuilder, $state, $definition, $value)]]> + $state->serviceCollectorReferenceToListOfServices(]]> + + make(...)]]> + @@ -60,73 +49,90 @@ - + - make($definition->type()->name())]]> execute( [$serviceDelegateDefinition->classMethod()->class()->name(), $serviceDelegateDefinition->classMethod()->methodName()], - $this->parametersForServiceDelegateToArray($injector, $state, $serviceDelegateDefinition), + $parameterResolver->resolveParametersForServiceDelegate($injector, $state, $serviceDelegateDefinition), )]]> injector->make($id)]]> - - - - - - - - - + - $containerBuilder->get($value->name)]]> - $this->serviceCollectorReferenceToListOfServices($containerBuilder, $state, $definition, $value)]]> $closure()]]> $closure()]]> + + + - name()]]]> - - - call( [$target, $serviceDelegateDefinition->classMethod()->methodName()], - array_map(static fn(Closure $closure) => $closure(), $this->parametersForServiceDelegateToArray($container, $state, $serviceDelegateDefinition)), + array_map( + static fn(Closure $closure) => $closure(), + $this->parameterResolver->resolveParametersForServiceDelegate( + $container, + $this->state, + $serviceDelegateDefinition + ) + ), )]]> - get($definition->type()->name())]]> - get($definition->type()->name())]]> + + + + + + + + $container->get($value->name)]]> + $state->serviceCollectorReferenceToListOfServices($value, $injectDefinition, $container->get(...))]]> + + + get(...)]]> + + + name()]]]> + + + + + + + + + + - - - - - + name()]]> + + get(...)]]> + name()]]]> @@ -134,7 +140,6 @@ - @@ -142,9 +147,8 @@ call( [$serviceDelegateDefinition->classMethod()->class()->name(), $serviceDelegateDefinition->classMethod()->methodName()], - $this->parametersForServiceDelegateToArray($container, $state, $serviceDelegateDefinition), + $parameterResolver->resolveParametersForServiceDelegate($container, $state, $serviceDelegateDefinition), )]]> - get($definition->type()->name())]]> @@ -157,15 +161,27 @@ + valueType->name()]]> + + + + + listOf->toCollection($values)]]> + + + + name]]]> + + diff --git a/phpunit.xml b/phpunit.xml index 493e4762..d9d5d70a 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -20,10 +20,8 @@ - diff --git a/src/Bootstrap/Bootstrap.php b/src/Bootstrap/Bootstrap.php index fa2f0ec6..1e2db7f2 100644 --- a/src/Bootstrap/Bootstrap.php +++ b/src/Bootstrap/Bootstrap.php @@ -15,7 +15,7 @@ use Cspray\AnnotatedContainer\Bootstrap\DirectoryResolver\BootstrappingDirectoryResolver; use Cspray\AnnotatedContainer\Bootstrap\DirectoryResolver\VendorPresenceBasedBootstrappingDirectoryResolver; use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactory; -use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactoryOptionsBuilder; +use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactoryOptions; use Cspray\AnnotatedContainer\Definition\ContainerDefinition; use Cspray\AnnotatedContainer\Event\Emitter; use Cspray\AnnotatedContainer\Filesystem\Filesystem; @@ -42,7 +42,7 @@ private function __construct( public static function fromAnnotatedContainerConventions( ContainerFactory $containerFactory, - Emitter $emitter, + Emitter $emitter = new Emitter(), ParameterStoreFactory $parameterStoreFactory = new DefaultParameterStoreFactory(), ListenerFactory $listenerFactory = new DefaultListenerFactory(), DefinitionProviderFactory $definitionProviderFactory = new DefaultDefinitionProviderFactory(), @@ -156,9 +156,9 @@ private function createContainer( $this->containerFactory->addParameterStore($parameterStore); } - $factoryOptions = ContainerFactoryOptionsBuilder::forProfiles($activeProfiles); + $factoryOptions = ContainerFactoryOptions::fromProfiles($activeProfiles); - return $this->containerFactory->createContainer($containerDefinition, $factoryOptions->build()); + return $this->containerFactory->createContainer($containerDefinition, $factoryOptions); } private function createAnalytics( diff --git a/src/Bootstrap/Configuration/XmlBootstrappingConfiguration.php b/src/Bootstrap/Configuration/XmlBootstrappingConfiguration.php index 10abcd29..351ccf83 100644 --- a/src/Bootstrap/Configuration/XmlBootstrappingConfiguration.php +++ b/src/Bootstrap/Configuration/XmlBootstrappingConfiguration.php @@ -37,9 +37,9 @@ final class XmlBootstrappingConfiguration implements BootstrappingConfiguration public function __construct( private readonly Filesystem $filesystem, private readonly string $xmlFile, - private readonly ParameterStoreFactory $parameterStoreFactory, - private readonly DefinitionProviderFactory $definitionProviderFactory, - private readonly ListenerFactory $listenerFactory, + private readonly ParameterStoreFactory $parameterStoreFactory = new DefaultParameterStoreFactory(), + private readonly DefinitionProviderFactory $definitionProviderFactory = new DefaultDefinitionProviderFactory(), + private readonly ListenerFactory $listenerFactory = new DefaultListenerFactory(), ) { if (!$this->filesystem->isFile($this->xmlFile)) { throw InvalidBootstrapConfiguration::fromFileMissing($this->xmlFile); diff --git a/src/ContainerFactory/AbstractContainerFactory.php b/src/ContainerFactory/AbstractContainerFactory.php index 7f026bbb..e8d2f8d4 100644 --- a/src/ContainerFactory/AbstractContainerFactory.php +++ b/src/ContainerFactory/AbstractContainerFactory.php @@ -56,94 +56,7 @@ final public function createContainer(ContainerDefinition $containerDefinition, return $container; } - /** - * @param ContainerBuilder $containerBuilder - * @return array - */ - final protected function parametersForServiceConstructorToArray( - object $containerBuilder, - ContainerFactoryState $state, - ServiceDefinition $serviceDefinition - ) : array { - return $this->listOfInjectDefinitionsToArray( - $containerBuilder, - $state, - $state->constructorInjectDefinitionsForServiceDefinition($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) - ); - } - - /** - * @param ContainerBuilder $containerBuilder - * @return array - */ - final protected function parametersForServiceDelegateToArray( - object $containerBuilder, - ContainerFactoryState $state, - ServiceDelegateDefinition $definition, - ) : array { - return $this->listOfInjectDefinitionsToArray( - $containerBuilder, - $state, - $state->injectDefinitionsForServiceDelegateDefinition($definition) - ); - } - - /** - * @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; - } - $values[] = $this->retrieveServiceFromIntermediaryContainer($container, $serviceDefinition); - } - - 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 $params; - } /** * Add a custom ParameterStore, allowing you to Inject arbitrary values into your Services. @@ -157,21 +70,4 @@ final public function addParameterStore(ParameterStore $parameterStore): void { } abstract protected function createAnnotatedContainer(ContainerFactoryState $state) : AnnotatedContainer; - - /** - * @param ContainerBuilder $containerBuilder - */ - abstract protected function resolveParameterForInjectDefinition( - object $containerBuilder, - ContainerFactoryState $state, - InjectDefinition $definition, - ) : InjectParameterValue; - - /** - * @param IntermediaryContainer $container - */ - abstract protected function retrieveServiceFromIntermediaryContainer( - object $container, - ServiceDefinition $definition - ) : object; } diff --git a/src/ContainerFactory/AurynContainerFactory.php b/src/ContainerFactory/Auryn/AurynContainerFactory.php similarity index 72% rename from src/ContainerFactory/AurynContainerFactory.php rename to src/ContainerFactory/Auryn/AurynContainerFactory.php index d2cf46a3..405cd765 100644 --- a/src/ContainerFactory/AurynContainerFactory.php +++ b/src/ContainerFactory/Auryn/AurynContainerFactory.php @@ -1,6 +1,6 @@ injectParameterValueProvider()); - $this->addServiceDefinitionsToInjector($state, $injector); - $this->addServiceDelegateDefinitionsToInjector($state, $injector); + $this->addServiceDefinitionsToInjector($state, $injector, $resolver); + $this->addServiceDelegateDefinitionsToInjector($state, $injector, $resolver); return new class($injector, $state) implements AnnotatedContainer { @@ -138,7 +142,7 @@ private function convertAutowireableParameterSet(AutowireableParameterSet $param }; } - private function addServiceDefinitionsToInjector(ContainerFactoryState $state, Injector $injector) : void { + private function addServiceDefinitionsToInjector(ContainerFactoryState $state, Injector $injector, ParameterResolver $parameterResolver) : void { foreach ($state->serviceDefinitions() as $serviceDefinition) { $injector->share($serviceDefinition->type()->name()); @@ -149,7 +153,11 @@ private function addServiceDefinitionsToInjector(ContainerFactoryState $state, I } } - $constructorParams = $this->parametersForServiceConstructorToArray($injector, $state, $serviceDefinition); + $constructorParams = $parameterResolver->resolveParametersForServiceConstructor( + $injector, + $state, + $serviceDefinition + ); if ($constructorParams !== []) { $injector->define($serviceDefinition->type()->name(), $constructorParams); } @@ -158,11 +166,15 @@ private function addServiceDefinitionsToInjector(ContainerFactoryState $state, I if ($servicePrepares !== []) { $injector->prepare( $serviceDefinition->type()->name(), - function(object $object) use($state, $injector, $servicePrepares) : void { + function(object $object) use($state, $injector, $servicePrepares, $parameterResolver) : void { foreach ($servicePrepares as $servicePrepareDefinition) { $injector->execute( [$object, $servicePrepareDefinition->classMethod()->methodName()], - $this->parametersForServicePrepareToArray($injector, $state, $servicePrepareDefinition) + $parameterResolver->resolveParametersForServicePrepare( + $injector, + $state, + $servicePrepareDefinition + ) ); } } @@ -171,44 +183,52 @@ function(object $object) use($state, $injector, $servicePrepares) : void { } } - private function addServiceDelegateDefinitionsToInjector(ContainerFactoryState $state, Injector $injector) : void { + private function addServiceDelegateDefinitionsToInjector( + ContainerFactoryState $state, + Injector $injector, + ParameterResolver $parameterResolver + ) : void { foreach ($state->serviceDelegateDefinitions() as $serviceDelegateDefinition) { $injector->delegate( $serviceDelegateDefinition->service()->name(), - function() use($injector, $state, $serviceDelegateDefinition) : object { + function() use($injector, $state, $serviceDelegateDefinition, $parameterResolver) : object { return $injector->execute( [$serviceDelegateDefinition->classMethod()->class()->name(), $serviceDelegateDefinition->classMethod()->methodName()], - $this->parametersForServiceDelegateToArray($injector, $state, $serviceDelegateDefinition), + $parameterResolver->resolveParametersForServiceDelegate($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; - } + protected function injectParameterValueProvider() : InjectParameterValueProvider { + return new class implements InjectParameterValueProvider { + + public function resolveInjectParameterValue( + object $container, ContainerFactoryState $state, InjectDefinition $injectDefinition + ) : InjectParameterValue { + $key = $injectDefinition->classMethodParameter()->parameterName(); + $value = $injectDefinition->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() => $state->serviceCollectorReferenceToListOfServices( + $value, + $injectDefinition, + $container->make(...), + ); + } elseif ($value instanceof ValueFetchedFromParameterStore) { + $key = '+' . $key; + $value = static fn() : mixed => $value->get(); + } else { + $key = ':' . $key; + } - return new InjectParameterValue($key, $value); - } + return new InjectParameterValue($key, $value); + } - protected function retrieveServiceFromIntermediaryContainer(object $container, ServiceDefinition $definition) : object { - return $container->make($definition->type()->name()); + }; } } diff --git a/src/ContainerFactory/ContainerFactoryOptions.php b/src/ContainerFactory/ContainerFactoryOptions.php index d148bea7..f0fa3944 100644 --- a/src/ContainerFactory/ContainerFactoryOptions.php +++ b/src/ContainerFactory/ContainerFactoryOptions.php @@ -9,12 +9,22 @@ * * @see ContainerFactoryOptionsBuilder */ -interface ContainerFactoryOptions { +final readonly class ContainerFactoryOptions { + + private function __construct( + private Profiles $profiles, + ) {} + + public static function fromProfiles(Profiles $profiles) : self { + return new self($profiles); + } /** * A list of profiles that should be considered active. * * @return Profiles */ - public function profiles() : Profiles; + public function profiles() : Profiles { + return $this->profiles; + } } diff --git a/src/ContainerFactory/ContainerFactoryOptionsBuilder.php b/src/ContainerFactory/ContainerFactoryOptionsBuilder.php deleted file mode 100644 index 768a96ce..00000000 --- a/src/ContainerFactory/ContainerFactoryOptionsBuilder.php +++ /dev/null @@ -1,32 +0,0 @@ -activeProfiles = $profiles; - return $instance; - } - - public function build() : ContainerFactoryOptions { - return new class($this->activeProfiles) implements ContainerFactoryOptions { - public function __construct( - private readonly Profiles $activeProfiles, - ) { - } - - public function profiles(): Profiles { - return $this->activeProfiles; - } - }; - } -} diff --git a/src/ContainerFactory/Illuminate/IlluminateContainerBinder.php b/src/ContainerFactory/Illuminate/IlluminateContainerBinder.php new file mode 100644 index 00000000..2ca1fcfd --- /dev/null +++ b/src/ContainerFactory/Illuminate/IlluminateContainerBinder.php @@ -0,0 +1,91 @@ +state->serviceDefinitions() as $serviceDefinition) { + if ($serviceDefinition->isAbstract()) { + $aliasedType = $this->state->resolveAliasDefinitionForAbstractService($serviceDefinition); + if ($aliasedType !== null) { + $this->container->singleton($serviceDefinition->type()->name(), $aliasedType->name()); + } + } else { + $this->container->singleton($serviceDefinition->type()->name()); + } + + $name = $serviceDefinition->name(); + if ($name !== null) { + $this->container->alias($serviceDefinition->type()->name(), $name); + } + $serviceConstructorParameters = $this->parameterResolver->resolveParametersForServiceConstructor( + $this->container, + $this->state, + $serviceDefinition + ); + foreach ($serviceConstructorParameters as $key => $value) { + $this->container->when($serviceDefinition->type()->name())->needs($key)->give($value); + } + + $servicePrepares = $this->state->servicePrepareDefinitionsForServiceDefinition($serviceDefinition); + if ($servicePrepares !== []) { + $this->container->afterResolving($serviceDefinition->type()->name(), function(object $object) use($servicePrepares) : void { + foreach ($servicePrepares as $servicePrepare) { + $this->container->call( + [$object, $servicePrepare->classMethod()->methodName()], + array_map( + static fn(Closure $closure) => $closure(), + $this->parameterResolver->resolveParametersForServicePrepare( + $this->container, + $this->state, + $servicePrepare + ) + ), + ); + } + }); + } + } + + foreach ($this->state->serviceDelegateDefinitions() as $serviceDelegateDefinition) { + $this->container->singleton( + $serviceDelegateDefinition->service()->name(), + function (Container $container) use($serviceDelegateDefinition) : object { + if ($serviceDelegateDefinition->classMethod()->isStatic()) { + $target = $serviceDelegateDefinition->classMethod()->class()->name(); + } else { + $target = $container->get($serviceDelegateDefinition->classMethod()->class()->name()); + } + + return $container->call( + [$target, $serviceDelegateDefinition->classMethod()->methodName()], + array_map( + static fn(Closure $closure) => $closure(), + $this->parameterResolver->resolveParametersForServiceDelegate( + $container, + $this->state, + $serviceDelegateDefinition + ) + ), + ); + } + ); + } + + $this->container->instance(Profiles::class, $this->state->activeProfiles()); + } +} diff --git a/src/ContainerFactory/IlluminateContainerFactory.php b/src/ContainerFactory/Illuminate/IlluminateContainerFactory.php similarity index 51% rename from src/ContainerFactory/IlluminateContainerFactory.php rename to src/ContainerFactory/Illuminate/IlluminateContainerFactory.php index e1b44f90..3e439f01 100644 --- a/src/ContainerFactory/IlluminateContainerFactory.php +++ b/src/ContainerFactory/Illuminate/IlluminateContainerFactory.php @@ -1,21 +1,25 @@ injectParameterValueProvider()); - foreach ($state->serviceDefinitions() as $serviceDefinition) { - if ($serviceDefinition->isAbstract()) { - $aliasedType = $state->resolveAliasDefinitionForAbstractService($serviceDefinition); - if ($aliasedType !== null) { - $container->singleton($serviceDefinition->type()->name(), $aliasedType->name()); - } - } else { - $container->singleton($serviceDefinition->type()->name()); - } - - $name = $serviceDefinition->name(); - if ($name !== null) { - $container->alias($serviceDefinition->type()->name(), $name); - } - - foreach ($this->parametersForServiceConstructorToArray($container, $state, $serviceDefinition) as $key => $value) { - $container->when($serviceDefinition->type()->name())->needs($key)->give($value); - } - - $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)), - ); - } - }); - } - } - - 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()); - } - - return $container->call( - [$target, $serviceDelegateDefinition->classMethod()->methodName()], - array_map(static fn(Closure $closure) => $closure(), $this->parametersForServiceDelegateToArray($container, $state, $serviceDelegateDefinition)), - ); - } - ); - } - - $container->instance(Profiles::class, $state->activeProfiles()); + (new IlluminateContainerBinder( + $container, + $state, + $parameterResolver + ))->bindDependencies(); return new class($container) implements AnnotatedContainer { @@ -158,28 +117,29 @@ public function has(string $id) : bool { }; } - 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; - } + protected function injectParameterValueProvider() : InjectParameterValueProvider { + return new class implements InjectParameterValueProvider { - return new InjectParameterValue($key, $value); - } + public function resolveInjectParameterValue(object $container, ContainerFactoryState $state, InjectDefinition $injectDefinition) : InjectParameterValue { + $key = sprintf('%s', $injectDefinition->classMethodParameter()->parameterName()); + if ($injectDefinition->classMethodParameter()->methodName() === '__construct') { + $key = '$' . $key; + } + $value = $injectDefinition->value(); + if ($value instanceof ContainerReference) { + $key = $injectDefinition->classMethodParameter()->type()->name(); + $value = fn() => $container->get($value->name); + } elseif ($value instanceof ServiceCollectorReference) { + if (!$value->collectionType->equals(types()->array())) { + $key = $value->collectionType->name(); + } + $value = fn() => $state->serviceCollectorReferenceToListOfServices($value, $injectDefinition, $container->get(...)); + } else { + $value = fn() : mixed => $value instanceof ValueFetchedFromParameterStore ? $value->get() : $value; + } - protected function retrieveServiceFromIntermediaryContainer(object $container, ServiceDefinition $definition) : object { - return $container->get($definition->type()->name()); + return new InjectParameterValue($key, $value); + } + }; } } diff --git a/src/ContainerFactory/PhpDiContainerFactory.php b/src/ContainerFactory/PhpDi/PhpDiContainerFactory.php similarity index 77% rename from src/ContainerFactory/PhpDiContainerFactory.php rename to src/ContainerFactory/PhpDi/PhpDiContainerFactory.php index e342a4fc..56a8ff2a 100644 --- a/src/ContainerFactory/PhpDiContainerFactory.php +++ b/src/ContainerFactory/PhpDi/PhpDiContainerFactory.php @@ -1,21 +1,25 @@ injectParameterValueProvider()); $definitions = []; $servicePrepareDefinitions = []; @@ -50,14 +55,14 @@ protected function createAnnotatedContainer(ContainerFactoryState $state) : Anno if ($serviceDelegateDefinition === null) { $definitions[$serviceDefinition->type()->name()] = autowire(); - foreach ($this->parametersForServiceConstructorToArray($containerBuilder, $state, $serviceDefinition) as $param => $value) { + foreach ($parameterResolver->resolveParametersForServiceConstructor($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 { + $definitions[$serviceDefinition->type()->name()] = function (Container $container) use($state, $serviceDelegateDefinition, $parameterResolver) : object { return $container->call( [$serviceDelegateDefinition->classMethod()->class()->name(), $serviceDelegateDefinition->classMethod()->methodName()], - $this->parametersForServiceDelegateToArray($container, $state, $serviceDelegateDefinition), + $parameterResolver->resolveParametersForServiceDelegate($container, $state, $serviceDelegateDefinition), ); }; } @@ -76,11 +81,11 @@ protected function createAnnotatedContainer(ContainerFactoryState $state) : Anno $servicePrepares = $state->servicePrepareDefinitionsForServiceDefinition($serviceDefinition); if ($servicePrepares !== []) { - $servicePrepareDefinitions[$serviceDefinition->type()->name()] = decorate(function (object $object, Container $container) use($servicePrepares, $state) : mixed { + $servicePrepareDefinitions[$serviceDefinition->type()->name()] = decorate(function (object $object, Container $container) use($servicePrepares, $state, $parameterResolver) : mixed { foreach ($servicePrepares as $servicePrepare) { $container->call( [$object, $servicePrepare->classMethod()->methodName()], - $this->parametersForServicePrepareToArray($container, $state, $servicePrepare), + $parameterResolver->resolveParametersForServicePrepare($container, $state, $servicePrepare), ); } @@ -175,26 +180,26 @@ 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 injectParameterValueProvider() : InjectParameterValueProvider { + return new class implements InjectParameterValueProvider { + + public function resolveInjectParameterValue(object $container, ContainerFactoryState $state, InjectDefinition $injectDefinition) : InjectParameterValue { + $value = $injectDefinition->value(); + + if ($value instanceof ContainerReference) { + $value = get($value->name); + } elseif ($value instanceof ServiceCollectorReference) { + $value = factory(fn(Container $container) : mixed => $state->serviceCollectorReferenceToListOfServices( + $value, + $injectDefinition, + $container->get(...), + )); + } elseif ($value instanceof ValueFetchedFromParameterStore) { + $value = factory($value->get(...)); + } - protected function retrieveServiceFromIntermediaryContainer(object $container, ServiceDefinition $definition) : object { - return $container->get($definition->type()->name()); + return new InjectParameterValue($injectDefinition->classMethodParameter()->parameterName(), $value); + } + }; } } diff --git a/src/ContainerFactory/State/ContainerFactoryState.php b/src/ContainerFactory/State/ContainerFactoryState.php index 9a5f8bd0..1ffff001 100644 --- a/src/ContainerFactory/State/ContainerFactoryState.php +++ b/src/ContainerFactory/State/ContainerFactoryState.php @@ -2,6 +2,7 @@ namespace Cspray\AnnotatedContainer\ContainerFactory\State; +use Closure; use Cspray\AnnotatedContainer\Attribute\InjectAttribute; use Cspray\AnnotatedContainer\ContainerFactory\AliasResolution\AliasDefinitionResolver; use Cspray\AnnotatedContainer\ContainerFactory\ListOf; @@ -231,4 +232,28 @@ public function typeForServiceName(string $name) : ?Type { return null; } + + /** + * @param Closure(string):object $serviceCreator + * @return array|object + */ + public function serviceCollectorReferenceToListOfServices( + ServiceCollectorReference $reference, + InjectDefinition $definition, + Closure $serviceCreator + ) : array|object { + $values = []; + foreach ($this->serviceDefinitions() as $serviceDefinition) { + if ($serviceDefinition->isAbstract() || + $serviceDefinition->type()->equals($definition->service()) || + !is_a($serviceDefinition->type()->name(), $reference->valueType->name(), true) + ) { + continue; + } + + $values[] = $serviceCreator($serviceDefinition->type()->name()); + } + + return $reference->listOf->toCollection($values); + } } diff --git a/src/ContainerFactory/State/InjectParameterValueProvider.php b/src/ContainerFactory/State/InjectParameterValueProvider.php new file mode 100644 index 00000000..aa2771ef --- /dev/null +++ b/src/ContainerFactory/State/InjectParameterValueProvider.php @@ -0,0 +1,17 @@ +listOfInjectDefinitionsToArray( + $container, + $state, + $state->constructorInjectDefinitionsForServiceDefinition($serviceDefinition) + ); + } + + public function resolveParametersForServicePrepare( + object $container, + ContainerFactoryState $state, + ServicePrepareDefinition $servicePrepareDefinition, + ) : array { + return $this->listOfInjectDefinitionsToArray( + $container, + $state, + $state->injectDefinitionsForServicePrepareDefinition($servicePrepareDefinition) + ); + } + + public function resolveParametersForServiceDelegate( + object $container, + ContainerFactoryState $state, + ServiceDelegateDefinition $serviceDelegateDefinition, + ) : array { + return $this->listOfInjectDefinitionsToArray( + $container, + $state, + $state->injectDefinitionsForServiceDelegateDefinition($serviceDelegateDefinition) + ); + } + + /** + * @param object $containerBuilder + * @param list $definitions + * @return array + */ + private function listOfInjectDefinitionsToArray(object $containerBuilder, ContainerFactoryState $state, array $definitions) : array { + $params = []; + foreach ($definitions as $injectDefinition) { + $injectParameterValue = $this->injectParameterValueProvider->resolveInjectParameterValue( + $containerBuilder, + $state, + $injectDefinition + ); + $params[$injectParameterValue->name] = $injectParameterValue->value; + } + + return $params; + } +} diff --git a/src/Function/definitions.php b/src/Function/definitions.php index 3e3fa4dc..a8485a37 100644 --- a/src/Function/definitions.php +++ b/src/Function/definitions.php @@ -2,10 +2,7 @@ namespace Cspray\AnnotatedContainer\Definition; -use Cspray\AnnotatedContainer\Attribute\Inject; -use Cspray\AnnotatedContainer\Attribute\Service; -use Cspray\AnnotatedContainer\Attribute\ServiceDelegate; -use Cspray\AnnotatedContainer\Attribute\ServicePrepare; +use Cspray\AnnotatedContainer\Internal\InjectDefinitionFromFunctionalApi; use Cspray\AnnotatedContainer\Internal\ServiceDelegateFromFunctionalApi; use Cspray\AnnotatedContainer\Internal\ServiceFromFunctionalApi; use Cspray\AnnotatedContainer\Internal\ServicePrepareFromFunctionalApi; @@ -77,7 +74,7 @@ function inject( $method, $type, $paramName, - new Inject($value, $from, $profiles) + new InjectDefinitionFromFunctionalApi($value, $profiles, $from) ); } diff --git a/src/Internal/InjectDefinitionFromFunctionalApi.php b/src/Internal/InjectDefinitionFromFunctionalApi.php new file mode 100644 index 00000000..101cc3ab --- /dev/null +++ b/src/Internal/InjectDefinitionFromFunctionalApi.php @@ -0,0 +1,38 @@ + $profiles + * @param non-empty-string|null $from + */ + public function __construct( + private mixed $value, + private array $profiles, + private ?string $from + ) {} + + public function value() : mixed { + return $this->value; + } + + /** + * @return list + */ + public function profiles() : array { + return $this->profiles; + } + + /** + * @return non-empty-string|null + */ + public function from() : ?string { + return $this->from; + } +} \ No newline at end of file diff --git a/test/Unit/Bootstrap/BootstrapTest.php b/test/Unit/Bootstrap/BootstrapTest.php index c42a8ce5..60ae6a11 100644 --- a/test/Unit/Bootstrap/BootstrapTest.php +++ b/test/Unit/Bootstrap/BootstrapTest.php @@ -15,10 +15,10 @@ use Cspray\AnnotatedContainer\Bootstrap\Listener\ServiceFromServiceDefinition; use Cspray\AnnotatedContainer\Bootstrap\Listener\ServiceGatherer; use Cspray\AnnotatedContainer\Bootstrap\Listener\ServiceWiringListener; -use Cspray\AnnotatedContainer\ContainerFactory\AurynContainerFactory; +use Cspray\AnnotatedContainer\ContainerFactory\Auryn\AurynContainerFactory; use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactory; use Cspray\AnnotatedContainer\ContainerFactory\ParameterStore; -use Cspray\AnnotatedContainer\ContainerFactory\PhpDiContainerFactory; +use Cspray\AnnotatedContainer\ContainerFactory\PhpDi\PhpDiContainerFactory; use Cspray\AnnotatedContainer\Definition\Cache\CacheKey; use Cspray\AnnotatedContainer\Definition\Cache\ContainerDefinitionCache; use Cspray\AnnotatedContainer\Definition\ContainerDefinition; diff --git a/test/Unit/ContainerFactory/AurynContainerFactoryTest.php b/test/Unit/ContainerFactory/AurynContainerFactoryTest.php index 74e3c8ce..7360890a 100644 --- a/test/Unit/ContainerFactory/AurynContainerFactoryTest.php +++ b/test/Unit/ContainerFactory/AurynContainerFactoryTest.php @@ -3,7 +3,7 @@ namespace Cspray\AnnotatedContainer\Unit\ContainerFactory; use Auryn\Injector; -use Cspray\AnnotatedContainer\ContainerFactory\AurynContainerFactory; +use Cspray\AnnotatedContainer\ContainerFactory\Auryn\AurynContainerFactory; use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactory; use Cspray\AnnotatedContainer\Event\Emitter; use Cspray\AnnotatedContainer\Reflection\Type; diff --git a/test/Unit/ContainerFactory/ContainerFactoryOptionsBuilderTest.php b/test/Unit/ContainerFactory/ContainerFactoryOptionsBuilderTest.php index 51dcdf6b..030fec8e 100644 --- a/test/Unit/ContainerFactory/ContainerFactoryOptionsBuilderTest.php +++ b/test/Unit/ContainerFactory/ContainerFactoryOptionsBuilderTest.php @@ -2,15 +2,14 @@ namespace Cspray\AnnotatedContainer\Unit\ContainerFactory; -use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactoryOptionsBuilder; +use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactoryOptions; use Cspray\AnnotatedContainer\Profiles; use PHPUnit\Framework\TestCase; final class ContainerFactoryOptionsBuilderTest extends TestCase { public function testGetProfiles() : void { - $options = ContainerFactoryOptionsBuilder::forProfiles(Profiles::fromList(['default', 'dev', 'local'])) - ->build(); + $options = ContainerFactoryOptions::fromProfiles(Profiles::fromList(['default', 'dev', 'local'])); self::assertSame(['default', 'dev', 'local'], $options->profiles()->toArray()); } diff --git a/test/Unit/ContainerFactory/ContainerFactoryTestCase.php b/test/Unit/ContainerFactory/ContainerFactoryTestCase.php index edbd772a..b8e871d7 100644 --- a/test/Unit/ContainerFactory/ContainerFactoryTestCase.php +++ b/test/Unit/ContainerFactory/ContainerFactoryTestCase.php @@ -6,6 +6,7 @@ use Cspray\AnnotatedContainer\Autowire\AutowireableFactory; use Cspray\AnnotatedContainer\Autowire\AutowireableInvoker; use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactory; +use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactoryOptions; use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactoryOptionsBuilder; use Cspray\AnnotatedContainer\ContainerFactory\ParameterStore; use Cspray\AnnotatedContainer\Definition\ContainerDefinition; @@ -66,13 +67,13 @@ private function getContainer( $compiler = $this->getContainerDefinitionCompiler(); $optionsBuilder = ContainerDefinitionAnalysisOptionsBuilder::scanDirectories($dir); $containerDefinition = $compiler->analyze($optionsBuilder->build()); - $containerOptions = ContainerFactoryOptionsBuilder::forProfiles($profiles ?? Profiles::fromList(['default'])); + $containerOptions = ContainerFactoryOptions::fromProfiles($profiles ?? Profiles::fromList(['default'])); $factory = $this->getContainerFactory($emitter); if ($parameterStore !== null) { $factory->addParameterStore($parameterStore); } - return $factory->createContainer($containerDefinition, $containerOptions->build()); + return $factory->createContainer($containerDefinition, $containerOptions); } public function testCreateServiceNotHasThrowsException() { diff --git a/test/Unit/ContainerFactory/IlluminateContainerFactoryTest.php b/test/Unit/ContainerFactory/IlluminateContainerFactoryTest.php index 980f51c2..2ea4aa08 100644 --- a/test/Unit/ContainerFactory/IlluminateContainerFactoryTest.php +++ b/test/Unit/ContainerFactory/IlluminateContainerFactoryTest.php @@ -3,7 +3,7 @@ namespace Cspray\AnnotatedContainer\Unit\ContainerFactory; use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactory; -use Cspray\AnnotatedContainer\ContainerFactory\IlluminateContainerFactory; +use Cspray\AnnotatedContainer\ContainerFactory\Illuminate\IlluminateContainerFactory; use Cspray\AnnotatedContainer\Event\Emitter; use Cspray\AnnotatedContainer\Reflection\Type; use Illuminate\Contracts\Container\Container; diff --git a/test/Unit/ContainerFactory/PhpDiContainerFactoryTest.php b/test/Unit/ContainerFactory/PhpDiContainerFactoryTest.php index 5660f3cd..4b25b2f2 100644 --- a/test/Unit/ContainerFactory/PhpDiContainerFactoryTest.php +++ b/test/Unit/ContainerFactory/PhpDiContainerFactoryTest.php @@ -3,7 +3,7 @@ namespace Cspray\AnnotatedContainer\Unit\ContainerFactory; use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactory; -use Cspray\AnnotatedContainer\ContainerFactory\PhpDiContainerFactory; +use Cspray\AnnotatedContainer\ContainerFactory\PhpDi\PhpDiContainerFactory; use Cspray\AnnotatedContainer\Event\Emitter; use Cspray\AnnotatedContainer\Reflection\Type; use DI\Container; diff --git a/test/Unit/ThirdPartyFunctionsTest.php b/test/Unit/ThirdPartyFunctionsTest.php index f45f65b2..e8864d1a 100644 --- a/test/Unit/ThirdPartyFunctionsTest.php +++ b/test/Unit/ThirdPartyFunctionsTest.php @@ -3,6 +3,10 @@ namespace Cspray\AnnotatedContainer\Unit; use Cspray\AnnotatedContainer\Fixture\Fixtures; +use Cspray\AnnotatedContainer\Internal\InjectDefinitionFromFunctionalApi; +use Cspray\AnnotatedContainer\Internal\ServiceDelegateFromFunctionalApi; +use Cspray\AnnotatedContainer\Internal\ServiceFromFunctionalApi; +use Cspray\AnnotatedContainer\Internal\ServicePrepareFromFunctionalApi; use PHPUnit\Framework\TestCase; use function Cspray\AnnotatedContainer\Definition\inject; use function Cspray\AnnotatedContainer\Definition\serviceDelegate; @@ -24,6 +28,19 @@ public function testHasServiceDefinitionForType() : void { ); } + public function testServiceHasCorrectAttributeAssociatedWithIt() : void { + $type = Fixtures::singleConcreteService()->fooImplementation(); + $serviceDefinition = service($type); + + self::assertInstanceOf( + ServiceFromFunctionalApi::class, + $serviceDefinition->attribute() + ); + self::assertNull($serviceDefinition->attribute()->name()); + self::assertFalse($serviceDefinition->attribute()->isPrimary()); + self::assertSame([], $serviceDefinition->attribute()->profiles()); + } + public function testAbstractDefinedServiceIsAbstract() { $serviceDefinition = service(Fixtures::implicitAliasedServices()->fooInterface()); @@ -33,51 +50,58 @@ public function testAbstractDefinedServiceIsAbstract() { public function testAbstractDefinedServiceGetName() { $serviceDefinition = service(Fixtures::implicitAliasedServices()->fooInterface(), 'fooService'); - $this->assertSame('fooService', $serviceDefinition->name()); + self::assertSame('fooService', $serviceDefinition->name()); + self::assertSame('fooService', $serviceDefinition->attribute()->name()); } public function testAbstractDefinedServiceGetProfiles() { $serviceDefinition = service(Fixtures::implicitAliasedServices()->fooInterface(), profiles: ['default', 'dev']); - $this->assertSame(['default', 'dev'], $serviceDefinition->profiles()); + self::assertSame(['default', 'dev'], $serviceDefinition->profiles()); + self::assertSame(['default', 'dev'], $serviceDefinition->attribute()->profiles()); } public function testSingleConcreteServiceIsConcrete() { $serviceDefinition = service(Fixtures::singleConcreteService()->fooImplementation()); - $this->assertTrue($serviceDefinition->isConcrete()); + self::assertTrue($serviceDefinition->isConcrete()); } public function testSingleConcreteServiceIsPrimary() { $serviceDefinition = service(Fixtures::singleConcreteService()->fooImplementation(), isPrimary: true); - $this->assertTrue($serviceDefinition->isPrimary()); + self::assertTrue($serviceDefinition->isPrimary()); + self::assertTrue($serviceDefinition->attribute()->isPrimary()); } public function testServiceDelegateDefinition() { $serviceDelegateDefinition = serviceDelegate(Fixtures::delegatedService()->serviceFactory(), 'createService'); - $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()); + self::assertSame(Fixtures::delegatedService()->serviceInterface()->name(), $serviceDelegateDefinition->service()->name()); + self::assertSame(Fixtures::delegatedService()->serviceFactory()->name(), $serviceDelegateDefinition->classMethod()->class()->name()); + self::assertSame('createService', $serviceDelegateDefinition->classMethod()->methodName()); + self::assertSame(['default'], $serviceDelegateDefinition->profiles()); + self::assertInstanceOf(ServiceDelegateFromFunctionalApi::class, $serviceDelegateDefinition->attribute()); + self::assertNull($serviceDelegateDefinition->attribute()->service()); } public function testServiceDelegateDefinitionWithExplicitProfiles() : void { $serviceDelegateDefinition = serviceDelegate(Fixtures::delegatedService()->serviceFactory(), 'createService', ['the', 'love', 'plug']); - $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()); + self::assertSame(Fixtures::delegatedService()->serviceInterface()->name(), $serviceDelegateDefinition->service()->name()); + self::assertSame(Fixtures::delegatedService()->serviceFactory()->name(), $serviceDelegateDefinition->classMethod()->class()->name()); + self::assertSame('createService', $serviceDelegateDefinition->classMethod()->methodName()); + self::assertSame(['the', 'love', 'plug'], $serviceDelegateDefinition->profiles()); + self::assertSame(['the', 'love', 'plug'], $serviceDelegateDefinition->attribute()->profiles()); } public function testServicePrepareDefinition() { $servicePrepareDefinition = servicePrepare(Fixtures::interfacePrepareServices()->fooInterface(), 'setBar'); - $this->assertServicePrepareTypes([ + self::assertServicePrepareTypes([ [Fixtures::interfacePrepareServices()->fooInterface()->name(), 'setBar'] ], [$servicePrepareDefinition]); + self::assertInstanceOf(ServicePrepareFromFunctionalApi::class, $servicePrepareDefinition->attribute()); } public function testInjectMethodParam() { @@ -89,15 +113,33 @@ public function testInjectMethodParam() { 42 ); - $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()); + self::assertSame(Fixtures::injectConstructorServices()->injectFloatService(), $inject->service()); + self::assertSame(Fixtures::injectConstructorServices()->injectFloatService(), $inject->classMethodParameter()->class()); + self::assertSame('__construct', $inject->classMethodParameter()->methodName()); + self::assertSame('dessert', $inject->classMethodParameter()->parameterName()); + self::assertSame(types()->int(), $inject->classMethodParameter()->type()); + self::assertFalse($inject->classMethodParameter()->isStatic()); + self::assertSame(42, $inject->value()); + self::assertSame(['default'], $inject->profiles()); + self::assertNull($inject->storeName()); + } + + public function testInjectHasCorrectAttribute() : void { + $inject = inject( + Fixtures::injectConstructorServices()->injectFloatService(), + '__construct', + 'dessert', + types()->int(), + 42 + ); + + self::assertInstanceOf( + InjectDefinitionFromFunctionalApi::class, + $inject->attribute() + ); + self::assertSame(42, $inject->attribute()->value()); + self::assertSame([], $inject->attribute()->profiles()); + self::assertNull($inject->attribute()->from()); } public function testInjectMethodParamProfiles() { @@ -110,7 +152,8 @@ public function testInjectMethodParamProfiles() { ['foo', 'bar', 'baz'] ); - $this->assertSame(['foo', 'bar', 'baz'], $inject->profiles()); + self::assertSame(['foo', 'bar', 'baz'], $inject->profiles()); + self::assertSame(['foo', 'bar', 'baz'], $inject->attribute()->profiles()); } public function testInjectMethodParamStoreName() { @@ -123,6 +166,7 @@ public function testInjectMethodParamStoreName() { from: 'store-name' ); - $this->assertSame('store-name', $inject->storeName()); + self::assertSame('store-name', $inject->storeName()); + self::assertSame('store-name', $inject->attribute()->from()); } } From 55b026159b2562aa582ca9a29ab52c42fbac0fbc Mon Sep 17 00:00:00 2001 From: Charles Sprayberry Date: Tue, 24 Jun 2025 11:49:08 -0400 Subject: [PATCH 2/6] Remove unnecessary builder object --- CHANGELOG.md | 85 ++++++++++++++++--- known-issues.xml | 10 --- .../BootstrappingConfiguration.php | 2 +- ...sOptionsFromBootstrappingConfiguration.php | 14 ++- src/Cli/AnnotatedContainerCliRunner.php | 5 +- .../ContainerFactoryOptions.php | 5 +- .../State/ParameterResolver.php | 6 +- .../InjectDefinitionFromFunctionalApi.php | 6 +- .../ContainerDefinitionAnalysisOptions.php | 40 ++++++++- ...tainerDefinitionAnalysisOptionsBuilder.php | 69 --------------- test/Unit/Bootstrap/BootstrapTest.php | 7 +- test/Unit/Cli/Command/BuildCommandTest.php | 12 +-- .../Cli/Command/CacheClearCommandTest.php | 12 +-- .../ContainerFactoryTestCase.php | 15 ++-- test/Unit/Definition/Cache/CacheKeyTest.php | 26 ++++-- ...FileBackedContainerDefinitionCacheTest.php | 6 +- test/Unit/Event/EmitterTest.php | 5 +- .../Check/DuplicateServiceDelegateTest.php | 20 ++--- .../Check/DuplicateServiceNameTest.php | 17 ++-- .../Check/DuplicateServicePrepareTest.php | 21 +++-- .../Check/DuplicateServiceTypeTest.php | 20 ++--- .../MultiplePrimaryForAbstractServiceTest.php | 13 +-- .../Check/NonPublicServiceDelegateTest.php | 20 ++--- .../Check/NonPublicServicePrepareTest.php | 20 ++--- .../LogicalConstraintValidatorTest.php | 14 +-- .../XmlContainerDefinitionSerializerTest.php | 4 +- ...getContainerDefinitionAnalyzerTestCase.php | 11 +-- ...dTargetContainerDefinitionAnalyzerTest.php | 9 +- ...heAwareContainerDefinitionAnalyzerTest.php | 5 +- ...erDefinitionAnalysisOptionsBuilderTest.php | 34 -------- ...ContainerDefinitionAnalysisOptionsTest.php | 40 +++++++++ 31 files changed, 301 insertions(+), 272 deletions(-) delete mode 100644 src/StaticAnalysis/ContainerDefinitionAnalysisOptionsBuilder.php delete mode 100644 test/Unit/StaticAnalysis/ContainerDefinitionAnalysisOptionsBuilderTest.php create mode 100644 test/Unit/StaticAnalysis/ContainerDefinitionAnalysisOptionsTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 94cf687f..3c567c36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,16 +2,79 @@ All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v3.0.0](https://github.com/cspray/annotated-container/tree/v3.0.0) +The v3.0 release represents a substantial improvement in several areas of the project, but also includes several backwards compatability breaks; particularly if you were using container caching, providing Definitions via a DefinitionProvider, or utilizing the Bootstrap observer system. + +### Added + +- Version attribute to the `annotated-container.xml` XML configuration. In future iterations work may be done to ensure versions for your configuration are compatible with the version of the library installed. +- A more complete event system with informative events being emitted throughout the entire Annotated Container lifecycle. Previously, this functionality was much more limited and provided by Bootstrap observers. +- A Definition factory that allows creating all definitions, except for a `ContainerDefinition`, in a more complete, concise manner. Previously, this functionality was provided by Definition Builders using a Fluent API. +- More feature-complete caching system for a `ContainerDefinition`. Previously, this was powered by direct calls to PHP's filesystem functions. Now a purpose-built interface, `Cspray\AnnotatedContainer\Definition\Cache\ContainerDefinitionCache`, allows whatever caching strategy is most appropriate. +- A `Cspray\AnnotatedContainer\Filesystem\Filesystem` interface and implementation that allows for executing common tasks on a file. Previously, the native PHP functions for accessing a filesystem were being used. This change was primarily implemented to improve testability without requiring for a heavyweight abstraction from a third-party library. +- `Cspray\AnnotatedContainer\Profiles` value object that indicates what profiles are active when a container is created. +- Normalized the conventions around setting up the Annotated Container CLI tool. You can now use `Cspray\AnnotatedContainer\Cli\AnnotatedContainerCliRunner` in your own scripts, allowing you to easily customize caching, among other things. +- A bootstrapping utility class, `Cspray\AnnotatedContainer\Bootstrap\ContainerDefinitionAnalysisOptionsFromBootstrappingConfiguration` that allows consistently creating the `ContainerDefinitionAnalysisOptions` to be used. +- A discrete interface, `Cspray\AnnotatedContainer\Bootstrap\PackagesComposerJsonPathProvider`, to determine what path should be used when scanning a `composer.json` for third-party dependencies. This is utilized by the CLI tool to generate the appropriate configuration file. +- A discrete `DefaultDefinitionProviderFactory` implementation, previously this functionality was inlined into the bootstrap. + +### Changed + +- **Updated the required PHP version to 8.2!** +- Updated the version of `nikic/php-parser` used to 5.3. +- All interactions with the `Bootstrap` object are available through named, static methods. The `Bootstrap` constructor is now private and the `new` construct cannot be used. Generally speaking, you should make use of the new `Bootstrap::fromAnnotatedContainerConventions` method. +- Container factory to use is no longer implicit and must be explicitly provided during your bootstrapping. +- All Definitions that had a nullable `attribute` method were changed to require a non-null Attribute value. Please see the ADR "Require Definitions To Provide Attribute" for more information. +- Improved the `XmlContainerDefinitionSerializer` and corresponding XSD to remove redundant information being stored in the Attribute. +- The `AnnotatedContainerVersion` class was updated to use the `composer/runtime` dependency directly, instead of using `ocramius/package-versions`. +- Added template parameters to the `AutowireableFactory::make` method, allowing more type information to be conveyed. +- Several implementations had their `get` prefix removed, as it was redundant and not necessary. +- Moved test code that was in `fixture_src` under `test/Fixture`. +- When utilizing the `composer.json` configuration for configuring third-party initializers, more checks are made to ensure that a valid data structure has been passed. Previously, this had undefined behavior when invalid data types or values were present. +- Refactored the `ContainerFactoryOptions` interface into a value object. + +### Fixed + +- Several places where `declare(strict_types=1)` was not properly set. +- Fixed an error where the interface `AutowireableParameterSet` was implementing a template instead of extending it. + +### Removed + +- All code associated with Bootstrap observers, including but not limited to: + - `Cspray\AnnotatedContainer\Bootstrap\ObserverFactory` + - `Cspray\AnnotatedContainer\Bootstrap\ContainerAnalyticsObserver` + - `Cspray\AnnotatedContainer\Bootstrap\ContainerCreatedObserver` + - `Cspray\AnnotatedContainer\Bootstrap\PostAnalysisObserver` + - `Cspray\AnnotatedContainer\Bootstrap\PreAnalysisObserver` + - `Cspray\AnnotatedContainer\Bootstrap\ServiceWiringObserver` (See `ServiceWiringListener`) + - `Cspray\AnnotatedContainer\Bootstrap\ThirdPartyInitializer::getObserverClasses` +- All Definition builder objects, except for the `ContainerDefinitionBuilder`. See the new `DefinitionFactory` for equivalent functionality. +- The `ActiveProfiles` value object. See the new `Profiles` value object for a replacement. +- All code associated with Configuration, including but not limited to: + - `Cspray\AnnotatedContainer\Definition\ConfigurationDefinition` + - `Cspray\AnnotatedContainer\Definition\ConfigurationDefinitionBuilder` + - `Cspray\AnnotatedContainer\Attribute\ConfigurationAttribute` + - `Cspray\AnnotatedContainer\Attribute\Configuration` +- Removed the `Cspray\AnnotatedContainer\ContainerFactory\ContainerFactoryOptionsBuilder` object. Please use the `ContainerFactoryOptions` value object directly instead. +- Removed the `Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptionsBuilder`. Please use the `ContainerFactoryOptions` value object directly instead. +- Removed built-in file caching used in bootstrapping. This functionality is replaced by implementing your own `ContainerDefinitionCache` implementation, or explicitly using the `FileBackedContainerDefinitionCache` provided out-of-the-box. +- Removed built-in logging used throughout the library. This functionality will be replaced at a future date with a set of listeners that will log the same information. These listeners will require installing a separate repo and explicitly opting in. +- Removed the following Composer dependencies: + - brick/varexporter + - cspray/typiphy + - ocramius/package-versions + - psr/log ## [v2.4.0](https://github.com/cspray/annotated-container/tree/v2.4.0) - 2024-06-08 ### Added - Added the ability to inject a collection of services as an array or a custom collection by passing an implementation of `Cspray\AnnotatedContainer\ContainerFactory\ListOf` to an `#[Inject]` attribute. -- Added `Cspray\AnnotatedContainer\ContainerFactory\ListOfAsArray` implementation of to allow implementing a collection of services as an array out-of-the-box +- Added `Cspray\AnnotatedContainer\ContainerFactory\ListOfAsArray` implementation of to allow implementing a collection of services as an array out-of-the-box ### Deprecated @@ -57,14 +120,14 @@ In v3 caching functionality is drastically improved and much more control is pro ### Changed - Changed static analysis step to no longer throw an error if a ServiceDelegate is encountered without an explicitly -defined Service. Now, a ServiceDefinition will be implicitly added as if the corresponding class was added with all -default parameters using the functional API. + defined Service. Now, a ServiceDefinition will be implicitly added as if the corresponding class was added with all + default parameters using the functional API. ### Deprecated - All observers have been deprecated. They will be replaced in 3.0.0. Please see our ADR document for more details. -- All implementations in Cspray\AnnotatedContainer\Profiles have been deprecated. They will be replaced with a single -value object in 3.0.0. Please see our ADR document for more details. +- All implementations in Cspray\AnnotatedContainer\Profiles have been deprecated. They will be replaced with a single + value object in 3.0.0. Please see our ADR document for more details. ## [v2.2.0](https://github.com/cspray/annotated-container/tree/v2.2.0) - 2023-05-29 @@ -270,7 +333,7 @@ This release only deprecates code constructs replaced in v2. ## [v1.3.0](https://github.com/cspray/annotated-container/tree/v1.3.0) - 2022-08-06 -### Added +### Added - Added an event system for programmatic access to the ContainerDefinition and Container before and after each is created. @@ -355,7 +418,7 @@ This release only deprecates code constructs replaced in v2. ### Removed - Removed the ability to mark a `#[Service]` as shared or not. All services are shared by default, and you cannot "unshare" a service. This functionality has a lot of odd behavior around it and other mechanisms should be used to gain this functionality. -- Removed the `AnnotatedTarget`, `AnnotatatedTargetParser`, and `StaticAnalysisAnnotatedTargetParser`. +- Removed the `AnnotatedTarget`, `AnnotatatedTargetParser`, and `StaticAnalysisAnnotatedTargetParser`. ### Changed @@ -372,7 +435,7 @@ This release only deprecates code constructs replaced in v2. - A new `fixture_src/` directory that stores example source code used for the automated test suite. - Several improvements to the way that Fixtures are handled in the test suite such that the code examples in `fixture_src/` have a first-class representation in the test suite through the `Cspray\AnnotatedContainerFixture\Fixtures` class. -- Implementations for +- Implementations for ### Changed @@ -430,7 +493,7 @@ This release only deprecates code constructs replaced in v2. - An error in the README documentation referencing an incorrect variable. - Directory paths in all tests point to new directory structure. - A dev-only dependency, `mikey179/vfsStream` was inadvertently included in the `require` section. This dependency is now properly a `require-dev` dependency. -- Arguments passed to Attributes better differentiates between compile and runtime values by introducing an AnnotationValue. Many +- Arguments passed to Attributes better differentiates between compile and runtime values by introducing an AnnotationValue. Many ### Removed @@ -472,7 +535,7 @@ This release only deprecates code constructs replaced in v2. on a `Service` constructor or `ServicePrepare` method. - `InjectorDefinitionCompiler` to turn annotated PHP source code in a directory into an `InjectorDefinition` which defines how to construct the corresponding `Injector`. An implementation using PHP-Parser is also provided. -- `InjectorFactory` to take an `InjectorDefinition` and turn it into a DI container. An implementation that +- `InjectorFactory` to take an `InjectorDefinition` and turn it into a DI container. An implementation that wires an Auryn `Injector` is also provided. diff --git a/known-issues.xml b/known-issues.xml index a24d93cf..bc92a408 100644 --- a/known-issues.xml +++ b/known-issues.xml @@ -1,10 +1,5 @@ - - - - - @@ -254,9 +249,4 @@ - - - directories]]> - - diff --git a/src/Bootstrap/Configuration/BootstrappingConfiguration.php b/src/Bootstrap/Configuration/BootstrappingConfiguration.php index d79bd7fd..df46c617 100644 --- a/src/Bootstrap/Configuration/BootstrappingConfiguration.php +++ b/src/Bootstrap/Configuration/BootstrappingConfiguration.php @@ -11,7 +11,7 @@ interface BootstrappingConfiguration { /** - * @return list + * @return list */ public function scanDirectories() : array; diff --git a/src/Bootstrap/Configuration/ContainerDefinitionAnalysisOptionsFromBootstrappingConfiguration.php b/src/Bootstrap/Configuration/ContainerDefinitionAnalysisOptionsFromBootstrappingConfiguration.php index f7b7af79..63095086 100644 --- a/src/Bootstrap/Configuration/ContainerDefinitionAnalysisOptionsFromBootstrappingConfiguration.php +++ b/src/Bootstrap/Configuration/ContainerDefinitionAnalysisOptionsFromBootstrappingConfiguration.php @@ -19,12 +19,18 @@ public function create() : ContainerDefinitionAnalysisOptions { foreach ($this->configuration->scanDirectories() as $scanDirectory) { $scanPaths[] = $this->directoryResolver->rootPath($scanDirectory); } - $analysisOptions = ContainerDefinitionAnalysisOptionsBuilder::scanDirectories(...$scanPaths); + /** @var list $scanPaths */ $containerDefinitionConsumer = $this->configuration->containerDefinitionProvider(); - if ($containerDefinitionConsumer !== null) { - $analysisOptions = $analysisOptions->withDefinitionProvider($containerDefinitionConsumer); + + if ($containerDefinitionConsumer === null) { + $analysisOptions = ContainerDefinitionAnalysisOptions::fromScanDirectories($scanPaths); + } else { + $analysisOptions = ContainerDefinitionAnalysisOptions::fromScanDirectoriesAndDefinitionProvider( + $scanPaths, + $containerDefinitionConsumer + ); } - return $analysisOptions->build(); + return $analysisOptions; } } diff --git a/src/Cli/AnnotatedContainerCliRunner.php b/src/Cli/AnnotatedContainerCliRunner.php index 9a864245..13b64310 100644 --- a/src/Cli/AnnotatedContainerCliRunner.php +++ b/src/Cli/AnnotatedContainerCliRunner.php @@ -113,9 +113,6 @@ public function commands() : array { * @param list $argv */ public function run(array $argv) : void { - $this->commandExecutor->execute( - (new InputParser())->parse($argv), - new TerminalOutput() - ); + $this->commandExecutor->execute((new InputParser())->parse($argv), new TerminalOutput()); } } diff --git a/src/ContainerFactory/ContainerFactoryOptions.php b/src/ContainerFactory/ContainerFactoryOptions.php index f0fa3944..d859634f 100644 --- a/src/ContainerFactory/ContainerFactoryOptions.php +++ b/src/ContainerFactory/ContainerFactoryOptions.php @@ -6,14 +6,13 @@ /** * A set of options used by a ContainerFactory when creating your Container. - * - * @see ContainerFactoryOptionsBuilder */ final readonly class ContainerFactoryOptions { private function __construct( private Profiles $profiles, - ) {} + ) { + } public static function fromProfiles(Profiles $profiles) : self { return new self($profiles); diff --git a/src/ContainerFactory/State/ParameterResolver.php b/src/ContainerFactory/State/ParameterResolver.php index fa545bc3..733efe99 100644 --- a/src/ContainerFactory/State/ParameterResolver.php +++ b/src/ContainerFactory/State/ParameterResolver.php @@ -54,15 +54,15 @@ public function resolveParametersForServiceDelegate( } /** - * @param object $containerBuilder + * @param object $container * @param list $definitions * @return array */ - private function listOfInjectDefinitionsToArray(object $containerBuilder, ContainerFactoryState $state, array $definitions) : array { + private function listOfInjectDefinitionsToArray(object $container, ContainerFactoryState $state, array $definitions) : array { $params = []; foreach ($definitions as $injectDefinition) { $injectParameterValue = $this->injectParameterValueProvider->resolveInjectParameterValue( - $containerBuilder, + $container, $state, $injectDefinition ); diff --git a/src/Internal/InjectDefinitionFromFunctionalApi.php b/src/Internal/InjectDefinitionFromFunctionalApi.php index 101cc3ab..42ca18dc 100644 --- a/src/Internal/InjectDefinitionFromFunctionalApi.php +++ b/src/Internal/InjectDefinitionFromFunctionalApi.php @@ -2,7 +2,6 @@ namespace Cspray\AnnotatedContainer\Internal; - use Cspray\AnnotatedContainer\Attribute\InjectAttribute; final readonly class InjectDefinitionFromFunctionalApi implements InjectAttribute { @@ -16,7 +15,8 @@ public function __construct( private mixed $value, private array $profiles, private ?string $from - ) {} + ) { + } public function value() : mixed { return $this->value; @@ -35,4 +35,4 @@ public function profiles() : array { public function from() : ?string { return $this->from; } -} \ No newline at end of file +} diff --git a/src/StaticAnalysis/ContainerDefinitionAnalysisOptions.php b/src/StaticAnalysis/ContainerDefinitionAnalysisOptions.php index 297f9b54..ef79633a 100644 --- a/src/StaticAnalysis/ContainerDefinitionAnalysisOptions.php +++ b/src/StaticAnalysis/ContainerDefinitionAnalysisOptions.php @@ -7,14 +7,46 @@ /** * Represents configurable details for the compilation of a ContainerDefinition. */ -interface ContainerDefinitionAnalysisOptions { +final readonly class ContainerDefinitionAnalysisOptions { + + /** + * @param list $scanDirectories + * @param DefinitionProvider|null $definitionProvider + */ + private function __construct( + private array $scanDirectories, + private ?DefinitionProvider $definitionProvider, + ) { + } + + /** + * @param list $scanDirectories + * @return self + */ + public static function fromScanDirectories(array $scanDirectories) : self { + return new self($scanDirectories, null); + } + + /** + * @param list $scanDirectories + * @param DefinitionProvider $definitionProvider + * @return self + */ + public static function fromScanDirectoriesAndDefinitionProvider( + array $scanDirectories, + DefinitionProvider $definitionProvider, + ) : self { + return new self($scanDirectories, $definitionProvider); + } /** * Return a list of directories to scan for annotated services. * * @return list */ - public function scanDirectories() : array; + public function scanDirectories() : array { + return $this->scanDirectories; + } /** * If you need to modify the ContainerDefinitionBuilder return a proper consumer, otherwise null. @@ -24,5 +56,7 @@ public function scanDirectories() : array; * @return DefinitionProvider|null */ #[SingleEntrypointDefinitionProvider] - public function definitionProvider() : ?DefinitionProvider; + public function definitionProvider() : ?DefinitionProvider { + return $this->definitionProvider; + } } diff --git a/src/StaticAnalysis/ContainerDefinitionAnalysisOptionsBuilder.php b/src/StaticAnalysis/ContainerDefinitionAnalysisOptionsBuilder.php deleted file mode 100644 index c450168b..00000000 --- a/src/StaticAnalysis/ContainerDefinitionAnalysisOptionsBuilder.php +++ /dev/null @@ -1,69 +0,0 @@ - */ - private array $directories = []; - - private ?DefinitionProvider $consumer = null; - - private function __construct() { - } - - /** - * Specify the directories that should be parsed when generating the ContainerDefinition - * - * @param non-empty-string ...$directories - * @return static - */ - public static function scanDirectories(string ...$directories) : self { - $instance = new self(); - $instance->directories = array_values($directories); - return $instance; - } - - /** - * Specify that the ContainerDefinitionBuilder should be modified before the ContainerDefinition is built. - * - * @param DefinitionProvider $consumer - * @return $this - */ - #[SingleEntrypointDefinitionProvider] - public function withDefinitionProvider(DefinitionProvider $consumer) : self { - $instance = clone $this; - $instance->consumer = $consumer; - return $instance; - } - - public function build() : ContainerDefinitionAnalysisOptions { - return new class( - $this->directories, - $this->consumer, - ) implements ContainerDefinitionAnalysisOptions { - /** - * @param non-empty-list $directories - * @param DefinitionProvider|null $consumer - */ - public function __construct( - private readonly array $directories, - private readonly ?DefinitionProvider $consumer, - ) { - } - - public function scanDirectories(): array { - return $this->directories; - } - - public function definitionProvider(): ?DefinitionProvider { - return $this->consumer; - } - }; - } -} diff --git a/test/Unit/Bootstrap/BootstrapTest.php b/test/Unit/Bootstrap/BootstrapTest.php index 60ae6a11..03e0b010 100644 --- a/test/Unit/Bootstrap/BootstrapTest.php +++ b/test/Unit/Bootstrap/BootstrapTest.php @@ -30,6 +30,7 @@ use Cspray\AnnotatedContainer\Fixture\CustomServiceAttribute\Repository; use Cspray\AnnotatedContainer\Fixture\Fixtures; use Cspray\AnnotatedContainer\Profiles; +use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptions; use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptionsBuilder; use Cspray\AnnotatedContainer\StaticAnalysis\DefinitionProvider; use Cspray\AnnotatedContainer\Unit\Helper\FixtureBootstrappingDirectoryResolver; @@ -411,9 +412,9 @@ public function testBootstrapHandlesConfigurationWithCacheCorrectly() : void { $directoryResolver = new FixtureBootstrappingDirectoryResolver(); $cacheKey = CacheKey::fromContainerDefinitionAnalysisOptions( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - $directoryResolver->rootPath('SingleConcreteService') - )->build() + ContainerDefinitionAnalysisOptions::fromScanDirectories( + [$directoryResolver->rootPath('SingleConcreteService')] + ) ); $cache = $this->getMockBuilder(ContainerDefinitionCache::class)->getMock(); $cache->expects($this->once()) diff --git a/test/Unit/Cli/Command/BuildCommandTest.php b/test/Unit/Cli/Command/BuildCommandTest.php index 347c5556..ce54ee13 100644 --- a/test/Unit/Cli/Command/BuildCommandTest.php +++ b/test/Unit/Cli/Command/BuildCommandTest.php @@ -21,7 +21,7 @@ class BuildCommandTest extends TestCase { private TerminalOutput $output; private FixtureBootstrappingDirectoryResolver $directoryResolver; private BuildCommand $subject; - private MockObject&ContainerDefinitionAnalysisOptions $analysisOptions; + private ContainerDefinitionAnalysisOptions $analysisOptions; private MockObject&ContainerDefinitionCache $cache; protected function setUp() : void { @@ -30,8 +30,10 @@ protected function setUp() : void { $this->output = new TerminalOutput($this->stdout, $this->stderr); $this->cache = $this->createMock(ContainerDefinitionCache::class); - $this->analysisOptions = $this->createMock(ContainerDefinitionAnalysisOptions::class); $this->directoryResolver = new FixtureBootstrappingDirectoryResolver(); + $this->analysisOptions = ContainerDefinitionAnalysisOptions::fromScanDirectories( + [$this->directoryResolver->rootPath('SingleConcreteService')] + ); $this->subject = new BuildCommand( $this->cache, @@ -80,12 +82,6 @@ public function testGetHelp() : void { } public function testBuildRemovesAndSetsCorrectCacheEntryAndSendsCorrectOutput() : void { - $this->analysisOptions->method('scanDirectories') - ->willReturn([$this->directoryResolver->rootPath('SingleConcreteService')]); - - $this->analysisOptions->method('definitionProvider') - ->willReturn(null); - $cacheKeyMatchesExpected = function (CacheKey $cacheKey) { return md5($this->directoryResolver->rootPath('SingleConcreteService')) === $cacheKey->asString(); diff --git a/test/Unit/Cli/Command/CacheClearCommandTest.php b/test/Unit/Cli/Command/CacheClearCommandTest.php index 6ab4f8c8..a248c901 100644 --- a/test/Unit/Cli/Command/CacheClearCommandTest.php +++ b/test/Unit/Cli/Command/CacheClearCommandTest.php @@ -20,7 +20,7 @@ final class CacheClearCommandTest extends TestCase { private TerminalOutput $output; private MockObject&ContainerDefinitionCache $cache; - private MockObject&ContainerDefinitionAnalysisOptions $analysisOptions; + private ContainerDefinitionAnalysisOptions $analysisOptions; private FixtureBootstrappingDirectoryResolver $directoryResolver; private CacheClearCommand $subject; @@ -33,7 +33,9 @@ protected function setUp() : void { $this->directoryResolver = new FixtureBootstrappingDirectoryResolver(); $this->cache = $this->createMock(ContainerDefinitionCache::class); - $this->analysisOptions = $this->createMock(ContainerDefinitionAnalysisOptions::class); + $this->analysisOptions = ContainerDefinitionAnalysisOptions::fromScanDirectories( + [$this->directoryResolver->rootPath('SingleConcreteService')] + ); $this->subject = new CacheClearCommand( $this->cache, @@ -77,12 +79,6 @@ public function testGetHelp() : void { } public function testCallingCommandCallsCacheRemoveWithCorrectCacheKey() : void { - $this->analysisOptions->method('scanDirectories') - ->willReturn([$this->directoryResolver->rootPath('SingleConcreteService')]); - - $this->analysisOptions->method('definitionProvider') - ->willReturn(null); - $this->cache->expects($this->once()) ->method('remove') ->with($this->callback( diff --git a/test/Unit/ContainerFactory/ContainerFactoryTestCase.php b/test/Unit/ContainerFactory/ContainerFactoryTestCase.php index b8e871d7..e0dc7fce 100644 --- a/test/Unit/ContainerFactory/ContainerFactoryTestCase.php +++ b/test/Unit/ContainerFactory/ContainerFactoryTestCase.php @@ -7,7 +7,6 @@ use Cspray\AnnotatedContainer\Autowire\AutowireableInvoker; use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactory; use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactoryOptions; -use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactoryOptionsBuilder; use Cspray\AnnotatedContainer\ContainerFactory\ParameterStore; use Cspray\AnnotatedContainer\Definition\ContainerDefinition; use Cspray\AnnotatedContainer\Definition\ContainerDefinitionBuilder; @@ -16,12 +15,14 @@ use Cspray\AnnotatedContainer\Exception\InvalidAlias; use Cspray\AnnotatedContainer\Exception\MultipleInjectOnSameParameter; use Cspray\AnnotatedContainer\Exception\ParameterStoreNotFound; +use Cspray\AnnotatedContainer\Fixture\DelegatedServiceWithInjectedParameter\FooService; +use Cspray\AnnotatedContainer\Fixture\DelegatedServiceWithInjectedParameter\ServiceInterface; use Cspray\AnnotatedContainer\Profiles; use Cspray\AnnotatedContainer\Reflection\Type; use Cspray\AnnotatedContainer\Reflection\TypeUnion; use Cspray\AnnotatedContainer\Reflection\TypeIntersect; use Cspray\AnnotatedContainer\StaticAnalysis\AnnotatedTargetContainerDefinitionAnalyzer; -use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptionsBuilder; +use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptions; use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalyzer; use Cspray\AnnotatedContainer\Unit\Helper\HasMockDefinitions; use Cspray\AnnotatedContainer\Unit\Helper\StubContainerFactoryListener; @@ -65,8 +66,8 @@ private function getContainer( Emitter $emitter = new Emitter() ) : AnnotatedContainer { $compiler = $this->getContainerDefinitionCompiler(); - $optionsBuilder = ContainerDefinitionAnalysisOptionsBuilder::scanDirectories($dir); - $containerDefinition = $compiler->analyze($optionsBuilder->build()); + $optionsBuilder = ContainerDefinitionAnalysisOptions::fromScanDirectories([$dir]); + $containerDefinition = $compiler->analyze($optionsBuilder); $containerOptions = ContainerFactoryOptions::fromProfiles($profiles ?? Profiles::fromList(['default'])); $factory = $this->getContainerFactory($emitter); @@ -453,7 +454,7 @@ public static function deserializeContainerProvider() : array { public function testDeserializingContainerWithInjectAllowsServiceCreation(Fixture $fixture, callable $assertions) { $serializer = new XmlContainerDefinitionSerializer(); $containerDefinition = $this->getContainerDefinitionCompiler()->analyze( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories($fixture->getPath())->build() + ContainerDefinitionAnalysisOptions::fromScanDirectories([$fixture->getPath()]) ); $serialized = $serializer->serialize($containerDefinition); @@ -625,10 +626,10 @@ public function testCreatingServiceWithMultipleInjectOnServicePrepareThrowsExcep public function testCreatingDelegatedServiceWithInstancedFactoryWithInjectDefinition() : void { $container = $this->getContainer(__DIR__ . '/../../Fixture/DelegatedServiceWithInjectedParameter'); - $service = $container->get(\Cspray\AnnotatedContainer\Fixture\DelegatedServiceWithInjectedParameter\ServiceInterface::class); + $service = $container->get(ServiceInterface::class); self::assertInstanceOf( - \Cspray\AnnotatedContainer\Fixture\DelegatedServiceWithInjectedParameter\FooService::class, + FooService::class, $service ); self::assertSame('my injected value', $service->value); diff --git a/test/Unit/Definition/Cache/CacheKeyTest.php b/test/Unit/Definition/Cache/CacheKeyTest.php index 649c9940..e52d7148 100644 --- a/test/Unit/Definition/Cache/CacheKeyTest.php +++ b/test/Unit/Definition/Cache/CacheKeyTest.php @@ -5,7 +5,6 @@ use Cspray\AnnotatedContainer\Definition\Cache\CacheKey; use Cspray\AnnotatedContainer\StaticAnalysis\CompositeDefinitionProvider; use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptions; -use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptionsBuilder; use Cspray\AnnotatedContainer\Unit\Helper\StubDefinitionProvider; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; @@ -14,14 +13,23 @@ final class CacheKeyTest extends TestCase { public static function optionsCacheKeyProvider() : array { return [ - [ContainerDefinitionAnalysisOptionsBuilder::scanDirectories('dir')->build(), md5('dir')], - [ContainerDefinitionAnalysisOptionsBuilder::scanDirectories('foo', 'bar', 'baz')->build(), md5('barbazfoo')], - [ContainerDefinitionAnalysisOptionsBuilder::scanDirectories('foo') - ->withDefinitionProvider(new StubDefinitionProvider())->build(), md5('foo/' . StubDefinitionProvider::class)], - [ContainerDefinitionAnalysisOptionsBuilder::scanDirectories('qux', 'quz', 'quy') - ->withDefinitionProvider(new StubDefinitionProvider())->build(), md5('quxquyquz/' . StubDefinitionProvider::class)], - [ContainerDefinitionAnalysisOptionsBuilder::scanDirectories('zux', 'zuw', 'zuv') - ->withDefinitionProvider($composite = new CompositeDefinitionProvider(new StubDefinitionProvider(), new StubDefinitionProvider()))->build(), md5('zuvzuwzux/' . $composite)] + [ContainerDefinitionAnalysisOptions::fromScanDirectories(['dir']), md5('dir')], + [ContainerDefinitionAnalysisOptions::fromScanDirectories(['foo', 'bar', 'baz']), md5('barbazfoo')], + [ContainerDefinitionAnalysisOptions::fromScanDirectoriesAndDefinitionProvider( + ['foo'], + new StubDefinitionProvider() + ), md5('foo/' . StubDefinitionProvider::class)], + [ContainerDefinitionAnalysisOptions::fromScanDirectoriesAndDefinitionProvider( + ['qux', 'quz', 'quy'], + new StubDefinitionProvider() + ), md5('quxquyquz/' . StubDefinitionProvider::class)], + [ContainerDefinitionAnalysisOptions::fromScanDirectoriesAndDefinitionProvider( + ['zux', 'zuw', 'zuv'], + $composite = new CompositeDefinitionProvider( + new StubDefinitionProvider(), + new StubDefinitionProvider() + ) + ), md5('zuvzuwzux/' . $composite)] ]; } diff --git a/test/Unit/Definition/Cache/FileBackedContainerDefinitionCacheTest.php b/test/Unit/Definition/Cache/FileBackedContainerDefinitionCacheTest.php index 2e084d79..7f854c6d 100644 --- a/test/Unit/Definition/Cache/FileBackedContainerDefinitionCacheTest.php +++ b/test/Unit/Definition/Cache/FileBackedContainerDefinitionCacheTest.php @@ -11,9 +11,7 @@ use Cspray\AnnotatedContainer\Exception\CacheDirectoryNotWritable; use Cspray\AnnotatedContainer\Exception\MismatchedContainerDefinitionSerializerVersions; use Cspray\AnnotatedContainer\Filesystem\Filesystem; -use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptionsBuilder; -use org\bovigo\vfs\vfsStream; -use org\bovigo\vfs\vfsStreamDirectory; +use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptions; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -27,7 +25,7 @@ protected function setUp() : void { $this->serializer = $this->createMock(ContainerDefinitionSerializer::class); $this->filesystem = $this->createMock(Filesystem::class); $this->cacheKey = CacheKey::fromContainerDefinitionAnalysisOptions( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories('foo', 'bar', 'baz')->build() + ContainerDefinitionAnalysisOptions::fromScanDirectories(['foo', 'bar', 'baz']) ); } diff --git a/test/Unit/Event/EmitterTest.php b/test/Unit/Event/EmitterTest.php index 639d2d62..672be2b8 100644 --- a/test/Unit/Event/EmitterTest.php +++ b/test/Unit/Event/EmitterTest.php @@ -20,7 +20,6 @@ use Cspray\AnnotatedContainer\Event\Listener\ContainerFactory\AfterContainerCreation; use Cspray\AnnotatedContainer\Event\Listener\ContainerFactory\BeforeContainerCreation; use Cspray\AnnotatedContainer\Event\Listener\ContainerFactory\InjectingMethodParameter; -use Cspray\AnnotatedContainer\Event\Listener\ContainerFactory\InjectingProperty; use Cspray\AnnotatedContainer\Event\Listener\ContainerFactory\ServiceAliasResolution; use Cspray\AnnotatedContainer\Event\Listener\ContainerFactory\ServiceDelegated; use Cspray\AnnotatedContainer\Event\Listener\ContainerFactory\ServiceFilteredDueToProfiles; @@ -66,7 +65,7 @@ public static function listenerData() : array { BeforeContainerAnalysis::class => [ BeforeContainerAnalysis::class, fn() => [ - $this->getMockBuilder(ContainerDefinitionAnalysisOptions::class)->getMock(), + ContainerDefinitionAnalysisOptions::fromScanDirectories(['src']), ], fn(ContainerDefinitionAnalysisOptions $analysisOptions) => $this->subject->emitBeforeContainerAnalysis($analysisOptions) ], @@ -158,7 +157,7 @@ public static function listenerData() : array { AfterContainerAnalysis::class => [ AfterContainerAnalysis::class, fn() => [ - $this->getMockBuilder(ContainerDefinitionAnalysisOptions::class)->getMock(), + ContainerDefinitionAnalysisOptions::fromScanDirectories(['src']), $this->getMockBuilder(ContainerDefinition::class)->getMock(), ], fn(ContainerDefinitionAnalysisOptions $analysisOptions, ContainerDefinition $containerDefinition) => diff --git a/test/Unit/LogicalConstraint/Check/DuplicateServiceDelegateTest.php b/test/Unit/LogicalConstraint/Check/DuplicateServiceDelegateTest.php index c51df6ac..8baa6c59 100644 --- a/test/Unit/LogicalConstraint/Check/DuplicateServiceDelegateTest.php +++ b/test/Unit/LogicalConstraint/Check/DuplicateServiceDelegateTest.php @@ -6,6 +6,7 @@ use Cspray\AnnotatedContainer\LogicalConstraint\Check\DuplicateServiceDelegate; use Cspray\AnnotatedContainer\LogicalConstraint\LogicalConstraintViolationType; use Cspray\AnnotatedContainer\Profiles; +use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptions; use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptionsBuilder; use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalyzer; use Cspray\AnnotatedContainer\StaticAnalysis\DefinitionProvider; @@ -27,9 +28,9 @@ protected function setUp() : void { public function testNoDuplicateDelegateHasNoViolations() : void { $definition = $this->analyzer->analyze( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - Fixtures::implicitServiceDelegateType()->getPath() - )->build() + ContainerDefinitionAnalysisOptions::fromScanDirectories( + [Fixtures::implicitServiceDelegateType()->getPath()] + ) ); $violations = $this->subject->constraintViolations($definition, Profiles::defaultOnly()); @@ -39,9 +40,9 @@ public function testNoDuplicateDelegateHasNoViolations() : void { public function testDuplicateDelegateAttributeForSameServiceHasCorrectViolation() : void { $definition = $this->analyzer->analyze( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - LogicalConstraintFixtures::duplicateServiceDelegate()->getPath() - )->build() + ContainerDefinitionAnalysisOptions::fromScanDirectories( + [LogicalConstraintFixtures::duplicateServiceDelegate()->getPath()] + ) ); $violations = $this->subject->constraintViolations($definition, Profiles::defaultOnly()); @@ -69,9 +70,8 @@ public function testDuplicateDelegateAttributeForSameServiceHasCorrectViolation( public function testDuplicateDelegateAddedWithFunctionalApi() : void { $definition = $this->analyzer->analyze( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - Fixtures::implicitServiceDelegateType()->getPath() - )->withDefinitionProvider( + ContainerDefinitionAnalysisOptions::fromScanDirectoriesAndDefinitionProvider( + [Fixtures::implicitServiceDelegateType()->getPath()], new class implements DefinitionProvider { public function consume(DefinitionProviderContext $context) : void { @@ -83,7 +83,7 @@ public function consume(DefinitionProviderContext $context) : void { ); } } - )->build() + ) ); $violations = $this->subject->constraintViolations($definition, Profiles::defaultOnly()); diff --git a/test/Unit/LogicalConstraint/Check/DuplicateServiceNameTest.php b/test/Unit/LogicalConstraint/Check/DuplicateServiceNameTest.php index 2556d8c9..4d807e8e 100644 --- a/test/Unit/LogicalConstraint/Check/DuplicateServiceNameTest.php +++ b/test/Unit/LogicalConstraint/Check/DuplicateServiceNameTest.php @@ -5,12 +5,13 @@ use Cspray\AnnotatedContainer\LogicalConstraint\Check\DuplicateServiceName; use Cspray\AnnotatedContainer\LogicalConstraint\LogicalConstraintViolationType; use Cspray\AnnotatedContainer\Profiles; -use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptionsBuilder; +use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptions; use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalyzer; use Cspray\AnnotatedContainer\Fixture\Fixtures; use Cspray\AnnotatedContainer\Fixture\LogicalConstraints\DuplicateServiceName\BarService; use Cspray\AnnotatedContainer\Fixture\LogicalConstraints\DuplicateServiceName\FooService; use Cspray\AnnotatedContainer\Fixture\LogicalConstraints\LogicalConstraintFixtures; +use PHPUnit\Framework\Attributes\DataProvider; final class DuplicateServiceNameTest extends LogicalConstraintTestCase { @@ -24,9 +25,9 @@ protected function setUp() : void { } public function testServiceWithMultipleNamesReturnsCorrectViolation() : void { - $options = ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - LogicalConstraintFixtures::duplicateServiceName()->getPath() - )->build(); + $options = ContainerDefinitionAnalysisOptions::fromScanDirectories( + [LogicalConstraintFixtures::duplicateServiceName()->getPath()] + ); $definition = $this->analyzer->analyze($options); @@ -55,11 +56,11 @@ public static function duplicateServiceNameProfiles() : array { ]; } - #[\PHPUnit\Framework\Attributes\DataProvider('duplicateServiceNameProfiles')] + #[DataProvider('duplicateServiceNameProfiles')] public function testServiceWithMultipleNamesOnDifferentProfilesHasNoViolation(string $profile) : void { - $options = ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - Fixtures::duplicateNamedServiceDifferentProfiles()->getPath() - )->build(); + $options = ContainerDefinitionAnalysisOptions::fromScanDirectories( + [Fixtures::duplicateNamedServiceDifferentProfiles()->getPath()] + ); $definition = $this->getAnalyzer()->analyze($options); diff --git a/test/Unit/LogicalConstraint/Check/DuplicateServicePrepareTest.php b/test/Unit/LogicalConstraint/Check/DuplicateServicePrepareTest.php index 91a9f394..f2df58c0 100644 --- a/test/Unit/LogicalConstraint/Check/DuplicateServicePrepareTest.php +++ b/test/Unit/LogicalConstraint/Check/DuplicateServicePrepareTest.php @@ -6,7 +6,7 @@ use Cspray\AnnotatedContainer\LogicalConstraint\Check\DuplicateServicePrepare; use Cspray\AnnotatedContainer\LogicalConstraint\LogicalConstraintViolationType; use Cspray\AnnotatedContainer\Profiles; -use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptionsBuilder; +use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptions; use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalyzer; use Cspray\AnnotatedContainer\StaticAnalysis\DefinitionProvider; use Cspray\AnnotatedContainer\StaticAnalysis\DefinitionProviderContext; @@ -28,9 +28,9 @@ protected function setUp() : void { public function testNoDuplicatePreparesHasZeroViolations() : void { $definition = $this->analyzer->analyze( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - Fixtures::multiplePrepareServices()->getPath() - )->build() + ContainerDefinitionAnalysisOptions::fromScanDirectories( + [Fixtures::multiplePrepareServices()->getPath()] + ) ); $results = $this->subject->constraintViolations($definition, Profiles::fromList(['default'])); @@ -40,9 +40,9 @@ public function testNoDuplicatePreparesHasZeroViolations() : void { public function testDuplicatePreparesHasViolation() : void { $definition = $this->analyzer->analyze( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - LogicalConstraintFixtures::duplicateServicePrepare()->getPath() - )->build() + ContainerDefinitionAnalysisOptions::fromScanDirectories( + [LogicalConstraintFixtures::duplicateServicePrepare()->getPath()] + ) ); $results = $this->subject->constraintViolations($definition, Profiles::fromList(['default'])); @@ -75,9 +75,8 @@ public function testDuplicatePreparesHasViolation() : void { public function testDuplicatePreparesWithDefinitionProviderHasViolation() : void { $definition = $this->analyzer->analyze( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - Fixtures::singleConcreteService()->getPath() - )->withDefinitionProvider( + ContainerDefinitionAnalysisOptions::fromScanDirectoriesAndDefinitionProvider( + [Fixtures::singleConcreteService()->getPath()], new class implements DefinitionProvider { public function consume(DefinitionProviderContext $context) : void { $context->addServicePrepareDefinition( @@ -92,7 +91,7 @@ public function consume(DefinitionProviderContext $context) : void { ); } } - )->build() + ) ); $results = $this->subject->constraintViolations($definition, Profiles::fromList(['default'])); diff --git a/test/Unit/LogicalConstraint/Check/DuplicateServiceTypeTest.php b/test/Unit/LogicalConstraint/Check/DuplicateServiceTypeTest.php index 01ea4a82..e4af7e48 100644 --- a/test/Unit/LogicalConstraint/Check/DuplicateServiceTypeTest.php +++ b/test/Unit/LogicalConstraint/Check/DuplicateServiceTypeTest.php @@ -6,6 +6,7 @@ use Cspray\AnnotatedContainer\LogicalConstraint\Check\DuplicateServiceType; use Cspray\AnnotatedContainer\LogicalConstraint\LogicalConstraintViolationType; use Cspray\AnnotatedContainer\Profiles; +use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptions; use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptionsBuilder; use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalyzer; use Cspray\AnnotatedContainer\StaticAnalysis\DefinitionProvider; @@ -31,9 +32,9 @@ protected function setUp() : void { public function testNoDuplicateServicesHasNoViolations() : void { $definition = $this->analyzer->analyze( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - Fixtures::profileResolvedServices()->getPath() - )->build() + ContainerDefinitionAnalysisOptions::fromScanDirectories( + [Fixtures::profileResolvedServices()->getPath()] + ) ); $results = $this->subject->constraintViolations($definition, Profiles::fromList(['default'])); @@ -43,9 +44,9 @@ public function testNoDuplicateServicesHasNoViolations() : void { public function testDuplicateServiceTypesWithAttributes() : void { $definition = $this->analyzer->analyze( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - LogicalConstraintFixtures::duplicateServiceType()->getPath() - )->build() + ContainerDefinitionAnalysisOptions::fromScanDirectories( + [LogicalConstraintFixtures::duplicateServiceType()->getPath()] + ) ); $results = $this->subject->constraintViolations($definition, Profiles::fromList(['default'])); @@ -75,16 +76,15 @@ public function testDuplicateServiceTypesWithAttributes() : void { public function testDuplicateServiceTypesWithOnlyMultipleFunctionCalls() : void { $definition = $this->analyzer->analyze( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - Fixtures::nonAnnotatedServices()->getPath() - )->withDefinitionProvider( + ContainerDefinitionAnalysisOptions::fromScanDirectoriesAndDefinitionProvider( + [Fixtures::nonAnnotatedServices()->getPath()], new class implements DefinitionProvider { public function consume(DefinitionProviderContext $context) : void { $context->addServiceDefinition(service(types()->class(NotAnnotatedObject::class))); $context->addServiceDefinition(service(types()->class(NotAnnotatedObject::class))); } } - )->build() + ) ); $results = $this->subject->constraintViolations($definition, Profiles::fromList(['default'])); diff --git a/test/Unit/LogicalConstraint/Check/MultiplePrimaryForAbstractServiceTest.php b/test/Unit/LogicalConstraint/Check/MultiplePrimaryForAbstractServiceTest.php index 5f35d570..1e57628e 100644 --- a/test/Unit/LogicalConstraint/Check/MultiplePrimaryForAbstractServiceTest.php +++ b/test/Unit/LogicalConstraint/Check/MultiplePrimaryForAbstractServiceTest.php @@ -5,6 +5,7 @@ use Cspray\AnnotatedContainer\LogicalConstraint\Check\MultiplePrimaryForAbstractService; use Cspray\AnnotatedContainer\LogicalConstraint\LogicalConstraintViolationType; use Cspray\AnnotatedContainer\Profiles; +use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptions; use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptionsBuilder; use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalyzer; use Cspray\AnnotatedContainer\Fixture\Fixture; @@ -25,9 +26,9 @@ protected function setUp() : void { public function testSinglePrimaryPerServiceHasNoViolations() : void { $definition = $this->analyzer->analyze( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - Fixtures::primaryAliasedServices()->getPath() - )->build() + ContainerDefinitionAnalysisOptions::fromScanDirectories( + [Fixtures::primaryAliasedServices()->getPath()] + ) ); $violations = $this->subject->constraintViolations($definition, Profiles::fromList(['default'])); @@ -37,9 +38,9 @@ public function testSinglePrimaryPerServiceHasNoViolations() : void { public function testMultiplePrimaryServiceAttributedHasCorrectViolation() : void { $definition = $this->analyzer->analyze( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - LogicalConstraintFixtures::multiplePrimaryService()->getPath() - )->build() + ContainerDefinitionAnalysisOptions::fromScanDirectories( + [LogicalConstraintFixtures::multiplePrimaryService()->getPath()] + ) ); $violations = $this->subject->constraintViolations($definition, Profiles::fromList(['default'])); diff --git a/test/Unit/LogicalConstraint/Check/NonPublicServiceDelegateTest.php b/test/Unit/LogicalConstraint/Check/NonPublicServiceDelegateTest.php index 6c7287dc..ad39a5b6 100644 --- a/test/Unit/LogicalConstraint/Check/NonPublicServiceDelegateTest.php +++ b/test/Unit/LogicalConstraint/Check/NonPublicServiceDelegateTest.php @@ -5,7 +5,7 @@ use Cspray\AnnotatedContainer\LogicalConstraint\Check\NonPublicServiceDelegate; use Cspray\AnnotatedContainer\LogicalConstraint\LogicalConstraintViolationType; use Cspray\AnnotatedContainer\Profiles; -use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptionsBuilder; +use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptions; use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalyzer; use Cspray\AnnotatedContainer\Fixture\Fixtures; use Cspray\AnnotatedContainer\Fixture\LogicalConstraints\LogicalConstraintFixtures; @@ -23,9 +23,9 @@ protected function setUp(): void { } public function testServiceDelegateIsPublicMethodHasNoLogicalConstraints() : void { - $options = ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - Fixtures::implicitServiceDelegateType()->getPath() - )->build(); + $options = ContainerDefinitionAnalysisOptions::fromScanDirectories( + [Fixtures::implicitServiceDelegateType()->getPath()] + ); $definition = $this->analyzer->analyze($options); @@ -35,9 +35,9 @@ public function testServiceDelegateIsPublicMethodHasNoLogicalConstraints() : voi } public function testServiceDelegateIsProtectedMethodHasCorrectLogicalConstraint() : void { - $options = ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - LogicalConstraintFixtures::protectedServiceDelegate()->getPath() - )->build(); + $options = ContainerDefinitionAnalysisOptions::fromScanDirectories( + [LogicalConstraintFixtures::protectedServiceDelegate()->getPath()] + ); $definition = $this->analyzer->analyze($options); @@ -52,9 +52,9 @@ public function testServiceDelegateIsProtectedMethodHasCorrectLogicalConstraint( } public function testServiceDelegateIsPrivateMethodHasCorrectLogicalConstraint() : void { - $options = ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - LogicalConstraintFixtures::privateServiceDelegate()->getPath() - )->build(); + $options = ContainerDefinitionAnalysisOptions::fromScanDirectories( + [LogicalConstraintFixtures::privateServiceDelegate()->getPath()] + ); $definition = $this->analyzer->analyze($options); diff --git a/test/Unit/LogicalConstraint/Check/NonPublicServicePrepareTest.php b/test/Unit/LogicalConstraint/Check/NonPublicServicePrepareTest.php index f963cf94..0e85e7ce 100644 --- a/test/Unit/LogicalConstraint/Check/NonPublicServicePrepareTest.php +++ b/test/Unit/LogicalConstraint/Check/NonPublicServicePrepareTest.php @@ -5,7 +5,7 @@ use Cspray\AnnotatedContainer\LogicalConstraint\Check\NonPublicServicePrepare; use Cspray\AnnotatedContainer\LogicalConstraint\LogicalConstraintViolationType; use Cspray\AnnotatedContainer\Profiles; -use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptionsBuilder; +use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptions; use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalyzer; use Cspray\AnnotatedContainer\Fixture\Fixtures; use Cspray\AnnotatedContainer\Fixture\LogicalConstraints\LogicalConstraintFixtures; @@ -25,9 +25,9 @@ protected function setUp() : void { public function testServicePrepareIsPublicMethodHasNoViolations() : void { $definition = $this->analyzer->analyze( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - Fixtures::interfacePrepareServices()->getPath() - )->build() + ContainerDefinitionAnalysisOptions::fromScanDirectories( + [Fixtures::interfacePrepareServices()->getPath()] + ) ); $collection = $this->subject->constraintViolations($definition, Profiles::fromList(['default'])); @@ -37,9 +37,9 @@ public function testServicePrepareIsPublicMethodHasNoViolations() : void { public function testServicePrepareIsPrivateMethodHasCorrectViolation() : void { $definition = $this->analyzer->analyze( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - LogicalConstraintFixtures::privateServicePrepare()->getPath() - )->build() + ContainerDefinitionAnalysisOptions::fromScanDirectories( + [LogicalConstraintFixtures::privateServicePrepare()->getPath()] + ) ); $collection = $this->subject->constraintViolations($definition, Profiles::fromList(['default'])); @@ -57,9 +57,9 @@ public function testServicePrepareIsPrivateMethodHasCorrectViolation() : void { public function testServicePrepareIsProtectedMethodHasCorrectViolation() : void { $definition = $this->analyzer->analyze( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - LogicalConstraintFixtures::protectedServicePrepare()->getPath() - )->build() + ContainerDefinitionAnalysisOptions::fromScanDirectories( + [LogicalConstraintFixtures::protectedServicePrepare()->getPath()] + ) ); $collection = $this->subject->constraintViolations($definition, Profiles::fromList(['default'])); diff --git a/test/Unit/LogicalConstraint/LogicalConstraintValidatorTest.php b/test/Unit/LogicalConstraint/LogicalConstraintValidatorTest.php index 055991d1..a925fe4d 100644 --- a/test/Unit/LogicalConstraint/LogicalConstraintValidatorTest.php +++ b/test/Unit/LogicalConstraint/LogicalConstraintValidatorTest.php @@ -10,7 +10,7 @@ use Cspray\AnnotatedContainer\LogicalConstraint\LogicalConstraintViolationType; use Cspray\AnnotatedContainer\Profiles; use Cspray\AnnotatedContainer\StaticAnalysis\AnnotatedTargetContainerDefinitionAnalyzer; -use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptionsBuilder; +use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptions; use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalyzer; use Cspray\AnnotatedContainer\Fixture\Fixtures; use Cspray\AnnotatedTarget\PhpParserAnnotatedTargetParser; @@ -29,9 +29,9 @@ protected function setUp(): void { public function testLogicalValidatorPassesContainerDefinitionToLogicalConstraintChecks() : void { $definition = $this->analyzer->analyze( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - Fixtures::singleConcreteService()->getPath() - )->build() + ContainerDefinitionAnalysisOptions::fromScanDirectories( + [Fixtures::singleConcreteService()->getPath()] + ) ); $profiles = Profiles::fromList(['default']); @@ -50,9 +50,9 @@ public function testLogicalValidatorPassesContainerDefinitionToLogicalConstraint public function testLogicalValidatorMergesLogicalConstraintViolations() : void { $definition = $this->analyzer->analyze( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories( - Fixtures::singleConcreteService()->getPath() - )->build() + ContainerDefinitionAnalysisOptions::fromScanDirectories( + [Fixtures::singleConcreteService()->getPath()] + ) ); $profiles = Profiles::fromList(['default']); diff --git a/test/Unit/Serializer/XmlContainerDefinitionSerializerTest.php b/test/Unit/Serializer/XmlContainerDefinitionSerializerTest.php index a85525d7..bfc36033 100644 --- a/test/Unit/Serializer/XmlContainerDefinitionSerializerTest.php +++ b/test/Unit/Serializer/XmlContainerDefinitionSerializerTest.php @@ -15,7 +15,7 @@ use Cspray\AnnotatedContainer\Exception\InvalidInjectDefinition; use Cspray\AnnotatedContainer\Exception\MismatchedContainerDefinitionSerializerVersions; use Cspray\AnnotatedContainer\StaticAnalysis\AnnotatedTargetContainerDefinitionAnalyzer; -use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptionsBuilder; +use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptions; use Cspray\AnnotatedContainer\Unit\Helper\HasMockDefinitions; use Cspray\AnnotatedContainer\Unit\Helper\UnserializableObject; use Cspray\AnnotatedContainer\Fixture\Fixture; @@ -1213,7 +1213,7 @@ public function testScannedAndSerializedContainerDefinitionMatchesDeserialized(F $subject = new XmlContainerDefinitionSerializer(); $containerDefinition = $compiler->analyze( - ContainerDefinitionAnalysisOptionsBuilder::scanDirectories($fixture->getPath())->build() + ContainerDefinitionAnalysisOptions::fromScanDirectories([$fixture->getPath()]) ); $expected = $subject->serialize($containerDefinition); diff --git a/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalysisTests/AnnotatedTargetContainerDefinitionAnalyzerTestCase.php b/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalysisTests/AnnotatedTargetContainerDefinitionAnalyzerTestCase.php index 5283d081..9036650e 100755 --- a/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalysisTests/AnnotatedTargetContainerDefinitionAnalyzerTestCase.php +++ b/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalysisTests/AnnotatedTargetContainerDefinitionAnalyzerTestCase.php @@ -5,7 +5,7 @@ use Cspray\AnnotatedContainer\Definition\ContainerDefinition; use Cspray\AnnotatedContainer\Event\Emitter; use Cspray\AnnotatedContainer\StaticAnalysis\AnnotatedTargetContainerDefinitionAnalyzer; -use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptionsBuilder; +use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptions; use Cspray\AnnotatedContainer\StaticAnalysis\DefinitionProvider; use Cspray\AnnotatedContainer\Unit\ContainerDefinitionAssertionsTrait; use Cspray\AnnotatedContainer\Unit\Helper\AnalysisEventCollection; @@ -20,7 +20,7 @@ abstract class AnnotatedTargetContainerDefinitionAnalyzerTestCase extends TestCa private AnnotatedTargetContainerDefinitionAnalyzer $analyzer; - private ContainerDefinitionAnalysisOptionsBuilder $builder; + private ContainerDefinitionAnalysisOptions $analysisOptions; private StubAnalysisListener $stubAnalysisListener; @@ -52,10 +52,11 @@ protected function setUp() : void { $dirs[] = $fixture->getPath(); } - $this->builder = ContainerDefinitionAnalysisOptionsBuilder::scanDirectories(...$dirs); $consumer = $this->getDefinitionProvider(); if (!is_null($consumer)) { - $this->builder = $this->builder->withDefinitionProvider($consumer); + $this->analysisOptions = ContainerDefinitionAnalysisOptions::fromScanDirectoriesAndDefinitionProvider($dirs, $consumer); + } else { + $this->analysisOptions = ContainerDefinitionAnalysisOptions::fromScanDirectories($dirs); } } @@ -69,6 +70,6 @@ protected function getDefinitionProvider() : ?DefinitionProvider { } final protected function getSubject() : ContainerDefinition { - return $this->analyzer->analyze($this->builder->build()); + return $this->analyzer->analyze($this->analysisOptions); } } diff --git a/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalyzerTest.php b/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalyzerTest.php index 6ac19e5e..93ad22eb 100644 --- a/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalyzerTest.php +++ b/test/Unit/StaticAnalysis/AnnotatedTargetContainerDefinitionAnalyzerTest.php @@ -13,6 +13,7 @@ use Cspray\AnnotatedContainer\Exception\ServiceDelegateReturnsUnknownType; use Cspray\AnnotatedContainer\StaticAnalysis\AnnotatedTargetContainerDefinitionAnalyzer; use Cspray\AnnotatedContainer\StaticAnalysis\AnnotatedTargetDefinitionConverter; +use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptions; use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptionsBuilder; use Cspray\AnnotatedContainer\StaticAnalysis\DefinitionProvider; use Cspray\AnnotatedContainer\Unit\ContainerDefinitionAssertionsTrait; @@ -41,13 +42,13 @@ private function runAnalysisDirectory( if (is_string($dir)) { $dir = [$dir]; } - $options = ContainerDefinitionAnalysisOptionsBuilder::scanDirectories(...$dir); - if ($consumer !== null) { - $options = $options->withDefinitionProvider($consumer); + $options = ContainerDefinitionAnalysisOptions::fromScanDirectoriesAndDefinitionProvider($dir, $consumer); + } else { + $options = ContainerDefinitionAnalysisOptions::fromScanDirectories($dir); } - return $this->subject->analyze($options->build()); + return $this->subject->analyze($options); } public function testEmptyScanDirectoriesThrowsException() : void { diff --git a/test/Unit/StaticAnalysis/CacheAwareContainerDefinitionAnalyzerTest.php b/test/Unit/StaticAnalysis/CacheAwareContainerDefinitionAnalyzerTest.php index 2f39d3a4..dc509efa 100644 --- a/test/Unit/StaticAnalysis/CacheAwareContainerDefinitionAnalyzerTest.php +++ b/test/Unit/StaticAnalysis/CacheAwareContainerDefinitionAnalyzerTest.php @@ -7,6 +7,7 @@ use Cspray\AnnotatedContainer\Event\Emitter; use Cspray\AnnotatedContainer\StaticAnalysis\AnnotatedTargetContainerDefinitionAnalyzer; use Cspray\AnnotatedContainer\StaticAnalysis\CacheAwareContainerDefinitionAnalyzer; +use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptions; use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptionsBuilder; use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalyzer; use Cspray\AnnotatedContainer\Fixture\Fixtures; @@ -29,7 +30,7 @@ protected function setUp(): void { public function testContainerDefinitionCacheHitDoesNotCallUnderlyingAnalyzer() { $dir = Fixtures::implicitAliasedServices()->getPath(); - $analysisOptions = ContainerDefinitionAnalysisOptionsBuilder::scanDirectories($dir)->build(); + $analysisOptions = ContainerDefinitionAnalysisOptions::fromScanDirectories([$dir]); $containerDefinition = $this->annotatedTargetContainerDefinitionAnalyzer->analyze($analysisOptions); $this->cache->expects($this->once()) ->method('get') @@ -53,7 +54,7 @@ public function testContainerDefinitionCacheHitDoesNotCallUnderlyingAnalyzer() { public function testContainerDefinitionCacheMissesDoesCallUnderlyingAnalyzer() : void { $dir = Fixtures::implicitAliasedServices()->getPath(); - $analysisOptions = ContainerDefinitionAnalysisOptionsBuilder::scanDirectories($dir)->build(); + $analysisOptions = ContainerDefinitionAnalysisOptions::fromScanDirectories([$dir]); $containerDefinition = $this->annotatedTargetContainerDefinitionAnalyzer->analyze($analysisOptions); $expectedCacheKey = CacheKey::fromContainerDefinitionAnalysisOptions($analysisOptions); diff --git a/test/Unit/StaticAnalysis/ContainerDefinitionAnalysisOptionsBuilderTest.php b/test/Unit/StaticAnalysis/ContainerDefinitionAnalysisOptionsBuilderTest.php deleted file mode 100644 index 87b2a5ab..00000000 --- a/test/Unit/StaticAnalysis/ContainerDefinitionAnalysisOptionsBuilderTest.php +++ /dev/null @@ -1,34 +0,0 @@ -getPath())->build(); - - self::assertNull($compilerOptions->definitionProvider()); - } - - public function testWithDefinitionProviderImmutable() : void { - $a = ContainerDefinitionAnalysisOptionsBuilder::scanDirectories(Fixtures::singleConcreteService()->getPath()); - $b = $a->withDefinitionProvider(new CallableDefinitionProvider(function() { - })); - - self::assertNotSame($a, $b); - } - - public function testWithDefinitionProviderReturnsCorrectInstance() : void { - $compilerOptions = ContainerDefinitionAnalysisOptionsBuilder::scanDirectories(Fixtures::singleConcreteService()->getPath()) - ->withDefinitionProvider($expected = new CallableDefinitionProvider(function() { - })) - ->build(); - - self::assertSame($expected, $compilerOptions->definitionProvider()); - } -} diff --git a/test/Unit/StaticAnalysis/ContainerDefinitionAnalysisOptionsTest.php b/test/Unit/StaticAnalysis/ContainerDefinitionAnalysisOptionsTest.php new file mode 100644 index 00000000..cdfa839b --- /dev/null +++ b/test/Unit/StaticAnalysis/ContainerDefinitionAnalysisOptionsTest.php @@ -0,0 +1,40 @@ +getPath()] + ); + + self::assertSame( + [Fixtures::singleConcreteService()->getPath()], + $compilerOptions->scanDirectories() + ); + } + + public function testByDefaultDefinitionProviderIsNull() : void { + $compilerOptions = ContainerDefinitionAnalysisOptions::fromScanDirectories( + [Fixtures::singleConcreteService()->getPath()] + ); + + self::assertNull($compilerOptions->definitionProvider()); + } + + public function testWithDefinitionProviderReturnsCorrectInstance() : void { + $compilerOptions = ContainerDefinitionAnalysisOptions::fromScanDirectoriesAndDefinitionProvider( + [Fixtures::singleConcreteService()->getPath()], + $expected = new CallableDefinitionProvider(function() { + }) + ); + + self::assertSame($expected, $compilerOptions->definitionProvider()); + } +} From 22cc2a6d35eb15f02108e5d6217c79efeb425f86 Mon Sep 17 00:00:00 2001 From: Charles Sprayberry Date: Tue, 12 Aug 2025 10:21:28 -0400 Subject: [PATCH 3/6] Improving docs for v3 --- docs/README.md | 2 +- docs/how-to/02-bootstrap-your-container.md | 13 ++-- docs/references/01-attributes-list.md | 1 - docs/references/02-functional-api.md | 75 ++++++++++++------- docs/tutorials/01-getting-started.md | 4 +- .../02-alias-resolution-with-profiles.md | 10 ++- .../10-annotated-container-events.md | 11 +++ .../10-annotated-container-observers.md | 53 ------------- 8 files changed, 77 insertions(+), 92 deletions(-) create mode 100644 docs/tutorials/10-annotated-container-events.md delete mode 100644 docs/tutorials/10-annotated-container-observers.md diff --git a/docs/README.md b/docs/README.md index 7fc226d0..a6bf592f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,7 +10,7 @@ 6. [Injecting Scalar Values](./tutorials/06-injecting-scalar-values.md) 7. [Autowire Aware Factory](./tutorials/08-autowire-aware-factory.md) 8. [Autowire aware Invoker](./tutorials/09-autowire-aware-invoker.md) -9. [Annotated Container Observer](./tutorials/10-annotated-container-observers.md) +9. [Annotated Container Observer](./tutorials/10-annotated-container-events) ## How To diff --git a/docs/how-to/02-bootstrap-your-container.md b/docs/how-to/02-bootstrap-your-container.md index 78b54169..5a913484 100644 --- a/docs/how-to/02-bootstrap-your-container.md +++ b/docs/how-to/02-bootstrap-your-container.md @@ -16,7 +16,7 @@ If successful, you'll get a configuration file named `annotated-container.xml` i + version="3.0.0"> src @@ -59,7 +59,7 @@ Now, upgrade the configuration to let bootstrapping know which class to use. + version="3.0.0"> src @@ -84,6 +84,9 @@ Somewhere in your source code: namespace Acme\Demo; use Cspray\Annotatedcontainer\ContainerFactory\ParameterStore; +use Cspray\AnnotatedContainer\Reflection\Type; +use Cspray\AnnotatedContainer\Reflection\TypeUnion; +use Cspray\AnnotatedContainer\Reflection\TypeIntersect; final class MyCustomParameterStore implements ParameterStore { @@ -104,7 +107,7 @@ Next, update your configuration. + version="3.0.0"> src @@ -186,7 +189,7 @@ You have the ability to control specific aspects of the Bootstrapping process by #### Specifying Profiles -The only argument, `$profiles`, passed to `bootstrapContainer` should be an instance of `Cspray\AnnotatedContainer\Profiles`. This value object has a variety of static constructor methods on it that allow creating an instance with the appropriate values for your use case. If you don't provide any instance of this value object the active profiles will be `['default']`. If you specify your own `Profiles` instance it is HIGHLY RECOMMENDED you included the `default` profile. Otherwise, it is highly expected that your Container will not be wired correctly. +The only argument, `$profiles`, passed to `bootstrapContainer` should be an instance of `Cspray\AnnotatedContainer\Profiles`. This value object has a variety of static constructor methods on it that allow creating an instance with the appropriate values for your use case. If you don't provide any instance of this value object the active profiles will be `['default']`. If you specify your own `Profiles` instance it is HIGHLY RECOMMENDED you include the `default` profile. Otherwise, it is highly expected that your Container will not be wired correctly. ```php bootstrapContainer(Profiles::fromList(['default', 'prod'])); ``` diff --git a/docs/references/01-attributes-list.md b/docs/references/01-attributes-list.md index 1b4fda7b..a679f2f7 100644 --- a/docs/references/01-attributes-list.md +++ b/docs/references/01-attributes-list.md @@ -9,4 +9,3 @@ The following Attributes are made available through this library. All Attributes | `ServicePrepare` | `Attribute::TARGET_METHOD` | Describes a method, on an interface or class, that should be invoked when that type is created. | | `ServiceDelegate` | `Attribute::TARGET_METHOD` | Defines a method that will be used to generate a defined type. | | `Inject` | `Attribute::TARGET_PARAMETER`, `Attribute::TARGET_PROPERTY` | Defines a value that should be injected, can handle either scalar or service values based on the type-hint of the parameter. Inject Attributes on properties are only recognized if on `[#Configuration]` objects. | -| `Configuration` | `Attribute::TARGET_CLASS` | Defines an instance as being a Configuration object. Properties can have values injected into them. | diff --git a/docs/references/02-functional-api.md b/docs/references/02-functional-api.md index e615c138..7195dd58 100644 --- a/docs/references/02-functional-api.md +++ b/docs/references/02-functional-api.md @@ -10,54 +10,73 @@ This document lists the functions for each purpose. ## Defining Services ```php -\Cspray\AnnotatedContainer\service( - \Cspray\Typiphy\ObjectType $service, +bootstrapContainer(); +$container = Bootstrap::fromAnnotatedContainerConventions( + new YourContainerFactory() +)->bootstrapContainer(); $emitter = $container->get(BlobStorageEventEmitter::class); $emitter->onStore(fn(string $identifier) -> echo "Stored $identifier"); diff --git a/docs/tutorials/02-alias-resolution-with-profiles.md b/docs/tutorials/02-alias-resolution-with-profiles.md index e33f1ae6..b9a3a822 100644 --- a/docs/tutorials/02-alias-resolution-with-profiles.md +++ b/docs/tutorials/02-alias-resolution-with-profiles.md @@ -93,8 +93,11 @@ Now that we have our services properly annotated it is important to update the b bootstrapContainer(profiles: ['default', 'cloud']); +$container = Bootstrap::fromAnnotatedContainerConventions( + new YourContainerFactory(), +)->bootstrapContainer(Profiles::fromList(['default', 'cloud'])); ``` If we were on a local machine we'd want to build the options with the following code: @@ -103,8 +106,11 @@ If we were on a local machine we'd want to build the options with the following bootstrapContainer(profiles: ['default', 'local']); +$container = Bootstrap::fromAnnotatedContainerConventions( + new YourContainerFactory(), +)->bootstrapContainer(Profiles::fromList(['default', 'local'])); ``` It is important...**always include the 'default' profile**! Many services will not be annotated with a specific profile and will be implicitly added to the 'default' profile. Failure to include it in the list of active profiles will likely make the vast majority of services configured incorrectly or unavailable. diff --git a/docs/tutorials/10-annotated-container-events.md b/docs/tutorials/10-annotated-container-events.md new file mode 100644 index 00000000..21af217a --- /dev/null +++ b/docs/tutorials/10-annotated-container-events.md @@ -0,0 +1,11 @@ +# Annotated Container Events + +In v3 Annotated Container's Observer system was replaced with a more thorough Event system. The previous system was largely restricted to + +## Implementing Listeners + +## Registering Listeners with Configuration + +## Registering Listeners Programmatically + +## Listener Reference diff --git a/docs/tutorials/10-annotated-container-observers.md b/docs/tutorials/10-annotated-container-observers.md deleted file mode 100644 index 5568d600..00000000 --- a/docs/tutorials/10-annotated-container-observers.md +++ /dev/null @@ -1,53 +0,0 @@ -# Annotated Container Observers - -Annotated Container has a bootstrapping observer system that allows you to get access to pertinent information during the creation of your container. This system could be used to gather information about the static analysis process, perform action on the Container post creation, or some other action that might be necessary. Below we'll talk about how to take advantage of this system to bring more complex functionality to applications powered by Annotated Container. - -## Registering Observers - - First, you'll need to decide on what type of observer you want to implement. The example will implement all possible interfaces, to show what's possible. You do not have to implement all of them; you can choose any of them to implement. - -```php -addObserver(new MyContainerObserver()); -$container = $bootstrap->bootstrapContainer(); -``` - From b5b679bcd7f1c84737a55f05e39b7e836b4021cd Mon Sep 17 00:00:00 2001 From: Charles Sprayberry Date: Sat, 13 Jun 2026 09:39:33 -0400 Subject: [PATCH 4/6] Improve documentation --- README.md | 45 ++++++++--- docs/README.md | 21 +++-- docs/references/03-event-listeners.md | 25 ++++++ docs/tutorials/01-getting-started.md | 48 +++++++++--- .../02-alias-resolution-with-profiles.md | 41 +++++++--- .../03-alias-resolution-with-attributes.md | 33 ++++++-- docs/tutorials/04-using-service-factories.md | 25 ++++-- .../05-calling-post-construct-methods.md | 13 +++- docs/tutorials/06-injecting-scalar-values.md | 17 +++- docs/tutorials/07-autowire-aware-factory.md | 78 +++++++++++++++++++ docs/tutorials/08-autowire-aware-factory.md | 65 ---------------- ...nvoker.md => 08-autowire-aware-invoker.md} | 10 ++- .../09-annotated-container-events.md | 38 +++++++++ .../10-annotated-container-events.md | 11 --- .../AbstractContainerFactory.php | 6 -- .../AnalyzedContainerDefinitionFromCache.php | 6 +- .../DefinitionProviderContext.php | 1 - 17 files changed, 339 insertions(+), 144 deletions(-) create mode 100644 docs/references/03-event-listeners.md create mode 100644 docs/tutorials/07-autowire-aware-factory.md delete mode 100644 docs/tutorials/08-autowire-aware-factory.md rename docs/tutorials/{09-autowire-aware-invoker.md => 08-autowire-aware-invoker.md} (69%) create mode 100644 docs/tutorials/09-annotated-container-events.md delete mode 100644 docs/tutorials/10-annotated-container-events.md diff --git a/README.md b/README.md index 45c093f4..961cea7a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ [![Unit Tests](https://github.com/cspray/annotated-container/actions/workflows/php.yml/badge.svg)](https://github.com/cspray/annotated-container/actions/workflows/php.yml) -A Dependency Injection framework for creating an autowired, feature-rich, [PSR-11](https://www.php-fig.org/psr/psr-11/) compatible Container using PHP 8 Attributes! +A Dependency Injection framework for creating an autowired, feature-rich, [PSR-11](https://www.php-fig.org/psr/psr-11/) compatible Container using +PHP 8 Attributes! - Designate an interface as a Service and easily configure which concrete implementations to use - Delegate service construction to a factory @@ -15,7 +16,9 @@ A Dependency Injection framework for creating an autowired, feature-rich, [PSR-1 ## Quick Start -This quick start is intended to get you familiar with Annotated Container's core functionality and get a working Container created. First, a simple example showing how an interface can be aliased to a concrete Service. After that we'll show you how to get a Container to create the Service. +This quick start is intended to get you familiar with Annotated Container's core functionality and get a working +Container created. First, a simple example showing how an interface can be aliased to a concrete Service. After that +we'll show you how to get a Container to create the Service. ### Code Example @@ -54,19 +57,28 @@ This example is built upon in the docs. Check out the tutorials for more example ### Bootstrapping Your Container -Annotated Container ships with a built-in CLI tool to easily create a configuration detailing how to build your Container and a corresponding `Cspray\AnnotatedContainer\Bootstrap\Bootstrap` implementation to create your Container using that configuration. It is highly recommended to use the provided tooling to create your Container. +Annotated Container ships with a built-in CLI tool to easily create a configuration detailing how to build your +Container and a corresponding `Cspray\AnnotatedContainer\Bootstrap\Bootstrap` implementation to create your Container +using that configuration. It is highly recommended to use the provided tooling to create your Container. -> The CLI tool offers extensive documentation detailing how to run commands and what options are available. If you're ever looking for more info run: `./vendor/bin/annotated-container help` +> The CLI tool offers extensive documentation detailing how to run commands and what options are available. If you're +> ever looking for more info run: `./vendor/bin/annotated-container help` -The first step is to create the configuration. By default, the tooling will look at your `composer.json` to determine what directories to scan and create a directory that the ContainerDefinition can be cached in. Run the following command to complete this step. +The first step is to create the configuration. By default, the tooling will look at your `composer.json` to determine +what directories to scan and create a directory that the ContainerDefinition can be cached in. Run the following +command to complete this step. ``` ./vendor/bin/annotated-container init ``` -The configuration file will be created in the root of your project named "annotated-container.xml". The cache directory will also be created in the root of your project named ".annotated-container-cache". Check out the command's help documentation for available options, including how to customize these values. +The configuration file will be created in the root of your project named "annotated-container.xml". The cache directory +will also be created in the root of your project named ".annotated-container-cache". Check out the command's help +documentation for available options, including how to customize these values. -Be sure to review the generated configuration! A "normal" Composer setup might result in a configuration that looks like the following. If there are any directories that should be scanned but aren't listed in `` be sure to include them. Conversely, if there are directories included that _shouldn't_ be scanned be sure to remove them. +Be sure to review the generated configuration! A "normal" Composer setup might result in a configuration that looks +like the following. If there are any directories that should be scanned but aren't listed in `` be +sure to include them. Conversely, if there are directories included that _shouldn't_ be scanned be sure to remove them. ```xml @@ -119,7 +131,9 @@ composer require cspray/annotated-container ### Choose a Backing Container -AnnotatedContainer does not provide any of the actual Container functionality. We provide Attributes and definition objects that can determine how actual implementations are intended to be setup. AnnotatedContainer currently supports the following backing Containers: +AnnotatedContainer does not provide any of the actual Container functionality. We provide Attributes and definition +objects that can determine how actual implementations are intended to be setup. AnnotatedContainer currently supports +the following backing Containers: #### [rdlowrey/auryn](https://github.com/rdlowrey/auryn) @@ -141,13 +155,20 @@ composer require illuminate/container ## Documentation -This library is thoroughly documented in-repo under the `/docs` directory. The documentation is split into three parts; Tutorials, How Tos, and References. +This library is thoroughly documented in-repo under the `/docs` directory. The documentation is split into three parts; +Tutorials, How Tos, and References. -**Tutorials** are where you'll want to start. It'll expand on the examples in the "Quick Start" and teach you how to do most of the things you'll want to do with the library. This documentation tends to split the difference between the amount of code and the amount of explanation. +**Tutorials** are where you'll want to start. It'll expand on the examples in the "Quick Start" and teach you how to do +most of the things you'll want to do with the library. This documentation tends to split the difference between the +amount of code and the amount of explanation. -**How Tos** are where you'll go to get step-by-step guides on how to achieve specific functionality. These documents tend to be more code and less explanation. We assume you've gotten an understanding of the library and have questions on how to do something beyond the "normal" use cases. +**How Tos** are where you'll go to get step-by-step guides on how to achieve specific functionality. These documents +tend to be more code and less explanation. We assume you've gotten an understanding of the library and have questions +on how to do something beyond the "normal" use cases. -**References** are where you can get into the real internal, technical workings of the library. List of APIs and more technically-explicit documentation can be found here. References may be a lot of code, a lot of explanation, or a split between the two depending on the context. +**References** are where you can get into the real internal, technical workings of the library. List of APIs and more +technically-explicit documentation can be found here. References may be a lot of code, a lot of explanation, or a split +between the two depending on the context. ## Roadmap diff --git a/docs/README.md b/docs/README.md index a6bf592f..af2a7195 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,10 @@ -# AnnotatedContainer Docs Table of Contents +# AnnotatedContainer Docs -## Tutorials + + +## Table of Contents + +### Tutorials 1. [Getting Started](./tutorials/01-getting-started.md) 2. [Alias Resolution with Profiles](./tutorials/02-alias-resolution-with-profiles.md) @@ -8,16 +12,17 @@ 4. [Using Service Factories](./tutorials/04-using-service-factories.md) 5. [Calling Post Construct Methods](./tutorials/05-calling-post-construct-methods.md) 6. [Injecting Scalar Values](./tutorials/06-injecting-scalar-values.md) -7. [Autowire Aware Factory](./tutorials/08-autowire-aware-factory.md) -8. [Autowire aware Invoker](./tutorials/09-autowire-aware-invoker.md) -9. [Annotated Container Observer](./tutorials/10-annotated-container-events) +7. [Autowire Aware Factory](tutorials/07-autowire-aware-factory.md) +8. [Autowire aware Invoker](tutorials/08-autowire-aware-invoker.md) +9. [Annotated Container Observer](./tutorials/09-annotated-container-events) -## How To +### How To 1. [Add Third-Party Services](./how-to/01-add-third-party-services.md) 2. [Bootstrap Your Container](./how-to/02-bootstrap-your-container.md) -## References +### References 1. [Attributes List](./references/01-attributes-list.md) -2. [Functional API](./references/02-functional-api.md) \ No newline at end of file +2. [Functional API](./references/02-functional-api.md) +3. [Event Listeners](./references/03-event-listeners.md) \ No newline at end of file diff --git a/docs/references/03-event-listeners.md b/docs/references/03-event-listeners.md new file mode 100644 index 00000000..f2fc19d7 --- /dev/null +++ b/docs/references/03-event-listeners.md @@ -0,0 +1,25 @@ +# Event Listener + +## Bootstrap Listeners + +These listeners are invoked from the bootstrapping context of Annotated Container. + +### `Cspray\AnnotatedContainer\Bootstrap\Listener\BeforeBootstrap` + +The first event invoked, before any bootstrapping is performed and provides the configuration that will be used for +bootstrapping. + +**Available Data** + +- `Cspray\AnnotatedContainer\Bootstrap\Configuration\BootstrappingConfiguration` + +### `Cspray\AnnotatedContainer\Bootstrap\Listener\AfterBootstrap` + +The last event invoked, after all static analysis has concluded and a fully-wired container has been constructed. + +**Available Data** + +- `Cspray\AnnotatedContainer\Bootstrap\Configuration\BootstrappingConfiguration` +- `Cspray\AnnotatedContainer\Definition\ContainerDefinition` +- `Cspray\AnnotatedContainer\AnnotatedContainer` +- `Cspray\AnnotatedContainer\Bootstrap\ContainerAnalytics` \ No newline at end of file diff --git a/docs/tutorials/01-getting-started.md b/docs/tutorials/01-getting-started.md index 631642a0..330a6ef3 100644 --- a/docs/tutorials/01-getting-started.md +++ b/docs/tutorials/01-getting-started.md @@ -1,10 +1,15 @@ # Getting Started -Thanks for choosing to learn more about Annotated Container! In this document we'll show you how to get started with the library by expanding on the [Quick Start from the README](../../README.md) and go step-by-step what's going on. After that we'll give you some places to go next based on what you might be interested in learning. +Thanks for choosing to learn more about Annotated Container! In this document we'll show you how to get started with +the library by expanding on the [Quick Start from the README](../../README.md) and go step-by-step what's going on. After that +we'll give you some places to go next based on what you might be interested in learning. -Our requirements have some external code executed when a blob is stored or retrieved. We decide to introduce a new abstract and concrete Service that will emit blob storage events. Our existing Service will become dependent on this new Service. Let's start digging into some code! +Our requirements have some external code executed when a blob is stored or retrieved. We decide to introduce a new +abstract and concrete Service that will emit blob storage events. Our existing Service will become dependent on this +new Service. Let's start digging into some code! -> The rest of this guide assumes you've installed a backing container library! Please check out the README Installation for more details. +> The rest of this guide assumes you've installed a backing container library! Please check out the README Installation +> for more details. ## Abstract Services @@ -38,7 +43,12 @@ interface BlobStorageEventEmitter { } ``` -Here we have annotated 2 interfaces, `BlobStorage` and `BlobStorageEventEmitter`. When an interface or abstract class is annotated with the `#[Service]` attribute they are called _abstract services_. Abstract services cannot be instantiated, but you want to type-hint in constructors and "share" with the container. Sharing a service with the container ensures that 1 instance is created and shared wherever you might type-hint it or whenever you call `ContainerInterface::get()`. In other words, for our example, you can call `ContainerInterface::get(BlobStorage::class)` and get the same instance every time. +Here we have annotated 2 interfaces, `BlobStorage` and `BlobStorageEventEmitter`. When an interface or abstract class is +annotated with the `#[Service]` attribute they are called _abstract services_. Abstract services cannot be instantiated, +but you want to type-hint in constructors and "share" with the container. Sharing a service with the container ensures +that 1 instance is created and shared wherever you might type-hint it or whenever you call `ContainerInterface::get()`. +In other words, for our example, you can call `ContainerInterface::get(BlobStorage::class)` and get the same +instance every time. ## Concrete Services @@ -94,11 +104,21 @@ class FilesystemStorage implements BlobStorage { } ``` -We also annotated 2 concrete classes, `BlobStorageEventEmitter` and `FilesystemStorage`, which are called _concrete services_. While it is not necessary for a concrete service to satisfy a contract from an abstract service it is expected that the Annotated Container will often be used in this manner. Because there's only 1 concrete service for each abstract service we know, for example, when `ContainerInterface::get(BlobStorage::class)`is called you really want to get an instance of`FilesystemStorage`. +We also annotated 2 concrete classes, `BlobStorageEventEmitter` and `FilesystemStorage`, which are called +_concrete services_. While it is not necessary for a concrete service to satisfy a contract from an abstract service it +is expected that the Annotated Container will often be used in this manner. Because there's only 1 concrete service for +each abstract service we know, for example, when `ContainerInterface::get(BlobStorage::class)`is called you really want +to get an instance of`FilesystemStorage`. -When a concrete service is used to instantiate an abstract service it is referred to as an _alias_; an alias that is inferred from the static analysis of your codebase is considered _implicit_. It is possible for an abstract service to have multiple concrete services, and an alias can't be inferred. We'll talk about how to resolve those types of conflicts in a separate document. +When a concrete service is used to instantiate an abstract service it is referred to as an _alias_; an alias that is +inferred from the static analysis of your codebase is considered _implicit_. It is possible for an abstract service to +have multiple concrete services, and an alias can't be inferred. We'll talk about how to resolve those types of +conflicts in a separate document. -The `FilesystemStorage` service has been refactored from our example in README.md by adding an event emitter. We added the`BlobStorageEventEmitter` type-hint to our constructor. Our Container is autowired and knows that you're looking for a Service to be injected. Since the `BlobStorageEventEmitter` has an implicit alias we don't need to specify which concrete implementation to use. +The `FilesystemStorage` service has been refactored from our example in README.md by adding an event emitter. We +added the`BlobStorageEventEmitter` type-hint to our constructor. Our Container is autowired and knows that you're +looking for a Service to be injected. Since the `BlobStorageEventEmitter` has an implicit alias we don't need to specify +which concrete implementation to use. ## Using our Services @@ -120,11 +140,17 @@ $storage = $container->get(BlobStorage::class); $storage->store('foo.txt', 'bar'); ``` -A key aspect of AnnotatedContainer is how each Service has 1 instance associated with it. See in the calling code that uses the services we were able to call `ContainerInterface::get(BlobStorageEventEmitter::class)`. Changes to that object were present in the service injected into `FilesystemStorage`. All Services are shared and will have the same instance injected through autowiring and returned from `ContainerInterface::get()`. +A key aspect of AnnotatedContainer is how each Service has 1 instance associated with it. See in the calling code that +uses the services we were able to call `ContainerInterface::get(BlobStorageEventEmitter::class)`. Changes to that object +were present in the service injected into `FilesystemStorage`. All Services are shared and will have the same instance +injected through autowiring and returned from `ContainerInterface::get()`. ## Wrapping Up -In this document we learned some important concepts in AnnotatedContainer; abstract and concrete services, aliasing abstract services, how to auto-wire service injection, and how all services are effectively singletons. This example is meant to show you some bread & butter aspects of AnnotatedContainer that we expect is the "regular" use-case. There's a lot more functionality available! We highly recommend you checking out the rest of the documents for more details. +In this document we learned some important concepts in AnnotatedContainer; abstract and concrete services, aliasing +abstract services, how to auto-wire service injection, and how all services are effectively singletons. This example is +meant to show you some bread & butter aspects of AnnotatedContainer that we expect is the "regular" use-case. There's a +lot more functionality available! We highly recommend you checking out the rest of the documents for more details. ## Next Steps @@ -133,5 +159,5 @@ In this document we learned some important concepts in AnnotatedContainer; abstr - [Using Factory to Create Services](./04-using-service-factories.md) - [Invoking Methods Post-Construct](./05-calling-post-construct-methods.md) - [Injecting Non-Service Values](./06-injecting-scalar-values.md) -- [Using Autowire Aware Factory](./08-autowire-aware-factory.md) -- [Using Autowire Aware Invoker](./09-autowire-aware-invoker.md) \ No newline at end of file +- [Using Autowire Aware Factory](07-autowire-aware-factory.md) +- [Using Autowire Aware Invoker](08-autowire-aware-invoker.md) \ No newline at end of file diff --git a/docs/tutorials/02-alias-resolution-with-profiles.md b/docs/tutorials/02-alias-resolution-with-profiles.md index b9a3a822..1137d06b 100644 --- a/docs/tutorials/02-alias-resolution-with-profiles.md +++ b/docs/tutorials/02-alias-resolution-with-profiles.md @@ -1,10 +1,19 @@ # Alias Resolution with Profiles -Resolving aliases properly is an important part of any autowired dependency injection solution. In the code examples in the README there's only 1 concrete service, but it is common for multiple implementations to be available for an abstract service. In those types of situations AnnotatedContainer can't infer the appropriate service to use. You'll need to provide a little more configuration to solve this problem; profiles are a great way to deal with this issue. +Resolving aliases properly is an important part of any autowired dependency injection solution. In the code examples in +the README there's only 1 concrete service, but it is common for multiple implementations to be available for an +abstract service. In those types of situations AnnotatedContainer can't infer the appropriate service to use. You'll +need to provide a little more configuration to solve this problem; profiles are a great way to deal with this issue. -Profiles are an important part of AnnotatedContainer. They provide a way to narrowly specify which services are available for a given runtime of your application. All Services always have at least 1 profile and can contain any number. Services implicitly belong to the 'default' profile if one hasn't been explicitly set. At time of static analysis active profiles are specified and only those services matching the profile are included in the container. +Profiles are an important part of AnnotatedContainer. They provide a way to narrowly specify which services are +available for a given runtime of your application. All Services always have at least 1 profile and can contain any +number. Services implicitly belong to the 'default' profile if one hasn't been explicitly set. At time of static +analysis active profiles are specified and only those services matching the profile are included in the container. -We're going to expand on the example found in our README by adding a second concrete service. We'll then show how you could use profiles to specify which concrete service to use based on different runtime requirements. Assume we've been given requirements to create a new implementation that will store the blob on some cloud storage provider instead of the local filesystem. Here's our example from the README with our new implementation. +We're going to expand on the example found in our README by adding a second concrete service. We'll then show how you +could use profiles to specify which concrete service to use based on different runtime requirements. Assume we've been +given requirements to create a new implementation that will store the blob on some cloud storage provider instead of the +local filesystem. Here's our example from the README with our new implementation. ```php bootstrapContainer(Profiles::fromList(['default', 'local'])); ``` -It is important...**always include the 'default' profile**! Many services will not be annotated with a specific profile and will be implicitly added to the 'default' profile. Failure to include it in the list of active profiles will likely make the vast majority of services configured incorrectly or unavailable. +It is important...**always include the 'default' profile**! Many services will not be annotated with a specific profile +and will be implicitly added to the 'default' profile. Failure to include it in the list of active profiles will likely +make the vast majority of services configured incorrectly or unavailable. ## Profiles != Environments -It is important to keep in mind that AnnotatedContainer profiles are _not_ a 1-for-1 mapping to environments. Sometimes a profile name will make sense to match an environment name. Other times, it won't. Imagine a third `BlobStorage` implementation might come along for a different cloud-provider or a client that still uses FTP. In this instance they'd both be considered a "production" environment, perhaps, but that wouldn't be enough to distinguish which service to use. If it helps, think of Profiles as a way to tag and identify certain attributes of an environment that helps determine which service might be used. +It is important to keep in mind that AnnotatedContainer profiles are _not_ a 1-for-1 mapping to environments. Sometimes +a profile name will make sense to match an environment name. Other times, it won't. Imagine a third `BlobStorage` +implementation might come along for a different cloud-provider or a client that still uses FTP. In this instance they'd +both be considered a "production" environment, perhaps, but that wouldn't be enough to distinguish which service to use. +If it helps, think of Profiles as a way to tag and identify certain attributes of an environment that helps determine +which service might be used. diff --git a/docs/tutorials/03-alias-resolution-with-attributes.md b/docs/tutorials/03-alias-resolution-with-attributes.md index a618ef6a..ba038394 100644 --- a/docs/tutorials/03-alias-resolution-with-attributes.md +++ b/docs/tutorials/03-alias-resolution-with-attributes.md @@ -1,8 +1,15 @@ # Alias Resolution with Attributes -Resolving aliases properly is an important part of any autowired dependency injection solution. In the code examples in the README there's only 1 concrete service, but it is common for multiple implementations to be available for an abstract service. In those types of situations AnnotatedContainer can't infer the appropriate service to use. You'll need to provide a little more configuration to solve this problem; explicitly defining which service to inject might be an appropriate solution. +Resolving aliases properly is an important part of any autowired dependency injection solution. In the code examples in +the README there's only 1 concrete service, but it is common for multiple implementations to be available for an +abstract service. In those types of situations AnnotatedContainer can't infer the appropriate service to use. You'll +need to provide a little more configuration to solve this problem; explicitly defining which service to inject might be +an appropriate solution. -Normally it's recommended that you use Profiles to specify which aliases to use. Sometimes that might not be a viable solution. Instead, you can specify exactly which service to use where you type-hint the Service in your constructors and container-executed methods. We'll use the same code from `/docs/tutorials/02-alias-resolution-with-profiles.md` and show a different way of resolving the alias. +Normally it's recommended that you use Profiles to specify which aliases to use. Sometimes that might not be a viable +solution. Instead, you can specify exactly which service to use where you type-hint the Service in your constructors +and container-executed methods. We'll use the same code from `/docs/tutorials/02-alias-resolution-with-profiles.md` +and show a different way of resolving the alias. ```php bootstrapContainer(); + +$fooWidget = $autowiredFactory->make(FooWidget::class); +$fooWidget->service instanceof FooService; // true + +$barWidget = $autowiredFactory->make(BarWidget::class, autowiredParams(rawParam('foo', 'bar'))); +$barWidget->service instanceof BarService; // true +$barWidget->foo === 'bar'; // true +``` + +## Making Defined Services + +It should be noted that using the `AutowireableFactory` interface to create defined Services is not recommended. Making +a shared service won't recreate the instance the way you'd might expect. Which means if you call +`AutowireableFactory::make` and attempt to override the parameters that are used it will still use what is defined in +the Container. If you have defined the class in the Container you should use the `ContainerInterface::get()` method to +retrieve it. + + diff --git a/docs/tutorials/08-autowire-aware-factory.md b/docs/tutorials/08-autowire-aware-factory.md deleted file mode 100644 index 1c1660d3..00000000 --- a/docs/tutorials/08-autowire-aware-factory.md +++ /dev/null @@ -1,65 +0,0 @@ -# Autowire Aware Factory - -Sometimes you might want to take advantage of the autowiring capabilities of Annotated Container when you're creating an object that isn't a Service. The Container returned from a `ContainerFactory` is a [type intersect](https://www.php.net/manual/en/language.types.declarations.php#language.types.declarations.composite.intersection) that includes the `Cspray\AnnotatedContainer\AutowireableFactory` interface. You can depend on this type in your factory constructors to create autowired objects! - -## Example - -In our example we're going to create a `WidgetFactory` that creates `Widget` implementations. Some of those implementations depend on services from the Container, while other implementations depend on scalar values that must be provided when you create the object. Before we look at how to use the `AutowireableFactory` let's take a look at an example codebase. - -```php -bootstrapContainer(); - -$fooWidget = $autowiredFactory->make(FooWidget::class); -$fooWidget->service instanceof FooService; // true - -$barWidget = $autowiredFactory->make(BarWidget::class, autowiredParams(rawParam('foo', 'bar'))); -$barWidget->service instanceof BarService; // true -$barWidget->foo === 'bar'; // true -``` - -## Making Defined Services - -It should be noted that using the `AutowireableFactory` interface to create defined Services is not recommended. Making a shared service won't recreate the instance the way you'd might expect. Which means if you call `AutowireableFactory::make` and attempt to override the parameters that are used it will still use what is defined in the Container. If you have defined the class in the Container you should use the `ContainerInterface::get()` method to retrieve it. - - diff --git a/docs/tutorials/09-autowire-aware-invoker.md b/docs/tutorials/08-autowire-aware-invoker.md similarity index 69% rename from docs/tutorials/09-autowire-aware-invoker.md rename to docs/tutorials/08-autowire-aware-invoker.md index 2bd1b8ec..cfcaeedf 100644 --- a/docs/tutorials/09-autowire-aware-invoker.md +++ b/docs/tutorials/08-autowire-aware-invoker.md @@ -1,10 +1,16 @@ # Autowire Aware Invoker -It is common in PHP to use callables for a wide variety of functionality. Annotated Container provides functionality to invoke a callable and recursively autowire the dependencies it requires from available services. The Container returned from a `ContainerFactory` is a [type intersect](https://www.php.net/manual/en/language.types.declarations.php#language.types.declarations.composite.intersection) that includes the `Cspray\AnnotatedContainer\AutowireableInvoker`. You can depend on this type in your constructors to invoke autowired callables! +It is common in PHP to use callables for a wide variety of functionality. Annotated Container provides functionality to +invoke a callable and recursively autowire the dependencies it requires from available services. The Container returned +from a `ContainerFactory` is a [type intersect](https://www.php.net/manual/en/language.types.declarations.php#language.types.declarations.composite.intersection) that includes the `Cspray\AnnotatedContainer\AutowireableInvoker`. +You can depend on this type in your constructors to invoke autowired callables! ## Example -In our example we're going to create some callables that interact with `Widget` implementations. Some of those implementations depend on services from the Container, while other implementations depend on scalar values that must be provided when you create the object. Before we look at how to use the `AutowireableInvoker` let's take a look at an example codebase. +In our example we're going to create some callables that interact with `Widget` implementations. Some of those +implementations depend on services from the Container, while other implementations depend on scalar values that must be +provided when you create the object. Before we look at how to use the `AutowireableInvoker` let's take a look at an +example codebase. ```php cache() === null) { + throw new RuntimeException('A container definition cache must be provided with bootstrapping configuration'); + } + } + +} +``` + +Now that we have a listener implemented we need to make sure it is added to the Emitter to be invoked at the appropriate +time. You can do this through your configuration or by programmatically constructing the Listener and adding it to the +Emitter. Check out the [How To: Bootstrap Your Container](../how-to/02-bootstrap-your-container.md) for more information +on how to register your listener. + + diff --git a/docs/tutorials/10-annotated-container-events.md b/docs/tutorials/10-annotated-container-events.md deleted file mode 100644 index 21af217a..00000000 --- a/docs/tutorials/10-annotated-container-events.md +++ /dev/null @@ -1,11 +0,0 @@ -# Annotated Container Events - -In v3 Annotated Container's Observer system was replaced with a more thorough Event system. The previous system was largely restricted to - -## Implementing Listeners - -## Registering Listeners with Configuration - -## Registering Listeners Programmatically - -## Listener Reference diff --git a/src/ContainerFactory/AbstractContainerFactory.php b/src/ContainerFactory/AbstractContainerFactory.php index e8d2f8d4..250b8e28 100644 --- a/src/ContainerFactory/AbstractContainerFactory.php +++ b/src/ContainerFactory/AbstractContainerFactory.php @@ -6,14 +6,8 @@ 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; -use Cspray\AnnotatedContainer\Definition\ServiceDefinition; -use Cspray\AnnotatedContainer\Definition\ServiceDelegateDefinition; -use Cspray\AnnotatedContainer\Definition\ServicePrepareDefinition; use Cspray\AnnotatedContainer\Event\ContainerFactoryEmitter; use Cspray\AnnotatedContainer\Profiles; diff --git a/src/Event/Listener/StaticAnalysis/AnalyzedContainerDefinitionFromCache.php b/src/Event/Listener/StaticAnalysis/AnalyzedContainerDefinitionFromCache.php index 8e2606fe..d221d766 100644 --- a/src/Event/Listener/StaticAnalysis/AnalyzedContainerDefinitionFromCache.php +++ b/src/Event/Listener/StaticAnalysis/AnalyzedContainerDefinitionFromCache.php @@ -2,10 +2,14 @@ namespace Cspray\AnnotatedContainer\Event\Listener\StaticAnalysis; +use Cspray\AnnotatedContainer\Definition\Cache\CacheKey; use Cspray\AnnotatedContainer\Definition\ContainerDefinition; use Cspray\AnnotatedContainer\Event\Listener; interface AnalyzedContainerDefinitionFromCache extends Listener { - public function handleAnalyzedContainerDefinitionFromCache(ContainerDefinition $containerDefinition, string $cacheFile) : void; + public function handleAnalyzedContainerDefinitionFromCache( + ContainerDefinition $containerDefinition, + CacheKey $cacheKey, + ) : void; } diff --git a/src/StaticAnalysis/DefinitionProviderContext.php b/src/StaticAnalysis/DefinitionProviderContext.php index 8773bb88..c36d9911 100644 --- a/src/StaticAnalysis/DefinitionProviderContext.php +++ b/src/StaticAnalysis/DefinitionProviderContext.php @@ -2,7 +2,6 @@ namespace Cspray\AnnotatedContainer\StaticAnalysis; -use Cspray\AnnotatedContainer\Definition\AliasDefinition; use Cspray\AnnotatedContainer\Definition\ContainerDefinitionBuilder; use Cspray\AnnotatedContainer\Definition\InjectDefinition; use Cspray\AnnotatedContainer\Definition\ServiceDefinition; From 412834d67fa5bf258a07178ea4d40b6362d2d539 Mon Sep 17 00:00:00 2001 From: Charles Sprayberry Date: Sat, 13 Jun 2026 10:05:27 -0400 Subject: [PATCH 5/6] Revert listener method signature change until complete refactor --- .../StaticAnalysis/AnalyzedContainerDefinitionFromCache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Event/Listener/StaticAnalysis/AnalyzedContainerDefinitionFromCache.php b/src/Event/Listener/StaticAnalysis/AnalyzedContainerDefinitionFromCache.php index d221d766..c6503150 100644 --- a/src/Event/Listener/StaticAnalysis/AnalyzedContainerDefinitionFromCache.php +++ b/src/Event/Listener/StaticAnalysis/AnalyzedContainerDefinitionFromCache.php @@ -10,6 +10,6 @@ interface AnalyzedContainerDefinitionFromCache extends Listener { public function handleAnalyzedContainerDefinitionFromCache( ContainerDefinition $containerDefinition, - CacheKey $cacheKey, + string $cacheKey, ) : void; } From b1dec199055e9b7fa338db63df31d648056486cf Mon Sep 17 00:00:00 2001 From: Charles Sprayberry Date: Sat, 13 Jun 2026 10:12:22 -0400 Subject: [PATCH 6/6] Specify correct types for configuration scan directories --- .../Configuration/XmlBootstrappingConfiguration.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Bootstrap/Configuration/XmlBootstrappingConfiguration.php b/src/Bootstrap/Configuration/XmlBootstrappingConfiguration.php index 351ccf83..40181279 100644 --- a/src/Bootstrap/Configuration/XmlBootstrappingConfiguration.php +++ b/src/Bootstrap/Configuration/XmlBootstrappingConfiguration.php @@ -19,7 +19,7 @@ final class XmlBootstrappingConfiguration implements BootstrappingConfiguration { /** - * @var list + * @var list */ private readonly array $directories; private readonly ?DefinitionProvider $definitionProvider; @@ -46,6 +46,10 @@ public function __construct( } try { + // There are several assertions on the types of data we expect in the below code. This is because we + // expect the schema to define a document that specifies certain minimum lengths and expectations of + // data being present. Validating the schema will throw an error if these expectations are not met, and + // there's no way to test type expectations because if there are any it will not pass schema validation. $schemaFile = dirname(__DIR__, 3) . '/annotated-container.xsd'; $dom = new DOMDocument(); $dom->loadXML($this->filesystem->read($this->xmlFile)); @@ -61,7 +65,7 @@ public function __construct( $scanDirectories = []; foreach ($scanDirectoriesNodes as $scanDirectory) { $sourceDirectory = $scanDirectory->nodeValue; - assert($sourceDirectory !== null); + assert($sourceDirectory !== null && $sourceDirectory !== ''); $scanDirectories[] = $sourceDirectory; }