├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json └── src ├── Container.php ├── ContainerInterface.php ├── Exception ├── ContainerException.php ├── ParameterNotFoundException.php └── ServiceNotFoundException.php └── Reference ├── AbstractReference.php ├── ParameterReference.php └── ServiceReference.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 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 | ## master branch 6 | 7 | ### Added 8 | - The 'array' typehint to parameters on the `resolveArguments` and `initializeService` methods 9 | - ContainerInterface 10 | - `hasParameter` method on Container 11 | - Code climate badge 12 | - Scrutinizer configuration 13 | - SitePoint logo to README 14 | - Authors section to README 15 | 16 | ### Changed 17 | - README example (cleanup) 18 | - Spelling in container PHPDoc 19 | - Switched from coveralls to scrutinizer 20 | - Mistake in .gitattributes file 21 | - Fixed detection of circular references 22 | 23 | ### Removed 24 | - Unnecessary use declaration in container test 25 | - Unnecessary parameter 26 | 27 | ## 0.0.1 - 2015-10-24 28 | 29 | ### Added 30 | - Initial project classes (Container, References, Exceptions) 31 | - Test suite 32 | - Composer package file 33 | - Support for container-interop 34 | - README documentation 35 | - CHANGELOG 36 | - Git files (ignore, attributes) 37 | - Travis configuration file 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 php-diy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![SitePoint](https://avatars2.githubusercontent.com/u/15340853?v=3&s=40) Container 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/sitepoint/container/v/stable)](https://packagist.org/packages/sitepoint/container) 4 | [![Build Status](https://travis-ci.org/sitepoint/Container.svg?branch=master)](https://travis-ci.org/sitepoint/Container) 5 | [![Code Coverage](https://scrutinizer-ci.com/g/sitepoint/Container/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/sitepoint/Container/?branch=master) 6 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/sitepoint/Container/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/sitepoint/Container/?branch=master) 7 | [![Code Climate](https://codeclimate.com/github/sitepoint/Container/badges/gpa.svg)](https://codeclimate.com/github/sitepoint/Container) 8 | [![Total Downloads](https://poser.pugx.org/sitepoint/container/downloads)](https://packagist.org/packages/sitepoint/container) 9 | [![License](https://poser.pugx.org/sitepoint/container/license)](https://packagist.org/packages/sitepoint/container) 10 | 11 | A simple, easy to follow PHP dependency injection container. Designed to be forked, modified, extended and hacked. 12 | 13 | ## How to Use 14 | 15 | Although it isn't required to do so, a good practice is to split up the configuration for our container. In this example we'll use three files to create our container for the Monolog component. 16 | 17 | Another good practice is to use class and interface paths as service names. This provides a stricter naming convention that gives us more information about the services. 18 | 19 | In the service definitions file, we define three services. All of the services require constructor injection arguments. Some of these arguments are imported from the container parameters and some are defined directly. The logger service also requires two calls to the `pushHandler` method, each with a different handler service imported. 20 | 21 | ```PHP 22 | [ 35 | 'class' => StreamHandler::class, 36 | 'arguments' => [ 37 | new PR('logger.file'), 38 | Logger::DEBUG, 39 | ], 40 | ], 41 | NativeMailHandler::class => [ 42 | 'class' => NativeMailerHandler::class, 43 | 'arguments' => [ 44 | new PR('logger.mail.to_address'), 45 | new PR('logger.mail.subject'), 46 | new PR('logger.mail.from_address'), 47 | Logger::ERROR, 48 | ], 49 | ], 50 | LoggerInterface::class => [ 51 | 'class' => Logger::class, 52 | 'arguments' => [ 'channel-name' ], 53 | 'calls' => [ 54 | [ 55 | 'method' => 'pushHandler', 56 | 'arguments' => [ 57 | new SR(StreamHandler::class), 58 | ] 59 | ], 60 | [ 61 | 'method' => 'pushHandler', 62 | 'arguments' => [ 63 | new SR(NativeMailHandler::class), 64 | ] 65 | ] 66 | ] 67 | ] 68 | ]; 69 | ``` 70 | 71 | The parameters definitions file just returns an array of values. These are defined as an N-dimensional array, but they are accessed through references using the notation: `'logger.file'` or `'logger.mail.to_address'`. 72 | 73 | ```PHP 74 | [ 78 | 'file' => __DIR__.'/../app.log', 79 | 'mail' => [ 80 | 'to_address' => 'webmaster@domain.com', 81 | 'from_address' => 'alerts@domain.com', 82 | 'subject' => 'App Logs', 83 | ], 84 | ], 85 | ]; 86 | ``` 87 | 88 | The container file just extracts the service and parameter definitions and passes them to the `Container` class constructor. 89 | 90 | 91 | ```PHP 92 | get(LoggerInterface::class); 114 | $logger->debug('This will be logged to the file'); 115 | $logger->error('This will be logged to the file and the email'); 116 | ``` 117 | 118 | ## Authors 119 | 120 | - [Andrew Carter](https://twitter.com/AndrewCarterUK) 121 | 122 | ## Change Log 123 | 124 | This project maintains a [change log file](CHANGELOG.md) 125 | 126 | ## License 127 | 128 | The MIT License (MIT). Please see [LICENSE](LICENSE) for more information. 129 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sitepoint/container", 3 | "description": "A simple, easy to follow PHP dependency injection container", 4 | "type": "library", 5 | "keywords": ["container", "dependency", "injection"], 6 | "homepage": "https://github.com/sitepoint/Container", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Andrew Carter", 11 | "homepage": "http://andrewcarteruk.github.io/", 12 | "role": "Developer" 13 | } 14 | ], 15 | "require": { 16 | "container-interop/container-interop": "^1.1" 17 | }, 18 | "require-dev": { 19 | "satooshi/php-coveralls": "dev-master" 20 | }, 21 | "autoload": { 22 | "psr-4": { 23 | "SitePoint\\Container\\": "src/" 24 | } 25 | }, 26 | "autoload-dev": { 27 | "psr-4": { 28 | "SitePoint\\Container\\Test\\": "test/" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Container.php: -------------------------------------------------------------------------------- 1 | services = $services; 49 | $this->parameters = $parameters; 50 | $this->serviceStore = []; 51 | } 52 | 53 | /** 54 | * {@inheritDoc} 55 | */ 56 | public function get($name) 57 | { 58 | if (!$this->has($name)) { 59 | throw new ServiceNotFoundException('Service not found: '.$name); 60 | } 61 | 62 | // If we haven't created it, create it and save to store 63 | if (!isset($this->serviceStore[$name])) { 64 | $this->serviceStore[$name] = $this->createService($name); 65 | } 66 | 67 | // Return service from store 68 | return $this->serviceStore[$name]; 69 | } 70 | 71 | /** 72 | * {@inheritDoc} 73 | */ 74 | public function has($name) 75 | { 76 | return isset($this->services[$name]); 77 | } 78 | 79 | /** 80 | * {@inheritDoc} 81 | */ 82 | public function getParameter($name) 83 | { 84 | $tokens = explode('.', $name); 85 | $context = $this->parameters; 86 | 87 | while (null !== ($token = array_shift($tokens))) { 88 | if (!isset($context[$token])) { 89 | throw new ParameterNotFoundException('Parameter not found: '.$name); 90 | } 91 | 92 | $context = $context[$token]; 93 | } 94 | 95 | return $context; 96 | } 97 | 98 | /** 99 | * {@inheritDoc} 100 | */ 101 | public function hasParameter($name) 102 | { 103 | try { 104 | $this->getParameter($name); 105 | } catch (ParameterNotFoundException $exception) { 106 | return false; 107 | } 108 | 109 | return true; 110 | } 111 | 112 | /** 113 | * Attempt to create a service. 114 | * 115 | * @param string $name The service name. 116 | * 117 | * @return mixed The created service. 118 | * 119 | * @throws ContainerException On failure. 120 | */ 121 | private function createService($name) 122 | { 123 | $entry = &$this->services[$name]; 124 | 125 | if (!is_array($entry) || !isset($entry['class'])) { 126 | throw new ContainerException($name.' service entry must be an array containing a \'class\' key'); 127 | } elseif (!class_exists($entry['class'])) { 128 | throw new ContainerException($name.' service class does not exist: '.$entry['class']); 129 | } elseif (isset($entry['lock'])) { 130 | throw new ContainerException($name.' contains circular reference'); 131 | } 132 | 133 | $entry['lock'] = true; 134 | 135 | $arguments = isset($entry['arguments']) ? $this->resolveArguments($entry['arguments']) : []; 136 | 137 | $reflector = new \ReflectionClass($entry['class']); 138 | $service = $reflector->newInstanceArgs($arguments); 139 | 140 | if (isset($entry['calls'])) { 141 | $this->initializeService($service, $name, $entry['calls']); 142 | } 143 | 144 | return $service; 145 | } 146 | 147 | /** 148 | * Resolve argument definitions into an array of arguments. 149 | * 150 | * @param array $argumentDefinitions The service arguments definition. 151 | * 152 | * @return array The service constructor arguments. 153 | * 154 | * @throws ContainerException On failure. 155 | */ 156 | private function resolveArguments(array $argumentDefinitions) 157 | { 158 | $arguments = []; 159 | 160 | foreach ($argumentDefinitions as $argumentDefinition) { 161 | if ($argumentDefinition instanceof ServiceReference) { 162 | $argumentServiceName = $argumentDefinition->getName(); 163 | 164 | $arguments[] = $this->get($argumentServiceName); 165 | } elseif ($argumentDefinition instanceof ParameterReference) { 166 | $argumentParameterName = $argumentDefinition->getName(); 167 | 168 | $arguments[] = $this->getParameter($argumentParameterName); 169 | } else { 170 | $arguments[] = $argumentDefinition; 171 | } 172 | } 173 | 174 | return $arguments; 175 | } 176 | 177 | /** 178 | * Initialize a service using the call definitions. 179 | * 180 | * @param object $service The service. 181 | * @param string $name The service name. 182 | * @param array $callDefinitions The service calls definition. 183 | * 184 | * @throws ContainerException On failure. 185 | */ 186 | private function initializeService($service, $name, array $callDefinitions) 187 | { 188 | foreach ($callDefinitions as $callDefinition) { 189 | if (!is_array($callDefinition) || !isset($callDefinition['method'])) { 190 | throw new ContainerException($name.' service calls must be arrays containing a \'method\' key'); 191 | } elseif (!is_callable([$service, $callDefinition['method']])) { 192 | throw new ContainerException($name.' service asks for call to uncallable method: '.$callDefinition['method']); 193 | } 194 | 195 | $arguments = isset($callDefinition['arguments']) ? $this->resolveArguments($callDefinition['arguments']) : []; 196 | 197 | call_user_func_array([$service, $callDefinition['method']], $arguments); 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/ContainerInterface.php: -------------------------------------------------------------------------------- 1 | name = $name; 23 | } 24 | 25 | /** 26 | * Retrieve the service name. 27 | * 28 | * @return string The service name. 29 | */ 30 | public function getName() 31 | { 32 | return $this->name; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Reference/ParameterReference.php: -------------------------------------------------------------------------------- 1 |