├── .editorconfig ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── Argument ├── ArgumentInterface.php ├── ArgumentReflectorInterface.php ├── ArgumentReflectorTrait.php ├── ArgumentResolverInterface.php ├── ArgumentResolverTrait.php ├── DefaultValueArgument.php ├── DefaultValueInterface.php ├── Literal │ ├── ArrayArgument.php │ ├── BooleanArgument.php │ ├── CallableArgument.php │ ├── FloatArgument.php │ ├── IntegerArgument.php │ ├── ObjectArgument.php │ └── StringArgument.php ├── LiteralArgument.php ├── LiteralArgumentInterface.php ├── ResolvableArgument.php └── ResolvableArgumentInterface.php ├── Attribute ├── AttributeInterface.php ├── Inject.php └── Resolve.php ├── Container.php ├── ContainerAwareInterface.php ├── ContainerAwareTrait.php ├── Definition ├── Definition.php ├── DefinitionAggregate.php ├── DefinitionAggregateInterface.php └── DefinitionInterface.php ├── DefinitionContainerInterface.php ├── Exception ├── ContainerException.php └── NotFoundException.php ├── Inflector ├── Inflector.php ├── InflectorAggregate.php ├── InflectorAggregateInterface.php └── InflectorInterface.php ├── ReflectionContainer.php └── ServiceProvider ├── AbstractServiceProvider.php ├── BootableServiceProviderInterface.php ├── ServiceProviderAggregate.php ├── ServiceProviderAggregateInterface.php └── ServiceProviderInterface.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | max_line_length = 120 10 | tab_width = 4 -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All Notable changes to `League\Container` will be documented in this file 4 | 5 | ## 5.1.0 6 | 7 | ### Added 8 | - Attribute based resolution for dependencies using `#[Inject]` and `#[Resolve]` attributes. 9 | - Docs: [https://container.thephpleague.com/5.x/attribute-resolution/](https://container.thephpleague.com/5.x/attribute-resolution/) 10 | - Support for PHPUnit 12 (@ADmad) 11 | - Explicit non-support for auto-wiring union types. 12 | 13 | ### Changed 14 | - Small internal changes for stricter static analysis and type safety. (@ADmad) 15 | 16 | ### 5.0.1 17 | 18 | ### Fixed 19 | - Fixed a small unreachable code bug 20 | 21 | ## 5.0.0 22 | 23 | ### Added 24 | - Ability to overwrite a definition within the container, disabled by default 25 | 26 | ### Changed 27 | - PHP requirement now `>=8.1` 28 | - General language modernisation 29 | - General prep for future updates and container compilation 30 | 31 | ## 4.2.4 32 | 33 | ### Fixed 34 | - Now properly handle string based fully qualified class names in with preceding slashes. (@M1ke) 35 | 36 | ## 4.2.3 37 | 38 | ### Fixed 39 | - Warnings for PHP 8.4 implicit nullable types (@andypost) 40 | 41 | ## 4.2.1 42 | 43 | ### Fixed 44 | - Remove an unnecessary conditional in ServiceProviderAggregate. (@SubhanSh) 45 | - Fixed an infinite loop in resolving definitions. (@arai-ta) 46 | 47 | ## 4.2.0 48 | 49 | ### Added 50 | - Support for psr/container 1.1.0. 51 | 52 | ## 4.1.2 53 | 54 | ### Fixed 55 | - Fix bug that was causing an error on inflection due to introduced type hint. 56 | 57 | ## 4.1.1 58 | 59 | ### Changed 60 | - Move files autoload directive to dev (@ADmad) 61 | 62 | ## 4.1.0 63 | 64 | ### Added 65 | - Way to handle non-public controllers safely (@beryllium) 66 | 67 | ## 4.0.0 68 | 69 | ### Added 70 | - New definition interface that extends psr/container 71 | - Literal and resolvable argument wrappers for better explicitness in definitions 72 | 73 | ### Changed 74 | - PHP requirement now `>=7.2` 75 | - Updated `psr/container` to ^2.0.0 76 | - `Container::shared` convenience method is now explicit `Container::addShared` method 77 | - Removed third argument `$shared` from `Container::add`, use `Container::addShared` 78 | - `ServiceProviderInterface` now defines return types 79 | - Service providers now require implementation of a `provides` method rather than relying on a class property. 80 | 81 | ## 3.4.1 82 | 83 | ### Added 84 | - Way to handle non-public controllers safely (@beryllium) 85 | - PHPUnit ^7.0 for PHP versions that support it (@beryllium) 86 | 87 | ## 3.4.0 88 | 89 | ### Removed 90 | - Support for `psr/container` ^2.0.0 as the interface cannot be reconciled between versions 91 | 92 | ## 3.3.5 93 | 94 | ### Added 95 | - Support for `psr/container` ^2.0.0 96 | 97 | ## 3.3.4 98 | 99 | ### Fixed 100 | - Fixed an issue that caused a recursive `register` call. @pcoutinho 101 | - Fixed a return type declaration. @orbex 102 | 103 | ## 3.3.3 104 | 105 | ### Fixed 106 | - Fixed bug relating to `ReflectionContainer::call` on arrow functions. 107 | 108 | ## 3.3.2 109 | 110 | ### Added 111 | - Experimental support for PHP 8. 112 | 113 | ### Fixed 114 | - Fix issue when preventing reflection from using default value for arguments. 115 | 116 | ## 3.3.1 117 | 118 | ### Fixed 119 | - Respect `$new` argument when getting tagged definitions. 120 | 121 | ## 3.3.0 122 | 123 | ### Added 124 | - Support for PHP 7.3 125 | - `{set,get}LeagueContainer` methods added to ContainerAwareTrait as a temporary measure until next major release when this can be properly addressed, less hinting of `Psr\Container\ContainerInterface` 126 | 127 | ### Changed 128 | - Various internal code improvements 129 | 130 | ### Fixed 131 | - Fix for `setConcrete` not re-resolving class on when overriding (@jleeothon) 132 | - Fix stack overflow error incase a service provider lies about providing a specific service (@azjezz) 133 | - Fix issue where providers may be aggregated multiple times (@bwg) 134 | - Various documentation fixes 135 | 136 | ## 3.2.2 137 | 138 | ### Fixed 139 | - Fixed issue that prevented service providers from registering if a previous one in the aggregate was already registered. 140 | 141 | ## 3.2.1 142 | 143 | ### Fixed 144 | - Fixed issue where all service providers were registered regardless of whether they need to be. 145 | 146 | ## 3.2.0 147 | 148 | ### Added 149 | - Added ability to add definition as not shared when container is set to default to shared. 150 | - Added `{set|get}Concrete` to definitions to allow for better use of `extend`. 151 | 152 | ## 3.1.0 153 | 154 | ### Added 155 | - Re-added the `share` proxy method that was mistakenly removed in previous major release. 156 | - Added ability to set Container to "share" by default using `defaultToShared` method. 157 | - Added ability for `ReflectionContainer` to cache resolutions and pull from cache for following calls. 158 | 159 | ## 3.0.1 160 | 161 | ### Added 162 | - Allow definition aggregates to be built outside of container. 163 | 164 | ## 3.0.0 165 | 166 | ### Added 167 | - Service providers can now be pulled from the container if they are registered. 168 | - Definition logic now handled by aggregate for better separation. 169 | - Now able to add tags to a definition to return an array of items containing that tag. 170 | 171 | ### Changed 172 | - Updated minimum PHP requirements to 7.0. 173 | - Now depend directly on PSR-11 interfaces, including providing PSR-11 exceptions. 174 | - Refactored inflector logic to accept type on construction and use generator to iterate. 175 | - Refactored service provider logic with better separation and performance. 176 | - Merged service provider signature logic in to one interface and abstract. 177 | - Heavily simplified definition logic providing more control to user. 178 | 179 | ## 2.4.1 180 | 181 | ### Fixed 182 | - Ensures `ReflectionContainer` converts class name in array callable to object. 183 | 184 | ## 2.4.0 185 | 186 | ### Changed 187 | - Can now wrap shared objects as `RawArgument`. 188 | - Ability to override shared items. 189 | 190 | ### Fixed 191 | - Booleans now recognised as accepted values. 192 | - Various docblock fixes. 193 | - Unused imports removed. 194 | - Unreachable arguments no longer passed. 195 | 196 | ## 2.3.0 197 | 198 | ### Added 199 | - Now implementation of the PSR-11. 200 | 201 | ## 2.2.0 202 | 203 | ### Changed 204 | - Service providers can now be added multiple times by giving them a signature. 205 | 206 | ## 2.1.0 207 | 208 | ### Added 209 | - Allow resolving of `RawArgument` objects as first class dependencies. 210 | 211 | ### Changed 212 | - Unnecessary recursion removed from `Container::get`. 213 | 214 | ## 2.0.3 215 | 216 | ### Fixed 217 | - Bug where delegating container was not passed to delegate when needed. 218 | - Bug where `Container::extend` would not return a shared definition to extend. 219 | 220 | ## 2.0.2 221 | 222 | ### Fixed 223 | - Bug introduced in 2.0.1 where shared definitions registered via a service provider would never be returned as shared. 224 | 225 | ## 2.0.1 226 | 227 | ### Fixed 228 | - Bug where shared definitions were not stored as shared. 229 | 230 | ## 2.0.0 231 | 232 | ### Added 233 | - Now implementation of the container-interop project. 234 | - `BootableServiceProviderInterface` for eagerly loaded service providers. 235 | - Delegate container functionality. 236 | - `RawArgument` to ensure scalars are not resolved from the container but seen as an argument. 237 | 238 | ### Altered 239 | - Refactor of definition functionality. 240 | - `Container::share` replaces `singleton` functionality to improve understanding. 241 | - Auto wiring is now disabled by default. 242 | - Auto wiring abstracted to be a delegate container `ReflectionContainer` handling all reflection based functionality. 243 | - Inflection functionality abstracted to an aggregate. 244 | - Service provider functionality abstracted to an aggregate. 245 | - Much bloat removed. 246 | - `Container::call` now proxies to `ReflectionContainer::call` and handles argument resolution in a much more efficient way. 247 | 248 | ### Removed 249 | - Ability to register invokables, this functionality added a layer of complexity too large for the problem it solved. 250 | - Container no longer accepts a configuration array, this functionality will now be provided by an external service provider package. 251 | 252 | ## 1.4.0 253 | 254 | ### Added 255 | - Added `isRegisteredCallable` method to public API. 256 | - Invoking `call` now accepts named arguments at runtime. 257 | 258 | ### Fixed 259 | - Container now stores instantiated Service Providers after first instantiation. 260 | - Extending a definition now looks in Service Providers as well as just Definitions. 261 | 262 | ## 1.3.1 - 2015-02-21 263 | 264 | ### Fixed 265 | - Fixed bug where arbitrary values were attempted to be resolved as classes. 266 | 267 | ## 1.3.0 - 2015-02-09 268 | 269 | ### Added 270 | - Added `ServiceProvider` functionality to allow cleaner resolving of complex dependencies. 271 | - Added `Inflector` functionality to allow for manipulation of resolved objects of a specific type. 272 | - Improvements to DRY throughout the package. 273 | 274 | ### Fixed 275 | - Setter in `ContainerAwareTrait` now returns self (`$this`). 276 | 277 | ## 1.2.1 - 2015-01-29 278 | 279 | ### Fixed 280 | - Allow arbitrary values to be registered via container config. 281 | 282 | ## 1.2.0 - 2015-01-13 283 | 284 | ### Added 285 | - Improvements to `Container::call` functionality. 286 | 287 | ### Fixed 288 | - General code tidy. 289 | - Improvements to test suite. 290 | 291 | ## 1.1.1 - 2015-01-13 292 | 293 | ### Fixed 294 | - Allow singleton to be passed as method argument. 295 | 296 | ## 1.1.0 - 2015-01-12 297 | 298 | ### Added 299 | - Addition of `ContainerAwareTrait` to provide functionality from `ContainerAwareInterface`. 300 | 301 | ## 1.0.0 - 2015-01-12 302 | 303 | ### Added 304 | - Migrated from [Orno\Di](https://github.com/orno/di). 305 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on [GitHub](https://github.com/thephpleague/container). 6 | 7 | ## Pull Requests 8 | 9 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). 10 | 11 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 12 | 13 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 14 | 15 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 16 | 17 | - **Create feature branches** - Don't ask us to pull from your master branch. 18 | 19 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 20 | 21 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting. 22 | 23 | ## Running Tests 24 | 25 | ``` bash 26 | $ composer test 27 | ``` 28 | 29 | **Happy coding**! 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Phil Bennett 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Container (Dependency Injection) 2 | 3 | [![Author](https://img.shields.io/badge/author-Phil%20Bennett-blue?style=flat-square)](https://github.com/philipobenito) 4 | [![Latest Version](https://img.shields.io/github/release/thephpleague/container.svg?style=flat-square)](https://github.com/thephpleague/container/releases) 5 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 6 | [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/thephpleague/container/test.yml?style=flat-square)](https://github.com/thephpleague/container/actions/workflows/test.yml) 7 | [![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/container.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/container/code-structure) 8 | [![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/container.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/container) 9 | [![Total Downloads](https://img.shields.io/packagist/dt/league/container.svg?style=flat-square)](https://packagist.org/packages/league/container) 10 | 11 | This package is compliant with [PSR-1], [PSR-2], [PSR-12], [PSR-4], [PSR-11] and [PSR-12]. If you notice compliance oversights, please send a patch via pull request. 12 | 13 | [PSR-1]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md 14 | [PSR-2]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md 15 | [PSR-12]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-12-extended-coding-style-guide.md 16 | [PSR-4]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md 17 | [PSR-11]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-11-container.md 18 | [PSR-12]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-12-extended-coding-style-guide.md 19 | 20 | ## Install 21 | 22 | Via Composer 23 | 24 | ``` bash 25 | composer require league/container 26 | ``` 27 | 28 | ## Requirements 29 | 30 | The following versions of PHP are supported by this version. 31 | 32 | * PHP 8.1 33 | * PHP 8.2 34 | * PHP 8.3 35 | * PHP 8.4 36 | 37 | ## Documentation 38 | 39 | Container has [full documentation](http://container.thephpleague.com), powered by [Jekyll](http://jekyllrb.com/). 40 | 41 | Contribute to this documentation in the [docs/](https://github.com/thephpleague/container/tree/master/docs) sub-directory. 42 | 43 | ## Testing 44 | 45 | Testing includes PHPUnit and PHPStan (Level 7). 46 | ``` bash 47 | $ composer test 48 | ``` 49 | 50 | ## Contributing 51 | 52 | Please see [CONTRIBUTING](https://github.com/thephpleague/container/blob/master/CONTRIBUTING.md) for details. 53 | 54 | ## Security 55 | 56 | If you discover any security related issues, please email philipobenito@gmail.com instead of using the issue tracker. 57 | 58 | ## Credits 59 | 60 | - [Phil Bennett](https://github.com/philipobenito) 61 | - [All Contributors](https://github.com/thephpleague/container/contributors) 62 | - `Orno\Di` contributors 63 | 64 | ## License 65 | 66 | The MIT License (MIT). Please see [License File](https://github.com/thephpleague/container/blob/master/LICENSE.md) for more information. 67 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "league/container", 3 | "description": "A fast and intuitive dependency injection container.", 4 | "keywords": [ 5 | "league", 6 | "container", 7 | "dependency", 8 | "injection", 9 | "di", 10 | "service", 11 | "provider" 12 | ], 13 | "homepage": "https://github.com/thephpleague/container", 14 | "license": "MIT", 15 | "authors": [ 16 | { 17 | "name": "Phil Bennett", 18 | "email": "mail@philbennett.co.uk", 19 | "role": "Developer" 20 | } 21 | ], 22 | "require": { 23 | "php": "^8.1", 24 | "psr/container": "^2.0.2" 25 | }, 26 | "require-dev": { 27 | "nette/php-generator": "^4.1", 28 | "nikic/php-parser": "^5.0", 29 | "phpstan/phpstan": "^2.1.11", 30 | "phpunit/phpunit": "^10.5.45|^11.5.15|^12.0", 31 | "roave/security-advisories": "dev-latest", 32 | "scrutinizer/ocular": "^1.9", 33 | "squizlabs/php_codesniffer": "^3.9" 34 | }, 35 | "provide": { 36 | "psr/container-implementation": "^1.0" 37 | }, 38 | "replace": { 39 | "orno/di": "~2.0" 40 | }, 41 | "autoload": { 42 | "psr-4": { 43 | "League\\Container\\": "src" 44 | } 45 | }, 46 | "autoload-dev": { 47 | "psr-4": { 48 | "League\\Container\\Test\\": "tests" 49 | }, 50 | "files": [ 51 | "tests/Asset/function.php" 52 | ] 53 | }, 54 | "extra": { 55 | "branch-alias": { 56 | "dev-master": "5.x-dev", 57 | "dev-5.x": "5.x-dev", 58 | "dev-4.x": "4.x-dev", 59 | "dev-3.x": "3.x-dev", 60 | "dev-2.x": "2.x-dev", 61 | "dev-1.x": "1.x-dev" 62 | } 63 | }, 64 | "scripts": { 65 | "test": [ 66 | "vendor/bin/phpunit", 67 | "vendor/bin/phpstan analyse" 68 | ] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Argument/ArgumentInterface.php: -------------------------------------------------------------------------------- 1 | getParameters(); 30 | $arguments = []; 31 | 32 | foreach ($params as $param) { 33 | $name = $param->getName(); 34 | 35 | // if we've been given a value for the argument, treat as literal 36 | if (array_key_exists($name, $args)) { 37 | $arguments[] = new LiteralArgument($args[$name]); 38 | continue; 39 | } 40 | 41 | // next we see if we have an attribute that can resolve the argument (if enabled) 42 | if ($this->getMode() & ReflectionContainer::ATTRIBUTE_RESOLUTION) { 43 | $attrs = $param->getAttributes(); 44 | 45 | foreach ($attrs as $attr) { 46 | if ($argument = $this->resolveArgumentFromAttribute($attr)) { 47 | $arguments[] = $argument; 48 | continue 2; 49 | } 50 | } 51 | } 52 | 53 | $type = $param->getType(); 54 | 55 | // if we have a union type, loop until we can resolve 56 | if ($type instanceof ReflectionUnionType) { 57 | $this->throwParameterException( 58 | $name, 59 | 'union', 60 | $param->getDeclaringClass()?->getName(), 61 | $method->getName(), 62 | $method instanceof ReflectionMethod ? $method->isClosure() : false, 63 | 'Union types are not supported' 64 | ); 65 | } 66 | 67 | // then we check if we have a type hint (if auto wiring is enabled) 68 | if ($this->getMode() & ReflectionContainer::AUTO_WIRING && $type instanceof ReflectionNamedType) { 69 | $arguments[] = $this->resolveArgumentForNamedType($param, $type); 70 | continue; 71 | } 72 | 73 | // finally we check if we have a default value 74 | if ($param->isDefaultValueAvailable()) { 75 | $arguments[] = new LiteralArgument($param->getDefaultValue()); 76 | continue; 77 | } 78 | 79 | $this->throwParameterException( 80 | $name, 81 | $type instanceof ReflectionNamedType ? $type->getName() : 'unknown', 82 | $param->getDeclaringClass()?->getName(), 83 | $method->getName(), 84 | $method instanceof ReflectionMethod ? $method->isClosure() : false, 85 | 'No default value available and no type hint to resolve' 86 | ); 87 | } 88 | 89 | return $this->resolveArguments($arguments); 90 | } 91 | 92 | protected function resolveArgumentFromAttribute(ReflectionAttribute $attribute): LiteralArgumentInterface|false 93 | { 94 | $attrClass = $attribute->getName(); 95 | 96 | if (is_subclass_of($attrClass, AttributeInterface::class)) { 97 | $attrClass = $attribute->newInstance(); 98 | 99 | if ($attrClass instanceof ContainerAwareInterface) { 100 | $attrClass->setContainer($this->getContainer()); 101 | } 102 | 103 | // purposely don't define a type here so that any typing errors 104 | // from the consuming code bubble up 105 | /** @var AttributeInterface $attrClass */ 106 | return new LiteralArgument($attrClass->resolve(), null); 107 | } 108 | 109 | return false; 110 | } 111 | 112 | protected function resolveArgumentForNamedType( 113 | ReflectionParameter $param, 114 | ReflectionNamedType $type, 115 | ): ResolvableArgumentInterface { 116 | $typeHint = $type->getName(); 117 | 118 | if ($type->getName() === 'mixed') { 119 | $this->throwParameterException( 120 | $param->getName(), 121 | 'mixed', 122 | $param->getDeclaringClass()?->getName(), 123 | $param->getDeclaringFunction()->getName(), 124 | $param->getDeclaringFunction()->isClosure(), 125 | 'Mixed types are not supported' 126 | ); 127 | } 128 | 129 | if ($param->isDefaultValueAvailable()) { 130 | return new DefaultValueArgument($typeHint, $param->getDefaultValue()); 131 | } 132 | 133 | return new ResolvableArgument($typeHint); 134 | } 135 | 136 | public function throwParameterException( 137 | string $name, 138 | string $type, 139 | ?string $declaringClass = null, 140 | ?string $declaringFunction = null, 141 | bool $isClosure = false, 142 | ?string $additionalMessage = null 143 | ): void { 144 | throw new NotFoundException(sprintf( 145 | 'Unable to resolve parameter ($%s) with type (%s) in %s%s%s()%s', 146 | $name, 147 | $type, 148 | $declaringClass ? $declaringClass . '::' : '', 149 | $declaringFunction ?? '', 150 | $isClosure ? ' [closure]' : '', 151 | $additionalMessage ? ' - ' . $additionalMessage : '' 152 | )); 153 | } 154 | 155 | abstract public function getContainer(): DefinitionContainerInterface; 156 | abstract public function getMode(): int; 157 | abstract public function resolveArguments(array $arguments): array; 158 | } 159 | -------------------------------------------------------------------------------- /src/Argument/ArgumentResolverInterface.php: -------------------------------------------------------------------------------- 1 | getContainer(); 23 | } catch (ContainerException) { 24 | $container = ($this instanceof ReflectionContainer) ? $this : null; 25 | } 26 | 27 | foreach ($arguments as &$arg) { 28 | // if we have a literal, we don't want to do anything more with it 29 | if ($arg instanceof LiteralArgumentInterface) { 30 | $arg = $arg->getValue(); 31 | continue; 32 | } 33 | 34 | if ($arg instanceof ArgumentInterface) { 35 | $argValue = $arg->getValue(); 36 | } else { 37 | $argValue = $arg; 38 | } 39 | 40 | if (!is_string($argValue)) { 41 | continue; 42 | } 43 | 44 | // resolve the argument from the container, if it happens to be another 45 | // argument wrapper, use that value 46 | if ($container instanceof ContainerInterface && $container->has($argValue)) { 47 | try { 48 | $arg = $container->get($argValue); 49 | 50 | if ($arg instanceof ArgumentInterface) { 51 | $arg = $arg->getValue(); 52 | } 53 | 54 | continue; 55 | } catch (NotFoundException) { 56 | } 57 | } 58 | 59 | // if we have a default value, we use that, no more resolution as 60 | // we expect a default/optional argument value to be literal 61 | if ($arg instanceof DefaultValueInterface) { 62 | $arg = $arg->getDefaultValue(); 63 | } 64 | } 65 | 66 | return $arguments; 67 | } 68 | 69 | abstract public function getContainer(): DefinitionContainerInterface; 70 | } 71 | -------------------------------------------------------------------------------- /src/Argument/DefaultValueArgument.php: -------------------------------------------------------------------------------- 1 | defaultValue; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Argument/DefaultValueInterface.php: -------------------------------------------------------------------------------- 1 | value = $value; 33 | } else { 34 | throw new InvalidArgumentException('Incorrect type for value.'); 35 | } 36 | } 37 | 38 | public function getValue(): mixed 39 | { 40 | return $this->value; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Argument/LiteralArgumentInterface.php: -------------------------------------------------------------------------------- 1 | value; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Argument/ResolvableArgumentInterface.php: -------------------------------------------------------------------------------- 1 | getContainer()->get($this->id); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Attribute/Resolve.php: -------------------------------------------------------------------------------- 1 | getContainer()->get($this->resolver); 27 | 28 | foreach (explode('.', $this->path) as $segment) { 29 | $resolved = $this->getResolvedValue($resolved, $segment); 30 | } 31 | 32 | return $resolved; 33 | } 34 | 35 | protected function getResolvedValue(mixed $previous, string $next): mixed 36 | { 37 | if (is_object($previous) && method_exists($previous, $next)) { 38 | return $previous->{$next}(); 39 | } 40 | 41 | if (is_object($previous) && property_exists($previous, $next)) { 42 | return $previous->{$next}; 43 | } 44 | 45 | if (is_array($previous) && array_key_exists($next, $previous)) { 46 | return $previous[$next]; 47 | } 48 | 49 | throw new NotFoundException( 50 | sprintf( 51 | 'Unable to resolve value for path (%s) on resolver (%s)', 52 | $this->path, 53 | $this->resolver 54 | ) 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Container.php: -------------------------------------------------------------------------------- 1 | definitions->setContainer($this); 32 | $this->providers->setContainer($this); 33 | $this->inflectors->setContainer($this); 34 | } 35 | 36 | public function add(string $id, mixed $concrete = null, bool $overwrite = false): DefinitionInterface 37 | { 38 | $toOverwrite = $this->defaultToOverwrite || $overwrite; 39 | $concrete = $concrete ?? $id; 40 | 41 | if (true === $this->defaultToShared) { 42 | return $this->addShared($id, $concrete, $toOverwrite); 43 | } 44 | 45 | return $this->definitions->add($id, $concrete, $toOverwrite); 46 | } 47 | 48 | public function addShared(string $id, mixed $concrete = null, bool $overwrite = false): DefinitionInterface 49 | { 50 | $toOverwrite = $this->defaultToOverwrite || $overwrite; 51 | $concrete = $concrete ?? $id; 52 | return $this->definitions->addShared($id, $concrete, $toOverwrite); 53 | } 54 | 55 | public function defaultToShared(bool $shared = true): ContainerInterface 56 | { 57 | $this->defaultToShared = $shared; 58 | return $this; 59 | } 60 | 61 | public function defaultToOverwrite(bool $overwrite = true): ContainerInterface 62 | { 63 | $this->defaultToOverwrite = $overwrite; 64 | return $this; 65 | } 66 | 67 | public function extend(string $id): DefinitionInterface 68 | { 69 | if ($this->providers->provides($id)) { 70 | $this->providers->register($id); 71 | } 72 | 73 | if ($this->definitions->has($id)) { 74 | return $this->definitions->getDefinition($id); 75 | } 76 | 77 | throw new NotFoundException(sprintf( 78 | 'Unable to extend alias (%s) as it is not being managed as a definition', 79 | $id 80 | )); 81 | } 82 | 83 | public function addServiceProvider(ServiceProviderInterface $provider): DefinitionContainerInterface 84 | { 85 | $this->providers->add($provider); 86 | return $this; 87 | } 88 | 89 | public function get(string $id) 90 | { 91 | return $this->resolve($id); 92 | } 93 | 94 | /** 95 | * @throws ContainerExceptionInterface 96 | * @throws NotFoundExceptionInterface 97 | */ 98 | public function getNew(string $id): mixed 99 | { 100 | return $this->resolve($id, true); 101 | } 102 | 103 | public function has(string $id): bool 104 | { 105 | if ($this->definitions->has($id)) { 106 | return true; 107 | } 108 | 109 | if ($this->definitions->hasTag($id)) { 110 | return true; 111 | } 112 | 113 | if ($this->providers->provides($id)) { 114 | return true; 115 | } 116 | 117 | foreach ($this->delegates as $delegate) { 118 | if ($delegate->has($id)) { 119 | return true; 120 | } 121 | } 122 | 123 | return false; 124 | } 125 | 126 | public function inflector(string $type, ?callable $callback = null): InflectorInterface 127 | { 128 | return $this->inflectors->add($type, $callback); 129 | } 130 | 131 | public function delegate(ContainerInterface $container): self 132 | { 133 | $this->delegates[] = $container; 134 | 135 | if ($container instanceof ContainerAwareInterface) { 136 | $container->setContainer($this); 137 | } 138 | 139 | return $this; 140 | } 141 | 142 | /** 143 | * @throws ContainerExceptionInterface 144 | * @throws NotFoundExceptionInterface 145 | */ 146 | protected function resolve(string $id, bool $new = false): mixed 147 | { 148 | if ($this->definitions->has($id)) { 149 | $resolved = (true === $new) ? $this->definitions->resolveNew($id) : $this->definitions->resolve($id); 150 | return $this->inflectors->inflect($resolved); 151 | } 152 | 153 | if ($this->definitions->hasTag($id)) { 154 | $arrayOf = (true === $new) 155 | ? $this->definitions->resolveTaggedNew($id) 156 | : $this->definitions->resolveTagged($id); 157 | 158 | array_walk($arrayOf, function (&$resolved) { 159 | $resolved = $this->inflectors->inflect($resolved); 160 | }); 161 | 162 | return $arrayOf; 163 | } 164 | 165 | if ($this->providers->provides($id)) { 166 | $this->providers->register($id); 167 | 168 | if (false === $this->definitions->has($id) && false === $this->definitions->hasTag($id)) { // @phpstan-ignore-line 169 | throw new ContainerException(sprintf('Service provider lied about providing (%s) service', $id)); 170 | } 171 | 172 | return $this->resolve($id, $new); // @phpstan-ignore-line 173 | } 174 | 175 | foreach ($this->delegates as $delegate) { 176 | if ($delegate->has($id)) { 177 | $resolved = $delegate->get($id); 178 | return $this->inflectors->inflect($resolved); 179 | } 180 | } 181 | 182 | throw new NotFoundException(sprintf('Alias (%s) is not being managed by the container or delegates', $id)); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/ContainerAwareInterface.php: -------------------------------------------------------------------------------- 1 | container = $container; 20 | 21 | if ($this instanceof ContainerAwareInterface) { 22 | return $this; 23 | } 24 | 25 | throw new BadMethodCallException(sprintf( 26 | 'Attempt to use (%s) while not implementing (%s)', 27 | ContainerAwareTrait::class, 28 | ContainerAwareInterface::class 29 | )); 30 | } 31 | 32 | public function getContainer(): DefinitionContainerInterface 33 | { 34 | if ($this->container instanceof DefinitionContainerInterface) { 35 | return $this->container; 36 | } 37 | 38 | throw new ContainerException('No container implementation has been set.'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Definition/Definition.php: -------------------------------------------------------------------------------- 1 | setId($this->id); 36 | $this->concrete ??= $this->id; 37 | } 38 | 39 | public function addTag(string $tag): DefinitionInterface 40 | { 41 | $this->tags[$tag] = true; 42 | return $this; 43 | } 44 | 45 | public function hasTag(string $tag): bool 46 | { 47 | return isset($this->tags[$tag]); 48 | } 49 | 50 | public function setId(string $id): DefinitionInterface 51 | { 52 | $this->id = static::normaliseAlias($id); 53 | return $this; 54 | } 55 | 56 | public function getId(): string 57 | { 58 | return static::normaliseAlias($this->id); 59 | } 60 | 61 | public function setAlias(string $id): DefinitionInterface 62 | { 63 | return $this->setId($id); 64 | } 65 | 66 | public function getAlias(): string 67 | { 68 | return $this->getId(); 69 | } 70 | 71 | public function setShared(bool $shared = true): DefinitionInterface 72 | { 73 | $this->shared = $shared; 74 | return $this; 75 | } 76 | 77 | public function isShared(): bool 78 | { 79 | return $this->shared; 80 | } 81 | 82 | public function getConcrete(): mixed 83 | { 84 | return $this->concrete; 85 | } 86 | 87 | public function setConcrete(mixed $concrete): DefinitionInterface 88 | { 89 | $this->concrete = $concrete; 90 | $this->resolved = null; 91 | return $this; 92 | } 93 | 94 | public function addArgument(mixed $arg): DefinitionInterface 95 | { 96 | $this->arguments[] = $arg; 97 | return $this; 98 | } 99 | 100 | public function addArguments(array $args): DefinitionInterface 101 | { 102 | foreach ($args as $arg) { 103 | $this->addArgument($arg); 104 | } 105 | 106 | return $this; 107 | } 108 | 109 | public function addMethodCall(string $method, array $args = []): DefinitionInterface 110 | { 111 | $this->methods[] = [ 112 | 'method' => $method, 113 | 'arguments' => $args 114 | ]; 115 | 116 | return $this; 117 | } 118 | 119 | public function addMethodCalls(array $methods = []): DefinitionInterface 120 | { 121 | foreach ($methods as $method => $args) { 122 | $this->addMethodCall($method, $args); 123 | } 124 | 125 | return $this; 126 | } 127 | 128 | /** 129 | * @throws ContainerExceptionInterface 130 | * @throws NotFoundExceptionInterface 131 | * @throws ReflectionException 132 | */ 133 | public function resolve(): mixed 134 | { 135 | if (null !== $this->resolved && $this->isShared()) { 136 | return $this->resolved; 137 | } 138 | 139 | return $this->resolveNew(); 140 | } 141 | 142 | /** 143 | * @throws ContainerExceptionInterface 144 | * @throws NotFoundExceptionInterface 145 | * @throws ReflectionException 146 | */ 147 | public function resolveNew(): mixed 148 | { 149 | $concrete = $this->concrete; 150 | 151 | if (is_callable($concrete)) { 152 | $concrete = $this->resolveCallable($concrete); 153 | } 154 | 155 | if ($concrete instanceof LiteralArgumentInterface) { 156 | $this->resolved = $concrete->getValue(); 157 | return $concrete->getValue(); 158 | } 159 | 160 | if ($concrete instanceof ArgumentInterface) { 161 | $concrete = $concrete->getValue(); 162 | } 163 | 164 | if (is_string($concrete) && class_exists($concrete)) { 165 | $concrete = $this->resolveClass($concrete); 166 | } 167 | 168 | if (is_object($concrete)) { 169 | $concrete = $this->invokeMethods($concrete); 170 | } 171 | 172 | try { 173 | $container = $this->getContainer(); 174 | } catch (ContainerException) { 175 | $container = null; 176 | } 177 | 178 | if (is_string($concrete)) { 179 | if (class_exists($concrete)) { 180 | $concrete = $this->resolveClass($concrete); 181 | } elseif ($this->getAlias() === $concrete) { 182 | return $concrete; 183 | } 184 | } 185 | 186 | // if we still have a string, try to pull it from the container 187 | // this allows for `alias -> alias -> ... -> concrete 188 | if (is_string($concrete) && $container instanceof ContainerInterface && $container->has($concrete)) { 189 | $this->recursiveCheck[] = $concrete; 190 | $concrete = $container->get($concrete); 191 | } 192 | 193 | $this->resolved = $concrete; 194 | return $concrete; 195 | } 196 | 197 | /** 198 | * @throws ReflectionException 199 | * @throws ContainerExceptionInterface 200 | * @throws NotFoundExceptionInterface 201 | */ 202 | protected function resolveCallable(callable $concrete): mixed 203 | { 204 | $resolved = $this->resolveArguments($this->arguments); 205 | return call_user_func_array($concrete, $resolved); 206 | } 207 | 208 | /** 209 | * @throws NotFoundExceptionInterface 210 | * @throws ReflectionException 211 | * @throws ContainerExceptionInterface 212 | */ 213 | protected function resolveClass(string $concrete): object 214 | { 215 | $resolved = $this->resolveArguments($this->arguments); 216 | $reflection = new ReflectionClass($concrete); 217 | return $reflection->newInstanceArgs($resolved); 218 | } 219 | 220 | /** 221 | * @throws ReflectionException 222 | * @throws ContainerExceptionInterface 223 | * @throws NotFoundExceptionInterface 224 | */ 225 | protected function invokeMethods(object $instance): object 226 | { 227 | foreach ($this->methods as $method) { 228 | $args = $this->resolveArguments($method['arguments']); 229 | $callable = [$instance, $method['method']]; 230 | call_user_func_array($callable, $args); 231 | } 232 | 233 | return $instance; 234 | } 235 | 236 | public static function normaliseAlias(string $alias): string 237 | { 238 | return ltrim($alias, "\\"); 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/Definition/DefinitionAggregate.php: -------------------------------------------------------------------------------- 1 | definitions = array_filter($this->definitions, static function ($definition) { 18 | return ($definition instanceof DefinitionInterface); 19 | }); 20 | } 21 | 22 | public function add(string $id, mixed $definition, bool $overwrite = false): DefinitionInterface 23 | { 24 | if (true === $overwrite) { 25 | $this->remove($id); 26 | } 27 | 28 | if (false === ($definition instanceof DefinitionInterface)) { 29 | $definition = new Definition($id, $definition); 30 | } 31 | 32 | $this->definitions[] = $definition->setAlias($id); 33 | 34 | return $definition; 35 | } 36 | 37 | public function addShared(string $id, mixed $definition, bool $overwrite = false): DefinitionInterface 38 | { 39 | $definition = $this->add($id, $definition, $overwrite); 40 | return $definition->setShared(true); 41 | } 42 | 43 | public function has(string $id): bool 44 | { 45 | $id = Definition::normaliseAlias($id); 46 | 47 | foreach ($this as $definition) { 48 | if ($id === $definition->getAlias()) { 49 | return true; 50 | } 51 | } 52 | 53 | return false; 54 | } 55 | 56 | public function hasTag(string $tag): bool 57 | { 58 | foreach ($this as $definition) { 59 | if ($definition->hasTag($tag)) { 60 | return true; 61 | } 62 | } 63 | 64 | return false; 65 | } 66 | 67 | public function getDefinition(string $id): DefinitionInterface 68 | { 69 | $id = Definition::normaliseAlias($id); 70 | 71 | foreach ($this as $definition) { 72 | if ($id === $definition->getAlias()) { 73 | return $definition->setContainer($this->getContainer()); 74 | } 75 | } 76 | 77 | throw new NotFoundException(sprintf('Alias (%s) is not being handled as a definition.', $id)); 78 | } 79 | 80 | public function resolve(string $id): mixed 81 | { 82 | return $this->getDefinition($id)->resolve(); 83 | } 84 | 85 | public function resolveNew(string $id): mixed 86 | { 87 | return $this->getDefinition($id)->resolveNew(); 88 | } 89 | 90 | public function resolveTagged(string $tag): array 91 | { 92 | $arrayOf = []; 93 | 94 | foreach ($this as $definition) { 95 | if ($definition->hasTag($tag)) { 96 | $arrayOf[] = $definition->setContainer($this->getContainer())->resolve(); 97 | } 98 | } 99 | 100 | return $arrayOf; 101 | } 102 | 103 | public function resolveTaggedNew(string $tag): array 104 | { 105 | $arrayOf = []; 106 | 107 | foreach ($this as $definition) { 108 | if ($definition->hasTag($tag)) { 109 | $arrayOf[] = $definition->setContainer($this->getContainer())->resolveNew(); 110 | } 111 | } 112 | 113 | return $arrayOf; 114 | } 115 | 116 | public function remove(string $id): void 117 | { 118 | $id = Definition::normaliseAlias($id); 119 | 120 | foreach ($this as $key => $definition) { 121 | if ($id === $definition->getAlias()) { 122 | unset($this->definitions[$key]); 123 | } 124 | } 125 | } 126 | 127 | public function getIterator(): Generator 128 | { 129 | yield from $this->definitions; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Definition/DefinitionAggregateInterface.php: -------------------------------------------------------------------------------- 1 | callback = $callback; 32 | } 33 | 34 | public function oncePerMatch(): InflectorInterface 35 | { 36 | $this->oncePerMatch = true; 37 | return $this; 38 | } 39 | 40 | public function getType(): string 41 | { 42 | return $this->type; 43 | } 44 | 45 | public function invokeMethod(string $name, array $args): InflectorInterface 46 | { 47 | $this->methods[$name] = $args; 48 | return $this; 49 | } 50 | 51 | public function invokeMethods(array $methods): InflectorInterface 52 | { 53 | foreach ($methods as $name => $args) { 54 | $this->invokeMethod($name, $args); 55 | } 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * @throws ContainerExceptionInterface 62 | * @throws ReflectionException 63 | * @throws NotFoundExceptionInterface 64 | */ 65 | public function setProperty(string $property, mixed $value): InflectorInterface 66 | { 67 | $this->properties[$property] = $this->resolveArguments([$value])[0]; 68 | return $this; 69 | } 70 | 71 | /** 72 | * @throws ReflectionException 73 | * @throws ContainerExceptionInterface 74 | * @throws NotFoundExceptionInterface 75 | */ 76 | public function setProperties(array $properties): InflectorInterface 77 | { 78 | foreach ($properties as $property => $value) { 79 | $this->setProperty($property, $value); 80 | } 81 | 82 | return $this; 83 | } 84 | 85 | /** 86 | * @throws NotFoundExceptionInterface 87 | * @throws ContainerExceptionInterface 88 | * @throws ReflectionException 89 | */ 90 | public function inflect(object $object): void 91 | { 92 | if (true === $this->oncePerMatch && in_array($object, $this->inflected, true)) { 93 | return; 94 | } 95 | 96 | $properties = $this->resolveArguments(array_values($this->properties)); 97 | $properties = array_combine(array_keys($this->properties), $properties); 98 | 99 | // array_combine() can technically return false 100 | foreach ($properties ?: [] as $property => $value) { 101 | $object->{$property} = $value; 102 | } 103 | 104 | foreach ($this->methods as $method => $args) { 105 | $args = $this->resolveArguments($args); 106 | $callable = [$object, $method]; 107 | call_user_func_array($callable, $args); 108 | } 109 | 110 | if ($this->callback !== null) { 111 | call_user_func($this->callback, $object); 112 | } 113 | 114 | if (true === $this->oncePerMatch) { 115 | $this->inflected[] = $object; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Inflector/InflectorAggregate.php: -------------------------------------------------------------------------------- 1 | inflectors[] = $inflector; 23 | return $inflector; 24 | } 25 | 26 | public function inflect(mixed $object): mixed 27 | { 28 | foreach ($this as $inflector) { 29 | $type = $inflector->getType(); 30 | 31 | if ($object instanceof $type) { 32 | $inflector->setContainer($this->getContainer()); 33 | $inflector->inflect($object); 34 | } 35 | } 36 | 37 | return $object; 38 | } 39 | 40 | public function getIterator(): Generator 41 | { 42 | yield from $this->inflectors; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Inflector/InflectorAggregateInterface.php: -------------------------------------------------------------------------------- 1 | mode = $mode; 38 | } 39 | 40 | public function getMode(): int 41 | { 42 | return $this->mode; 43 | } 44 | 45 | /** 46 | * @throws ContainerExceptionInterface 47 | * @throws NotFoundExceptionInterface 48 | * @throws ReflectionException 49 | */ 50 | public function get(string $id, array $args = []) 51 | { 52 | if (true === $this->cacheResolutions && array_key_exists($id, $this->cache)) { 53 | return $this->cache[$id]; 54 | } 55 | 56 | if (!$this->has($id)) { 57 | throw new NotFoundException( 58 | sprintf('Alias (%s) is not an existing class and therefore cannot be resolved', $id) 59 | ); 60 | } 61 | 62 | $reflector = new ReflectionClass($id); 63 | $construct = $reflector->getConstructor(); 64 | 65 | if ($construct && !$construct->isPublic()) { 66 | throw new NotFoundException( 67 | sprintf('Alias (%s) has a non-public constructor and therefore cannot be instantiated', $id) 68 | ); 69 | } 70 | 71 | $resolution = $construct === null 72 | ? new $id() 73 | : $reflector->newInstanceArgs($this->reflectArguments($construct, $args)) 74 | ; 75 | 76 | if ($this->cacheResolutions === true) { 77 | $this->cache[$id] = $resolution; 78 | } 79 | 80 | return $resolution; 81 | } 82 | 83 | public function has(string $id): bool 84 | { 85 | return class_exists($id); 86 | } 87 | 88 | /** 89 | * @throws ContainerExceptionInterface 90 | * @throws NotFoundExceptionInterface 91 | * @throws ReflectionException 92 | */ 93 | public function call(callable $callable, array $args = []): mixed 94 | { 95 | if (is_string($callable) && str_contains($callable, '::')) { 96 | $callable = explode('::', $callable); 97 | } 98 | 99 | if (is_array($callable)) { 100 | if (is_string($callable[0])) { 101 | // if we have a definition container, try that first, otherwise, reflect 102 | try { 103 | $callable[0] = $this->getContainer()->get($callable[0]); 104 | } catch (ContainerExceptionInterface | NotFoundExceptionInterface) { 105 | $callable[0] = $this->get($callable[0]); 106 | } 107 | } 108 | 109 | $reflection = new ReflectionMethod($callable[0], $callable[1]); 110 | 111 | if ($reflection->isStatic()) { 112 | $callable[0] = null; 113 | } 114 | 115 | return $reflection->invokeArgs($callable[0], $this->reflectArguments($reflection, $args)); 116 | } 117 | 118 | if (is_object($callable) && method_exists($callable, '__invoke')) { 119 | /** @var object $callable */ 120 | $reflection = new ReflectionMethod($callable, '__invoke'); 121 | return $reflection->invokeArgs($callable, $this->reflectArguments($reflection, $args)); 122 | } 123 | 124 | $reflection = new ReflectionFunction($callable(...)); 125 | return $reflection->invokeArgs($this->reflectArguments($reflection, $args)); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/ServiceProvider/AbstractServiceProvider.php: -------------------------------------------------------------------------------- 1 | identifier)) { 18 | $this->identifier = get_class($this); 19 | } 20 | 21 | return $this->identifier; 22 | } 23 | 24 | public function setIdentifier(string $id): ServiceProviderInterface 25 | { 26 | $this->identifier = $id; 27 | return $this; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/ServiceProvider/BootableServiceProviderInterface.php: -------------------------------------------------------------------------------- 1 | providers, true)) { 24 | return $this; 25 | } 26 | 27 | $provider->setContainer($this->getContainer()); 28 | 29 | if ($provider instanceof BootableServiceProviderInterface) { 30 | $provider->boot(); 31 | } 32 | 33 | $this->providers[] = $provider; 34 | return $this; 35 | } 36 | 37 | public function provides(string $id): bool 38 | { 39 | foreach ($this as $provider) { 40 | if ($provider->provides($id)) { 41 | return true; 42 | } 43 | } 44 | 45 | return false; 46 | } 47 | 48 | public function getIterator(): Generator 49 | { 50 | yield from $this->providers; 51 | } 52 | 53 | public function register(string $service): void 54 | { 55 | if (false === $this->provides($service)) { 56 | throw new ContainerException( 57 | sprintf('(%s) is not provided by a service provider', $service) 58 | ); 59 | } 60 | 61 | foreach ($this as $provider) { 62 | if (in_array($provider->getIdentifier(), $this->registered, true)) { 63 | continue; 64 | } 65 | 66 | if ($provider->provides($service)) { 67 | $provider->register(); 68 | $this->registered[] = $provider->getIdentifier(); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/ServiceProvider/ServiceProviderAggregateInterface.php: -------------------------------------------------------------------------------- 1 |