diff --git a/composer.json b/composer.json index 9b28d74..4408dbe 100644 --- a/composer.json +++ b/composer.json @@ -82,7 +82,7 @@ "missing-returntypes": "psalm --alter --issues=MissingReturnType", "normalizer": "@composer normalize --no-check-lock", "phpstan": "vendor/bin/phpstan --ansi --verbose --xdebug", - "phpunit": "phpunit --colors=always --testdox --stop-on-failure", + "phpunit": "vendor/bin/phpunit", "psalm": "@psalm:shepherd", "psalm:baseline": "psalm --set-baseline=psalm-baseline.xml", "psalm:dry-run": "psalm --alter --issues=all --dry-run", diff --git a/composer.lock b/composer.lock index 2c4fa9c..e100619 100644 --- a/composer.lock +++ b/composer.lock @@ -1030,16 +1030,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.15.1", + "version": "v3.16.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "d48755372a113bddb99f749e34805d83f3acfe04" + "reference": "d40f9436e1c448d309fa995ab9c14c5c7a96f2dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/d48755372a113bddb99f749e34805d83f3acfe04", - "reference": "d48755372a113bddb99f749e34805d83f3acfe04", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/d40f9436e1c448d309fa995ab9c14c5c7a96f2dc", + "reference": "d40f9436e1c448d309fa995ab9c14c5c7a96f2dc", "shasum": "" }, "require": { @@ -1114,7 +1114,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.15.1" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.16.0" }, "funding": [ { @@ -1122,7 +1122,7 @@ "type": "github" } ], - "time": "2023-03-13T23:26:30+00:00" + "time": "2023-04-02T19:30:06+00:00" }, { "name": "ghostwriter/coding-standard", @@ -1130,22 +1130,22 @@ "source": { "type": "git", "url": "https://github.com/ghostwriter/coding-standard.git", - "reference": "93d21f4c3dda787e70737c1ee7b985d393309ccf" + "reference": "65de38824b37e92d3d580d9c260fce2f2e52a646" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ghostwriter/coding-standard/zipball/93d21f4c3dda787e70737c1ee7b985d393309ccf", - "reference": "93d21f4c3dda787e70737c1ee7b985d393309ccf", + "url": "https://api.github.com/repos/ghostwriter/coding-standard/zipball/65de38824b37e92d3d580d9c260fce2f2e52a646", + "reference": "65de38824b37e92d3d580d9c260fce2f2e52a646", "shasum": "" }, "require": { - "friendsofphp/php-cs-fixer": "^3.15.1", + "friendsofphp/php-cs-fixer": "^3.16.0", "infection/infection": "^0.26.19", "php": ">=8.1,<8.3", "phpunit/phpunit": "^10.0.19", "psalm/plugin-phpunit": "^0.18.4", - "rector/rector": "^0.15.23", - "slevomat/coding-standard": "^8.9.1", + "rector/rector": "^0.15.24", + "slevomat/coding-standard": "^8.9.2", "squizlabs/php_codesniffer": "^3.7.2", "symplify/easy-coding-standard": "^11.3.2", "vimeo/psalm": "^5.9.0" @@ -1183,7 +1183,7 @@ "type": "github" } ], - "time": "2023-03-30T11:06:59+00:00" + "time": "2023-04-07T12:49:21+00:00" }, { "name": "infection/abstract-testframework-adapter", @@ -2085,16 +2085,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.16.1", + "version": "1.17.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "e27e92d939e2e3636f0a1f0afaba59692c0bf571" + "reference": "d3753fcb3abc6f78f5de6f72153d4b9c99c72dee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/e27e92d939e2e3636f0a1f0afaba59692c0bf571", - "reference": "e27e92d939e2e3636f0a1f0afaba59692c0bf571", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/d3753fcb3abc6f78f5de6f72153d4b9c99c72dee", + "reference": "d3753fcb3abc6f78f5de6f72153d4b9c99c72dee", "shasum": "" }, "require": { @@ -2124,22 +2124,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.16.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.17.1" }, - "time": "2023-02-07T18:11:17+00:00" + "time": "2023-04-04T11:11:22+00:00" }, { "name": "phpstan/phpstan", - "version": "1.10.10", + "version": "1.10.11", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "f1e22c9b17a879987f8743d81533250a5fff47f9" + "reference": "8aa62e6ea8b58ffb650e02940e55a788cbc3fe21" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f1e22c9b17a879987f8743d81533250a5fff47f9", - "reference": "f1e22c9b17a879987f8743d81533250a5fff47f9", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8aa62e6ea8b58ffb650e02940e55a788cbc3fe21", + "reference": "8aa62e6ea8b58ffb650e02940e55a788cbc3fe21", "shasum": "" }, "require": { @@ -2188,7 +2188,7 @@ "type": "tidelift" } ], - "time": "2023-04-01T17:06:15+00:00" + "time": "2023-04-04T19:17:42+00:00" }, { "name": "phpunit/php-code-coverage", @@ -2873,16 +2873,16 @@ }, { "name": "rector/rector", - "version": "0.15.23", + "version": "0.15.24", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "f4984ebd62b3613002869b0ddd6868261d62819e" + "reference": "716473919bcfdc27bdd2a32afb72adbf4c224e59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/f4984ebd62b3613002869b0ddd6868261d62819e", - "reference": "f4984ebd62b3613002869b0ddd6868261d62819e", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/716473919bcfdc27bdd2a32afb72adbf4c224e59", + "reference": "716473919bcfdc27bdd2a32afb72adbf4c224e59", "shasum": "" }, "require": { @@ -2922,7 +2922,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/0.15.23" + "source": "https://github.com/rectorphp/rector/tree/0.15.24" }, "funding": [ { @@ -2930,7 +2930,7 @@ "type": "github" } ], - "time": "2023-03-22T15:22:45+00:00" + "time": "2023-04-05T08:49:11+00:00" }, { "name": "sanmai/later", @@ -3966,32 +3966,32 @@ }, { "name": "slevomat/coding-standard", - "version": "8.9.1", + "version": "8.9.2", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "3d4fe0c803ae15829ef72d90d3d4eee3dd9f79b2" + "reference": "c9b39061ebd5c58b71ecae26042eb7b054c380dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/3d4fe0c803ae15829ef72d90d3d4eee3dd9f79b2", - "reference": "3d4fe0c803ae15829ef72d90d3d4eee3dd9f79b2", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/c9b39061ebd5c58b71ecae26042eb7b054c380dd", + "reference": "c9b39061ebd5c58b71ecae26042eb7b054c380dd", "shasum": "" }, "require": { "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", "php": "^7.2 || ^8.0", - "phpstan/phpdoc-parser": ">=1.16.0 <1.17.0", + "phpstan/phpdoc-parser": ">=1.16.0 <1.18.0", "squizlabs/php_codesniffer": "^3.7.1" }, "require-dev": { "phing/phing": "2.17.4", "php-parallel-lint/php-parallel-lint": "1.3.2", - "phpstan/phpstan": "1.4.10|1.10.8", + "phpstan/phpstan": "1.4.10|1.10.11", "phpstan/phpstan-deprecation-rules": "1.1.3", - "phpstan/phpstan-phpunit": "1.0.0|1.3.10", - "phpstan/phpstan-strict-rules": "1.5.0", - "phpunit/phpunit": "7.5.20|8.5.21|9.6.5" + "phpstan/phpstan-phpunit": "1.0.0|1.3.11", + "phpstan/phpstan-strict-rules": "1.5.1", + "phpunit/phpunit": "7.5.20|8.5.21|9.6.6" }, "type": "phpcodesniffer-standard", "extra": { @@ -4015,7 +4015,7 @@ ], "support": { "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.9.1" + "source": "https://github.com/slevomat/coding-standard/tree/8.9.2" }, "funding": [ { @@ -4027,7 +4027,7 @@ "type": "tidelift" } ], - "time": "2023-03-27T11:00:16+00:00" + "time": "2023-04-05T06:23:58+00:00" }, { "name": "spatie/array-to-xml", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 55b5be8..d4756b7 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -2,30 +2,31 @@ +> tests/Unit diff --git a/src/Container.php b/src/Container.php index e914fbb..2f3d3ac 100644 --- a/src/Container.php +++ b/src/Container.php @@ -71,17 +71,16 @@ final class Container implements ContainerInterface */ private array $cache = self::DEFAULT; - private static ?self $instance = null; + private static self|null $instance = null; - private function __construct( - private readonly Reflector $reflector = new Reflector() - ) { + private function __construct() + { // singleton } public function __destruct() { - $this->cache = self::DEFAULT; + self::$instance->cache = self::DEFAULT; } public function __clone() @@ -101,11 +100,11 @@ public function __unserialize(array $data): void public function alias(string $abstract, string $concrete): void { - if ('' === trim($abstract)) { + if (trim($abstract) === '') { throw $this->throwServiceIdMustBeNonEmptyString(); } - if ('' === trim($concrete)) { + if (trim($concrete) === '') { throw $this->throwServiceIdMustBeNonEmptyString(); } @@ -117,36 +116,27 @@ public function alias(string $abstract, string $concrete): void throw $this->throwNotFoundException('Service "%s" was not found.', $concrete); } - $this->cache[self::ALIASES][$abstract] = $concrete; + self::$instance->cache[self::ALIASES][$abstract] = $concrete; } - public function bind(string $abstract, ?string $concrete = null, array $tags = []): void + public function bind(string $abstract, string|null $concrete = null, array $tags = []): void { $concrete ??= $abstract; - if ('' === trim($abstract)) { - throw $this->throwServiceIdMustBeNonEmptyString(); - } - - if ('' === trim($concrete)) { + if (trim($abstract) === '' || trim($concrete) === '') { throw $this->throwServiceIdMustBeNonEmptyString(); } - if (array_key_exists($abstract, $this->cache[self::ALIASES])) { - throw $this->throwInvalidArgument('Service AlreadyRegisteredException %s', $abstract); - } - - if (array_key_exists($abstract, $this->cache[self::SERVICES])) { - throw $this->throwInvalidArgument('Service AlreadyRegisteredException %s', $abstract); - } - - if (array_key_exists($abstract, $this->cache[self::FACTORIES])) { + if (array_key_exists($abstract, self::$instance->cache[self::ALIASES]) || + array_key_exists($abstract, self::$instance->cache[self::SERVICES]) || + array_key_exists($abstract, self::$instance->cache[self::FACTORIES]) + ) { throw $this->throwInvalidArgument('Service AlreadyRegisteredException %s', $abstract); } - $this->cache[self::FACTORIES][$abstract] = + self::$instance->cache[self::FACTORIES][$abstract] = static fn (ContainerInterface $container): object => $container->build($concrete); - if ([] === $tags) { + if ($tags === []) { return; } @@ -155,29 +145,33 @@ public function bind(string $abstract, ?string $concrete = null, array $tags = [ public function build(string $class, array $arguments = []): object { - if ('' === trim($class)) { + if (trim($class) === '') { throw $this->throwServiceIdMustBeNonEmptyString(); } - if (self::class === $class) { + if ($class === self::class) { return $this; } - if (array_key_exists($class, $this->cache[self::PROVIDERS])) { + if (array_key_exists($class, self::$instance->cache[self::PROVIDERS])) { throw $this->throwInvalidArgument('ServiceProvider "%s" is already registered.', $class); } - $dependencies = $this->cache[self::DEPENDENCIES]; + $dependencies = self::$instance->cache[self::DEPENDENCIES]; if (array_key_exists($class, $dependencies)) { throw $this->throwNotFoundException( 'Circular dependency: %s -> %s', - implode(' -> ', $dependencies), + implode(' -> ', array_keys($dependencies)), $class, ); } - $reflectionClass = $this->reflector->reflect($class); + $reflectionClass = Reflector::getReflectionClass($class); + + if (! $reflectionClass->isInstantiable()) { + throw $this->throwInvalidArgument('Class "%s" is not instantiable.', $class); + } $reflectionMethod = $reflectionClass->getConstructor(); @@ -185,25 +179,25 @@ public function build(string $class, array $arguments = []): object $service = new $class(); if ($service instanceof ServiceProviderInterface) { - $this->cache[self::PROVIDERS][$class] = true; + self::$instance->cache[self::PROVIDERS][$class] = true; } - return $this->cache[self::SERVICES][$class] = $service; + return self::$instance->cache[self::SERVICES][$class] = $service; } - $this->cache[self::DEPENDENCIES][$class] = $class; + self::$instance->cache[self::DEPENDENCIES][$class] = true; $parameters = $this->buildParameters($reflectionMethod->getParameters(), $arguments); - unset($this->cache[self::DEPENDENCIES][$class]); + unset(self::$instance->cache[self::DEPENDENCIES][$class]); $service = new $class(...$parameters); if ($service instanceof ServiceProviderInterface) { - $this->cache[self::PROVIDERS][$class] = true; + self::$instance->cache[self::PROVIDERS][$class] = true; } - return $this->cache[self::SERVICES][$class] = $service; + return self::$instance->cache[self::SERVICES][$class] = $service; } public function call(callable|string $invokable, array $arguments = []): mixed @@ -222,12 +216,12 @@ public function call(callable|string $invokable, array $arguments = []): mixed public function extend(string $class, callable $extension): void { - if ('' === trim($class)) { + if (trim($class) === '') { throw $this->throwServiceIdMustBeNonEmptyString(); } - $factories = $this->cache[self::FACTORIES]; - $extensions = $this->cache[self::EXTENSIONS]; + $factories = self::$instance->cache[self::FACTORIES]; + $extensions = self::$instance->cache[self::EXTENSIONS]; if (! array_key_exists($class, $extensions) && ! array_key_exists($class, $factories) && @@ -236,7 +230,7 @@ public function extend(string $class, callable $extension): void throw $this->throwNotFoundException('Service "%s" was not found.', $class); } - $this->cache[self::EXTENSIONS][$class] = array_key_exists($class, $extensions) ? + self::$instance->cache[self::EXTENSIONS][$class] = array_key_exists($class, $extensions) ? static fn ( ContainerInterface $container, object $service @@ -251,19 +245,18 @@ public function get(string $id): mixed { $id = $this->resolve($id); - if (self::class === $id) { - return $this; - } - - if (array_key_exists($id, $this->cache)) { - return $this->cache[$id]; + if (array_key_exists($id, self::$instance->cache)) { + return self::$instance->cache[$id]; } - if (array_key_exists($id, $this->cache[self::SERVICES])) { - return $this->cache[self::SERVICES][$id]; + if (array_key_exists($id, self::$instance->cache[self::SERVICES])) { + return match (true) { + $id === self::class => $this, + default => self::$instance->cache[self::SERVICES][$id] + }; } - $factories = $this->cache[self::FACTORIES]; + $factories = self::$instance->cache[self::FACTORIES]; if (! array_key_exists($id, $factories) && ! class_exists($id)) { throw $this->throwNotFoundException('Service "%s" was not found.', $id); @@ -271,9 +264,9 @@ public function get(string $id): mixed $serviceFactory = $factories[$id] ?? static fn (Container $container): object => $container->build($id); - $extensions = $this->cache[self::EXTENSIONS]; + $extensions = self::$instance->cache[self::EXTENSIONS]; - return $this->cache[self::SERVICES][$id] = array_key_exists($id, $extensions) ? + return self::$instance->cache[self::SERVICES][$id] = array_key_exists($id, $extensions) ? $extensions[$id]($this, $serviceFactory($this)) : $serviceFactory($this); } @@ -287,9 +280,9 @@ public function has(string $id): bool { $id = $this->resolve($id); - return array_key_exists($id, $this->cache[self::SERVICES]) || - array_key_exists($id, $this->cache[self::FACTORIES]) || - array_key_exists($id, $this->cache[self::ALIASES]); + return array_key_exists($id, self::$instance->cache[self::SERVICES]) || + array_key_exists($id, self::$instance->cache[self::FACTORIES]) || + array_key_exists($id, self::$instance->cache[self::ALIASES]); } public function register(string $serviceProvider): void @@ -307,7 +300,7 @@ public function register(string $serviceProvider): void public function remove(string $id): void { - if ('' === trim($id)) { + if (trim($id) === '') { throw $this->throwServiceIdMustBeNonEmptyString(); } @@ -316,11 +309,11 @@ public function remove(string $id): void } unset( - $this->cache[self::ALIASES][$id], - $this->cache[self::EXTENSIONS][$id], - $this->cache[self::FACTORIES][$id], - $this->cache[self::SERVICES][$id], - $this->cache[self::TAGS][$id] + self::$instance->cache[self::ALIASES][$id], + self::$instance->cache[self::EXTENSIONS][$id], + self::$instance->cache[self::FACTORIES][$id], + self::$instance->cache[self::SERVICES][$id], + self::$instance->cache[self::TAGS][$id] ); } @@ -332,11 +325,11 @@ public function replace(string $id, mixed $value, array $tags = []): void public function resolve(string $id): string { - if ('' === trim($id)) { + if (trim($id) === '') { throw $this->throwServiceIdMustBeNonEmptyString(); } - $aliases = $this->cache[self::ALIASES]; + $aliases = self::$instance->cache[self::ALIASES]; while (array_key_exists($id, $aliases)) { $id = $aliases[$id]; } @@ -346,29 +339,29 @@ public function resolve(string $id): string public function set(string $id, mixed $value, array $tags = []): void { - if ('' === trim($id)) { + if (trim($id) === '') { throw $this->throwServiceIdMustBeNonEmptyString(); } - if (array_key_exists($id, $this->cache[self::SERVICES])) { + if (array_key_exists($id, self::$instance->cache[self::SERVICES])) { throw $this->throwServiceAlreadyRegisteredException($id); } - if (array_key_exists($id, $this->cache[self::FACTORIES])) { + if (array_key_exists($id, self::$instance->cache[self::FACTORIES])) { throw $this->throwServiceAlreadyRegisteredException($id); } - if (array_key_exists($id, $this->cache[self::ALIASES])) { + if (array_key_exists($id, self::$instance->cache[self::ALIASES])) { throw $this->throwServiceAlreadyRegisteredException($id); } if (is_callable($value)) { - $this->cache[self::FACTORIES][$id] = $value; + self::$instance->cache[self::FACTORIES][$id] = $value; } else { - $this->cache[self::SERVICES][$id] = $value; + self::$instance->cache[self::SERVICES][$id] = $value; } - if ([] === $tags) { + if ($tags === []) { return; } @@ -380,21 +373,21 @@ public function set(string $id, mixed $value, array $tags = []): void */ public function tag(string $id, array $tags): void { - if ('' === trim($id)) { + if (trim($id) === '') { throw $this->throwServiceIdMustBeNonEmptyString(); } - $serviceTags = $this->cache[self::TAGS]; + $serviceTags = self::$instance->cache[self::TAGS]; foreach ($tags as $tag) { - if ('' === trim($tag)) { + if (trim($tag) === '') { throw $this->throwServiceIdMustBeNonEmptyString(); } $serviceTags[$tag][$id] ??= $id; } - $this->cache[self::TAGS] = $serviceTags; + self::$instance->cache[self::TAGS] = $serviceTags; } /** @@ -406,7 +399,7 @@ public function tag(string $id, array $tags): void public function tagged(string $tag): Generator { /** @var class-string|string $service */ - foreach ($this->cache[self::TAGS][$tag] ?? [] as $service) { + foreach (self::$instance->cache[self::TAGS][$tag] ?? [] as $service) { yield $this->get($service); } } @@ -420,7 +413,7 @@ private function buildParameters(array $reflectionParameters, array $arguments): */ function (ReflectionParameter $reflectionParameter) use (&$arguments) { $parameterName = $reflectionParameter->getName(); - if ([] !== $arguments) { + if ($arguments !== []) { $parameterKey = array_key_exists($parameterName, $arguments) ? $parameterName : array_key_first($arguments); diff --git a/src/Contract/ContainerInterface.php b/src/Contract/ContainerInterface.php index 269f996..e9df53d 100644 --- a/src/Contract/ContainerInterface.php +++ b/src/Contract/ContainerInterface.php @@ -51,7 +51,7 @@ public function alias(string $abstract, string $concrete): void; * @throws NotFoundExceptionInterface * @throws ContainerExceptionInterface */ - public function bind(string $abstract, ?string $concrete = null, array $tags = []): void; + public function bind(string $abstract, string|null $concrete = null, array $tags = []): void; /** * Create an object using the given Container to resolve dependencies. diff --git a/tests/Unit/ContainerTest.php b/tests/Unit/ContainerTest.php index 0ae282c..62be543 100644 --- a/tests/Unit/ContainerTest.php +++ b/tests/Unit/ContainerTest.php @@ -12,6 +12,7 @@ use Ghostwriter\Container\Contract\Exception\NotFoundExceptionInterface; use Ghostwriter\Container\Contract\ServiceProviderInterface; use Ghostwriter\Container\Reflector; +use Ghostwriter\Container\ReflectorException; use Ghostwriter\Container\Tests\Fixture\Bar; use Ghostwriter\Container\Tests\Fixture\Baz; use Ghostwriter\Container\Tests\Fixture\CircularDependency\ClassA; @@ -62,13 +63,6 @@ #[CoversClass(Container::class)] #[UsesClass(Reflector::class)] #[Small] -/** - * @internal - * - * @small - * - * @coversNothing - */ final class ContainerTest extends TestCase { private Container $container; @@ -389,7 +383,8 @@ public function testCircularDependencyException(): void public function testClassDoseNotExistException(): void { $this->expectException(ContainerExceptionInterface::class); - $this->expectExceptionMessage('Class "dose-not-exist" does not exist.'); + $this->expectException(ReflectorException::class); + $this->expectExceptionMessage('Class "dose-not-exist" does not exist'); $this->container->build('dose-not-exist'); }