From 4c11a6439bfb50ca4289a8c7279ecb8c6a0c22ac Mon Sep 17 00:00:00 2001 From: Andrei Pisarevskii Date: Sun, 3 Dec 2023 12:05:17 +0300 Subject: [PATCH] Performance improvement: add reflection cache. - The resolve_object() function has been decomposed into several small ones. - Add ignore error in the phpstan.neon. --- README.md | 3 ++ phpstan.neon | 2 +- src/Container.php | 75 +++++++++++++++++++++++++++++++++-------------- 3 files changed, 57 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 612d641..89dc99a 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 @@ -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? diff --git a/phpstan.neon b/phpstan.neon index 28daef7..08d4dbe 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -16,4 +16,4 @@ parameters: - ./src ignoreErrors: - + - '#Property Pisarevskii\\SimpleDIC\\Container::\$reflection_cache with generic class ReflectionClass does not specify its types#' \ No newline at end of file diff --git a/src/Container.php b/src/Container.php index bf7b8e2..d439ca5 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.2 + * Version: 0.2.3 * Source Code: https://github.com/renakdup/simple-php-dic * * Licence: MIT License @@ -22,6 +22,7 @@ use InvalidArgumentException; use ReflectionClass; use ReflectionException; +use ReflectionParameter; use function array_key_exists; use function class_exists; @@ -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. @@ -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 ] ); } /** @@ -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(); @@ -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" . @@ -220,6 +212,45 @@ 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 = ''; @@ -227,7 +258,7 @@ protected function get_stack_trace(): string { 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'] ?? '';