├── .gitignore ├── .php_cs ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── UPGRADE.md ├── composer.json ├── phpunit.xml ├── src ├── ApplicationConfig.php ├── ApplicationConfigIterator.php ├── Configurator.php ├── ContainerAdapter.php ├── ContainerAdapterFactory.php ├── Exception │ ├── EntryDoesNotExistException.php │ ├── Exception.php │ ├── InvalidConfigException.php │ ├── MissingDependencyException.php │ ├── NoMatchingFilesException.php │ ├── NotClassDefinitionException.php │ ├── NotContainerAdapterException.php │ ├── ReadOnlyException.php │ ├── UnknownContainerException.php │ ├── UnknownFileTypeException.php │ └── UnknownSettingException.php ├── FileReader │ ├── FileLocator.php │ ├── FileReader.php │ ├── JSONFileReader.php │ ├── PHPFileReader.php │ ├── ReaderFactory.php │ └── YAMLFileReader.php ├── InflectorConfig.php ├── InflectorDefinition.php ├── League │ ├── ApplicationConfigServiceProvider.php │ ├── InflectorServiceProvider.php │ ├── LeagueContainerAdapter.php │ └── ServiceServiceProvider.php ├── Pimple │ └── PimpleContainerAdapter.php ├── ServiceConfig.php └── ServiceDefinition.php └── tests ├── acceptance ├── AbstractContainerAdapterTest.php ├── LeagueContainerAdapterTest.php ├── PimpleContainerAdapterTest.php ├── PimpleContainerWrapper.php ├── SupportsApplicationConfig.php ├── SupportsInflectorConfig.php └── SupportsServiceConfig.php ├── mocks ├── BootableServiceProvider.php ├── ExampleClass.php ├── ExampleClassWithArgs.php ├── ExampleContainer.php ├── ExampleContainerAdapter.php ├── ExampleExtendedContainer.php ├── ExampleFactory.php ├── ExampleInterface.php ├── FileReader │ └── CustomFileReader.php └── NotContainerAdapter.php ├── support └── TestFileCreator.php └── unit ├── ApplicationConfigIteratorTest.php ├── ApplicationConfigTest.php ├── ConfiguratorTest.php ├── ContainerAdapterFactoryTest.php ├── Exception ├── EntryDoesNotExistExceptionTest.php ├── InvalidConfigExceptionTest.php ├── MissingDependencyExceptionTest.php ├── NoMatchingFilesExceptionTest.php ├── NotClassDefinitionExceptionTest.php ├── NotContainerAdapterExceptionTest.php ├── ReadOnlyExceptionTest.php ├── UnknownContainerExceptionTest.php ├── UnknownFileTypeExceptionTest.php └── UnknownSettingExceptionTest.php ├── FileReader ├── FileLocatorTest.php ├── JSONFileReaderTest.php ├── PHPFileReaderTest.php ├── ReaderFactoryTest.php └── YAMLFileReaderTest.php ├── InflectorConfigTest.php ├── InflectorDefinitionTest.php ├── ServiceConfigTest.php └── ServiceDefinitionTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | tests/.test-config 3 | .php_cs.cache 4 | composer.lock 5 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | in(__DIR__); 4 | 5 | return PhpCsFixer\Config::create() 6 | ->setRules([ 7 | '@PSR2' => true, 8 | 'array_syntax' => [ 9 | 'syntax' => 'short', 10 | ], 11 | 'binary_operator_spaces' => [ 12 | 'align_double_arrow' => true, 13 | 'align_equals' => true, 14 | ], 15 | 'concat_space' => [ 16 | 'spacing' => 'one', 17 | ], 18 | 'function_typehint_space' => true, 19 | 'hash_to_slash_comment' => true, 20 | 'include' => true, 21 | 'lowercase_cast' => true, 22 | 'method_separation' => true, 23 | 'native_function_casing' => true, 24 | 'new_with_braces' => true, 25 | 'no_alias_functions' => true, 26 | 'no_blank_lines_after_class_opening' => true, 27 | 'no_blank_lines_after_phpdoc' => true, 28 | 'no_empty_comment' => true, 29 | 'no_empty_phpdoc' => true, 30 | 'no_empty_statement' => true, 31 | 'no_extra_consecutive_blank_lines' => true, 32 | 'no_leading_import_slash' => true, 33 | 'no_leading_namespace_whitespace' => true, 34 | 'no_multiline_whitespace_around_double_arrow' => true, 35 | 'no_multiline_whitespace_before_semicolons' => true, 36 | 'no_short_bool_cast' => true, 37 | 'no_singleline_whitespace_before_semicolons' => true, 38 | 'no_spaces_around_offset' => true, 39 | 'no_trailing_comma_in_list_call' => true, 40 | 'no_trailing_comma_in_singleline_array' => true, 41 | 'no_unreachable_default_argument_value' => true, 42 | 'no_unused_imports' => true, 43 | 'no_useless_else' => true, 44 | 'no_useless_return' => true, 45 | 'no_whitespace_before_comma_in_array' => true, 46 | 'object_operator_without_whitespace' => true, 47 | 'ordered_imports' => true, 48 | 'phpdoc_align' => true, 49 | 'phpdoc_indent' => true, 50 | 'phpdoc_inline_tag' => true, 51 | 'phpdoc_no_access' => true, 52 | 'phpdoc_no_alias_tag' => [ 53 | 'type' => 'var', 54 | ], 55 | 'phpdoc_no_package' => true, 56 | 'phpdoc_order' => true, 57 | 'phpdoc_scalar' => true, 58 | 'phpdoc_separation' => true, 59 | 'phpdoc_single_line_var_spacing' => true, 60 | 'phpdoc_summary' => true, 61 | 'phpdoc_to_comment' => true, 62 | 'phpdoc_trim' => true, 63 | 'phpdoc_types' => true, 64 | 'self_accessor' => true, 65 | 'short_scalar_cast' => true, 66 | 'single_blank_line_before_namespace' => true, 67 | 'single_quote' => true, 68 | 'space_after_semicolon' => true, 69 | 'standardize_not_equals' => true, 70 | 'trailing_comma_in_multiline_array' => true, 71 | 'trim_array_spaces' => true, 72 | 'unary_operator_spaces' => true, 73 | 'whitespace_after_comma_in_array' => true, 74 | ]) 75 | ->setRiskyAllowed(true) 76 | ->setUsingCache(true) 77 | ->setFinder($finder); 78 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.0 6 | - 7.1 7 | - hhvm 8 | 9 | cache: 10 | directories: 11 | - $HOME/.composer/cache 12 | 13 | before_install: 14 | - composer self-update 15 | - composer validate 16 | 17 | install: 18 | - composer install --prefer-dist 19 | 20 | script: 21 | - composer test 22 | 23 | matrix: 24 | fast_finish: true 25 | allow_failures: 26 | - php: hhvm 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## 1.0.0 - 2015-19-31 6 | ### Added 7 | * Inflector support for Pimple. 8 | * Custom file readers with `withFileReader(string $extension, string $readerClass);`. 9 | * Custom container adapters with `withContainerAdapter(string $containerName, string $adapterName);`. 10 | 11 | ## 0.5.2 - 2016-09-30 12 | ### Added 13 | * Service aliases. 14 | * Container injection using `Configurator::container()`. 15 | 16 | ### Fixed 17 | * Non-string arguments can be provided to Pimple services. 18 | 19 | ## 0.5.1 - 2016-09-18 20 | ### Added 21 | * `TomPHP\ContainerConfigurator\FileReader\YAMLFileReader` for reading 22 | YAML files (requires `symfony/yaml` to be installed). 23 | * Service factories. 24 | 25 | ## 0.5.0 - 2016-09-13 26 | ### Added 27 | * `TomPHP\ContainerConfigurator\Configurator` as the main API. 28 | * If `class` is left out of the config for a service, then the service name 29 | is assumed to be the name of the class. 30 | * Services can be set as singleton by default. 31 | * All exceptions implement `TomPHP\ContainerConfigurator\Exception\Exception`. 32 | * Support for Pimple. 33 | 34 | ### Changed 35 | * The composer package has been renamed to `container-configurator`. 36 | * The package namespace has changed to `TomPHP\ContainerConfigurator`. 37 | * Minimum supported PHP version is now `5.6`. 38 | * Exception base-classes have been updated. 39 | * File reader classes have moved to `TomPHP\ContainerConfigurator\FileReader`. 40 | 41 | ### Removed 42 | * `TomPHP\ConfigServiceProvider\ConfigServiceProvider` 43 | * Custom configurable service providers (`TomPHP\ConfigServiceProvider\ConfigurableServiceProvider`). 44 | * Custom sub-providers. 45 | * `TomPHP\ConfigServiceProvider\Exception\RuntimeException` 46 | 47 | ## [0.4.0] - 2016-05-25 48 | ### Added 49 | * Exception thrown when no files are found when using the `fromFiles` 50 | constructor 51 | 52 | ### Changed 53 | * Config containing class names will remain as strings and not be converted to 54 | instances 55 | 56 | ## [0.3.3] - 2015-10-10 57 | ### Added 58 | * Configuring DI via the config 59 | 60 | ## [0.3.2] - 2015-09-24 61 | ### Added 62 | * Reading JSON config files via the `fromFiles` constructor 63 | * Support from braces in file globbing patterns 64 | 65 | ## [0.3.1] - 2015-09-23 66 | ### Added 67 | * Reading config files (PHP and JSON) 68 | 69 | ## [0.3.0] - 2015-09-23 70 | ### Added 71 | * `ConfigServiceProvider::fromConfig()` factory method 72 | * Sub providers 73 | 74 | ### Changed 75 | * `TomPHP\ConfigServiceProvider\InflectorConfigServiceProvider` is 76 | now a sub provider 77 | * Provider classes are marked as final 78 | 79 | ### Removed 80 | * `TomPHP\ConfigServiceProvider\Config` static factory 81 | 82 | ## [0.2.1] - 2015-09-21 83 | ### Added 84 | * Support to set up inflectors via configuration 85 | * `TomPHP\ConfigServiceProvider\Config` - a static class to enable easy setup. 86 | 87 | ## [0.2.0] - 2015-09-03 88 | ### Changed 89 | * Now depends on League Container `^2.0.2` 90 | * `TomPHP\ConfigServiceProvider\ConfigServiceProvider` now extends 91 | `League\Container\ServiceProvider\AbstractServiceProvider` 92 | 93 | ## [0.1.2] - 2014-04-12 94 | ### Added 95 | * Contributing guidelines 96 | * `composer test` to run test suite 97 | * Make sub-arrays accessible directly 98 | 99 | ### Changed 100 | * Make League Container dependency stricter (use `^` version) 101 | 102 | ## [0.1.1] - 2014-04-12 103 | ### Added 104 | * CHANGELOG.md 105 | * Homepage field to composer.json 106 | 107 | ### Fixed 108 | * Typo in README.md 109 | 110 | ## [0.1.0] - 2015-04-12 111 | ### Added 112 | * Service provider for `league/container` 113 | * Support for multiple levels of config 114 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for considering to contribute to this project. 4 | 5 | ## Submitting Pull Requests 6 | 7 | Please submit your pull requests to the `master` branch. 8 | 9 | ## Tests 10 | 11 | All new features or bug fixes should include supporting tests. To run the test 12 | suite you need to first install the dependencies using composer: 13 | 14 | ``` 15 | $ composer install 16 | ``` 17 | 18 | Then the tests can be run using the following command: 19 | 20 | ``` 21 | $ composer test 22 | ``` 23 | 24 | This will also check for the PSR-2 Coding Standard compliance. 25 | 26 | ## Travis 27 | 28 | Once you have submitted a pull request, Travis CI will automatically run the 29 | tests. The tests **must** pass for the PR to be accepted. 30 | 31 | ## Coding Standard 32 | 33 | Please stick PSR-1 and PSR-2 standards - this will be verified by Travis CI: 34 | 35 | * https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md 36 | * https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md 37 | 38 | Also, keep the code tidy and well refactored - don't let methods get too long 39 | or there be too many levels of indentation. 40 | 41 | Happy coding and thank you for contributing. 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Tom Oram 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Container Configurator 2 | 3 | [![Build Status](https://api.travis-ci.org/tomphp/container-configurator.svg)](https://api.travis-ci.org/tomphp/container-configurator) 4 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/tomphp/container-configurator/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/tomphp/container-configurator/?branch=master) 5 | [![Latest Stable Version](https://poser.pugx.org/tomphp/container-configurator/v/stable)](https://packagist.org/packages/tomphp/container-configurator) 6 | [![Total Downloads](https://poser.pugx.org/tomphp/container-configurator/downloads)](https://packagist.org/packages/tomphp/container-configurator) 7 | [![Latest Unstable Version](https://poser.pugx.org/tomphp/container-configurator/v/unstable)](https://packagist.org/packages/tomphp/container-configurator) 8 | [![License](https://poser.pugx.org/tomphp/container-configurator/license)](https://packagist.org/packages/tomphp/container-configurator) 9 | 10 | This package enables you to configure your application and the Dependency 11 | Injection Container (DIC) via config arrays or files. Currently, supported 12 | containers are: 13 | 14 | * [League Of Extraordinary Packages' Container](https://github.com/thephpleague/container) 15 | * [Pimple](http://pimple.sensiolabs.org/) 16 | 17 | ## Installation 18 | 19 | Installation can be done easily using composer: 20 | 21 | ``` 22 | $ composer require tomphp/container-configurator 23 | ``` 24 | 25 | ## Example Usage 26 | 27 | ```php 28 | [ 35 | 'name' => 'example_db', 36 | 'username' => 'dbuser', 37 | 'password' => 'dbpass', 38 | ], 39 | 'di' => [ 40 | 'services' => [ 41 | 'database_connection' => [ 42 | 'class' => DatabaseConnection::class, 43 | 'arguments' => [ 44 | 'config.db.name', 45 | 'config.db.username', 46 | 'config.db.password', 47 | ], 48 | ], 49 | ], 50 | ], 51 | ]; 52 | 53 | $container = new Container(); 54 | Configurator::apply()->configFromArray($config)->to($container); 55 | 56 | $db = $container->get('database_connection'); 57 | ``` 58 | 59 | ## Reading Files From Disk 60 | 61 | Instead of providing the config as an array, you can also provide a list of 62 | file pattern matches to the `fromFiles` function. 63 | 64 | ```php 65 | Configurator::apply() 66 | ->configFromFile('config_dir/config.global.php') 67 | ->configFromFiles('json_dir/*.json') 68 | ->configFromFiles('config_dir/*.local.php') 69 | ->to($container); 70 | ``` 71 | 72 | `configFromFile(string $filename)` reads config in from a single file. 73 | 74 | `configFromFiles(string $pattern)` reads config from multiple files using 75 | globbing patterns. 76 | 77 | ### Merging 78 | 79 | The reader matches files in the order they are specified. As files are 80 | read their config is merged in; overwriting any matching keys. 81 | 82 | ### Supported Formats 83 | 84 | Currently `.php` and `.json` files are supported out of the box. PHP 85 | config files **must** return a PHP array. 86 | 87 | `.yaml` and `.yml` files can be read when the package `symfony/yaml` is 88 | available. Run 89 | 90 | ``` 91 | composer require symfony/yaml 92 | ``` 93 | 94 | to install it. 95 | 96 | ## Application Configuration 97 | 98 | All values in the config array are made accessible via the DIC with the keys 99 | separated by a separator (default: `.`) and prefixed with constant string (default: 100 | `config`). 101 | 102 | #### Example 103 | 104 | ```php 105 | $config = [ 106 | 'db' => [ 107 | 'name' => 'example_db', 108 | 'username' => 'dbuser', 109 | 'password' => 'dbpass', 110 | ], 111 | ]; 112 | 113 | $container = new Container(); 114 | Configurator::apply()->configFromArray($config)->to($container); 115 | 116 | var_dump($container->get('config.db.name')); 117 | /* 118 | * OUTPUT: 119 | * string(10) "example_db" 120 | */ 121 | ``` 122 | 123 | ### Accessing A Whole Sub-Array 124 | 125 | Whole sub-arrays are also made available for cases where you want them instead 126 | of individual values. 127 | 128 | #### Example 129 | 130 | ```php 131 | $config = [ 132 | 'db' => [ 133 | 'name' => 'example_db', 134 | 'username' => 'dbuser', 135 | 'password' => 'dbpass', 136 | ], 137 | ]; 138 | 139 | $container = new Container(); 140 | Configurator::apply()->configFromArray($config)->to($container); 141 | 142 | var_dump($container->get('config.db')); 143 | /* 144 | * OUTPUT: 145 | * array(3) { 146 | * ["name"]=> 147 | * string(10) "example_db" 148 | * ["username"]=> 149 | * string(6) "dbuser" 150 | * ["password"]=> 151 | * string(6) "dbpass" 152 | * } 153 | */ 154 | ``` 155 | 156 | ## Configuring Services 157 | 158 | Another feature is the ability to add services to your container via the 159 | config. By default, this is done by adding a `services` key under a `di` key in 160 | the config in the following format: 161 | 162 | ```php 163 | $config = [ 164 | 'di' => [ 165 | 'services' => [ 166 | 'logger' => [ 167 | 'class' => Logger::class, 168 | 'singleton' => true, 169 | 'arguments' => [ 170 | StdoutLogger::class, 171 | ], 172 | 'methods' => [ 173 | 'setLogLevel' => [ 'info' ], 174 | ], 175 | ], 176 | StdoutLogger::class => [], 177 | ], 178 | ], 179 | ]; 180 | 181 | $container = new Container(); 182 | Configurator::apply()->configFromArray($config)->to($container); 183 | 184 | $logger = $container->get('logger')); 185 | ``` 186 | 187 | ### Service Aliases 188 | 189 | You can create an alias to another service by using the `service` keyword 190 | instead of `class`: 191 | 192 | ```php 193 | $config = [ 194 | 'database' => [ /* ... */ ], 195 | 'di' => [ 196 | 'services' => [ 197 | DatabaseConnection::class => [ 198 | 'service' => MySQLDatabaseConnection::class, 199 | ], 200 | MySQLDatabaseConnection::class => [ 201 | 'arguments' => [ 202 | 'config.database.host', 203 | 'config.database.username', 204 | 'config.database.password', 205 | 'config.database.dbname', 206 | ], 207 | ], 208 | ], 209 | ], 210 | ]; 211 | ``` 212 | 213 | ### Service Factories 214 | 215 | If you require some addition additional logic when creating a service, you can 216 | define a Service Factory. A service factory is simply an invokable class which 217 | can take a list of arguments and returns the service instance. 218 | 219 | Services are added to the container by using the `factory` key instead of the 220 | `class` key. 221 | 222 | #### Example Config 223 | ```php 224 | $appConfig = [ 225 | 'db' => [ 226 | 'host' => 'localhost', 227 | 'database' => 'example_db', 228 | 'username' => 'example_user', 229 | 'password' => 'example_password', 230 | ], 231 | 'di' => [ 232 | 'services' => [ 233 | 'database' => [ 234 | 'factory' => MySQLPDOFactory::class, 235 | 'singleton' => true, 236 | 'arguments' => [ 237 | 'config.db.host', 238 | 'config.db.database', 239 | 'config.db.username', 240 | 'config.db.password', 241 | ], 242 | ], 243 | ], 244 | ], 245 | ]; 246 | ``` 247 | 248 | #### Example Service Factory 249 | ```php 250 | setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 259 | 260 | return $pdo; 261 | } 262 | } 263 | ``` 264 | 265 | ### Injecting The Container 266 | 267 | In the rare case that you want to inject the container in as a dependency to 268 | one of your services, you can use `Configurator::container()` as the name 269 | of the injected dependency. This will only work in PHP config files, it's not 270 | available with YAML or JSON. 271 | 272 | ```php 273 | $config = [ 274 | 'di' => [ 275 | 'services' => [ 276 | ContainerAwareService::class => [ 277 | 'arguments' => [Configurator::container()], 278 | ], 279 | ], 280 | ], 281 | ]; 282 | ``` 283 | 284 | ### Configuring Inflectors 285 | 286 | It is also possible to set up 287 | [Inflectors](http://container.thephpleague.com/inflectors/) by adding an 288 | `inflectors` key to the `di` section of the config. 289 | 290 | ```php 291 | $appConfig = [ 292 | 'di' => [ 293 | 'inflectors' => [ 294 | LoggerAwareInterface::class => [ 295 | 'setLogger' => ['Some\Logger'] 296 | ], 297 | ], 298 | ], 299 | ]; 300 | ``` 301 | 302 | ## Extra Settings 303 | 304 | The behaviour of the `Configurator` can be adjusted by using the 305 | `withSetting(string $name, $value` method: 306 | 307 | ```php 308 | Configurator::apply() 309 | ->configFromFiles('*.cfg.php'), 310 | ->withSetting(Configurator::SETTING_PREFIX, 'settings') 311 | ->withSetting(Configurator::SETTING_SEPARATOR, '/') 312 | ->to($container); 313 | ``` 314 | 315 | Available settings are: 316 | 317 | | Name | Description | Default | 318 | |------------------------------------|-------------------------------------------------|-----------------| 319 | | SETTING_PREFIX | Sets prefix name for config value keys. | `config` | 320 | | SETTING_SEPARATOR | Sets the separator for config key. | `.` | 321 | | SETTING_SERVICES_KEY | Where the config for the services is. | `di.services` | 322 | | SETTING_INFLECTORS_KEY | Where the config for the inflectors is. | `di.inflectors` | 323 | | SETTING_DEFAULT_SINGLETON_SERVICES | Sets whether services are singleton by default. | `false` | 324 | 325 | ## Advanced Customisation 326 | 327 | ### Adding A Custom File Reader 328 | 329 | You can create your own custom file reader by implementing the 330 | `TomPHP\ContainerConfigurator\FileReader\FileReader` interface. Once you have 331 | created it, you can use the 332 | `withFileReader(string $extension, string $readerClassName)` method to enable 333 | the it. 334 | 335 | **IMPORTANT**: `withFileReader()` must be called before calling 336 | `configFromFile()` or `configFromFiles()`! 337 | 338 | ```php 339 | Configurator::apply() 340 | ->withFileReader('.xml', MyCustomXMLFileReader::class) 341 | ->configFromFile('config.xml'), 342 | ->to($container); 343 | ``` 344 | 345 | ### Adding A Custom Container Adapter 346 | 347 | You can create your own container adapter so that you can configure other 348 | containers. This is done by implementing the 349 | `TomPHP\ContainerConfigurator\FileReader\ContainerAdapter` interface. Once you 350 | have created your adapter, you can use the 351 | `withContainerAdapter(string $containerName, string $adapterName)` method to 352 | enable the it: 353 | 354 | ```php 355 | Configurator::apply() 356 | ->withContainerAdapter(MyContainer::class, MyContainerAdapter::class) 357 | ->configFromArray($appConfig), 358 | ->to($container); 359 | ``` 360 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | # Upgrade Instructions 2 | 3 | ## v0.4.\* -> v0.5.\* 4 | 5 | There are three major changes which occurred in this update: 6 | 7 | ### Package Renamed 8 | 9 | This package is now called **Container Configurator**. It's not installed with 10 | the command: 11 | 12 | ``` 13 | composer require tomphp/container-configurator 14 | ``` 15 | 16 | For existing projects, you can fix this by running: 17 | 18 | ``` 19 | composer remove tomphp/config-service-provider && composer require tomphp/container-configurator 20 | ``` 21 | 22 | To require it in your project you need to: 23 | 24 | ``` 25 | use TomPHP\ContainerConfigurator\Configurator; 26 | ``` 27 | 28 | ### New API 29 | 30 | The API had a major rewrite in v0.5.0. Rather than creating a service provider 31 | like this: 32 | 33 | ```php 34 | $container->addServiceProvider(ConfigServiceProvider::fromFile(['config.php']); 35 | ``` 36 | 37 | You now configure the container like this: 38 | 39 | ```php 40 | Configurator::apply() 41 | ->configFromFiles('config/*.inc.php') 42 | ->to($container); 43 | ``` 44 | 45 | If you had multiple file pattern matches, you can chain more `configFromFiles` 46 | calls like so: 47 | 48 | ```php 49 | Configurator::apply() 50 | ->configFromFiles('*.global.php') 51 | ->configFromFiles('*.local.php') 52 | ->to($container); 53 | ``` 54 | 55 | Also: 56 | 57 | ```php 58 | $container->addServiceProvider(ConfigServiceProvider::fromConfig($config); 59 | ``` 60 | 61 | Would now be replaced with: 62 | 63 | ```php 64 | Configurator::apply() 65 | ->configFromArray($config) 66 | ->to($container); 67 | ``` 68 | 69 | ### Default DI Config Has Changed Structure 70 | 71 | There's been some changes to where the service and inflector config go by 72 | default. Before v0.5.0 the config would look like this: 73 | 74 | ```php 75 | $config = [ 76 | 'di' => [ 77 | // Config for services goes here 78 | ], 79 | 'inflectors' => [ 80 | // Config for inflectors goes here 81 | ], 82 | ]; 83 | ``` 84 | 85 | By default, v0.5.0 now has this format: 86 | 87 | ```php 88 | $config = [ 89 | 'di' => [ 90 | 'services' => [ 91 | // Config for services goes here 92 | ], 93 | 'inflectors' => [ 94 | // Config for inflectors goes here 95 | ], 96 | ], 97 | ]; 98 | ``` 99 | 100 | If you don't wish to adopt this new format, you can set the Configurator up to 101 | behave in the old way by using the following settings: 102 | 103 | ```php 104 | Configurator::apply() 105 | ->configFromArray($config) 106 | ->withSetting(Configurator::SETTING_SERVICES_KEY, 'di') 107 | ->withSetting(Configurator::SETTING_INFLECTORS_KEY, 'inflectors') 108 | ->to($container); 109 | ``` 110 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tomphp/container-configurator", 3 | "description": "Configure your application and the Dependency Injection Container (DIC) via config arrays or config files.", 4 | "license": "MIT", 5 | "type": "library", 6 | "homepage": "https://github.com/tomphp/config-service-provider", 7 | "keywords": ["di", "dependency injection", "container", "league"], 8 | "authors": [ 9 | { 10 | "name": "Tom Oram", 11 | "email": "tom@x2k.co.uk", 12 | "homepage": "https://github.com/tomphp", 13 | "role": "Developer" 14 | } 15 | ], 16 | "suggest": { 17 | "league/container": "Small but powerful dependency injection container http://container.thephpleague.com", 18 | "pimple/pimple": "A small PHP 5.3 dependency injection container http://pimple.sensiolabs.org", 19 | "symfony/yaml": "For reading configuration from YAML files" 20 | }, 21 | "require": { 22 | "php": "^5.6|^7.0", 23 | "beberlei/assert": "^2.6", 24 | "tomphp/exception-constructor-tools": "^1.0.0" 25 | }, 26 | "require-dev": { 27 | "friendsofphp/php-cs-fixer": "^2.1.0", 28 | "league/container": "^2.0.2", 29 | "phpunit/phpunit": "^5.5.4", 30 | "pimple/pimple": "^3.0.0", 31 | "squizlabs/php_codesniffer": "^2.8.0", 32 | "symfony/yaml": "^3.1.4" 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "TomPHP\\ContainerConfigurator\\": "src/" 37 | } 38 | }, 39 | "autoload-dev": { 40 | "psr-4": { 41 | "tests\\unit\\TomPHP\\ContainerConfigurator\\": "tests/unit/", 42 | "tests\\acceptance\\": "tests/acceptance/", 43 | "tests\\support\\": "tests/support/", 44 | "tests\\mocks\\": "tests/mocks/" 45 | }, 46 | "files": [ 47 | "vendor/phpunit/phpunit/src/Framework/Assert/Functions.php" 48 | ] 49 | }, 50 | "config": { 51 | "sort-packages": true 52 | }, 53 | "scripts": { 54 | "cs:fix": [ 55 | "phpcbf --standard=psr2 src tests; exit 0", 56 | "php-cs-fixer fix --verbose; exit 0" 57 | ], 58 | "cs:check": [ 59 | "phpcs --standard=psr2 src tests", 60 | "php-cs-fixer fix --dry-run --verbose" 61 | ], 62 | "test": [ 63 | "@cs:check", 64 | "phpunit --colors=always" 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | tests/unit 22 | 23 | 24 | tests/acceptance 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/ApplicationConfig.php: -------------------------------------------------------------------------------- 1 | string()->notEmpty(); 35 | 36 | $this->config = $config; 37 | $this->separator = $separator; 38 | } 39 | 40 | public function merge(array $config) 41 | { 42 | $this->config = array_replace_recursive($this->config, $config); 43 | } 44 | 45 | /** 46 | * @param string $separator 47 | * 48 | * @throws InvalidArgumentException 49 | * 50 | * @return void 51 | */ 52 | public function setSeparator($separator) 53 | { 54 | \Assert\that($separator)->string()->notEmpty(); 55 | 56 | $this->separator = $separator; 57 | } 58 | 59 | public function getIterator() 60 | { 61 | return new ApplicationConfigIterator($this); 62 | } 63 | 64 | /** 65 | * @return array 66 | */ 67 | public function getKeys() 68 | { 69 | return array_keys(iterator_to_array(new ApplicationConfigIterator($this))); 70 | } 71 | 72 | public function offsetExists($offset) 73 | { 74 | try { 75 | $this->traverseConfig($this->getPath($offset)); 76 | } catch (EntryDoesNotExistException $e) { 77 | return false; 78 | } 79 | 80 | return true; 81 | } 82 | 83 | /** 84 | * @param mixed $offset 85 | * 86 | * @throws EntryDoesNotExistException 87 | * 88 | * @return mixed 89 | */ 90 | public function offsetGet($offset) 91 | { 92 | return $this->traverseConfig($this->getPath($offset)); 93 | } 94 | 95 | /** 96 | * @param mixed $offset 97 | * @param mixed $value 98 | * 99 | * @throws ReadOnlyException 100 | */ 101 | public function offsetSet($offset, $value) 102 | { 103 | throw ReadOnlyException::fromClassName(__CLASS__); 104 | } 105 | 106 | /** 107 | * @param mixed $offset 108 | * 109 | * @throws ReadOnlyException 110 | */ 111 | public function offsetUnset($offset) 112 | { 113 | throw ReadOnlyException::fromClassName(__CLASS__); 114 | } 115 | 116 | /** 117 | * @return array 118 | */ 119 | public function asArray() 120 | { 121 | return $this->config; 122 | } 123 | 124 | /** 125 | * @return string 126 | */ 127 | public function getSeparator() 128 | { 129 | return $this->separator; 130 | } 131 | 132 | /** 133 | * @param string $offset 134 | * 135 | * @return array 136 | */ 137 | private function getPath($offset) 138 | { 139 | return explode($this->separator, $offset); 140 | } 141 | 142 | /** 143 | * @param array $path 144 | * 145 | * @throws EntryDoesNotExistException 146 | * 147 | * @return mixed 148 | */ 149 | private function traverseConfig(array $path) 150 | { 151 | $pointer = &$this->config; 152 | 153 | foreach ($path as $node) { 154 | if (!is_array($pointer) || !array_key_exists($node, $pointer)) { 155 | throw EntryDoesNotExistException::fromKey(implode($this->separator, $path)); 156 | } 157 | 158 | $pointer = &$pointer[$node]; 159 | } 160 | 161 | return $pointer; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/ApplicationConfigIterator.php: -------------------------------------------------------------------------------- 1 | asArray()), 30 | RecursiveIteratorIterator::SELF_FIRST 31 | ); 32 | $this->separator = $config->getSeparator(); 33 | } 34 | 35 | public function key() 36 | { 37 | return implode($this->separator, array_merge($this->path, [parent::key()])); 38 | } 39 | 40 | public function next() 41 | { 42 | if ($this->callHasChildren()) { 43 | array_push($this->path, parent::key()); 44 | } 45 | 46 | parent::next(); 47 | } 48 | 49 | public function rewind() 50 | { 51 | $this->path = []; 52 | 53 | parent::rewind(); 54 | } 55 | 56 | public function endChildren() 57 | { 58 | array_pop($this->path); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Configurator.php: -------------------------------------------------------------------------------- 1 | FileReader\JSONFileReader::class, 20 | '.php' => FileReader\PHPFileReader::class, 21 | '.yaml' => FileReader\YAMLFileReader::class, 22 | '.yml' => FileReader\YAMLFileReader::class, 23 | ]; 24 | 25 | const CONTAINER_ADAPTERS = [ 26 | \League\Container\Container::class => League\LeagueContainerAdapter::class, 27 | \Pimple\Container::class => Pimple\PimpleContainerAdapter::class, 28 | ]; 29 | 30 | /** 31 | * @var ApplicationConfig 32 | */ 33 | private $config; 34 | 35 | /** 36 | * @var FileReader\ReaderFactory 37 | */ 38 | private $readerFactory; 39 | 40 | /** 41 | * @var mixed[] 42 | */ 43 | private $settings = [ 44 | self::SETTING_PREFIX => 'config', 45 | self::SETTING_SEPARATOR => '.', 46 | self::SETTING_SERVICES_KEY => 'di.services', 47 | self::SETTING_INFLECTORS_KEY => 'di.inflectors', 48 | self::SETTING_DEFAULT_SINGLETON_SERVICES => false, 49 | ]; 50 | 51 | /** 52 | * @var string[] 53 | */ 54 | private $fileReaders = self::FILE_READERS; 55 | 56 | /** 57 | * @var string[] 58 | */ 59 | private $containerAdapters = self::CONTAINER_ADAPTERS; 60 | 61 | /** 62 | * @var string 63 | */ 64 | private static $containerIdentifier; 65 | 66 | /** 67 | * @return Configurator 68 | */ 69 | public static function apply() 70 | { 71 | return new self(); 72 | } 73 | 74 | private function __construct() 75 | { 76 | $this->config = new ApplicationConfig([]); 77 | } 78 | 79 | /** 80 | * @return string 81 | */ 82 | public static function container() 83 | { 84 | if (!self::$containerIdentifier) { 85 | self::$containerIdentifier = uniqid(__CLASS__ . '::CONTAINER_ID::'); 86 | } 87 | 88 | return self::$containerIdentifier; 89 | } 90 | 91 | /** 92 | * @param array $config 93 | * 94 | * @return $this 95 | */ 96 | public function configFromArray(array $config) 97 | { 98 | $this->config->merge($config); 99 | 100 | return $this; 101 | } 102 | 103 | /** 104 | * @param string $filename 105 | * 106 | * @throws InvalidArgumentException 107 | * 108 | * @return $this 109 | */ 110 | public function configFromFile($filename) 111 | { 112 | Assertion::file($filename); 113 | 114 | $this->readFileAndMergeConfig($filename); 115 | 116 | return $this; 117 | } 118 | 119 | /** 120 | * @param string $pattern 121 | * 122 | * @throws NoMatchingFilesException 123 | * @throws InvalidArgumentException 124 | * 125 | * @return $this 126 | */ 127 | public function configFromFiles($pattern) 128 | { 129 | Assertion::string($pattern); 130 | 131 | $locator = new FileReader\FileLocator(); 132 | 133 | $files = $locator->locate($pattern); 134 | 135 | if (count($files) === 0) { 136 | throw NoMatchingFilesException::fromPattern($pattern); 137 | } 138 | 139 | foreach ($files as $filename) { 140 | $this->readFileAndMergeConfig($filename); 141 | } 142 | 143 | return $this; 144 | } 145 | 146 | /** 147 | * @param string $name 148 | * @param mixed $value 149 | * 150 | * @throws UnknownSettingException 151 | * @throws InvalidArgumentException 152 | * 153 | * @return $this 154 | */ 155 | public function withSetting($name, $value) 156 | { 157 | Assertion::string($name); 158 | Assertion::scalar($value); 159 | 160 | if (!array_key_exists($name, $this->settings)) { 161 | throw UnknownSettingException::fromSetting($name, array_keys($this->settings)); 162 | } 163 | 164 | $this->settings[$name] = $value; 165 | 166 | return $this; 167 | } 168 | 169 | /** 170 | * @param string $extension 171 | * @param string $className 172 | * 173 | * @return $this 174 | */ 175 | public function withFileReader($extension, $className) 176 | { 177 | $this->fileReaders[$extension] = $className; 178 | 179 | return $this; 180 | } 181 | 182 | /** 183 | * @param string $containerName 184 | * @param string $adapterName 185 | * 186 | * @return $this 187 | */ 188 | public function withContainerAdapter($containerName, $adapterName) 189 | { 190 | $this->containerAdapters[$containerName] = $adapterName; 191 | 192 | return $this; 193 | } 194 | 195 | /** 196 | * @param object $container 197 | * 198 | * @return void 199 | */ 200 | public function to($container) 201 | { 202 | $this->config->setSeparator($this->settings[self::SETTING_SEPARATOR]); 203 | 204 | $factory = new ContainerAdapterFactory($this->containerAdapters); 205 | 206 | $configurator = $factory->create($container); 207 | 208 | $configurator->addApplicationConfig($this->config, $this->settings[self::SETTING_PREFIX]); 209 | 210 | if (isset($this->config[$this->settings[self::SETTING_SERVICES_KEY]])) { 211 | $configurator->addServiceConfig(new ServiceConfig( 212 | $this->config[$this->settings[self::SETTING_SERVICES_KEY]], 213 | $this->settings[self::SETTING_DEFAULT_SINGLETON_SERVICES] 214 | )); 215 | } 216 | 217 | if (isset($this->config[$this->settings[self::SETTING_INFLECTORS_KEY]])) { 218 | $configurator->addInflectorConfig(new InflectorConfig( 219 | $this->config[$this->settings[self::SETTING_INFLECTORS_KEY]] 220 | )); 221 | } 222 | } 223 | 224 | /** 225 | * @param string $filename 226 | * 227 | * @return void 228 | */ 229 | private function readFileAndMergeConfig($filename) 230 | { 231 | $reader = $this->getReaderFor($filename); 232 | 233 | $this->config->merge($reader->read($filename)); 234 | } 235 | 236 | /** 237 | * @param string $filename 238 | * 239 | * @return FileReader\FileReader 240 | */ 241 | private function getReaderFor($filename) 242 | { 243 | if (!$this->readerFactory) { 244 | $this->readerFactory = new FileReader\ReaderFactory($this->fileReaders); 245 | } 246 | 247 | return $this->readerFactory->create($filename); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/ContainerAdapter.php: -------------------------------------------------------------------------------- 1 | config = $config; 21 | } 22 | 23 | /** 24 | * @param object $container 25 | * 26 | * @throws UnknownContainerException 27 | * @throws NotContainerAdapterException 28 | * 29 | * @return ContainerAdapter 30 | */ 31 | public function create($container) 32 | { 33 | $class = ''; 34 | 35 | foreach ($this->config as $containerClass => $configuratorClass) { 36 | if ($container instanceof $containerClass) { 37 | $class = $configuratorClass; 38 | break; 39 | } 40 | } 41 | 42 | if (!$class) { 43 | throw UnknownContainerException::fromContainerName( 44 | get_class($container), 45 | array_keys($this->config) 46 | ); 47 | } 48 | 49 | $instance = new $class(); 50 | 51 | if (!$instance instanceof ContainerAdapter) { 52 | throw NotContainerAdapterException::fromClassName($class); 53 | } 54 | 55 | $instance->setContainer($container); 56 | 57 | return $instance; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Exception/EntryDoesNotExistException.php: -------------------------------------------------------------------------------- 1 | null, 15 | JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', 16 | JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch', 17 | JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', 18 | JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON', 19 | JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', 20 | ]; 21 | 22 | /** 23 | * @var string 24 | */ 25 | private $filename; 26 | 27 | public function read($filename) 28 | { 29 | Assertion::file($filename); 30 | 31 | $this->filename = $filename; 32 | 33 | $config = json_decode(file_get_contents($filename), true); 34 | 35 | if (json_last_error() !== JSON_ERROR_NONE) { 36 | throw InvalidConfigException::fromJSONFileError($filename, $this->getJsonError()); 37 | } 38 | 39 | return $config; 40 | } 41 | 42 | /** 43 | * @return string 44 | */ 45 | private function getJsonError() 46 | { 47 | if (function_exists('json_last_error_msg')) { 48 | return json_last_error_msg(); 49 | } 50 | 51 | return self::JSON_ERRORS[json_last_error()]; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/FileReader/PHPFileReader.php: -------------------------------------------------------------------------------- 1 | filename = $filename; 20 | 21 | $config = include $this->filename; 22 | 23 | $this->assertConfigIsValid($config); 24 | 25 | return $config; 26 | } 27 | 28 | private function assertConfigIsValid($config) 29 | { 30 | if (!is_array($config)) { 31 | throw InvalidConfigException::fromPHPFileError($this->filename); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/FileReader/ReaderFactory.php: -------------------------------------------------------------------------------- 1 | config = $config; 30 | } 31 | 32 | /** 33 | * @param string $filename 34 | * 35 | * @throws InvalidArgumentException 36 | * 37 | * @return FileReader 38 | */ 39 | public function create($filename) 40 | { 41 | Assertion::file($filename); 42 | 43 | $readerClass = $this->getReaderClass($filename); 44 | 45 | if (!isset($this->readers[$readerClass])) { 46 | $this->readers[$readerClass] = new $readerClass(); 47 | } 48 | 49 | return $this->readers[$readerClass]; 50 | } 51 | 52 | /** 53 | * @param string $filename 54 | * 55 | * @throws UnknownFileTypeException 56 | * 57 | * @return string 58 | */ 59 | private function getReaderClass($filename) 60 | { 61 | $readerClass = null; 62 | 63 | foreach ($this->config as $extension => $className) { 64 | if ($this->endsWith($filename, $extension)) { 65 | $readerClass = $className; 66 | break; 67 | } 68 | } 69 | 70 | if ($readerClass === null) { 71 | throw UnknownFileTypeException::fromFileExtension( 72 | $filename, 73 | array_keys($this->config) 74 | ); 75 | } 76 | 77 | return $readerClass; 78 | } 79 | 80 | /** 81 | * @param string $haystack 82 | * @param string $needle 83 | * 84 | * @return bool 85 | */ 86 | private function endsWith($haystack, $needle) 87 | { 88 | return $needle === substr($haystack, -strlen($needle)); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/FileReader/YAMLFileReader.php: -------------------------------------------------------------------------------- 1 | getMessage()); 33 | } 34 | 35 | return $config; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/InflectorConfig.php: -------------------------------------------------------------------------------- 1 | inflectors = []; 24 | 25 | foreach ($config as $interfaceName => $methods) { 26 | $this->inflectors[] = new InflectorDefinition( 27 | $interfaceName, 28 | $methods 29 | ); 30 | } 31 | } 32 | 33 | public function getIterator() 34 | { 35 | return new ArrayIterator($this->inflectors); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/InflectorDefinition.php: -------------------------------------------------------------------------------- 1 | interface = $interface; 27 | $this->methods = $methods; 28 | } 29 | 30 | /** 31 | * @return string 32 | */ 33 | public function getInterface() 34 | { 35 | return $this->interface; 36 | } 37 | 38 | /** 39 | * @return array 40 | */ 41 | public function getMethods() 42 | { 43 | return $this->methods; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/League/ApplicationConfigServiceProvider.php: -------------------------------------------------------------------------------- 1 | prefix = $prefix; 36 | $this->config = $config; 37 | $this->provides = array_map( 38 | function ($key) { 39 | return $this->keyPrefix() . $key; 40 | }, 41 | $config->getKeys() 42 | ); 43 | } 44 | 45 | public function register() 46 | { 47 | $prefix = $this->keyPrefix(); 48 | 49 | foreach ($this->config as $key => $value) { 50 | $this->container->share($prefix . $key, function () use ($value) { 51 | return $value; 52 | }); 53 | } 54 | } 55 | 56 | /** 57 | * @return string 58 | */ 59 | private function keyPrefix() 60 | { 61 | if (empty($this->prefix)) { 62 | return ''; 63 | } 64 | 65 | return $this->prefix . $this->config->getSeparator(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/League/InflectorServiceProvider.php: -------------------------------------------------------------------------------- 1 | config = $config; 26 | } 27 | 28 | public function register() 29 | { 30 | } 31 | 32 | public function boot() 33 | { 34 | foreach ($this->config as $definition) { 35 | $this->configureInterface($definition); 36 | } 37 | } 38 | 39 | /** 40 | * @param InflectorDefinition $definition 41 | */ 42 | private function configureInterface(InflectorDefinition $definition) 43 | { 44 | foreach ($definition->getMethods() as $method => $args) { 45 | $this->addInflectorMethod( 46 | $definition->getInterface(), 47 | $method, 48 | $args 49 | ); 50 | } 51 | } 52 | 53 | /** 54 | * @param string $interface 55 | * @param string $method 56 | * @param array $args 57 | */ 58 | private function addInflectorMethod($interface, $method, array $args) 59 | { 60 | $this->getContainer() 61 | ->inflector($interface) 62 | ->invokeMethod($method, $args); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/League/LeagueContainerAdapter.php: -------------------------------------------------------------------------------- 1 | container = $container; 28 | } 29 | 30 | public function addApplicationConfig(ApplicationConfig $config, $prefix = 'config') 31 | { 32 | Assertion::string($prefix); 33 | 34 | $this->container->addServiceProvider(new ApplicationConfigServiceProvider($config, $prefix)); 35 | } 36 | 37 | public function addServiceConfig(ServiceConfig $config) 38 | { 39 | $this->container->addServiceProvider(new ServiceServiceProvider($config)); 40 | } 41 | 42 | public function addInflectorConfig(InflectorConfig $config) 43 | { 44 | $this->container->addServiceProvider(new InflectorServiceProvider($config)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/League/ServiceServiceProvider.php: -------------------------------------------------------------------------------- 1 | config = $config; 28 | $this->provides = $config->getKeys(); 29 | } 30 | 31 | public function register() 32 | { 33 | foreach ($this->config as $config) { 34 | $this->registerService($config); 35 | } 36 | } 37 | 38 | /** 39 | * @param ServiceDefinition $definition 40 | * 41 | * @throws NotClassDefinitionException 42 | * 43 | * @return void 44 | */ 45 | private function registerService(ServiceDefinition $definition) 46 | { 47 | if ($definition->isFactory()) { 48 | $this->getContainer()->add( 49 | $definition->getName(), 50 | $this->createFactoryFactory($definition), 51 | $definition->isSingleton() 52 | ); 53 | 54 | return; 55 | } 56 | 57 | if ($definition->isAlias()) { 58 | $this->getContainer()->add( 59 | $definition->getName(), 60 | $this->createAliasFactory($definition) 61 | ); 62 | 63 | return; 64 | } 65 | 66 | $service = $this->getContainer()->add( 67 | $definition->getName(), 68 | $definition->getClass(), 69 | $definition->isSingleton() 70 | ); 71 | 72 | if (!$service instanceof ClassDefinition) { 73 | throw NotClassDefinitionException::fromServiceName($definition->getName()); 74 | } 75 | 76 | $service->withArguments($this->injectContainer($definition->getArguments())); 77 | $this->addMethodCalls($service, $definition); 78 | } 79 | 80 | /** 81 | * @param ClassDefinition $service 82 | * @param ServiceDefinition $definition 83 | */ 84 | private function addMethodCalls(ClassDefinition $service, ServiceDefinition $definition) 85 | { 86 | foreach ($definition->getMethods() as $method => $args) { 87 | $service->withMethodCall($method, $this->injectContainer($args)); 88 | } 89 | } 90 | 91 | /** 92 | * @param ServiceDefinition $definition 93 | * 94 | * @return \Closure 95 | */ 96 | private function createAliasFactory(ServiceDefinition $definition) 97 | { 98 | return function () use ($definition) { 99 | return $this->getContainer()->get($definition->getClass()); 100 | }; 101 | } 102 | 103 | /** 104 | * @param ServiceDefinition $definition 105 | * 106 | * @return \Closure 107 | */ 108 | private function createFactoryFactory(ServiceDefinition $definition) 109 | { 110 | return function () use ($definition) { 111 | $className = $definition->getClass(); 112 | $factory = new $className(); 113 | 114 | return $factory(...$this->resolveArguments($definition->getArguments())); 115 | }; 116 | } 117 | 118 | /** 119 | * @param array $arguments 120 | * 121 | * @return array 122 | */ 123 | private function injectContainer(array $arguments) 124 | { 125 | return array_map( 126 | function ($argument) { 127 | return ($argument === Configurator::container()) 128 | ? $this->container 129 | : $argument; 130 | }, 131 | $arguments 132 | ); 133 | } 134 | 135 | /** 136 | * @param array $arguments 137 | * 138 | * @return array 139 | */ 140 | private function resolveArguments(array $arguments) 141 | { 142 | return array_map( 143 | function ($argument) { 144 | if ($argument === Configurator::container()) { 145 | return $this->container; 146 | } 147 | 148 | if ($this->container->has($argument)) { 149 | return $this->container->get($argument); 150 | } 151 | 152 | return $argument; 153 | }, 154 | $arguments 155 | ); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/Pimple/PimpleContainerAdapter.php: -------------------------------------------------------------------------------- 1 | container = $container; 37 | } 38 | 39 | public function addApplicationConfig(ApplicationConfig $config, $prefix = 'config') 40 | { 41 | Assertion::string($prefix); 42 | 43 | if (!empty($prefix)) { 44 | $prefix .= $config->getSeparator(); 45 | } 46 | 47 | foreach ($config as $key => $value) { 48 | $this->container[$prefix . $key] = $value; 49 | } 50 | } 51 | 52 | public function addServiceConfig(ServiceConfig $config) 53 | { 54 | foreach ($config as $definition) { 55 | $this->addServiceToContainer($definition); 56 | } 57 | } 58 | 59 | public function addInflectorConfig(InflectorConfig $config) 60 | { 61 | foreach ($config as $definition) { 62 | $this->inflectors[$definition->getInterface()] = $this->createInflector($definition); 63 | } 64 | } 65 | 66 | private function addServiceToContainer(ServiceDefinition $definition) 67 | { 68 | $factory = $this->createFactory($definition); 69 | 70 | if (!$definition->isSingleton()) { 71 | $factory = $this->container->factory($factory); 72 | } 73 | 74 | $this->container[$definition->getName()] = $factory; 75 | } 76 | 77 | /** 78 | * @param ServiceDefinition $definition 79 | * 80 | * @return Closure 81 | */ 82 | private function createFactory(ServiceDefinition $definition) 83 | { 84 | if ($definition->isFactory()) { 85 | return $this->applyInflectors($this->createFactoryFactory($definition)); 86 | } 87 | 88 | if ($definition->isAlias()) { 89 | return $this->createAliasFactory($definition); 90 | } 91 | 92 | return $this->applyInflectors($this->createInstanceFactory($definition)); 93 | } 94 | 95 | /** 96 | * @param ServiceDefinition $definition 97 | * 98 | * @return Closure 99 | */ 100 | private function createFactoryFactory(ServiceDefinition $definition) 101 | { 102 | return function () use ($definition) { 103 | $className = $definition->getClass(); 104 | $factory = new $className(); 105 | 106 | return $factory(...$this->resolveArguments($definition->getArguments())); 107 | }; 108 | } 109 | 110 | /** 111 | * @param ServiceDefinition $definition 112 | * 113 | * @return Closure 114 | */ 115 | private function createAliasFactory(ServiceDefinition $definition) 116 | { 117 | return function () use ($definition) { 118 | return $this->container[$definition->getClass()]; 119 | }; 120 | } 121 | 122 | /** 123 | * @param ServiceDefinition $definition 124 | * 125 | * @return Closure 126 | */ 127 | private function createInstanceFactory(ServiceDefinition $definition) 128 | { 129 | return function () use ($definition) { 130 | $className = $definition->getClass(); 131 | $instance = new $className(...$this->resolveArguments($definition->getArguments())); 132 | 133 | foreach ($definition->getMethods() as $name => $args) { 134 | $instance->$name(...$this->resolveArguments($args)); 135 | } 136 | 137 | return $instance; 138 | }; 139 | } 140 | 141 | /** 142 | * @param InflectorDefinition $definition 143 | * 144 | * @return Closure 145 | */ 146 | private function createInflector(InflectorDefinition $definition) 147 | { 148 | return function ($subject) use ($definition) { 149 | foreach ($definition->getMethods() as $method => $arguments) { 150 | $subject->$method(...$this->resolveArguments($arguments)); 151 | } 152 | }; 153 | } 154 | 155 | /** 156 | * @param Closure $factory 157 | * 158 | * @return Closure 159 | */ 160 | private function applyInflectors(Closure $factory) 161 | { 162 | return function () use ($factory) { 163 | $instance = $factory(); 164 | 165 | foreach ($this->inflectors as $interface => $inflector) { 166 | if ($instance instanceof $interface) { 167 | $inflector($instance); 168 | } 169 | } 170 | 171 | return $instance; 172 | }; 173 | } 174 | 175 | /** 176 | * @param array $arguments 177 | * 178 | * @return array 179 | */ 180 | private function resolveArguments(array $arguments) 181 | { 182 | return array_map( 183 | function ($argument) { 184 | if (!is_string($argument)) { 185 | return $argument; 186 | } 187 | 188 | if ($argument === Configurator::container()) { 189 | return $this->container; 190 | } 191 | 192 | if (isset($this->container[$argument])) { 193 | return $this->container[$argument]; 194 | } 195 | 196 | return $argument; 197 | }, 198 | $arguments 199 | ); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/ServiceConfig.php: -------------------------------------------------------------------------------- 1 | $serviceConfig) { 31 | $this->config[] = new ServiceDefinition($key, $serviceConfig, $singletonDefault); 32 | } 33 | } 34 | 35 | /** 36 | * @return array 37 | */ 38 | public function getKeys() 39 | { 40 | return array_map( 41 | function (ServiceDefinition $definition) { 42 | return $definition->getName(); 43 | }, 44 | $this->config 45 | ); 46 | } 47 | 48 | public function getIterator() 49 | { 50 | return new ArrayIterator($this->config); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/ServiceDefinition.php: -------------------------------------------------------------------------------- 1 | name = $name; 63 | $this->class = $this->className($name, $config); 64 | $this->isSingleton = isset($config['singleton']) ? $config['singleton'] : $singletonDefault; 65 | $this->isFactory = isset($config['factory']); 66 | $this->isAlias = isset($config['service']); 67 | $this->arguments = isset($config['arguments']) ? $config['arguments'] : []; 68 | $this->methods = isset($config['methods']) ? $config['methods'] : []; 69 | } 70 | 71 | /** 72 | * @return string 73 | */ 74 | public function getName() 75 | { 76 | return $this->name; 77 | } 78 | 79 | /** 80 | * @return string 81 | */ 82 | public function getClass() 83 | { 84 | return $this->class; 85 | } 86 | 87 | /** 88 | * @return bool 89 | */ 90 | public function isSingleton() 91 | { 92 | return $this->isSingleton; 93 | } 94 | 95 | /** 96 | * @return bool 97 | */ 98 | public function isFactory() 99 | { 100 | return $this->isFactory; 101 | } 102 | 103 | /** 104 | * @return bool 105 | */ 106 | public function isAlias() 107 | { 108 | return $this->isAlias; 109 | } 110 | 111 | /** 112 | * @return array 113 | */ 114 | public function getArguments() 115 | { 116 | return $this->arguments; 117 | } 118 | 119 | /** 120 | * @return array 121 | */ 122 | public function getMethods() 123 | { 124 | return $this->methods; 125 | } 126 | 127 | /** 128 | * @param string $name 129 | * @param array $config 130 | * 131 | * @throws InvalidConfigException 132 | * 133 | * @return string 134 | */ 135 | private function className($name, array $config) 136 | { 137 | if (isset($config['class']) && isset($config['factory'])) { 138 | throw InvalidConfigException::fromNameWhenClassAndFactorySpecified($name); 139 | } 140 | 141 | if (isset($config['class']) && isset($config['service'])) { 142 | throw InvalidConfigException::fromNameWhenClassAndServiceSpecified($name); 143 | } 144 | 145 | if (isset($config['factory']) && isset($config['service'])) { 146 | throw InvalidConfigException::fromNameWhenFactoryAndServiceSpecified($name); 147 | } 148 | 149 | if (isset($config['service'])) { 150 | return $config['service']; 151 | } 152 | 153 | if (isset($config['class'])) { 154 | return $config['class']; 155 | } 156 | 157 | if (isset($config['factory'])) { 158 | return $config['factory']; 159 | } 160 | 161 | return $name; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /tests/acceptance/AbstractContainerAdapterTest.php: -------------------------------------------------------------------------------- 1 | 'example-value']; 25 | 26 | $this->createJSONConfigFile('config.json', $config); 27 | 28 | Configurator::apply() 29 | ->configFromFile($this->getTestPath('config.json')) 30 | ->to($this->container); 31 | 32 | assertSame('example-value', $this->container->get('config.example-key')); 33 | } 34 | 35 | public function testItCanBeConfiguredFromFiles() 36 | { 37 | $config = ['example-key' => 'example-value']; 38 | 39 | $this->createJSONConfigFile('config.json', $config); 40 | 41 | Configurator::apply() 42 | ->configFromFiles($this->getTestPath('*')) 43 | ->to($this->container); 44 | 45 | assertSame('example-value', $this->container->get('config.example-key')); 46 | } 47 | 48 | public function testItAddToConfigUsingFiles() 49 | { 50 | $config = ['keyB' => 'valueB']; 51 | 52 | $this->createJSONConfigFile('config.json', $config); 53 | 54 | Configurator::apply() 55 | ->configFromArray(['keyA' => 'valueA', 'keyB' => 'valueX']) 56 | ->configFromFiles($this->getTestPath('*')) 57 | ->to($this->container); 58 | 59 | assertSame('valueA', $this->container->get('config.keyA')); 60 | assertSame('valueB', $this->container->get('config.keyB')); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/acceptance/LeagueContainerAdapterTest.php: -------------------------------------------------------------------------------- 1 | container = new Container(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/acceptance/PimpleContainerAdapterTest.php: -------------------------------------------------------------------------------- 1 | container = new PimpleContainerWrapper(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/acceptance/PimpleContainerWrapper.php: -------------------------------------------------------------------------------- 1 | 'valueA']; 12 | 13 | Configurator::apply() 14 | ->configFromArray($config) 15 | ->to($this->container); 16 | 17 | assertEquals('valueA', $this->container->get('config.keyA')); 18 | } 19 | 20 | public function testItCascadeAddsConfigToTheContainer() 21 | { 22 | Configurator::apply() 23 | ->configFromArray(['keyA' => 'valueA', 'keyB' => 'valueX']) 24 | ->configFromArray(['keyB' => 'valueB']) 25 | ->to($this->container); 26 | 27 | assertEquals('valueA', $this->container->get('config.keyA')); 28 | } 29 | 30 | public function testItAddsGroupedConfigToTheContainer() 31 | { 32 | Configurator::apply() 33 | ->configFromArray(['group1' => ['keyA' => 'valueA']]) 34 | ->to($this->container); 35 | 36 | assertEquals(['keyA' => 'valueA'], $this->container->get('config.group1')); 37 | assertEquals('valueA', $this->container->get('config.group1.keyA')); 38 | } 39 | 40 | public function testItAddsConfigToTheContainerWithAnAlternativeSeparator() 41 | { 42 | Configurator::apply() 43 | ->configFromArray(['keyA' => 'valueA']) 44 | ->withSetting(Configurator::SETTING_SEPARATOR, '/') 45 | ->to($this->container); 46 | 47 | assertEquals('valueA', $this->container->get('config/keyA')); 48 | } 49 | 50 | public function testItAddsConfigToTheContainerWithAnAlternativePrefix() 51 | { 52 | Configurator::apply() 53 | ->configFromArray(['keyA' => 'valueA']) 54 | ->withSetting(Configurator::SETTING_PREFIX, 'settings') 55 | ->to($this->container); 56 | 57 | assertEquals('valueA', $this->container->get('settings.keyA')); 58 | } 59 | 60 | public function testItAddsConfigToTheContainerWithNoPrefix() 61 | { 62 | Configurator::apply() 63 | ->configFromArray(['keyA' => 'valueA']) 64 | ->withSetting(Configurator::SETTING_PREFIX, '') 65 | ->to($this->container); 66 | 67 | assertEquals('valueA', $this->container->get('keyA')); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/acceptance/SupportsInflectorConfig.php: -------------------------------------------------------------------------------- 1 | [ 16 | 'services' => [ 17 | 'example' => [ 18 | 'class' => ExampleClass::class, 19 | ], 20 | ], 21 | 'inflectors' => [ 22 | ExampleInterface::class => [ 23 | 'setValue' => ['test_value'], 24 | ], 25 | ], 26 | ], 27 | ]; 28 | 29 | Configurator::apply() 30 | ->configFromArray($config) 31 | ->to($this->container); 32 | 33 | assertEquals( 34 | 'test_value', 35 | $this->container->get('example')->getValue() 36 | ); 37 | } 38 | 39 | public function testItSetsUpAnInflectorForServiceFactory() 40 | { 41 | $config = [ 42 | 'class_name' => ExampleClass::class, 43 | 'di' => [ 44 | 'services' => [ 45 | 'example' => [ 46 | 'factory' => ExampleFactory::class, 47 | 'arguments' => [ 48 | 'config.class_name', 49 | ], 50 | ], 51 | ], 52 | 'inflectors' => [ 53 | ExampleInterface::class => [ 54 | 'setValue' => ['test_value'], 55 | ], 56 | ], 57 | ], 58 | ]; 59 | 60 | Configurator::apply() 61 | ->configFromArray($config) 62 | ->to($this->container); 63 | 64 | assertEquals( 65 | 'test_value', 66 | $this->container->get('example')->getValue() 67 | ); 68 | } 69 | 70 | public function testItResolvesInflectorArguments() 71 | { 72 | $config = [ 73 | 'argument' => 'test_value', 74 | 'di' => [ 75 | 'services' => [ 76 | 'example' => [ 77 | 'class' => ExampleClass::class, 78 | ], 79 | ], 80 | 'inflectors' => [ 81 | ExampleInterface::class => [ 82 | 'setValue' => ['config.argument'], 83 | ], 84 | ], 85 | ], 86 | ]; 87 | 88 | Configurator::apply() 89 | ->configFromArray($config) 90 | ->to($this->container); 91 | 92 | assertEquals( 93 | 'test_value', 94 | $this->container->get('example')->getValue() 95 | ); 96 | } 97 | 98 | public function testItSetsUpAnInflectorUsingCustomInflectorsKey() 99 | { 100 | $config = [ 101 | 'di' => [ 102 | 'services' => [ 103 | 'example' => [ 104 | 'class' => ExampleClass::class, 105 | ], 106 | ], 107 | ], 108 | 'inflectors' => [ 109 | ExampleInterface::class => [ 110 | 'setValue' => ['test_value'], 111 | ], 112 | ], 113 | ]; 114 | 115 | Configurator::apply() 116 | ->configFromArray($config) 117 | ->withSetting(Configurator::SETTING_INFLECTORS_KEY, 'inflectors') 118 | ->to($this->container); 119 | 120 | assertEquals( 121 | 'test_value', 122 | $this->container->get('example')->getValue() 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /tests/acceptance/SupportsServiceConfig.php: -------------------------------------------------------------------------------- 1 | [ 16 | 'services' => [ 17 | 'example_class' => [ 18 | 'class' => ExampleClass::class, 19 | ], 20 | ], 21 | ], 22 | ]; 23 | 24 | Configurator::apply() 25 | ->configFromArray($config) 26 | ->to($this->container); 27 | 28 | assertInstanceOf(ExampleClass::class, $this->container->get('example_class')); 29 | } 30 | 31 | public function testItAddsServicesToTheContainerForADifferentConfigKey() 32 | { 33 | $config = [ 34 | 'di' => [ 35 | 'example_class' => [ 36 | 'class' => ExampleClass::class, 37 | ], 38 | ], 39 | ]; 40 | 41 | Configurator::apply() 42 | ->configFromArray($config) 43 | ->withSetting(Configurator::SETTING_SERVICES_KEY, 'di') 44 | ->to($this->container); 45 | 46 | assertInstanceOf(ExampleClass::class, $this->container->get('example_class')); 47 | } 48 | 49 | public function testItCreatesUniqueServiceInstancesByDefault() 50 | { 51 | $config = [ 52 | 'di' => [ 53 | 'services' => [ 54 | 'example_class' => [ 55 | 'class' => ExampleClass::class, 56 | 'singleton' => false, 57 | ], 58 | ], 59 | ], 60 | ]; 61 | 62 | Configurator::apply() 63 | ->configFromArray($config) 64 | ->to($this->container); 65 | 66 | $instance1 = $this->container->get('example_class'); 67 | $instance2 = $this->container->get('example_class'); 68 | 69 | assertNotSame($instance1, $instance2); 70 | } 71 | 72 | public function testItCanCreateSingletonServiceInstances() 73 | { 74 | $config = [ 75 | 'di' => [ 76 | 'services' => [ 77 | 'example_class' => [ 78 | 'class' => ExampleClass::class, 79 | 'singleton' => true, 80 | ], 81 | ], 82 | ], 83 | ]; 84 | 85 | Configurator::apply() 86 | ->configFromArray($config) 87 | ->to($this->container); 88 | 89 | $instance1 = $this->container->get('example_class'); 90 | $instance2 = $this->container->get('example_class'); 91 | 92 | assertSame($instance1, $instance2); 93 | } 94 | 95 | public function testItCanCreateSingletonServiceInstancesByDefault() 96 | { 97 | $config = [ 98 | 'di' => [ 99 | 'services' => [ 100 | 'example_class' => [ 101 | 'class' => ExampleClass::class, 102 | ], 103 | ], 104 | ], 105 | ]; 106 | 107 | Configurator::apply() 108 | ->configFromArray($config) 109 | ->withSetting(Configurator::SETTING_DEFAULT_SINGLETON_SERVICES, true) 110 | ->to($this->container); 111 | 112 | $instance1 = $this->container->get('example_class'); 113 | $instance2 = $this->container->get('example_class'); 114 | 115 | assertSame($instance1, $instance2); 116 | } 117 | 118 | public function testItCanCreateUniqueServiceInstancesWhenSingletonIsDefault() 119 | { 120 | $config = [ 121 | 'di' => [ 122 | 'services' => [ 123 | 'example_class' => [ 124 | 'class' => ExampleClass::class, 125 | 'singleton' => false, 126 | ], 127 | ], 128 | ], 129 | ]; 130 | 131 | Configurator::apply() 132 | ->configFromArray($config) 133 | ->withSetting(Configurator::SETTING_DEFAULT_SINGLETON_SERVICES, true) 134 | ->to($this->container); 135 | 136 | $instance1 = $this->container->get('example_class'); 137 | $instance2 = $this->container->get('example_class'); 138 | 139 | assertNotSame($instance1, $instance2); 140 | } 141 | 142 | public function testItAddsConstructorArguments() 143 | { 144 | $config = [ 145 | 'di' => [ 146 | 'services' => [ 147 | 'example_class' => [ 148 | 'class' => ExampleClassWithArgs::class, 149 | 'arguments' => [ 150 | 'arg1', 151 | 'arg2', 152 | ], 153 | ], 154 | ], 155 | ], 156 | ]; 157 | 158 | Configurator::apply() 159 | ->configFromArray($config) 160 | ->to($this->container); 161 | 162 | $instance = $this->container->get('example_class'); 163 | 164 | assertEquals(['arg1', 'arg2'], $instance->getConstructorArgs()); 165 | } 166 | 167 | public function testItResolvesConstructorArgumentsIfTheyAreServiceNames() 168 | { 169 | $config = [ 170 | 'arg1' => 'value1', 171 | 'arg2' => 'value2', 172 | 'di' => [ 173 | 'services' => [ 174 | 'example_class' => [ 175 | 'class' => ExampleClassWithArgs::class, 176 | 'arguments' => [ 177 | 'config.arg1', 178 | 'config.arg2', 179 | ], 180 | ], 181 | ], 182 | ], 183 | ]; 184 | 185 | Configurator::apply() 186 | ->configFromArray($config) 187 | ->to($this->container); 188 | 189 | $instance = $this->container->get('example_class'); 190 | 191 | assertEquals(['value1', 'value2'], $instance->getConstructorArgs()); 192 | } 193 | 194 | public function testItUsesTheStringIfConstructorArgumentsAreClassNames() 195 | { 196 | $config = [ 197 | 'di' => [ 198 | 'services' => [ 199 | 'example_class' => [ 200 | 'class' => ExampleClassWithArgs::class, 201 | 'arguments' => [ 202 | ExampleClass::class, 203 | 'arg2', 204 | ], 205 | ], 206 | ], 207 | ], 208 | ]; 209 | 210 | Configurator::apply() 211 | ->configFromArray($config) 212 | ->to($this->container); 213 | 214 | $instance = $this->container->get('example_class'); 215 | 216 | assertEquals([ExampleClass::class, 'arg2'], $instance->getConstructorArgs()); 217 | } 218 | 219 | public function testItUsesComplexConstructorArguments() 220 | { 221 | $config = [ 222 | 'di' => [ 223 | 'services' => [ 224 | 'example_class' => [ 225 | 'class' => ExampleClassWithArgs::class, 226 | 'arguments' => [ 227 | ['example_array'], 228 | new \stdClass(), 229 | ], 230 | ], 231 | ], 232 | ], 233 | ]; 234 | 235 | Configurator::apply() 236 | ->configFromArray($config) 237 | ->to($this->container); 238 | 239 | $instance = $this->container->get('example_class'); 240 | 241 | assertEquals([['example_array'], new \stdClass()], $instance->getConstructorArgs()); 242 | } 243 | 244 | public function testItCallsSetterMethods() 245 | { 246 | $config = [ 247 | 'di' => [ 248 | 'services' => [ 249 | 'example_class' => [ 250 | 'class' => ExampleClass::class, 251 | 'methods' => [ 252 | 'setValue' => ['the value'], 253 | ], 254 | ], 255 | ], 256 | ], 257 | ]; 258 | 259 | Configurator::apply() 260 | ->configFromArray($config) 261 | ->to($this->container); 262 | 263 | $instance = $this->container->get('example_class'); 264 | 265 | assertEquals('the value', $instance->getValue()); 266 | } 267 | 268 | public function testItResolvesSetterMethodArgumentsIfTheyAreServiceNames() 269 | { 270 | $config = [ 271 | 'arg' => 'value', 272 | 'di' => [ 273 | 'services' => [ 274 | 'example_class' => [ 275 | 'class' => ExampleClass::class, 276 | 'methods' => [ 277 | 'setValue' => ['config.arg'], 278 | ], 279 | ], 280 | ], 281 | ], 282 | ]; 283 | 284 | Configurator::apply() 285 | ->configFromArray($config) 286 | ->to($this->container); 287 | 288 | $instance = $this->container->get('example_class'); 289 | 290 | assertEquals('value', $instance->getValue()); 291 | } 292 | 293 | public function testItUsesTheStringIffSetterMethodArgumentsAreClassNames() 294 | { 295 | $config = [ 296 | 'di' => [ 297 | 'services' => [ 298 | 'example_class' => [ 299 | 'class' => ExampleClass::class, 300 | 'methods' => [ 301 | 'setValue' => [ExampleClass::class], 302 | ], 303 | ], 304 | ], 305 | ], 306 | ]; 307 | 308 | Configurator::apply() 309 | ->configFromArray($config) 310 | ->to($this->container); 311 | 312 | $instance = $this->container->get('example_class'); 313 | 314 | assertSame(ExampleClass::class, $instance->getValue()); 315 | } 316 | 317 | public function testIsCreatesAServiceThroughAFactoryClass() 318 | { 319 | $config = [ 320 | 'class_name' => ExampleClassWithArgs::class, 321 | 'di' => [ 322 | 'services' => [ 323 | 'example_service' => [ 324 | 'factory' => ExampleFactory::class, 325 | 'arguments' => [ 326 | 'config.class_name', 327 | 'example_argument', 328 | ], 329 | ], 330 | ], 331 | ], 332 | ]; 333 | 334 | Configurator::apply() 335 | ->configFromArray($config) 336 | ->to($this->container); 337 | 338 | $instance = $this->container->get('example_service'); 339 | 340 | assertInstanceOf(ExampleClassWithArgs::class, $instance); 341 | assertSame(['example_argument'], $instance->getConstructorArgs()); 342 | } 343 | 344 | public function testItCanCreateAServiceAlias() 345 | { 346 | $config = [ 347 | 'di' => [ 348 | 'services' => [ 349 | 'example_class' => [ 350 | 'class' => ExampleClass::class, 351 | 'singleton' => true, 352 | ], 353 | 'example_alias' => [ 354 | 'service' => 'example_class', 355 | ], 356 | ], 357 | ], 358 | ]; 359 | 360 | Configurator::apply() 361 | ->configFromArray($config) 362 | ->to($this->container); 363 | 364 | assertSame($this->container->get('example_class'), $this->container->get('example_alias')); 365 | } 366 | 367 | public function testItInjectsTheContainerAsAConstructorDependency() 368 | { 369 | $config = [ 370 | 'di' => [ 371 | 'services' => [ 372 | 'example_service' => [ 373 | 'class' => ExampleClassWithArgs::class, 374 | 'arguments' => [Configurator::container()], 375 | ], 376 | ], 377 | ], 378 | ]; 379 | 380 | Configurator::apply() 381 | ->configFromArray($config) 382 | ->to($this->container); 383 | 384 | $instance = $this->container->get('example_service'); 385 | 386 | assertSame([$this->container], $instance->getConstructorArgs()); 387 | } 388 | 389 | public function testItInjectsTheContainerAsAMethodDependency() 390 | { 391 | $config = [ 392 | 'di' => [ 393 | 'services' => [ 394 | 'example_service' => [ 395 | 'class' => ExampleClass::class, 396 | 'methods' => [ 397 | 'setValue' => [Configurator::container()], 398 | ], 399 | ], 400 | ], 401 | ], 402 | ]; 403 | 404 | Configurator::apply() 405 | ->configFromArray($config) 406 | ->to($this->container); 407 | 408 | $instance = $this->container->get('example_service'); 409 | 410 | assertSame($this->container, $instance->getValue()); 411 | } 412 | 413 | public function testItInjectsTheContainerAsFactoryDependency() 414 | { 415 | $config = [ 416 | 'class_name' => ExampleClassWithArgs::class, 417 | 'di' => [ 418 | 'services' => [ 419 | 'example_service' => [ 420 | 'factory' => ExampleFactory::class, 421 | 'arguments' => [ 422 | 'config.class_name', 423 | Configurator::container(), 424 | ], 425 | ], 426 | ], 427 | ], 428 | ]; 429 | 430 | Configurator::apply() 431 | ->configFromArray($config) 432 | ->to($this->container); 433 | 434 | $instance = $this->container->get('example_service'); 435 | 436 | assertSame([$this->container], $instance->getConstructorArgs()); 437 | } 438 | } 439 | -------------------------------------------------------------------------------- /tests/mocks/BootableServiceProvider.php: -------------------------------------------------------------------------------- 1 | value = $value; 12 | } 13 | 14 | public function getValue() 15 | { 16 | return $this->value; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/mocks/ExampleClassWithArgs.php: -------------------------------------------------------------------------------- 1 | constructorArgs = $constructorArgs; 12 | } 13 | 14 | public function getConstructorArgs() 15 | { 16 | return $this->constructorArgs; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/mocks/ExampleContainer.php: -------------------------------------------------------------------------------- 1 | container = $container; 34 | } 35 | 36 | public function getContainer() 37 | { 38 | return $this->container; 39 | } 40 | 41 | public function addApplicationConfig(ApplicationConfig $config, $prefix = 'config') 42 | { 43 | } 44 | 45 | public function addServiceConfig(ServiceConfig $config) 46 | { 47 | } 48 | 49 | public function addInflectorConfig(InflectorConfig $config) 50 | { 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/mocks/ExampleExtendedContainer.php: -------------------------------------------------------------------------------- 1 | deleteTestFiles(); 15 | } 16 | 17 | /** 18 | * @param string $name 19 | * 20 | * @return string 21 | */ 22 | protected function getTestPath($name) 23 | { 24 | $this->ensurePathExists(); 25 | 26 | return "{$this->configFilePath}/$name"; 27 | } 28 | 29 | /** 30 | * @param string $filename 31 | * @param array $config 32 | */ 33 | protected function createPHPConfigFile($filename, array $config) 34 | { 35 | $code = 'createTestFile($filename, $code); 38 | } 39 | 40 | /** 41 | * @param string $filename 42 | * @param array $config 43 | */ 44 | protected function createJSONConfigFile($filename, array $config) 45 | { 46 | $code = json_encode($config); 47 | 48 | $this->createTestFile($filename, $code); 49 | } 50 | 51 | /** 52 | * @param string $name 53 | * @param string $content 54 | */ 55 | protected function createTestFile($name, $content = 'test content') 56 | { 57 | $this->ensurePathExists(); 58 | 59 | file_put_contents("{$this->configFilePath}/$name", $content); 60 | } 61 | 62 | private function deleteTestFiles() 63 | { 64 | $this->ensurePathExists(); 65 | 66 | // Test for safety! 67 | if (strpos($this->configFilePath, __DIR__) !== 0) { 68 | throw new \Exception('DANGER!!! - Config file is not local to this project'); 69 | } 70 | 71 | $files = glob("{$this->configFilePath}/*"); 72 | 73 | foreach ($files as $file) { 74 | unlink($file); 75 | } 76 | } 77 | 78 | private function ensurePathExists() 79 | { 80 | $this->configFilePath = __DIR__ . '/../.test-config'; 81 | 82 | if (!file_exists($this->configFilePath)) { 83 | mkdir($this->configFilePath); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/unit/ApplicationConfigIteratorTest.php: -------------------------------------------------------------------------------- 1 | 'valueA', 14 | 'keyB' => 'valueB', 15 | ]); 16 | 17 | assertEquals( 18 | [ 19 | 'keyA' => 'valueA', 20 | 'keyB' => 'valueB', 21 | ], 22 | iterator_to_array($iterator) 23 | ); 24 | } 25 | 26 | public function testItIteratesRecursively() 27 | { 28 | $iterator = new ApplicationConfig([ 29 | 'group1' => [ 30 | 'keyA' => 'valueA', 31 | ], 32 | 'group2' => [ 33 | 'keyB' => 'valueB', 34 | ], 35 | ]); 36 | 37 | assertEquals( 38 | [ 39 | 'group1' => [ 40 | 'keyA' => 'valueA', 41 | ], 42 | 'group1.keyA' => 'valueA', 43 | 'group2' => [ 44 | 'keyB' => 'valueB', 45 | ], 46 | 'group2.keyB' => 'valueB', 47 | ], 48 | iterator_to_array($iterator) 49 | ); 50 | } 51 | 52 | public function testItGoesMultipleLevels() 53 | { 54 | $iterator = new ApplicationConfig([ 55 | 'group1' => [ 56 | 'keyA' => 'valueA', 57 | 'group2' => [ 58 | 'keyB' => 'valueB', 59 | ], 60 | ], 61 | ]); 62 | 63 | assertEquals( 64 | [ 65 | 'group1' => [ 66 | 'keyA' => 'valueA', 67 | 'group2' => [ 68 | 'keyB' => 'valueB', 69 | ], 70 | ], 71 | 'group1.keyA' => 'valueA', 72 | 'group1.group2' => [ 73 | 'keyB' => 'valueB', 74 | ], 75 | 'group1.group2.keyB' => 'valueB', 76 | ], 77 | iterator_to_array($iterator) 78 | ); 79 | } 80 | 81 | public function testItRewinds() 82 | { 83 | $iterator = new ApplicationConfig([ 84 | 'group1' => [ 85 | 'keyA' => 'valueA', 86 | 'keyB' => 'valueB', 87 | 'keyC' => 'valueC', 88 | ], 89 | ]); 90 | 91 | next($iterator); 92 | next($iterator); 93 | next($iterator); 94 | 95 | assertEquals( 96 | [ 97 | 'group1' => [ 98 | 'keyA' => 'valueA', 99 | 'keyB' => 'valueB', 100 | 'keyC' => 'valueC', 101 | ], 102 | 'group1.keyA' => 'valueA', 103 | 'group1.keyB' => 'valueB', 104 | 'group1.keyC' => 'valueC', 105 | ], 106 | iterator_to_array($iterator) 107 | ); 108 | } 109 | 110 | public function testItUsesADifferentSeparator() 111 | { 112 | $iterator = new ApplicationConfig([ 113 | 'group1' => [ 114 | 'keyA' => 'valueA', 115 | ], 116 | ], '->'); 117 | 118 | assertEquals( 119 | [ 120 | 'group1' => [ 121 | 'keyA' => 'valueA', 122 | ], 123 | 'group1->keyA' => 'valueA', 124 | ], 125 | iterator_to_array($iterator) 126 | ); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /tests/unit/ApplicationConfigTest.php: -------------------------------------------------------------------------------- 1 | config = new ApplicationConfig([ 23 | 'keyA' => 'valueA', 24 | 'group1' => [ 25 | 'keyB' => 'valueB', 26 | 'null' => null, 27 | ], 28 | ]); 29 | } 30 | 31 | public function testItProvidesAccessToSimpleScalarValues() 32 | { 33 | assertEquals('valueA', $this->config['keyA']); 34 | } 35 | 36 | public function testItProvidesAccessToArrayValues() 37 | { 38 | assertEquals(['keyB' => 'valueB', 'null' => null], $this->config['group1']); 39 | } 40 | 41 | public function testItProvidesToSubValuesUsingDotNotation() 42 | { 43 | assertEquals('valueB', $this->config['group1.keyB']); 44 | } 45 | 46 | public function testItSaysIfAnEntryIsSet() 47 | { 48 | assertTrue(isset($this->config['group1.keyB'])); 49 | } 50 | 51 | public function testItSaysIfAnEntryIsNotSet() 52 | { 53 | assertFalse(isset($this->config['bad.entry'])); 54 | } 55 | 56 | public function testItSaysIfAnEntryIsSetIfItIsFalsey() 57 | { 58 | assertTrue(isset($this->config['group1.null'])); 59 | } 60 | 61 | public function testItReturnsAllItsKeys() 62 | { 63 | assertEquals( 64 | [ 65 | 'keyA', 66 | 'group1', 67 | 'group1.keyB', 68 | 'group1.null', 69 | ], 70 | $this->config->getKeys() 71 | ); 72 | } 73 | 74 | public function testItCanBeConvertedToAnArray() 75 | { 76 | assertEquals( 77 | [ 78 | 'keyA' => 'valueA', 79 | 'group1' => [ 80 | 'keyB' => 'valueB', 81 | 'null' => null, 82 | ], 83 | ], 84 | $this->config->asArray() 85 | ); 86 | } 87 | 88 | public function testItWorksWithADifferentSeperator() 89 | { 90 | $this->config = new ApplicationConfig([ 91 | 'group1' => [ 92 | 'keyA' => 'valueA', 93 | ], 94 | ], '->'); 95 | assertEquals('valueA', $this->config['group1->keyA']); 96 | } 97 | 98 | public function testItThrowsForAnEmptySeparatorOnConstruction() 99 | { 100 | $this->expectException(InvalidArgumentException::class); 101 | 102 | $this->config = new ApplicationConfig([], ''); 103 | } 104 | 105 | public function testItCannotHaveAValueSet() 106 | { 107 | $this->expectException(ReadOnlyException::class); 108 | 109 | $this->config['key'] = 'value'; 110 | } 111 | 112 | public function testItCannotHaveAValueRemoved() 113 | { 114 | $this->expectException(ReadOnlyException::class); 115 | 116 | unset($this->config['keyA']); 117 | } 118 | 119 | public function testItMergesInNewConfig() 120 | { 121 | $config = new ApplicationConfig([ 122 | 'group' => [ 123 | 'keyA' => 'valueA', 124 | 'keyB' => 'valueX', 125 | ], 126 | ]); 127 | 128 | $config->merge(['group' => ['keyB' => 'valueB']]); 129 | 130 | assertSame('valueA', $config['group.keyA']); 131 | assertSame('valueB', $config['group.keyB']); 132 | } 133 | 134 | public function testItUpdatesTheSeparator() 135 | { 136 | $config = new ApplicationConfig([ 137 | 'group' => [ 138 | 'keyA' => 'valueA', 139 | ], 140 | ]); 141 | 142 | $config->setSeparator('/'); 143 | 144 | assertSame('valueA', $config['group/keyA']); 145 | } 146 | 147 | public function testItThrowsForAnEmptySeparatorWhenSettingSeparator() 148 | { 149 | $this->expectException(InvalidArgumentException::class); 150 | 151 | $this->config = new ApplicationConfig([]); 152 | $this->config->setSeparator(''); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /tests/unit/ConfiguratorTest.php: -------------------------------------------------------------------------------- 1 | expectException(InvalidArgumentException::class); 23 | 24 | Configurator::apply()->configFromFile($this->getTestPath('config.php')); 25 | } 26 | 27 | public function testItThrowsAnExceptionWhenNoFilesAreNotFound() 28 | { 29 | $this->expectException(NoMatchingFilesException::class); 30 | 31 | Configurator::apply()->configFromFiles($this->getTestPath('config.php')); 32 | } 33 | 34 | public function testItThrowsWhenAnUnknownSettingIsSet() 35 | { 36 | $this->expectException(UnknownSettingException::class); 37 | 38 | Configurator::apply()->withSetting('unknown_setting', 'value'); 39 | } 40 | 41 | public function testTheContainerIdentifierStringIsAlwaysTheSame() 42 | { 43 | assertSame(Configurator::container(), Configurator::container()); 44 | } 45 | 46 | public function testItCanAcceptADifferentFileReader() 47 | { 48 | $container = new Container(); 49 | $this->createTestFile('custom.xxx'); 50 | CustomFileReader::reset(); 51 | 52 | $configFile = $this->getTestPath('custom.xxx'); 53 | Configurator::apply() 54 | ->withFileReader('.xxx', CustomFileReader::class) 55 | ->configFromFile($configFile) 56 | ->to($container); 57 | 58 | assertSame([$configFile], CustomFileReader::getReads()); 59 | } 60 | 61 | public function testItCanUseDifferentContainerAdapters() 62 | { 63 | $container = new ExampleContainer(); 64 | ExampleContainerAdapter::reset(); 65 | 66 | Configurator::apply() 67 | ->withContainerAdapter(ExampleContainer::class, ExampleContainerAdapter::class) 68 | ->configFromArray([]) 69 | ->to($container); 70 | 71 | assertSame(1, ExampleContainerAdapter::getNumberOfInstances()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/unit/ContainerAdapterFactoryTest.php: -------------------------------------------------------------------------------- 1 | subject = new ContainerAdapterFactory([ 24 | ExampleContainer::class => ExampleContainerAdapter::class, 25 | ]); 26 | } 27 | 28 | public function testItCreatesAnInstanceOfTheContainerAdapter() 29 | { 30 | assertInstanceOf( 31 | ExampleContainerAdapter::class, 32 | $this->subject->create(new ExampleContainer()) 33 | ); 34 | } 35 | 36 | public function testItCreatesAnInstanceOfTheConfiguratorForSubclassedContainer() 37 | { 38 | assertInstanceOf( 39 | ExampleContainerAdapter::class, 40 | $this->subject->create(new ExampleExtendedContainer()) 41 | ); 42 | } 43 | 44 | public function testItThrowsIfContainerIsNotKnown() 45 | { 46 | $this->expectException(UnknownContainerException::class); 47 | 48 | $this->subject->create(new \stdClass()); 49 | } 50 | 51 | public function testItThrowsIfNotAContainerAdapter() 52 | { 53 | $this->subject = new ContainerAdapterFactory([ 54 | ExampleContainer::class => NotContainerAdapter::class, 55 | ]); 56 | 57 | $this->expectException(NotContainerAdapterException::class); 58 | 59 | $this->subject->create(new ExampleContainer()); 60 | } 61 | 62 | public function testItSetsTheContainerOnTheConfigurator() 63 | { 64 | $container = new ExampleContainer(); 65 | $configurator = $this->subject->create($container); 66 | 67 | assertSame($container, $configurator->getContainer()); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/unit/Exception/EntryDoesNotExistExceptionTest.php: -------------------------------------------------------------------------------- 1 | getMessage() 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/unit/Exception/InvalidConfigExceptionTest.php: -------------------------------------------------------------------------------- 1 | getMessage() 27 | ); 28 | } 29 | 30 | public function testItCanBeCreatedWithAJSONFileError() 31 | { 32 | assertSame( 33 | 'Invalid JSON in "example.json": JSON Error Message', 34 | InvalidConfigException::fromJSONFileError('example.json', 'JSON Error Message')->getMessage() 35 | ); 36 | } 37 | 38 | public function testItCanBeCreatedFromYAMLFileError() 39 | { 40 | assertSame( 41 | 'Invalid YAML in "example.yml": YAML Error Message', 42 | InvalidConfigException::fromYAMLFileError('example.yml', 'YAML Error Message')->getMessage() 43 | ); 44 | } 45 | 46 | public function testItCanBeCreatedFromNameWhenClassAndFactoryAreSpecified() 47 | { 48 | assertSame( 49 | 'Both "class" and "factory" are specified for service "example"; these cannot be used together.', 50 | InvalidConfigException::fromNameWhenClassAndFactorySpecified('example')->getMessage() 51 | ); 52 | } 53 | 54 | public function testItCanBeCreatedFromNameWhenClassAndServiceAreSpecified() 55 | { 56 | assertSame( 57 | 'Both "class" and "service" are specified for service "example"; these cannot be used together.', 58 | InvalidConfigException::fromNameWhenClassAndServiceSpecified('example')->getMessage() 59 | ); 60 | } 61 | 62 | public function testItCanBeCreatedFromNameWhenFactoryAndServiceAreSpecified() 63 | { 64 | assertSame( 65 | 'Both "factory" and "service" are specified for service "example"; these cannot be used together.', 66 | InvalidConfigException::fromNameWhenFactoryAndServiceSpecified('example')->getMessage() 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/unit/Exception/MissingDependencyExceptionTest.php: -------------------------------------------------------------------------------- 1 | getMessage() 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/unit/Exception/NoMatchingFilesExceptionTest.php: -------------------------------------------------------------------------------- 1 | getMessage() 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/unit/Exception/NotClassDefinitionExceptionTest.php: -------------------------------------------------------------------------------- 1 | getMessage() 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/unit/Exception/NotContainerAdapterExceptionTest.php: -------------------------------------------------------------------------------- 1 | getMessage() 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/unit/Exception/ReadOnlyExceptionTest.php: -------------------------------------------------------------------------------- 1 | getMessage() 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/unit/Exception/UnknownContainerExceptionTest.php: -------------------------------------------------------------------------------- 1 | getMessage() 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/unit/Exception/UnknownFileTypeExceptionTest.php: -------------------------------------------------------------------------------- 1 | getMessage() 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/unit/Exception/UnknownSettingExceptionTest.php: -------------------------------------------------------------------------------- 1 | getMessage() 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/unit/FileReader/FileLocatorTest.php: -------------------------------------------------------------------------------- 1 | locator = new FileLocator(); 21 | } 22 | 23 | public function testItFindsFilesByGlobbing() 24 | { 25 | $this->createTestFile('config1.php'); 26 | $this->createTestFile('config2.php'); 27 | $this->createTestFile('config.json'); 28 | 29 | $files = $this->locator->locate($this->getTestPath('*.php')); 30 | 31 | assertEquals([ 32 | $this->getTestPath('config1.php'), 33 | $this->getTestPath('config2.php'), 34 | ], $files); 35 | } 36 | 37 | public function testItFindsFindsFilesByGlobbingWithBraces() 38 | { 39 | $this->createTestFile('global.php'); 40 | $this->createTestFile('database.local.php'); 41 | $this->createTestFile('nothing.php'); 42 | $this->createTestFile('nothing.php.dist'); 43 | 44 | $files = $this->locator->locate($this->getTestPath('{,*.}{global,local}.php')); 45 | 46 | assertEquals([ 47 | $this->getTestPath('global.php'), 48 | $this->getTestPath('database.local.php'), 49 | ], $files); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/unit/FileReader/JSONFileReaderTest.php: -------------------------------------------------------------------------------- 1 | reader = new JSONFileReader(); 24 | } 25 | 26 | public function testItIsAFileReader() 27 | { 28 | assertInstanceOf(FileReader::class, $this->reader); 29 | } 30 | 31 | public function testItThrowsIfFileDoesNotExist() 32 | { 33 | $this->expectException(InvalidArgumentException::class); 34 | 35 | $this->reader->read('file-which-does-not-exist'); 36 | } 37 | 38 | public function testReadsAPHPConfigFile() 39 | { 40 | $config = ['key' => 'value', 'sub' => ['key' => 'value']]; 41 | 42 | $this->createTestFile('config.json', json_encode($config)); 43 | 44 | assertEquals($config, $this->reader->read($this->getTestPath('config.json'))); 45 | } 46 | 47 | public function testItThrowsIfTheConfigIsInvalid() 48 | { 49 | $this->expectException(InvalidConfigException::class); 50 | 51 | $this->createTestFile('config.json', 'not json'); 52 | 53 | $this->reader->read($this->getTestPath('config.json')); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/unit/FileReader/PHPFileReaderTest.php: -------------------------------------------------------------------------------- 1 | reader = new PHPFileReader(); 24 | } 25 | 26 | public function testItIsAFileReader() 27 | { 28 | assertInstanceOf(FileReader::class, $this->reader); 29 | } 30 | 31 | public function testItThrowsIfFileDoesNotExist() 32 | { 33 | $this->expectException(InvalidArgumentException::class); 34 | 35 | $this->reader->read('file-which-does-not-exist'); 36 | } 37 | 38 | public function testReadsAPHPConfigFile() 39 | { 40 | $config = ['key' => 'value']; 41 | $code = 'createTestFile('config.php', $code); 44 | 45 | assertEquals($config, $this->reader->read($this->getTestPath('config.php'))); 46 | } 47 | 48 | public function testItThrowsIfTheConfigIsInvalid() 49 | { 50 | $this->expectException(InvalidConfigException::class); 51 | 52 | $code = 'createTestFile('config.php', $code); 54 | 55 | $this->reader->read($this->getTestPath('config.php')); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/unit/FileReader/ReaderFactoryTest.php: -------------------------------------------------------------------------------- 1 | factory = new ReaderFactory([ 26 | '.php' => PHPFileReader::class, 27 | '.json' => JSONFileReader::class, 28 | '.yaml' => YAMLFileReader::class, 29 | '.yml' => YAMLFileReader::class, 30 | ]); 31 | } 32 | 33 | /** 34 | * @dataProvider providerCreatesAppropriateFileReader 35 | * 36 | * @param string $extension 37 | * @param string $fileReaderClass 38 | */ 39 | public function testCreatesAppropriateFileReader($extension, $fileReaderClass) 40 | { 41 | $filename = 'test' . $extension; 42 | 43 | $this->createTestFile($filename); 44 | 45 | $reader = $this->factory->create($this->getTestPath($filename)); 46 | 47 | assertInstanceOf($fileReaderClass, $reader); 48 | } 49 | 50 | /** 51 | * @return \Generator 52 | */ 53 | public function providerCreatesAppropriateFileReader() 54 | { 55 | $extensions = [ 56 | '.json' => JSONFileReader::class, 57 | '.php' => PHPFileReader::class, 58 | '.yaml' => YAMLFileReader::class, 59 | '.yml' => YAMLFileReader::class, 60 | ]; 61 | 62 | foreach ($extensions as $extension => $fileReaderClass) { 63 | yield [ 64 | $extension, 65 | $fileReaderClass, 66 | ]; 67 | } 68 | } 69 | 70 | public function testReturnsTheSameReaderForTheSameFileType() 71 | { 72 | $this->createTestFile('test1.php'); 73 | $this->createTestFile('test2.php'); 74 | 75 | $reader1 = $this->factory->create($this->getTestPath('test1.php')); 76 | $reader2 = $this->factory->create($this->getTestPath('test2.php')); 77 | 78 | assertSame($reader1, $reader2); 79 | } 80 | 81 | public function testItThrowsIfTheArgumentIsNotAFileName() 82 | { 83 | $this->expectException(InvalidArgumentException::class); 84 | 85 | $this->factory->create('missing-file.xxx'); 86 | } 87 | 88 | public function testItThrowsIfThereIsNoRegisteredReaderForGivenFileType() 89 | { 90 | $this->createTestFile('test.unknown'); 91 | 92 | $this->expectException(UnknownFileTypeException::class); 93 | 94 | $this->factory->create($this->getTestPath('test.unknown')); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tests/unit/FileReader/YAMLFileReaderTest.php: -------------------------------------------------------------------------------- 1 | reader = new YAMLFileReader(); 25 | } 26 | 27 | public function testItIsAFileReader() 28 | { 29 | assertInstanceOf(FileReader::class, $this->reader); 30 | } 31 | 32 | public function testItThrowsIfFileDoesNotExist() 33 | { 34 | $this->expectException(InvalidArgumentException::class); 35 | 36 | $this->reader->read('file-which-does-not-exist'); 37 | } 38 | 39 | public function testReadsAYAMLConfigFile() 40 | { 41 | $config = ['key' => 'value', 'sub' => ['key' => 'value']]; 42 | 43 | $this->createTestFile('config.yml', Yaml\Yaml::dump($config)); 44 | 45 | assertEquals($config, $this->reader->read($this->getTestPath('config.yml'))); 46 | } 47 | 48 | public function testItThrowsIfTheConfigIsInvalid() 49 | { 50 | $this->expectException(InvalidConfigException::class); 51 | 52 | $this->createTestFile('config.yml', '[not yaml;'); 53 | 54 | $this->reader->read($this->getTestPath('config.yml')); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/unit/InflectorConfigTest.php: -------------------------------------------------------------------------------- 1 | ['arg1', 'arg2']]; 15 | 16 | $subject = new InflectorConfig([$interface => $methods]); 17 | 18 | assertEquals( 19 | [new InflectorDefinition($interface, $methods)], 20 | iterator_to_array($subject) 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/unit/InflectorDefinitionTest.php: -------------------------------------------------------------------------------- 1 | subject = new InflectorDefinition( 18 | 'interface_name', 19 | ['method1' => ['arg1', 'arg2']] 20 | ); 21 | } 22 | 23 | public function testGetInterfaceReturnsTheInterfaceName() 24 | { 25 | assertEquals('interface_name', $this->subject->getInterface()); 26 | } 27 | 28 | public function testGetMethodsReturnsTheMethods() 29 | { 30 | assertEquals( 31 | ['method1' => ['arg1', 'arg2']], 32 | $this->subject->getMethods() 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/unit/ServiceConfigTest.php: -------------------------------------------------------------------------------- 1 | __CLASS__, 15 | 'singleton' => false, 16 | 'arguments' => ['argument1', 'argument2'], 17 | 'method' => ['setSomething' => ['value']], 18 | ]; 19 | 20 | $config = new ServiceConfig(['service_name' => $serviceConfig]); 21 | 22 | assertEquals( 23 | [new ServiceDefinition('service_name', $serviceConfig)], 24 | iterator_to_array($config) 25 | ); 26 | } 27 | 28 | public function testItProvidesAListOfKeys() 29 | { 30 | $serviceConfig = [ 31 | 'class' => __CLASS__, 32 | 'singleton' => false, 33 | 'arguments' => ['argument1', 'argument2'], 34 | 'method' => ['setSomething' => ['value']], 35 | ]; 36 | 37 | $config = new ServiceConfig([ 38 | 'service1' => $serviceConfig, 39 | 'service2' => $serviceConfig, 40 | ]); 41 | 42 | assertEquals(['service1', 'service2'], $config->getKeys()); 43 | } 44 | 45 | public function testDefaultValueForSingletonCanBeSetToTrue() 46 | { 47 | $serviceConfig = ['class' => __CLASS__]; 48 | 49 | $config = new ServiceConfig(['service_name' => $serviceConfig], true); 50 | 51 | assertTrue(iterator_to_array($config)[0]->isSingleton()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/unit/ServiceDefinitionTest.php: -------------------------------------------------------------------------------- 1 | __CLASS__, 15 | 'singleton' => false, 16 | 'arguments' => ['argument1', 'argument2'], 17 | 'methods' => ['setSomething' => ['value']], 18 | ]; 19 | 20 | $definition = new ServiceDefinition('service_name', $config); 21 | 22 | assertEquals('service_name', $definition->getName()); 23 | assertEquals(__CLASS__, $definition->getClass()); 24 | assertFalse($definition->isFactory()); 25 | assertFalse($definition->isSingleton()); 26 | assertEquals(['argument1', 'argument2'], $definition->getArguments()); 27 | assertEquals(['setSomething' => ['value']], $definition->getMethods()); 28 | } 29 | 30 | public function testClassDefaultsToKey() 31 | { 32 | $definition = new ServiceDefinition('service_name', []); 33 | 34 | assertEquals('service_name', $definition->getClass()); 35 | } 36 | 37 | public function testSingletonDefaultsToFalse() 38 | { 39 | $definition = new ServiceDefinition('service_name', []); 40 | 41 | assertFalse($definition->isSingleton()); 42 | } 43 | 44 | public function testSingletonDefaultCanBeSetToToTrue() 45 | { 46 | $definition = new ServiceDefinition('service_name', [], true); 47 | 48 | assertTrue($definition->isSingleton()); 49 | } 50 | 51 | public function testArgumentsDefaultToAnEmptyList() 52 | { 53 | $definition = new ServiceDefinition('service_name', []); 54 | 55 | assertEquals([], $definition->getArguments()); 56 | } 57 | 58 | public function testMethodsDefaultToAnEmptyList() 59 | { 60 | $definition = new ServiceDefinition('service_name', []); 61 | 62 | assertEquals([], $definition->getMethods()); 63 | } 64 | 65 | public function testServiceFactoryDefinition() 66 | { 67 | $definition = new ServiceDefinition('service_name', ['factory' => __CLASS__]); 68 | 69 | assertTrue($definition->isFactory()); 70 | assertFalse($definition->isAlias()); 71 | assertSame(__CLASS__, $definition->getClass()); 72 | } 73 | 74 | public function testServiceAliasDefinition() 75 | { 76 | $definition = new ServiceDefinition('service_name', ['service' => __CLASS__]); 77 | 78 | assertTrue($definition->isAlias()); 79 | assertFalse($definition->isFactory()); 80 | assertSame(__CLASS__, $definition->getClass()); 81 | } 82 | 83 | public function testItThrowIfClassAndFactoryAreDefined() 84 | { 85 | $this->expectException(InvalidConfigException::class); 86 | 87 | new ServiceDefinition('service_name', ['class' => __CLASS__, 'factory' => __CLASS__]); 88 | } 89 | 90 | public function testItThrowIfClassAndServiceAreDefined() 91 | { 92 | $this->expectException(InvalidConfigException::class); 93 | 94 | new ServiceDefinition('service_name', ['class' => __CLASS__, 'service' => __CLASS__]); 95 | } 96 | 97 | public function testItThrowIfFactoryAndServiceAreDefined() 98 | { 99 | $this->expectException(InvalidConfigException::class); 100 | 101 | new ServiceDefinition('service_name', ['factory' => __CLASS__, 'service' => __CLASS__]); 102 | } 103 | } 104 | --------------------------------------------------------------------------------