├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── UPGRADE.md ├── composer.json └── src ├── Definition.php ├── Facades └── Factory.php ├── Factory.php ├── FactoryContract.php └── FactoryServiceProvider.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Laravel Array Factory Change Log 2 | ================================ 3 | 4 | 1.2.6, March 6, 2025 5 | -------------------- 6 | 7 | - Enh: Added support for "illuminate/support" 12.0 (klimov-paul) 8 | 9 | 10 | 1.2.5, March 25, 2024 11 | --------------------- 12 | 13 | - Enh: Added support for "illuminate/support" 11.0 (klimov-paul) 14 | 15 | 16 | 1.2.4, February 24, 2023 17 | ------------------------ 18 | 19 | - Enh #1: Added support for "illuminate/support" 10.0 (klimov-paul) 20 | 21 | 22 | 1.2.3, February 9, 2022 23 | ----------------------- 24 | 25 | - Enh: Added support for "illuminate/support" 9.0 (klimov-paul) 26 | 27 | 28 | 1.2.2, September 9, 2020 29 | ------------------------ 30 | 31 | - Enh: Added support for "illuminate/support" 8.0 (klimov-paul) 32 | 33 | 34 | 1.2.1, March 4, 2020 35 | -------------------- 36 | 37 | - Enh: Added support for "illuminate/support" 7.0 (klimov-paul) 38 | 39 | 40 | 1.2.0, September 6, 2019 41 | ------------------------ 42 | 43 | - Enh: Added support for "illuminate/support" 6.0 (klimov-paul) 44 | 45 | 46 | 1.1.0, March 6, 2019 47 | -------------------- 48 | 49 | - Enh: Added support for "illuminate/support" 5.8 (klimov-paul) 50 | 51 | 52 | 1.0.1, March 6, 2019 53 | -------------------- 54 | 55 | - Bug: Fixed `FactoryServiceProvider` is not loaded because lack of `provides()` declaration (klimov-paul) 56 | 57 | 58 | 1.0.0, February 19, 2019 59 | ------------------------ 60 | 61 | - Initial release. 62 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This is free software. It is released under the terms of the 2 | following BSD License. 3 | 4 | Copyright © 2019 by Illuminatech (https://github.com/illuminatech) 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions 9 | are met: 10 | 11 | * Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in 15 | the documentation and/or other materials provided with the 16 | distribution. 17 | * Neither the name of Illuminatech nor the names of its 18 | contributors may be used to endorse or promote products derived 19 | from this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

Laravel Array Factory

6 |
7 |

