Skip to content

Commit

Permalink
Add Container::make() method that allows resolve service by its name.…
Browse files Browse the repository at this point in the history
… It returns a new instance of service every time.

- Add method's descriptions of Container::class.
- Add new tests.
- Updated documentation.
  • Loading branch information
renakdup committed Dec 2, 2023
1 parent a9c3eac commit c8ed54f
Show file tree
Hide file tree
Showing 11 changed files with 209 additions and 37 deletions.
76 changes: 63 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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', [
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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.
Expand All @@ -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
36 changes: 27 additions & 9 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Author Email: [email protected]
* 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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 ) {
Expand All @@ -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;
}

Expand All @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions tests/Assets/AbstractClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare( strict_types=1 );

namespace PisarevskiiTests\SimpleDIC\Assets;

abstract class AbstractClass {

}
9 changes: 9 additions & 0 deletions tests/Assets/ChildClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare( strict_types=1 );

namespace PisarevskiiTests\SimpleDIC\Assets;

class ChildClass extends ParentClass {

}
6 changes: 3 additions & 3 deletions tests/Assets/ClassWithConstructorDepsException.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

class ClassWithConstructorDepsException {

private SimpleClass $simple_class;
private string $string;
private array $array;
public SimpleClass $simple_class;
public string $string;
public array $array;

public function __construct(
SimpleClass $simple_class,
Expand Down
18 changes: 18 additions & 0 deletions tests/Assets/ClassWithConstructorSupertypes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare( strict_types=1 );

namespace PisarevskiiTests\SimpleDIC\Assets;

class ClassWithConstructorSupertypes {

private ParentClass $parent_class;
private AbstractClass $abstract_class;
private SomeInterface $some;

public function __construct( ParentClass $parent_class, AbstractClass $abstract_class, SomeInterface $some ) {
$this->parent_class = $parent_class;
$this->abstract_class = $abstract_class;
$this->some = $some;
}
}
9 changes: 9 additions & 0 deletions tests/Assets/ParentClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare( strict_types=1 );

namespace PisarevskiiTests\SimpleDIC\Assets;

class ParentClass {

}
9 changes: 9 additions & 0 deletions tests/Assets/SomeInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare( strict_types=1 );

namespace PisarevskiiTests\SimpleDIC\Assets;

interface SomeInterface {

}
9 changes: 9 additions & 0 deletions tests/Assets/UseAbstractClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare( strict_types=1 );

namespace PisarevskiiTests\SimpleDIC\Assets;

class UseAbstractClass extends AbstractClass {

}
9 changes: 9 additions & 0 deletions tests/Assets/UseInterfaceClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare( strict_types=1 );

namespace PisarevskiiTests\SimpleDIC\Assets;

class UseInterfaceClass implements SomeInterface {

}
56 changes: 44 additions & 12 deletions tests/ContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,20 @@
use Pisarevskii\SimpleDIC\ContainerException;
use Pisarevskii\SimpleDIC\ContainerInterface;
use Pisarevskii\SimpleDIC\ContainerNotFoundException;
use PisarevskiiTests\SimpleDIC\Assets\AbstractClass;
use PisarevskiiTests\SimpleDIC\Assets\ClassWithConstructorSupertypes;
use PisarevskiiTests\SimpleDIC\Assets\InvocableClass;
use PisarevskiiTests\SimpleDIC\Assets\ClassWithConstructorPrimitives;
use PisarevskiiTests\SimpleDIC\Assets\ClassWithConstructor;
use PisarevskiiTests\SimpleDIC\Assets\ClassWithConstructorDepsException;
use PisarevskiiTests\SimpleDIC\Assets\StaticClass;
use PisarevskiiTests\SimpleDIC\Assets\ParentClass;
use PisarevskiiTests\SimpleDIC\Assets\SomeInterface;
use PisarevskiiTests\SimpleDIC\Assets\SimpleClass;
use PisarevskiiTests\SimpleDIC\Assets\UseAbstractClass;
use PisarevskiiTests\SimpleDIC\Assets\UseInterfaceClass;
use SplQueue;
use stdClass;
use Error;

final class ContainerTest extends TestCase {
private ?Container $container;
Expand Down Expand Up @@ -125,17 +131,22 @@ public function test_get__singleton_for_resolved_child_dependencies() {
}

public function test_get__autowiring() {
$obj1 = new SimpleClass();
$expected = new SimpleClass();
$this->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() {
Expand Down Expand Up @@ -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 ) );
// }
}

0 comments on commit c8ed54f

Please sign in to comment.