├── LICENSE ├── README.md ├── composer.json └── src ├── Container.php └── Exception ├── BadMethodCallException.php ├── NotFoundException.php └── SimpleContainerException.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2024 Ayesh Karunaratne, aye.sh 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Simple Container 3 | A fast and minimal PSR-11 compatible Dependency Injection Container with array-syntax and without auto-wiring. 4 | 5 | 6 | [![Latest Stable Version](https://poser.pugx.org/phpwatch/simple-container/v/stable)](https://packagist.org/packages/phpwatch/simple-container) ![CI](https://github.com/PHPWatch/simple-container/workflows/CI/badge.svg?branch=master) [![codecov](https://codecov.io/gh/PHPWatch/simple-container/branch/master/graph/badge.svg)](https://codecov.io/gh/PHPWatch/simple-container) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/PHPWatch/simple-container/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/PHPWatch/simple-container/?branch=master) [![License](https://poser.pugx.org/phpwatch/simple-container/license)](https://packagist.org/packages/phpwatch/simple-container) 7 | 8 | 9 | ## Design goals 10 | 11 | - Do one thing and do it well 12 | - ~100 LOC 13 | - **No auto-wiring** by design 14 | - Services are declared as closures 15 | - Array-syntax to access and set services (`$container['database']`) 16 | - Fully PSR-11 compliant 17 | - Support for protected services (return closures verbatim) 18 | - Support for factory services (return a new instance instead of returning the same instance) 19 | - 100% test coverage 20 | - Services can over overwritten later, marked factory/protected 21 | - Function at full speed without a compilation step 22 | 23 | ## Installation 24 | 25 | ```bash 26 | composer require phpwatch/simple-container 27 | ``` 28 | 29 | ## Usage 30 | 31 | *Simple Container* supports array-syntax for setting and fetching of services and values. You can mark certain services as factory or protected later too. 32 | 33 | ### Declare services and values 34 | 35 | ```php 36 | get('database.dsn')); 44 | }; 45 | $container['api.ipgeo'] = 'rhkg3...'; 46 | ``` 47 | 48 | ### Fetch services 49 | 50 | Do not use this class as a service locator. The closures you declare for each service will get the `Container` instance, from which you can fetch services using the array syntax: 51 | 52 | ```php 53 | get('database'); // \PDO 57 | ``` 58 | 59 | ### Create container from definitions 60 | 61 | ```php 62 | [ 68 | 'dsn' => 'sqlite...' 69 | ], 70 | 'prefix' => 'Foo', 71 | 'csprng' => static function (ContainerInterface $container) { 72 | return $container->get('prefix') . bin2hex(random_bytes(16)); 73 | } 74 | ]; 75 | 76 | $container = new Container($services); 77 | $container->get('prefix'); // Foo 78 | ``` 79 | 80 | ### Factory and Protected Services 81 | 82 | If the service definition is a closure (similar to the `database` example above), the return value will be cached, and returned for subsequent calls without instantiating again. This is often the use expected behavior for databases and other reusable services. 83 | 84 | #### Factory Services 85 | 86 | To execute the provided closure every-time a service is requested (for example, to return an HTTP client), you can use *factories*. 87 | 88 | ```php 89 | setFactory('http.client', static function(ContainerInterface $container) { 91 | $handler = new curl_init(); 92 | curl_setopt($handler,CURLOPT_USERAGENT, $container->get('http.user-agent')); 93 | 94 | return $handler; 95 | }; 96 | ``` 97 | 98 | The example above will always return a **new** curl handler resource everytime `$container->get('http.client')` is called, with the User-Agent string set to the `http.user-agent` value from container. 99 | 100 | You can also mark a container service as a factory method later if it's already set: 101 | 102 | ```php 103 | setFactory('http.client'); // Mark existing definition as a factory. 105 | ``` 106 | 107 | If you have already declared the `http.client` service, it will now be marked as factory. If the existing declaration is not set, or is not a `callable`, a `PHPWatch\SimpleContainer\Exception\BadMethodCallExceptionTest` exception will be thrown. 108 | 109 | #### Protected Services 110 | 111 | Simple Container expects the service declarations to be closures, and it will execute the closure by itself to return the service. However, in some situations, you need to return a closure itself as the service. 112 | 113 | ```php 114 | setProtected('csprng', static function(): string { 129 | return bin2hex(random_bytes(32)); 130 | }); 131 | 132 | $csprng = $container->get('csprng'); 133 | 134 | echo $csprng(); // eaa3e95d4102... 135 | echo $csprng(); // b857ce87400b... 136 | echo $csprng(); // a833e3db880... 137 | ``` 138 | 139 | --- 140 | 141 | ### Extend container 142 | 143 | Just use the array syntax and add/remove services 144 | 145 | ```php 146 | definitions = $definitions; 22 | } 23 | 24 | private function getService(string $id): mixed { 25 | if (!$this->has($id)) { 26 | throw new NotFoundException(sprintf('Container key "%s" is not defined', $id)); 27 | } 28 | 29 | if (array_key_exists($id, $this->generated)) { 30 | return $this->generated[$id]; 31 | } 32 | 33 | if (!is_callable($this->definitions[$id]) || isset($this->protected[$id])) { 34 | return $this->definitions[$id]; 35 | } 36 | 37 | if (isset($this->factories[$id])) { 38 | return $this->definitions[$id]($this); 39 | } 40 | 41 | return $this->generated[$id] = $this->definitions[$id]($this); 42 | } 43 | 44 | public function set(string $id, mixed $value): void { 45 | if (array_key_exists($id, $this->definitions)) { 46 | unset($this->generated[$id], $this->factories[$id], $this->protected[$id]); 47 | } 48 | $this->definitions[$id] = $value; 49 | } 50 | 51 | public function setProtected(string $id, ?callable $value = null): void { 52 | if ($value === null) { 53 | $value = $this->getDefaultDefinition($id, sprintf('Attempt to set container ID "%s" as protected, but it is not already set nor provided in the function call.', $id)); 54 | } 55 | 56 | $this->set($id, $value); 57 | $this->protected[$id] = true; 58 | } 59 | 60 | public function setFactory(string $id, ?callable $value = null): void { 61 | if ($value === null) { 62 | $value = $this->getDefaultDefinition($id, sprintf('Attempt to set container ID "%s" as factory, but it is not already set nor provided in the function call', $id)); 63 | } 64 | 65 | $this->set($id, $value); 66 | $this->factories[$id] = true; 67 | } 68 | 69 | private function getDefaultDefinition(string $id, string $exception_message): callable { 70 | if (!$this->has($id)) { 71 | throw new BadMethodCallException($exception_message); 72 | } 73 | if (!is_callable($this->definitions[$id])) { 74 | throw new BadMethodCallException(sprintf('Definition for "%s" expected to be a callable, "%s" found', $id, gettype($this->definitions[$id]))); 75 | } 76 | 77 | return $this->definitions[$id]; 78 | } 79 | 80 | public function offsetSet(mixed $offset, mixed $value): void { 81 | $this->set($offset, $value); 82 | } 83 | 84 | public function offsetUnset(mixed $offset): void { 85 | unset($this->definitions[$offset], $this->generated[$offset], $this->factories[$offset], $this->protected[$offset]); 86 | } 87 | 88 | public function offsetExists(mixed $offset): bool { 89 | return array_key_exists($offset, $this->definitions); 90 | } 91 | 92 | public function offsetGet(mixed $offset): mixed { 93 | return $this->getService($offset); 94 | } 95 | 96 | /** 97 | * @inheritDoc 98 | */ 99 | public function get(string $id): mixed { 100 | return $this->getService($id); 101 | } 102 | 103 | /** 104 | * @inheritDoc 105 | */ 106 | public function has(string $id): bool { 107 | return array_key_exists($id, $this->definitions); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Exception/BadMethodCallException.php: -------------------------------------------------------------------------------- 1 |