Skip to content

Commit

Permalink
Added showing stack trace of Container Exceptions. Improved try catch…
Browse files Browse the repository at this point in the history
… block coverage for Container::resolve_object()
  • Loading branch information
renakdup committed Dec 2, 2023
1 parent 1d2ddd9 commit a9c3eac
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 50 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Simple DI Container for WordPress
# Simple PHP DIC - DI Container in one file.
[![UnitTests](https://github.com/renakdup/simple-wordpress-dic/actions/workflows/phpunit.yaml/badge.svg)](https://github.com/renakdup/simple-wordpress-dic/actions/workflows/phpunit.yaml)
[![PHPStan](https://github.com/renakdup/simple-wordpress-dic/actions/workflows/phpstan.yaml/badge.svg)](https://github.com/renakdup/simple-wordpress-dic/actions/workflows/phpstan.yaml)



Simple DI Container with auto-wiring in a single file allows you to easily use it in your WordPress plugins and themes.
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 WordPress project, just copy one file.
2. Simple DI Container hasn't any dependencies on other scripts or libraries.
1. Easy to integrate into your PHP Applocation 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).
Expand Down Expand Up @@ -157,6 +157,8 @@ use Psr\Container\NotFoundExceptionInterface;
- [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.
- [ ] Fix deprecated `Use ReflectionParameter::getType() and the ReflectionType APIs should be used instead.` for PHP8
- [ ] Improve performance.
- [ ] Allow to set definitions via `__constructor`.
- [ ] Bind $container instance by default.
- [ ] Add supporting Code Driven IoC.
Expand Down
116 changes: 82 additions & 34 deletions src/Container.php
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
<?php
/**
* Simple PHP DI Container (DIC) for WordPress with auto-wiring allows
* you easily use it in your plugins and themes.
* Simple PHP DIC - DI Container in one file.
* Supports autowiring and allows you to easily use it in your simple PHP applications and
* especially convenient for WordPress plugins and themes.
*
* Author: Andrei Pisarevskii
* Author Email: [email protected]
* Author Site: https://wp-yoda.com/en/
*
* Version: 0.2
* Source Code: https://github.com/renakdup/simple-wordpress-dic
* Version: 0.2.1
* Source Code: https://github.com/renakdup/simple-php-dic
*
* Licence: MIT License
*/
Expand All @@ -20,6 +21,11 @@
use Closure;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionException;

use function array_key_exists;
use function class_exists;
use function is_string;

######## PSR11 2.0 interfaces #########
# If you want to support PSR11, then remove 3 interfaces below
Expand Down Expand Up @@ -107,7 +113,18 @@ public function get( string $id ) {
}

/**
* @inheritdoc
*/
public function has( string $id ): bool {
return array_key_exists( $id, $this->services );
}

/**
* @param string $id
*
* @return mixed
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
protected function resolve( string $id ) {
if ( $this->has( $id ) ) {
Expand All @@ -120,59 +137,90 @@ protected function resolve( string $id ) {
}

return $service;
} else {
if ( is_string( $id ) && class_exists( $id ) ) {
return $this->resolve_object( $id );
}
}

throw new ContainerNotFoundException( "Service '{$id}' not found in the Container." );
if ( class_exists( $id ) ) {
return $this->resolve_object( $id );
}

$message = "Service '{$id}' not found in the Container.\n"
. "Stack trace: \n"
. $this->get_stack_trace();
throw new ContainerNotFoundException( $message );
}

/**
* @param class-string $service
*
* @return object
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
protected function resolve_object( string $service ): object {
$reflected_class = new ReflectionClass( $service );
$constructor = $reflected_class->getConstructor();
try {
$reflected_class = new ReflectionClass( $service );

if ( ! $constructor ) {
return new $service();
}
$constructor = $reflected_class->getConstructor();

$params = $constructor->getParameters();
if ( ! $constructor ) {
return new $service();
}

if ( ! $params ) {
return new $service();
}
$params = $constructor->getParameters();

$constructor_args = [];
foreach ( $params as $param ) {
if ( $param_class = $param->getClass() ) {
$constructor_args[] = $this->get( $param_class->getName() );
continue;
if ( ! $params ) {
return new $service();
}

try {
$constructor_args = [];
foreach ( $params as $param ) {
if ( $param_class = $param->getClass() ) {
$constructor_args[] = $this->get( $param_class->getName() );
continue;
}

$default_value = $param->getDefaultValue();
if ( ! $default_value && $default_value !== null ) {
throw new ContainerException( 'Service "' . $reflected_class->getName() . '" could not be resolved due constructor parameter "' . $param->getName() . '"' );
$message = 'Service "' . $reflected_class->getName() . '" could not be resolved,' .
'because parameter of constructor "' . $param->getName() . '" has not default value.' . "\n" .
"Stack trace: \n" .
$this->get_stack_trace();
throw new ContainerException( $message );
}
} catch ( \ReflectionException $e ) {
throw new ContainerException( 'Service "' . $reflected_class->getName() . '" could not be resolved because parameter of constructor "' . $param . '" has the Reflection issue while resolving: ' . $e->getMessage() );
}

$constructor_args[] = $default_value;
$constructor_args[] = $default_value;
}
} catch ( ReflectionException $e ) {
throw new ContainerException(
"Service '{$service}' could not be resolved due the reflection issue:\n '" .
$e->getMessage() . "'\n" .
"Stack trace: \n" .
$e->getTraceAsString()
);
}

return new $service( ...$constructor_args );
}

/**
* @inheritdoc
*/
public function has( string $id ): bool {
return array_key_exists( $id, $this->services );
protected function get_stack_trace(): string {
$stackTraceArray = debug_backtrace();
$stackTraceString = '';

foreach ( $stackTraceArray as $item ) {
$file = $item['file'] ?? '[internal function]';
$line = $item['line'] ?? '';
$function = $item['function'] ?? ''; // @phpstan-ignore-line
$class = $item['class'] ?? '';
$type = $item['type'] ?? '';

$stackTraceString .= "{$file}({$line}): ";
if ( ! empty( $class ) ) {
$stackTraceString .= "{$class}{$type}";
}
$stackTraceString .= "{$function}()\n";
}

return $stackTraceString;
}
}

Expand Down
14 changes: 2 additions & 12 deletions tests/ContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,10 @@ public function test_get__autowiring_for_not_bound_invocable() {
}

public function test_get__autowiring_not_bound_deps() {
$obj1 = new SimpleClass();
$obj2 = new ClassWithConstructorPrimitives( $obj1 );
$obj3 = new ClassWithConstructor( $obj2 );
$obj = new ClassWithConstructor( new ClassWithConstructorPrimitives( new SimpleClass() ) );

$this->container->set( $name = ClassWithConstructor::class, ClassWithConstructor::class );
self::assertEquals( $obj3, $this->container->get( $name ) );
self::assertEquals( $obj, $this->container->get( $name ) );

$this->container->set( SplQueue::class , SplQueue::class );
self::assertInstanceOf( SplQueue::class, $this->container->get( SplQueue::class ) );
Expand All @@ -177,17 +175,9 @@ public function test_get__exception_not_found() {
$this->container->get( 'not-exist-service' );
}

// public function test_get__bind_autowiring_container_not_found_exception_class() {
// self::expectException( ContainerNotFoundException::class );
//
// $this->container->get( SimpleClass::class );
// }

public function test_get__autowiring__container_exception() {
self::expectException( ContainerException::class );

$this->container->set( SimpleClass::class, SimpleClass::class );
$this->container->set( ClassWithConstructorDepsException::class, ClassWithConstructorDepsException::class );
$this->container->get( ClassWithConstructorDepsException::class );
}

Expand Down

0 comments on commit a9c3eac

Please sign in to comment.