Skip to content

Commit

Permalink
Performance improvement: add reflection cache.
Browse files Browse the repository at this point in the history
- The resolve_object() function has been decomposed into several small ones.
- Add ignore error in the phpstan.neon.
  • Loading branch information
renakdup committed Dec 3, 2023
1 parent c8ed54f commit 4c11a64
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 23 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# 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)
[![Software License][ico-license]](LICENSE)



Expand Down Expand Up @@ -214,6 +215,7 @@ use Psr\Container\NotFoundExceptionInterface;
- [ ] Add `remove` method.
- [ ] Save cache in opcache.
- [ ] PHP 8 named arguments and autowiring.
- [ ] Add Performance Benchmarks

## Nice to have
- [x] Integrate CI with running autotests
Expand All @@ -225,3 +227,4 @@ use Psr\Container\NotFoundExceptionInterface;
- [ ] Add on packegist
- [ ] Add if class exist checks in the Container file?
- [ ] Rename Container.php to SimpleContainer.php
- [ ] Show stack trace when I have a debug only?
2 changes: 1 addition & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ parameters:
- ./src

ignoreErrors:

- '#Property Pisarevskii\\SimpleDIC\\Container::\$reflection_cache with generic class ReflectionClass does not specify its types#'
75 changes: 53 additions & 22 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.2
* Version: 0.2.3
* Source Code: https://github.com/renakdup/simple-php-dic
*
* Licence: MIT License
Expand All @@ -22,6 +22,7 @@
use InvalidArgumentException;
use ReflectionClass;
use ReflectionException;
use ReflectionParameter;

use function array_key_exists;
use function class_exists;
Expand Down Expand Up @@ -89,6 +90,11 @@ class Container implements ContainerInterface {
*/
protected array $resolved = [];

/**
* @var ReflectionClass[]
*/
protected array $reflection_cache = [];

/**
* Set service to the container. Allows to set configurable services
* using factory "function () {}" as passed service.
Expand All @@ -98,6 +104,7 @@ class Container implements ContainerInterface {
public function set( string $id, $service ): void {
$this->services[ $id ] = $service;
unset( $this->resolved[ $id ] );
unset( $this->reflection_cache[ $id ] );
}

/**
Expand Down Expand Up @@ -178,8 +185,9 @@ protected function resolve( string $id ) {
*/
protected function resolve_object( string $service ): object {
try {
$reflected_class = new ReflectionClass( $service );
$constructor = $reflected_class->getConstructor();
$reflected_class = $this->reflection_cache[ $service ] ?? new ReflectionClass( $service );

$constructor = $reflected_class->getConstructor();

if ( ! $constructor ) {
return new $service();
Expand All @@ -191,24 +199,8 @@ protected function resolve_object( string $service ): object {
return new $service();
}

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

$default_value = $param->getDefaultValue();
if ( ! $default_value && $default_value !== null ) {
$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 );
}

$resolved_params[] = $default_value;
}
$resolved_params = $this->resolve_parameters( $params );

} catch ( ReflectionException $e ) {
throw new ContainerException(
"Service '{$service}' could not be resolved due the reflection issue: '" . $e->getMessage() . "'\n" .
Expand All @@ -220,14 +212,53 @@ protected function resolve_object( string $service ): object {
return new $service( ...$resolved_params );
}

/**
* @param ReflectionParameter[] $params
*
* @return mixed[]
* @throws ContainerExceptionInterface
* @throws ReflectionException
*/
protected function resolve_parameters( array $params ): array {
$resolved_params = [];
foreach ( $params as $param ) {
$resolved_params[] = $this->resolve_parameter( $param );
}

return $resolved_params;
}

/**
* @param ReflectionParameter $param
*
* @return mixed|object
* @throws ContainerExceptionInterface
* @throws ReflectionException
*/
protected function resolve_parameter( ReflectionParameter $param ) {
if ( $param_class = $param->getClass() ) {
return $this->get( $param_class->getName() );
}

if ( $param->isOptional() ) {
return $param->getDefaultValue();
}

// @phpstan-ignore-next-line - Cannot call method getName() on ReflectionClass|null.
$message = "Parameter '{$param->getName()}' of '{$param->getDeclaringClass()->getName()}' cannot be resolved.\n" .
"Stack trace: \n" .
$this->get_stack_trace();
throw new ContainerException( $message );
}

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
$function = $item['function'] ?? ''; // @phpstan-ignore-line - 'function' on array always exists and is not nullable.
$class = $item['class'] ?? '';
$type = $item['type'] ?? '';

Expand Down

0 comments on commit 4c11a64

Please sign in to comment.