diff --git a/README.md b/README.md index 8bba080..612d641 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,12 @@ Simple DI Container with autowiring in a single file allows you to easily use it in your simple PHP applications and especially convenient for WordPress plugins and themes. ## Why choose Simple DI Container? -1. Easy to integrate into your PHP Applocation or WordPress project, just copy one file. +1. Easy to integrate into your PHP Application or WordPress project, just copy one file. 2. Simple PHP DI Container hasn't any dependencies on other scripts or libraries. 3. Supports auto-wiring `__constructor` parameters for classes as well as for scalar types that have default values. -4. Allow you following the best practices for developing your code. -5. Supports PSR11 (read more about below). +4. Supports Lazy Load class instantiating. +5. Allow you following the best practices for developing your code. +6. Supports PSR11 (read more about below). ## How to integrate it in a project? 1. Just copy the file `./src/Container.php` to your plugin directory or theme. @@ -52,6 +53,17 @@ $container->set( 'users_ids', [ 1, 2, 3, 4] ); $user_ids = $container->get( 'users_ids', [ 1, 2, 3, 4] ); ``` + +Method `get()` can resolve not set object in the `$container` and then save resolved results in the `$container`. It means when you run `$container->get( $service )` several times you get the same object. + +```php +$obj1 = $constructor->get( Paypal::class ); +$obj2 = $constructor->get( Paypal::class ); +var_dump( $obj1 === $obj2 ) // true +``` + +If you want to instantiate service several time use `make()` method. + --- ### Factory @@ -64,18 +76,19 @@ $container->set( Paypal::class, function () { } ); ``` -One of the main benefits as well is that the factory allows to create objects in Lazy Load mode. It means that object will be created just when you resolve it `$constructor->get( Paypal::class )`. +As well factories create objects in the Lazy Load mode. It means that object will be created just when you resolve it by using `get()` method: -> [!NOTE] -> If you get the same service from the Container several times, you will get the same object, because the object is created just 1 time, and then stored in storage. -> ```php -> $obj1 = $constructor->get( Paypal::class ); -> $obj2 = $constructor->get( Paypal::class ); -> var_dump( $obj1 === $obj2 ) // true -> ``` +```php +$container->set( Paypal::class, function () { + return new Paypal(); +} ); + +$paypal = $constructor->get( Paypal::class ); // PayPal instance created +``` --- +### Container inside factory **SimpleDIC** allows to get a `Container` instance inside a factory if you add parameter in a callback `( Container $c )`. This allows to get or resolve another services inside for building an object: ```php $container->set( 'config', [ @@ -127,6 +140,40 @@ You can use **auto-wiring** feature that allows to `Container` create an instanc --- +### Create an instance every time + +Method `make()` resolves services by its name. It returns a new instance of service every time and supports auto-wiring. + +```php +$conatainer->make( Paypal::class ); +``` + +> [!NOTE] +> Constructor's dependencies will not instantiate every time. +> If dependencies were resolved before then they will be passed as resolved dependencies. + +Consider example: +```php +class PayPalSDK {} + +class Paypal { + public PayPalSDK $pal_sdk; + public function __constructor( PayPalSDK $pal_sdk ) { + $this->pal_sdk = $pal_sdk; + } +} + +// if we create PayPal instances twice +$paypal1 = $container->make( Paypal::class ); +$paypal2 = $container->make( Paypal::class ); + +var_dump( $paypal1 !== $paypal2 ); // true +var_dump( $paypal1->pal_sdk === $paypal2->pal_sdk ); // true +``` +Dependencies of PayPal service will not be recreated and will be taken from already resolved objects. + +--- + ## PSR11 Compatibility This Simple DI Container compatible with PSR11 standards ver 2.0, to use it: 1. Just import PSR11 interfaces in `Container.php` @@ -156,9 +203,10 @@ use Psr\Container\NotFoundExceptionInterface; - [x] Add PSR11 interfaces in the Container.php. - [x] Add auto-wiring support for not bounded classes. - [x] Add resolved service storage (getting singleton). -- [ ] Add ability to create new instances of service every time. +- [x] Add ability to create new instances of service every time. +- [x] Improve performance. - [ ] Fix deprecated `Use ReflectionParameter::getType() and the ReflectionType APIs should be used instead.` for PHP8 -- [ ] Improve performance. +- [ ] Circular dependency protector. - [ ] Allow to set definitions via `__constructor`. - [ ] Bind $container instance by default. - [ ] Add supporting Code Driven IoC. @@ -175,3 +223,5 @@ use Psr\Container\NotFoundExceptionInterface; - [ ] Add descriptions in the code for functions. - [ ] Choose codestyle - [ ] Add on packegist +- [ ] Add if class exist checks in the Container file? +- [ ] Rename Container.php to SimpleContainer.php diff --git a/src/Container.php b/src/Container.php index e5ff9f3..bf7b8e2 100644 --- a/src/Container.php +++ b/src/Container.php @@ -8,7 +8,7 @@ * Author Email: renakdup@gmail.com * Author Site: https://wp-yoda.com/en/ * - * Version: 0.2.1 + * Version: 0.2.2 * Source Code: https://github.com/renakdup/simple-php-dic * * Licence: MIT License @@ -90,6 +90,9 @@ class Container implements ContainerInterface { protected array $resolved = []; /** + * Set service to the container. Allows to set configurable services + * using factory "function () {}" as passed service. + * * @param mixed $service */ public function set( string $id, $service ): void { @@ -120,8 +123,25 @@ public function has( string $id ): bool { } /** - * @param string $id + * Resolves service by its name. It returns a new instance of service every time, but the constructor's + * dependencies will not instantiate every time. If dependencies were resolved before + * then they will be passed as resolved dependencies. * + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function make( string $id ): object { + if ( ! class_exists( $id ) ) { + $message = "Service '{$id}' could not be resolved because class not exist.\n" + . "Stack trace: \n" + . $this->get_stack_trace(); + throw new ContainerException( $message ); + } + + return $this->resolve_object( $id ); + } + + /** * @return mixed * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface @@ -159,7 +179,6 @@ protected function resolve( string $id ) { protected function resolve_object( string $service ): object { try { $reflected_class = new ReflectionClass( $service ); - $constructor = $reflected_class->getConstructor(); if ( ! $constructor ) { @@ -172,10 +191,10 @@ protected function resolve_object( string $service ): object { return new $service(); } - $constructor_args = []; + $resolved_params = []; foreach ( $params as $param ) { if ( $param_class = $param->getClass() ) { - $constructor_args[] = $this->get( $param_class->getName() ); + $resolved_params[] = $this->get( $param_class->getName() ); continue; } @@ -188,18 +207,17 @@ protected function resolve_object( string $service ): object { throw new ContainerException( $message ); } - $constructor_args[] = $default_value; + $resolved_params[] = $default_value; } } catch ( ReflectionException $e ) { throw new ContainerException( - "Service '{$service}' could not be resolved due the reflection issue:\n '" . - $e->getMessage() . "'\n" . + "Service '{$service}' could not be resolved due the reflection issue: '" . $e->getMessage() . "'\n" . "Stack trace: \n" . $e->getTraceAsString() ); } - return new $service( ...$constructor_args ); + return new $service( ...$resolved_params ); } protected function get_stack_trace(): string { diff --git a/tests/Assets/AbstractClass.php b/tests/Assets/AbstractClass.php new file mode 100644 index 0000000..6a929c0 --- /dev/null +++ b/tests/Assets/AbstractClass.php @@ -0,0 +1,9 @@ +parent_class = $parent_class; + $this->abstract_class = $abstract_class; + $this->some = $some; + } +} \ No newline at end of file diff --git a/tests/Assets/ParentClass.php b/tests/Assets/ParentClass.php new file mode 100644 index 0000000..0816d5d --- /dev/null +++ b/tests/Assets/ParentClass.php @@ -0,0 +1,9 @@ +container->set( $name = SimpleClass::class, SimpleClass::class ); self::assertEquals( new SimpleClass(), $this->container->get( $name ) ); - $obj2 = new ClassWithConstructorPrimitives( $obj1 ); + $expected = new ClassWithConstructorPrimitives( $expected ); $this->container->set( $name = ClassWithConstructorPrimitives::class, ClassWithConstructorPrimitives::class ); - self::assertEquals( $obj2, $this->container->get( $name ) ); + self::assertEquals( $expected, $this->container->get( $name ) ); - $obj3 = new ClassWithConstructor( $obj2 ); + $expected = new ClassWithConstructor( $expected ); $this->container->set( $name = ClassWithConstructor::class, ClassWithConstructor::class ); - self::assertEquals( $obj3, $this->container->get( $name ) ); + self::assertEquals( $expected, $this->container->get( $name ) ); + + $expected = new ClassWithConstructorSupertypes( new ParentClass(), new UseAbstractClass(), new UseInterfaceClass() ); + $this->container->set( AbstractClass::class, UseAbstractClass::class ); + $this->container->set( SomeInterface::class, UseInterfaceClass::class ); + self::assertEquals( $expected, $this->container->get( ClassWithConstructorSupertypes::class ) ); } public function test_get__autowiring_for_invocable() { @@ -181,16 +192,37 @@ public function test_get__autowiring__container_exception() { $this->container->get( ClassWithConstructorDepsException::class ); } + public function test_get__error_for_not_bound_supertypes() { + self::expectException( Error::class); + $this->container->get( ClassWithConstructorSupertypes::class ); + } + + public function test_make() { + /** + * @var $obj1 ClassWithConstructorPrimitives + * @var $obj2 ClassWithConstructorPrimitives + */ + $obj1 = $this->container->make( ClassWithConstructorPrimitives::class ); + $obj2 = $this->container->make( ClassWithConstructorPrimitives::class ); + + self::assertNotSame( $obj1, $obj2 ); + self::assertEquals( $obj1, $obj2 ); + + self::assertSame( $obj1->simple_class, $obj1->simple_class ); + self::assertSame( $obj1->array, $obj1->array ); + self::assertSame( $obj1->string, $obj1->string ); + } + + public function test_make__exception() { + self::expectException( ContainerException::class ); + + $this->container->make( 'this-string-is-not-class' ); + } + public function test_has() { $this->container->set( $name = 'service', new stdClass() ); self::assertTrue( $this->container->has( $name ) ); self::assertFalse( $this->container->has( 'not-exist' ) ); } - -// TODO:: do we need it? -// public function test_get__static_method_from_array() { -// $this->container->bind( $name = 'service', [ StaticClass::class, 'get_string' ] ); -// self::assertSame( StaticClass::get_string(), $this->container->get( $name ) ); -// } }