8 | 9 | This extension allows DI aware object creation from array definition. 10 | 11 | For license information check the [LICENSE](LICENSE.md)-file. 12 | 13 | [![Latest Stable Version](https://img.shields.io/packagist/v/illuminatech/array-factory.svg)](https://packagist.org/packages/illuminatech/array-factory) 14 | [![Total Downloads](https://img.shields.io/packagist/dt/illuminatech/array-factory.svg)](https://packagist.org/packages/illuminatech/array-factory) 15 | [![Build Status](https://github.com/illuminatech/array-factory/workflows/build/badge.svg)](https://github.com/illuminatech/array-factory/actions) 16 | 17 | 18 | Installation 19 | ------------ 20 | 21 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 22 | 23 | Either run 24 | 25 | ``` 26 | php composer.phar require --prefer-dist illuminatech/array-factory 27 | ``` 28 | 29 | or add 30 | 31 | ```json 32 | "illuminatech/array-factory": "*" 33 | ``` 34 | 35 | to the require section of your composer.json. 36 | 37 | 38 | Usage 39 | ----- 40 | 41 | This extension allows DI aware object creation from array definition. 42 | Creation is performed by factory defined via `\Illuminatech\ArrayFactory\FactoryContract` contract. 43 | `\Illuminatech\ArrayFactory\Factory` can be used for particular implementation. 44 | Such factory allows creation of any object from its array definition. 45 | Keys in definition array are processed by following rules: 46 | 47 | - '__class': string, full qualified name of the class to be instantiated. 48 | - '__construct()': array, arguments to be bound during constructor invocation. 49 | - 'methodName()': array, list of arguments to be passed to the object method, which name defined via key. 50 | - 'fieldOrProperty': mixed, value to be assigned to the public field or passed to the setter method. 51 | - '()': callable, PHP callback to be invoked once object has been instantiated and all other configuration applied to it. 52 | 53 | Imagine we have the following class defined at our project: 54 | 55 | ```php 56 | condition = $condition; 73 | } 74 | 75 | public function setType(string $type) 76 | { 77 | $this->type = $type; 78 | } 79 | 80 | public function getType(): string 81 | { 82 | return $this->type; 83 | } 84 | 85 | public function color(string $color): self 86 | { 87 | $this->color = $color; 88 | 89 | return $this; 90 | } 91 | 92 | public function startEngine(): self 93 | { 94 | $this->engineRunning = true; 95 | 96 | return $this; 97 | } 98 | } 99 | ``` 100 | 101 | Instance of such class can be instantiated using array factory in the following way: 102 | 103 | ```php 104 | make([ 109 | '__class' => Car::class, // class name 110 | '__construct()' => ['condition' => 'good'], // constructor arguments 111 | 'registrationNumber' => 'AB1234', // set public field `Car::$registrationNumber` 112 | 'type' => 'sedan', // pass value to the setter `Car::setType()` 113 | 'color()' => ['red'], // pass arguments to the method `Car::color()` 114 | '()' => function (Car $car) { 115 | // final adjustments to be made after object creation and other config application: 116 | $car->startEngine(); 117 | }, 118 | ]); 119 | ``` 120 | 121 | The main benefit of array object definition is lazy loading: you can define entire object configuration as a mere array 122 | without even loading the class source file, and then instantiate actual object only in case it becomes necessary. 123 | 124 | Defined array configuration can be adjusted, applying default values for it. For example: 125 | 126 | ```php 127 | 'AB1234', 133 | 'type' => 'sedan', 134 | 'color()' => ['red'], 135 | ]; 136 | 137 | // ... 138 | 139 | $defaultCarConfig = [ 140 | '__class' => Car::class, 141 | 'type' => 'sedan', 142 | 'condition' => 'good', 143 | ]; 144 | 145 | $car = $factory->make(array_merge($defaultCarConfig, $config)); 146 | ``` 147 | 148 | You may use `\Illuminatech\ArrayFactory\Facades\Factory` facade for quick access to the factory functionality. 149 | For example: 150 | 151 | ```php 152 | Car::class, 158 | 'registrationNumber' => 'AB1234', 159 | 'type' => 'sedan', 160 | ]); 161 | ``` 162 | 163 | 164 | ## Service configuration 165 | 166 | The most common use case for array factory is creation of the universal configuration for particular application service. 167 | Imagine we create a library providing geo-location by IP address detection. Since there are plenty of external services 168 | and means to solve this task, we have created some high level contract, like following: 169 | 170 | ```php 171 | app->singleton(DetectorContract::class, function ($app) { 200 | $factory = new Factory($app); 201 | 202 | $factory->make(array_merge( 203 | ['__class' => DefaultDetector::class], // default config 204 | $app->config->get('geoip', []) // developer defined config 205 | )); 206 | }); 207 | } 208 | } 209 | ``` 210 | 211 | This allows developer to specify any particular detector class to be used along with its configuration. The actual 212 | configuration file 'config/geoip.php' may look like following: 213 | 214 | ```php 215 | \MyVendor\GeoLocation\SomeExternalApiDetector::class, 220 | 'apiEndpoint' => 'https://some.external.service/api', 221 | 'apiKey' => env('SOME_EXTERNAL_API_KEY'), 222 | ]; 223 | ``` 224 | 225 | It can also look like following: 226 | 227 | ```php 228 | \MyVendor\GeoLocation\LocalFileDetector::class, 233 | 'geoipDatabaseFile' => __DIR__.'/geoip/local.db', 234 | ]; 235 | ``` 236 | 237 | Both configuration will work fine with the service provider we created, and same will be for countless other possible 238 | configurations for different geo-location detectors, which may not even exist yet. 239 | 240 | **Heads up!** Remember to avoid usage of `\Closure`, while creating application configuration, otherwise you will 241 | face the error during configuration caching. 242 | 243 | 244 | ## Interaction with DI container 245 | 246 | `\Illuminatech\ArrayFactory\Factory` is DI aware: it performs object instantiation via `\Illuminate\Contracts\Container\Container::make()`. 247 | Thus bindings set within the container will affect object creation. For example: 248 | 249 | ```php 250 | bind(Car::class, function() { 260 | $car = new Car(); 261 | $car->setType('by-di-container'); 262 | 263 | return $car; 264 | }); 265 | 266 | /* @var $car Car */ 267 | $car = $factory->make([ 268 | '__class' => Car::class, 269 | 'registrationNumber' => 'AB1234', 270 | ]); 271 | 272 | var_dump($car->getType()); // outputs: 'by-di-container' 273 | ``` 274 | 275 | > Note: obviously, in case there is a DI container binding for the instantiated class, the key '__construct()' inside 276 | array configuration will be ignored. 277 | 278 | DI container is also used during configuration method invocations, allowing automatic arguments injection. For example: 279 | 280 | ```php 281 | carRents[] = ['car' => $car, 'price' => $price]; 293 | } 294 | } 295 | 296 | $container = Container::getInstance(); 297 | 298 | $factory = new Factory($container); 299 | 300 | $container->bind(Car::class, function() { 301 | $car = new Car(); 302 | $car->setType('by-di-container'); 303 | 304 | return $car; 305 | }); 306 | 307 | /* @var $person Person */ 308 | $person = $factory->make([ 309 | '__class' => Person::class, 310 | 'rentCar()' => ['price' => 12], 311 | ]); 312 | 313 | var_dump($person->carRents[0]['car']->getType()); // outputs: 'by-di-container' 314 | var_dump($person->carRents[0]['price']); // outputs: '12' 315 | ``` 316 | 317 | Note that final handler callback ('()' configuration key) is not DI aware and does not provide binding for its arguments. 318 | However, the factory instance is always passed as its second argument, allowing you to access to its DI container if needed. 319 | Following code will produce the same result as the one from previous example: 320 | 321 | ```php 322 | make([ 333 | '__class' => Person::class, 334 | '()' => function (Person $person, Factory $factory) { 335 | $factory->getContainer()->call([$person, 'rentCar'], ['price' => 12]); 336 | }, 337 | ]); 338 | ``` 339 | 340 | 341 | ## Standalone configuration 342 | 343 | You may use array factory to configure or re-configure already existing objects. For example: 344 | 345 | ```php 346 | setType('sedan'); 354 | $car->color('red'); 355 | 356 | /* @var $car Car */ 357 | $car = $factory->configure($car, [ 358 | 'type' => 'hatchback', 359 | 'color()' => ['green'], 360 | ]); 361 | 362 | var_dump($car->getType()); // outputs: 'hatchback' 363 | var_dump($car->getColor()); // outputs: 'green' 364 | ``` 365 | 366 | 367 | ## Type ensuring 368 | 369 | You may add extra check whether created object matches particular base class or interface, using `ensure()` method. 370 | For example: 371 | 372 | ```php 373 | ensure( 384 | [ 385 | '__class' => RedisStore::class, 386 | ], 387 | Store::class 388 | ); 389 | 390 | // throws an exception: 391 | $cache = $factory->ensure( 392 | [ 393 | '__class' => Carbon::class, 394 | ], 395 | Store::class 396 | ); 397 | ``` 398 | 399 | ## Immutable methods handling 400 | 401 | `\Illuminatech\ArrayFactory\Factory` handles immutable methods during object configuration, returning new object 402 | from their invocations. For example: in case we have following class: 403 | 404 | ```php 405 | type = $type; 413 | 414 | return $new; 415 | } 416 | 417 | public function color(string $color): self 418 | { 419 | $new = clone $this; // immutability 420 | $new->color = $color; 421 | 422 | return $new; 423 | } 424 | } 425 | ``` 426 | 427 | The following configuration will be applied correctly: 428 | 429 | ```php 430 | make([ 438 | '__class' => CarImmutable::class, 439 | 'type' => 'sedan', 440 | 'color()' => ['green'], 441 | ]); 442 | 443 | var_dump($car->getType()); // outputs: 'sedan' 444 | var_dump($car->getColor()); // outputs: 'green' 445 | ``` 446 | 447 | > Note: since there could be immutable method invocations during configuration, you should always use result 448 | of `\Illuminatech\ArrayFactory\FactoryContract::configure()` method instead of its argument. 449 | 450 | 451 | ## Recursive make 452 | 453 | For complex object, which stores other object as its inner property, there may be need to configure both host and resident 454 | objects using array definition and resolve them both via array factory. For this case definition like following may 455 | be created: 456 | 457 | ```php 458 | Car::class, 462 | // ... 463 | 'engine' => [ 464 | '__class' => InternalCombustionEngine::class, 465 | // ... 466 | ], 467 | ]; 468 | ``` 469 | 470 | However, nested definitions are not resolved by array factory automatically. Following example will not instantiate 471 | engine instance: 472 | 473 | ```php 474 | Car::class, 482 | // ... 483 | 'engine' => [ 484 | '__class' => InternalCombustionEngine::class, 485 | // ... 486 | ], 487 | ]; 488 | 489 | $car = $factory->make($config); 490 | var_dump($car->engine); // outputs array 491 | ``` 492 | 493 | This is done in order to allow setup of the slave internal configuration into created object, so it be can resolved 494 | in lazy way according to its own internal logic. 495 | 496 | However, you may enforce resolving of the nested definition wrapping it into `\Illuminatech\ArrayFactory\Definition` instance. 497 | For example: 498 | 499 | ```php 500 | Car::class, 509 | // ... 510 | 'engine' => new Definition([ 511 | '__class' => InternalCombustionEngine::class, 512 | // ... 513 | ]), 514 | ]; 515 | 516 | $car = $factory->make($config); 517 | var_dump($car->engine); // outputs object 518 | ``` 519 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | Upgrading Instructions for Laravel Array Factory 2 | ================================================ 3 | 4 | !!!IMPORTANT!!! 5 | 6 | The following upgrading instructions are cumulative. That is, 7 | if you want to upgrade from version A to version C and there is 8 | version B between A and C, you need to following the instructions 9 | for both A and B. 10 | 11 | Upgrade from 1.0.1 12 | ------------------ 13 | 14 | * "illuminate/support" package requirements were raised to 6.0. Make sure to upgrade your code accordingly. 15 | 16 | Upgrade from 1.0.1 17 | ------------------ 18 | 19 | * "illuminate/support" package requirements were raised to 5.8. Make sure to upgrade your code accordingly. 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "illuminatech/array-factory", 3 | "description": "Allows DI aware object creation from array definition", 4 | "keywords": ["laravel", "factory", "array", "configuration", "config"], 5 | "license": "BSD-3-Clause", 6 | "support": { 7 | "issues": "https://github.com/illuminatech/array-factory/issues", 8 | "wiki": "https://github.com/illuminatech/array-factory/wiki", 9 | "source": "https://github.com/illuminatech/array-factory" 10 | }, 11 | "authors": [ 12 | { 13 | "name": "Paul Klimov", 14 | "email": "klimov.paul@gmail.com" 15 | } 16 | ], 17 | "require": { 18 | "illuminate/support": "^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0" 19 | }, 20 | "require-dev": { 21 | "illuminate/container": "*", 22 | "phpunit/phpunit": "^7.5 || ^8.0 || ^9.3 || ^10.5" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Illuminatech\\ArrayFactory\\": "src" 27 | } 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "Illuminatech\\ArrayFactory\\Test\\": "tests" 32 | } 33 | }, 34 | "extra": { 35 | "branch-alias": { 36 | "dev-master": "1.0.x-dev" 37 | }, 38 | "laravel": { 39 | "providers": [ 40 | "Illuminatech\\ArrayFactory\\FactoryServiceProvider" 41 | ] 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/Definition.php: -------------------------------------------------------------------------------- 1 | make([ 24 | * '__class' => Car::class, 25 | * '__construct()' => [ 26 | * 'engine' => new Definition(Engine::class), 27 | * ], 28 | * 'driver' => new Definition([ 29 | * '__class' => Person::class, 30 | * 'name' => 'John Doe', 31 | * ]), 32 | * ]); 33 | * ``` 34 | * 35 | * @see FactoryContract::make() 36 | * 37 | * @author Paul Klimov 38 | * @since 1.0 39 | */ 40 | class Definition 41 | { 42 | /** 43 | * @var array|string raw definition for {@see FactoryContract::make()} 44 | */ 45 | public $definition; 46 | 47 | /** 48 | * Definition constructor. 49 | * 50 | * @param array|string $definition array factory compatible definition. 51 | */ 52 | public function __construct($definition) 53 | { 54 | $this->definition = $definition; 55 | } 56 | 57 | /** 58 | * Restores class state after using `var_export()`. 59 | * @see var_export() 60 | * 61 | * @param array $state state to be restored from. 62 | * @return static restored instance. 63 | * @throws InvalidArgumentException when $state property does not contain `id` parameter. 64 | */ 65 | public static function __set_state($state) 66 | { 67 | if (! isset($state['definition'])) { 68 | throw new InvalidArgumentException( 69 | 'Failed to instantiate class "'.get_called_class().'". Required parameter "definition" is missing.' 70 | ); 71 | } 72 | 73 | return new self($state['definition']); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Facades/Factory.php: -------------------------------------------------------------------------------- 1 | 26 | * @since 1.0 27 | */ 28 | class Factory extends Facade 29 | { 30 | /** 31 | * Get the registered name of the component. 32 | * 33 | * @return string 34 | */ 35 | protected static function getFacadeAccessor() 36 | { 37 | return FactoryContract::class; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Factory.php: -------------------------------------------------------------------------------- 1 | 21 | * @since 1.0 22 | */ 23 | class Factory implements FactoryContract 24 | { 25 | /** 26 | * @var \Illuminate\Contracts\Container\Container DI container to be used. 27 | */ 28 | private $container; 29 | 30 | /** 31 | * Constructor. 32 | * 33 | * @param Container|null $container DI container to be used. 34 | */ 35 | public function __construct(Container $container = null) 36 | { 37 | if ($container !== null) { 38 | $this->setContainer($container); 39 | } 40 | } 41 | 42 | /** 43 | * @return Container used DI container. 44 | */ 45 | public function getContainer(): Container 46 | { 47 | if ($this->container === null) { 48 | $this->container = $this->defaultContainer(); 49 | } 50 | 51 | return $this->container; 52 | } 53 | 54 | /** 55 | * @param Container $container DI container to be used. 56 | * @return static self reference. 57 | */ 58 | public function setContainer(Container $container): self 59 | { 60 | $this->container = $container; 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function make($definition, Container $container = null) 69 | { 70 | $container = $container ?? $this->getContainer(); 71 | 72 | if ($definition instanceof Definition) { 73 | $definition = $definition->definition; 74 | } 75 | 76 | if (is_array($definition)) { 77 | $class = Arr::pull($definition, '__class'); 78 | if ($class === null) { 79 | throw new InvalidArgumentException('Array definition must contain "__class" key.'); 80 | } 81 | $constructArgs = Arr::pull($definition, '__construct()', []); 82 | $config = $definition; 83 | } else { 84 | $class = $definition; 85 | $constructArgs = []; 86 | $config = []; 87 | } 88 | 89 | $constructArgs = $this->makeIfDefinitionArray($constructArgs, $container); 90 | 91 | $object = $container->make($class, $constructArgs); 92 | 93 | return $this->configure($object, $config); 94 | } 95 | 96 | /** 97 | * {@inheritdoc} 98 | */ 99 | public function configure($object, iterable $config, Container $container = null) 100 | { 101 | $finalHandler = null; 102 | 103 | foreach ($config as $action => $arguments) { 104 | if ($action === '()') { 105 | $finalHandler = $arguments; 106 | 107 | continue; 108 | } 109 | 110 | if (substr($action, -2) === '()') { 111 | // method call 112 | $container = $container ?? $this->getContainer(); 113 | 114 | $result = $container->call([$object, substr($action, 0, -2)], $this->makeIfDefinitionArray($arguments, $container)); 115 | 116 | // handle immutable methods 117 | $object = $this->chooseNewObject($object, $result); 118 | 119 | continue; 120 | } 121 | 122 | if (method_exists($object, $setter = 'set'.$action)) { 123 | // setter 124 | $result = call_user_func([$object, $setter], $this->makeIfDefinition($arguments, $container)); 125 | 126 | // handle immutable methods 127 | $object = $this->chooseNewObject($object, $result); 128 | 129 | continue; 130 | } 131 | 132 | // property 133 | if (property_exists($object, $action) || method_exists($object, '__set')) { 134 | $object->$action = $this->makeIfDefinition($arguments, $container); 135 | 136 | continue; 137 | } 138 | 139 | throw new InvalidArgumentException('Class "'.get_class($object).'" does not have property "'.$action.'"'); 140 | } 141 | 142 | if ($finalHandler !== null) { 143 | $result = call_user_func($finalHandler, $object, $this); 144 | 145 | // handle possible immutability 146 | $object = $this->chooseNewObject($object, $result); 147 | } 148 | 149 | return $object; 150 | } 151 | 152 | /** 153 | * {@inheritdoc} 154 | */ 155 | public function ensure($reference, string $type = null, Container $container = null) 156 | { 157 | if (! is_object($reference)) { 158 | $reference = $this->make($reference, $container); 159 | } 160 | 161 | if ($type !== null) { 162 | if (! $reference instanceof $type) { 163 | throw new InvalidArgumentException('Reference "'.get_class($reference).'" does not match type "'.$type.'"'); 164 | } 165 | } 166 | 167 | return $reference; 168 | } 169 | 170 | /** 171 | * Picks the new object to be used from original trusted one and new possible candidate. 172 | * This method is used to handle possible immutable creating methods, when method invocation 173 | * does not alters object state, but creates new object instead. 174 | * 175 | * @param object $original original object. 176 | * @param object|mixed $candidate candidate value. 177 | * @return object new object to be used. 178 | */ 179 | private function chooseNewObject($original, $candidate) 180 | { 181 | if (is_object($candidate) && $candidate !== $original && get_class($candidate) === get_class($original)) { 182 | return $candidate; 183 | } 184 | 185 | return $original; 186 | } 187 | 188 | /** 189 | * Checks if given value is a array factory compatible definition, performs make if it is, skips - if not. 190 | * 191 | * @param mixed $candidate candidate value to be checked. 192 | * @param Container $container DI container to be used for making object. 193 | * @return object|mixed resolved object or intact candidate. 194 | */ 195 | private function makeIfDefinition($candidate, Container $container = null) 196 | { 197 | $container = $container ?? $this->getContainer(); 198 | 199 | if ($candidate instanceof Definition) { 200 | return $this->make($candidate->definition, $container); 201 | } 202 | 203 | return $candidate; 204 | } 205 | 206 | /** 207 | * Iterates over definition candidates, performing build of the values, which are valid definitions. 208 | * 209 | * @param iterable $candidates candidate values to be checked. 210 | * @param Container $container DI container to be used for making objects. 211 | * @return array 212 | */ 213 | private function makeIfDefinitionArray(iterable $candidates, Container $container = null): array 214 | { 215 | $container = $container ?? $this->getContainer(); 216 | 217 | $result = []; 218 | foreach ($candidates as $key => $value) { 219 | $result[$key] = $this->makeIfDefinition($value, $container); 220 | } 221 | 222 | return $result; 223 | } 224 | 225 | /** 226 | * Returns default DI container to be used for {@see $container}. 227 | * 228 | * @return Container DI container instance. 229 | */ 230 | protected function defaultContainer(): Container 231 | { 232 | return \Illuminate\Container\Container::getInstance(); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/FactoryContract.php: -------------------------------------------------------------------------------- 1 | make([ 28 | * '__class' => Item::class, 29 | * '__construct()' => ['constructorArgument' => 'initial'], 30 | * 'publicField' => 'value assigned to public field', 31 | * 'virtualProperty' => 'value passed to setter method', 32 | * 'someMethod()' => ['argument1' => 'value1', 'argument2' => 'value2'], 33 | * '()' => function (Item $item) { 34 | * // final adjustments 35 | * }, 36 | * ]); 37 | * ``` 38 | * 39 | * @see Definition 40 | * 41 | * @author Paul Klimov 42 | * @since 1.0 43 | */ 44 | interface FactoryContract 45 | { 46 | /** 47 | * Creates new object from the definition. 48 | * 49 | * @param array|string $definition 50 | * @param Container|null $container DI container instance. 51 | * @return object created object. 52 | */ 53 | public function make($definition, Container $container = null); 54 | 55 | /** 56 | * Configures existing object applying given configuration to it. 57 | * 58 | * @param object $object object to be configured. 59 | * @param iterable $config configuration to be applied. 60 | * @param Container|null $container DI container instance. 61 | * @return object configured object. 62 | */ 63 | public function configure($object, iterable $config, Container $container = null); 64 | 65 | /** 66 | * Resolves the specified reference into the actual object and makes sure it is of the specified type. 67 | * 68 | * @param array|object|string $reference an object or its factory-compatible definition. 69 | * @param string|null $type the class/interface name to be checked. If null, type check will not be performed. 70 | * @param Container|null $container DI container instance. 71 | * @return object created object. 72 | */ 73 | public function ensure($reference, string $type = null, Container $container = null); 74 | } 75 | -------------------------------------------------------------------------------- /src/FactoryServiceProvider.php: -------------------------------------------------------------------------------- 1 | 20 | * @since 1.0 21 | */ 22 | class FactoryServiceProvider extends ServiceProvider implements DeferrableProvider 23 | { 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function register() 28 | { 29 | $this->app->singleton(FactoryContract::class, function ($app) { 30 | return new Factory($app); 31 | }); 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function provides() 38 | { 39 | return [ 40 | FactoryContract::class, 41 | ]; 42 | } 43 | } 44 | --------------------------------------------------------------------------------