From 6d17deb34093b43c0052c02a49068dacc4a516b3 Mon Sep 17 00:00:00 2001 From: Miguel Date: Tue, 16 Mar 2021 22:46:56 +0100 Subject: [PATCH 01/12] Removed placeholder files --- src/.gitkeep | 0 tests/functional/ThingTest.php | 15 --------------- 2 files changed, 15 deletions(-) delete mode 100644 src/.gitkeep delete mode 100644 tests/functional/ThingTest.php diff --git a/src/.gitkeep b/src/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/functional/ThingTest.php b/tests/functional/ThingTest.php deleted file mode 100644 index 4899f5a..0000000 --- a/tests/functional/ThingTest.php +++ /dev/null @@ -1,15 +0,0 @@ -expectException(ExpectationFailedException::class); - $this->assertTrue(false); - } -} From f37abe71726ba81836d71b2092c422cb99e0169e Mon Sep 17 00:00:00 2001 From: Miguel Date: Tue, 16 Mar 2021 22:47:14 +0100 Subject: [PATCH 02/12] Updated PHPStorm project --- .idea/php-test-framework.xml | 1 + .idea/php.xml | 10 ++++++---- .idea/service-tools.iml | 2 ++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.idea/php-test-framework.xml b/.idea/php-test-framework.xml index 6e18748..e4b38b3 100644 --- a/.idea/php-test-framework.xml +++ b/.idea/php-test-framework.xml @@ -6,6 +6,7 @@ + diff --git a/.idea/php.xml b/.idea/php.xml index d1ee7ac..47d76e1 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -56,6 +56,8 @@ + + @@ -71,7 +73,7 @@ - + @@ -86,7 +88,7 @@ - + /usr/local/etc/php/conf.d/docker-php-ext-sodium.ini /usr/local/etc/php/php.ini @@ -131,14 +133,14 @@ - + /usr/local/etc/php/conf.d/docker-php-ext-pcntl.ini, /usr/local/etc/php/conf.d/docker-php-ext-sodium.ini /usr/local/etc/php/php.ini - + diff --git a/.idea/service-tools.iml b/.idea/service-tools.iml index 4655c7b..b8c6440 100644 --- a/.idea/service-tools.iml +++ b/.idea/service-tools.iml @@ -60,6 +60,8 @@ + + From 4906a0826f4a2030bb3d9e973264c023cead2d6e Mon Sep 17 00:00:00 2001 From: Miguel Date: Tue, 16 Mar 2021 22:47:30 +0100 Subject: [PATCH 03/12] Installed deps --- composer.json | 4 +- composer.lock | 192 +++++++++++++++++++++++++++++++++++++------------- 2 files changed, 145 insertions(+), 51 deletions(-) diff --git a/composer.json b/composer.json index d819b75..741caf3 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,9 @@ } ], "require": { - "php": "^7.2 | ^8.0" + "php": "^7.2 | ^8.0", + "dhii/services-interface": "0.1.x-dev", + "container-interop/service-provider": "dev-master" }, "require-dev": { "phpunit/phpunit": "^8.0 | ^9.0", diff --git a/composer.lock b/composer.lock index b6ecd77..4e7c395 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,146 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "729391b2771e2af71923e9d4bc8db183", - "packages": [], + "content-hash": "cffacf5fa251540078ade8dc53ad5969", + "packages": [ + { + "name": "container-interop/service-provider", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/container-interop/service-provider.git", + "reference": "4969b9e49460690b7430b3f1a87cab07be61418a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/container-interop/service-provider/zipball/4969b9e49460690b7430b3f1a87cab07be61418a", + "reference": "4969b9e49460690b7430b3f1a87cab07be61418a", + "shasum": "" + }, + "require": { + "psr/container": "^1.0" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting container interoperability through standard service providers", + "homepage": "https://github.com/container-interop/service-provider", + "support": { + "issues": "https://github.com/container-interop/service-provider/issues", + "source": "https://github.com/container-interop/service-provider/tree/v0.4.0" + }, + "time": "2017-09-20T14:13:36+00:00" + }, + { + "name": "dhii/services-interface", + "version": "0.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/Dhii/services-interface.git", + "reference": "64d6fa439c1a5f02f9445d37838254f4517f49a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dhii/services-interface/zipball/64d6fa439c1a5f02f9445d37838254f4517f49a8", + "reference": "64d6fa439c1a5f02f9445d37838254f4517f49a8", + "shasum": "" + }, + "require": { + "php": "^7.1 | ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 | ^8.0 | ^9.0", + "slevomat/coding-standard": "^6.0", + "vimeo/psalm": "^4.0", + "webmozart/path-util": "^2.3@stable" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-initial": "0.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dhii\\Services\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Anton Ukhanev", + "email": "xedin.unknown@gmail.com", + "role": "Developer" + } + ], + "description": "Interfaces for services compatible with Service Provider spec", + "support": { + "issues": "https://github.com/Dhii/services-interface/issues", + "source": "https://github.com/Dhii/services-interface/tree/0.1.x" + }, + "time": "2021-03-16T06:29:41+00:00" + }, + { + "name": "psr/container", + "version": "1.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.x" + }, + "time": "2021-03-05T17:36:06+00:00" + } + ], "packages-dev": [ { "name": "amphp/amp", @@ -1675,54 +1813,6 @@ ], "time": "2021-02-23T15:50:35+00:00" }, - { - "name": "psr/container", - "version": "1.1.x-dev", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.x" - }, - "time": "2021-03-05T17:36:06+00:00" - }, { "name": "psr/log", "version": "dev-master", @@ -3645,6 +3735,8 @@ "aliases": [], "minimum-stability": "dev", "stability-flags": { + "dhii/services-interface": 20, + "container-interop/service-provider": 20, "webmozart/path-util": 0 }, "prefer-stable": false, From c85efad9a9426ed9fbe8307c9d5ab6367f5876b2 Mon Sep 17 00:00:00 2001 From: Miguel Date: Tue, 16 Mar 2021 22:53:02 +0100 Subject: [PATCH 04/12] Initial implementation --- src/AnalyzerInterface.php | 14 +++ src/Analyzers/AggregateAnalyzer.php | 32 ++++++ src/Analyzers/CircularDependencyAnalyzer.php | 62 +++++++++++ src/Analyzers/InvalidExtensionAnalyzer.php | 23 ++++ .../UnfulfilledDependencyAnalyzer.php | 36 +++++++ src/Issue.php | 44 ++++++++ src/Issues/CircularDepIssue.php | 37 +++++++ src/Issues/DependencyIssue.php | 28 +++++ src/Issues/ServiceIssue.php | 30 ++++++ src/Utils/GetServiceDepsCapableTrait.php | 20 ++++ src/Utils/ResolveServicesCapableTrait.php | 25 +++++ .../Analyzers/AggregateAnalyzerTest.php | 53 ++++++++++ .../CircularDependencyAnalyzerTest.php | 100 ++++++++++++++++++ .../InvalidExtensionAnalyzerTest.php | 57 ++++++++++ .../UnfulfilledDependencyAnalyzerTest.php | 82 ++++++++++++++ tests/functional/IssueTest.php | 27 +++++ .../Issues/CircularDepIssueTest.php | 63 +++++++++++ .../functional/Issues/DependencyIssueTest.php | 25 +++++ tests/functional/Issues/ServiceIssueTest.php | 23 ++++ .../Utils/GetServiceDepsCapableTraitTest.php | 38 +++++++ .../Utils/ResolveServicesCapableTraitTest.php | 48 +++++++++ 21 files changed, 867 insertions(+) create mode 100644 src/AnalyzerInterface.php create mode 100644 src/Analyzers/AggregateAnalyzer.php create mode 100644 src/Analyzers/CircularDependencyAnalyzer.php create mode 100644 src/Analyzers/InvalidExtensionAnalyzer.php create mode 100644 src/Analyzers/UnfulfilledDependencyAnalyzer.php create mode 100644 src/Issue.php create mode 100644 src/Issues/CircularDepIssue.php create mode 100644 src/Issues/DependencyIssue.php create mode 100644 src/Issues/ServiceIssue.php create mode 100644 src/Utils/GetServiceDepsCapableTrait.php create mode 100644 src/Utils/ResolveServicesCapableTrait.php create mode 100644 tests/functional/Analyzers/AggregateAnalyzerTest.php create mode 100644 tests/functional/Analyzers/CircularDependencyAnalyzerTest.php create mode 100644 tests/functional/Analyzers/InvalidExtensionAnalyzerTest.php create mode 100644 tests/functional/Analyzers/UnfulfilledDependencyAnalyzerTest.php create mode 100644 tests/functional/IssueTest.php create mode 100644 tests/functional/Issues/CircularDepIssueTest.php create mode 100644 tests/functional/Issues/DependencyIssueTest.php create mode 100644 tests/functional/Issues/ServiceIssueTest.php create mode 100644 tests/functional/Utils/GetServiceDepsCapableTraitTest.php create mode 100644 tests/functional/Utils/ResolveServicesCapableTraitTest.php diff --git a/src/AnalyzerInterface.php b/src/AnalyzerInterface.php new file mode 100644 index 0000000..2b9aaa6 --- /dev/null +++ b/src/AnalyzerInterface.php @@ -0,0 +1,14 @@ + + */ + public function analyze(array $factories, array $extensions): iterable; +} diff --git a/src/Analyzers/AggregateAnalyzer.php b/src/Analyzers/AggregateAnalyzer.php new file mode 100644 index 0000000..701cad0 --- /dev/null +++ b/src/Analyzers/AggregateAnalyzer.php @@ -0,0 +1,32 @@ +analyzers = $analyzers; + } + + /** @inheritDoc */ + public function analyze(array $factories, array $extensions): iterable + { + foreach ($this->analyzers as $analyzer) { + yield from $analyzer->analyze($factories, $extensions); + } + } +} diff --git a/src/Analyzers/CircularDependencyAnalyzer.php b/src/Analyzers/CircularDependencyAnalyzer.php new file mode 100644 index 0000000..ac4b570 --- /dev/null +++ b/src/Analyzers/CircularDependencyAnalyzer.php @@ -0,0 +1,62 @@ +walk($factories, $factories); + yield from $this->walk($factories, $extensions); + } + + /** + * Walks a list of services, recursively walking their dependencies while keeping track of what services have + * already been visited. If a service is visited twice, an {@link Issue} is yielded. + * + * @param array $factories The entire list of factories. Used to resolve service/dependency keys. + * @param array $toWalk The list of services to walk. + * @param array $visited A dictionary of keys for the services that have been "walked". This acts as a buffer + * stack and is used to detect when the walk re-visits an already visited service. + * @param array $skip A dictionary of keys for services that should be skipped. Services that were detected as + * being a part of a circular dependency are added to this list to prevent reporting the + * same circular chain multiple times. + * + * @return iterable + */ + protected function walk(array $factories, array $toWalk, array &$visited = [], array &$skip = []): iterable + { + foreach ($toWalk as $key => $service) { + if (array_key_exists($key, $skip)) { + continue; + } + + if (array_key_exists($key, $visited)) { + $skip = array_merge($skip, $visited); + yield new CircularDepIssue(array_values($visited)); + } else { + $visited[$key] = $key; + + $depKeys = $this->getServiceDeps($service); + $depServices = $this->resolveServices($factories, $depKeys); + + yield from $this->walk($factories, $depServices, $visited, $skip); + + unset($visited[$key]); + } + } + } +} diff --git a/src/Analyzers/InvalidExtensionAnalyzer.php b/src/Analyzers/InvalidExtensionAnalyzer.php new file mode 100644 index 0000000..b50a2e5 --- /dev/null +++ b/src/Analyzers/InvalidExtensionAnalyzer.php @@ -0,0 +1,23 @@ + $extension) { + if (!array_key_exists($key, $factories)) { + yield new ServiceIssue(Issue::WARNING, 'Extension extends unknown service', $key); + } + } + } +} diff --git a/src/Analyzers/UnfulfilledDependencyAnalyzer.php b/src/Analyzers/UnfulfilledDependencyAnalyzer.php new file mode 100644 index 0000000..494ba86 --- /dev/null +++ b/src/Analyzers/UnfulfilledDependencyAnalyzer.php @@ -0,0 +1,36 @@ +scan($factories, $factories); + yield from $this->scan($factories, $extensions); + } + + public function scan(array $factories, array $services): iterable + { + foreach ($services as $key => $service) { + $deps = $this->getServiceDeps($service); + + foreach ($deps as $dep) { + if (!array_key_exists($dep, $factories)) { + yield new DependencyIssue(Issue::WARNING, 'Unfulfilled dependency', $key, $dep); + } + } + } + } +} diff --git a/src/Issue.php b/src/Issue.php new file mode 100644 index 0000000..27633d5 --- /dev/null +++ b/src/Issue.php @@ -0,0 +1,44 @@ +severity = $severity; + $this->message = $message; + } + + /** + * @return int + */ + public function getSeverity(): int + { + return $this->severity; + } + + /** + * @return string + */ + public function getMessage(): string + { + return $this->message; + } +} diff --git a/src/Issues/CircularDepIssue.php b/src/Issues/CircularDepIssue.php new file mode 100644 index 0000000..f9e11c8 --- /dev/null +++ b/src/Issues/CircularDepIssue.php @@ -0,0 +1,37 @@ + ', $depChain) . ' -> ' . $service; + + parent::__construct(static::ERROR, $message, $service); + $this->depChain = $depChain; + } + + /** + * @return string[] + */ + public function getDependencyChain(): array + { + return $this->depChain; + } +} diff --git a/src/Issues/DependencyIssue.php b/src/Issues/DependencyIssue.php new file mode 100644 index 0000000..53ed34f --- /dev/null +++ b/src/Issues/DependencyIssue.php @@ -0,0 +1,28 @@ +dependency = $dependency; + } + + /** + * @return string + */ + public function getDependency(): string + { + return $this->dependency; + } +} diff --git a/src/Issues/ServiceIssue.php b/src/Issues/ServiceIssue.php new file mode 100644 index 0000000..6783dc4 --- /dev/null +++ b/src/Issues/ServiceIssue.php @@ -0,0 +1,30 @@ +service = $service; + } + + /** + * @return string + */ + public function getService(): string + { + return $this->service; + } +} diff --git a/src/Utils/GetServiceDepsCapableTrait.php b/src/Utils/GetServiceDepsCapableTrait.php new file mode 100644 index 0000000..2e2f2f7 --- /dev/null +++ b/src/Utils/GetServiceDepsCapableTrait.php @@ -0,0 +1,20 @@ +getDependencies() + : []; + } +} diff --git a/src/Utils/ResolveServicesCapableTrait.php b/src/Utils/ResolveServicesCapableTrait.php new file mode 100644 index 0000000..13d54f2 --- /dev/null +++ b/src/Utils/ResolveServicesCapableTrait.php @@ -0,0 +1,25 @@ +assertInstanceOf(AnalyzerInterface::class, $subject); + } + + public function testAnalyze() + { + $issues1 = [ + new Issue(Issue::WARNING, 'Oh no'), + new Issue(Issue::SMELL, 'Your code smells bad'), + ]; + $issues2 = [ + new Issue(Issue::ERROR, 'This needs fixing'), + new Issue(Issue::WARNING, 'This might end badly'), + ]; + + $factories = ['a' => function () { }, 'b' => function () { }]; + $extensions = ['a' => function () { }, 'b' => function () { }]; + + $analyzer1 = $this->createMock(AnalyzerInterface::class); + $analyzer2 = $this->createMock(AnalyzerInterface::class); + + $analyzer1->expects($this->once()) + ->method('analyze') + ->with($factories, $extensions) + ->willReturn($issues1); + $analyzer2->expects($this->once()) + ->method('analyze') + ->with($factories, $extensions) + ->willReturn($issues2); + + $subject = new AggregateAnalyzer([$analyzer1, $analyzer2]); + + $expected = array_merge($issues1, $issues2); + $actual = $subject->analyze($factories, $extensions); + $actual = is_array($actual) ? $actual : iterator_to_array($actual, false); + + $this->assertEquals($expected, $actual); + } +} diff --git a/tests/functional/Analyzers/CircularDependencyAnalyzerTest.php b/tests/functional/Analyzers/CircularDependencyAnalyzerTest.php new file mode 100644 index 0000000..6e6627c --- /dev/null +++ b/tests/functional/Analyzers/CircularDependencyAnalyzerTest.php @@ -0,0 +1,100 @@ +assertInstanceOf(AnalyzerInterface::class, $subject); + } + + public function testNoProblems() + { + $factories = [ + 'a' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['b']]), + 'b' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['c']]), + 'c' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => []]), + ]; + $extensions = [ + 'b' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => []]), + ]; + + $subject = new CircularDependencyAnalyzer(); + $issues = $subject->analyze($factories, $extensions); + $issues = is_array($issues) ? $issues : iterator_to_array($issues, false); + + $this->assertCount(0, $issues); + } + + public function testAnalyzeFactories() + { + $factories = [ + 'a' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['b']]), + 'b' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['c']]), + 'c' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['a']]), + ]; + $extensions = [ + 'b' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => []]), + ]; + + $subject = new CircularDependencyAnalyzer(); + $issues = $subject->analyze($factories, $extensions); + $issues = is_array($issues) ? $issues : iterator_to_array($issues, false); + + $this->assertCount(1, $issues); + $this->assertInstanceOf(CircularDepIssue::class, $issues[0]); + $this->assertEquals(['a', 'b', 'c'], $issues[0]->getDependencyChain()); + $this->assertEquals('a', $issues[0]->getService()); + } + + public function testAnalyzeExtensions() + { + $factories = [ + 'a' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['b']]), + 'b' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['c']]), + 'c' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => []]), + ]; + $extensions = [ + 'c' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['a']]), + ]; + + $subject = new CircularDependencyAnalyzer(); + $issues = $subject->analyze($factories, $extensions); + $issues = is_array($issues) ? $issues : iterator_to_array($issues, false); + + $this->assertCount(1, $issues); + $this->assertInstanceOf(CircularDepIssue::class, $issues[0]); + $this->assertEquals(['c', 'a', 'b'], $issues[0]->getDependencyChain()); + $this->assertEquals('c', $issues[0]->getService()); + } + + public function testAnalyzeMultipleChains() + { + $factories = [ + 'a' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['b']]), + 'b' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['c']]), + 'c' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['a']]), + 'd' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => []]), + ]; + $extensions = [ + 'd' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['a']]), + ]; + + $subject = new CircularDependencyAnalyzer(); + $issues = $subject->analyze($factories, $extensions); + $issues = is_array($issues) ? $issues : iterator_to_array($issues, false); + + $this->assertCount(2, $issues); + $this->assertEquals(['a', 'b', 'c'], $issues[0]->getDependencyChain()); + $this->assertEquals(['d', 'a', 'b', 'c'], $issues[1]->getDependencyChain()); + } +} diff --git a/tests/functional/Analyzers/InvalidExtensionAnalyzerTest.php b/tests/functional/Analyzers/InvalidExtensionAnalyzerTest.php new file mode 100644 index 0000000..06844ef --- /dev/null +++ b/tests/functional/Analyzers/InvalidExtensionAnalyzerTest.php @@ -0,0 +1,57 @@ +assertInstanceOf(AnalyzerInterface::class, $subject); + } + + public function testAnalyze() + { + $factories = [ + 'a' => $this->createMock(ServiceInterface::class), + 'b' => $this->createMock(ServiceInterface::class), + ]; + $extensions = [ + 'c' => $this->createMock(ServiceInterface::class), + ]; + + $subject = new InvalidExtensionAnalyzer(); + $issues = $subject->analyze($factories, $extensions); + $issues = is_array($issues) ? $issues : iterator_to_array($issues, false); + + $this->assertCount(1, $issues); + $this->assertInstanceOf(ServiceIssue::class, $issues[0]); + $this->assertEquals('c', $issues[0]->getService()); + $this->assertEquals(Issue::WARNING, $issues[0]->getSeverity()); + } + + public function testAnalyzeNoProblems() + { + $factories = [ + 'a' => $this->createMock(ServiceInterface::class), + 'b' => $this->createMock(ServiceInterface::class), + ]; + $extensions = [ + 'b' => $this->createMock(ServiceInterface::class), + ]; + + $subject = new InvalidExtensionAnalyzer(); + $issues = $subject->analyze($factories, $extensions); + $issues = is_array($issues) ? $issues : iterator_to_array($issues, false); + + $this->assertCount(0, $issues); + } +} diff --git a/tests/functional/Analyzers/UnfulfilledDependencyAnalyzerTest.php b/tests/functional/Analyzers/UnfulfilledDependencyAnalyzerTest.php new file mode 100644 index 0000000..508f194 --- /dev/null +++ b/tests/functional/Analyzers/UnfulfilledDependencyAnalyzerTest.php @@ -0,0 +1,82 @@ +assertInstanceOf(AnalyzerInterface::class, $subject); + } + + public function testAnalyzeFactories() + { + $factories = [ + 'a' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['b']]), + 'b' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['c', 'd']]), + 'c' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => []]), + ]; + $extensions = [ + 'c' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['a']]), + ]; + + $subject = new UnfulfilledDependencyAnalyzer(); + $issues = $subject->analyze($factories, $extensions); + $issues = is_array($issues) ? $issues : iterator_to_array($issues, false); + + $this->assertCount(1, $issues); + $this->assertInstanceOf(DependencyIssue::class, $issues[0]); + $this->assertEquals('b', $issues[0]->getService()); + $this->assertEquals('d', $issues[0]->getDependency()); + $this->assertEquals(Issue::WARNING, $issues[0]->getSeverity()); + } + + public function testAnalyzeExtensions() + { + $factories = [ + 'a' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['b']]), + 'b' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['c']]), + 'c' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => []]), + ]; + $extensions = [ + 'c' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['d']]), + ]; + + $subject = new UnfulfilledDependencyAnalyzer(); + $issues = $subject->analyze($factories, $extensions); + $issues = is_array($issues) ? $issues : iterator_to_array($issues, false); + + $this->assertCount(1, $issues); + $this->assertInstanceOf(DependencyIssue::class, $issues[0]); + $this->assertEquals('c', $issues[0]->getService()); + $this->assertEquals('d', $issues[0]->getDependency()); + $this->assertEquals(Issue::WARNING, $issues[0]->getSeverity()); + } + + public function testAnalyzeEverything() + { + $factories = [ + 'a' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['b']]), + 'b' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['c', 'd']]), + 'c' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => []]), + ]; + $extensions = [ + 'c' => $this->createConfiguredMock(ServiceInterface::class, ['getDependencies' => ['e']]), + ]; + + $subject = new UnfulfilledDependencyAnalyzer(); + $issues = $subject->analyze($factories, $extensions); + $issues = is_array($issues) ? $issues : iterator_to_array($issues, false); + + $this->assertCount(2, $issues); + } +} diff --git a/tests/functional/IssueTest.php b/tests/functional/IssueTest.php new file mode 100644 index 0000000..d7952e4 --- /dev/null +++ b/tests/functional/IssueTest.php @@ -0,0 +1,27 @@ +assertEquals($severity, $subject->getSeverity()); + $this->assertEquals($message, $subject->getMessage()); + } + + public function testSeverityValues() + { + $this->assertTrue(Issue::SMELL < Issue::WARNING); + $this->assertTrue(Issue::SMELL < Issue::ERROR); + $this->assertTrue(Issue::WARNING < Issue::ERROR); + } +} diff --git a/tests/functional/Issues/CircularDepIssueTest.php b/tests/functional/Issues/CircularDepIssueTest.php new file mode 100644 index 0000000..a5e88a7 --- /dev/null +++ b/tests/functional/Issues/CircularDepIssueTest.php @@ -0,0 +1,63 @@ +assertEquals(Issue::ERROR, $subject->getSeverity()); + $this->assertEquals($depChain, $subject->getDependencyChain()); + } + + public function testCreateWithEmptyChain() + { + $this->expectException(UnexpectedValueException::class); + + new CircularDepIssue([]); + } + + public function testMessageShortChain() + { + $depChain = ['chicken', 'egg']; + + $subject = new CircularDepIssue($depChain); + $expect = 'chicken -> egg -> chicken'; + + $this->assertStringContainsString($expect, $subject->getMessage()); + $this->assertEquals($depChain, $subject->getDependencyChain()); + $this->assertEquals('chicken', $subject->getService()); + } + + public function testMessageOneDepChain() + { + $depChain = ['love']; + + $subject = new CircularDepIssue($depChain); + $expect = 'love -> love'; + + $this->assertStringContainsString($expect, $subject->getMessage()); + $this->assertEquals($depChain, $subject->getDependencyChain()); + $this->assertEquals('love', $subject->getService()); + } + + public function testMessageLongChain() + { + $depChain = ['fear', 'anger', 'hate', 'suffering', 'dark side']; + + $subject = new CircularDepIssue($depChain); + $expect = 'fear -> anger -> hate -> suffering -> dark side'; + + $this->assertStringContainsString($expect, $subject->getMessage()); + $this->assertEquals($depChain, $subject->getDependencyChain()); + $this->assertEquals('fear', $subject->getService()); + } +} diff --git a/tests/functional/Issues/DependencyIssueTest.php b/tests/functional/Issues/DependencyIssueTest.php new file mode 100644 index 0000000..92c0616 --- /dev/null +++ b/tests/functional/Issues/DependencyIssueTest.php @@ -0,0 +1,25 @@ +assertEquals($severity, $subject->getSeverity()); + $this->assertEquals($message, $subject->getMessage()); + $this->assertEquals($service, $subject->getService()); + $this->assertEquals($dependency, $subject->getDependency()); + } +} diff --git a/tests/functional/Issues/ServiceIssueTest.php b/tests/functional/Issues/ServiceIssueTest.php new file mode 100644 index 0000000..0cd68cf --- /dev/null +++ b/tests/functional/Issues/ServiceIssueTest.php @@ -0,0 +1,23 @@ +assertEquals($severity, $subject->getSeverity()); + $this->assertEquals($message, $subject->getMessage()); + $this->assertEquals($service, $subject->getService()); + } +} diff --git a/tests/functional/Utils/GetServiceDepsCapableTraitTest.php b/tests/functional/Utils/GetServiceDepsCapableTraitTest.php new file mode 100644 index 0000000..652426c --- /dev/null +++ b/tests/functional/Utils/GetServiceDepsCapableTraitTest.php @@ -0,0 +1,38 @@ +subject = $this->getMockForTrait(GetServiceDepsCapableTrait::class); + } + + public function testGetDepsFromCallable() + { + $service = function () { }; + $actual = $this->subject->getServiceDeps($service); + + $this->assertEmpty($actual); + } + + public function testGetDepsFromServiceInterface() + { + $expected = ['Luke', 'Leia', 'Lytvynenko']; + $service = $this->createConfiguredMock(ServiceInterface::class, [ + 'getDependencies' => $expected, + ]); + + $actual = $this->subject->getServiceDeps($service); + + $this->assertEquals($expected, $actual); + } +} diff --git a/tests/functional/Utils/ResolveServicesCapableTraitTest.php b/tests/functional/Utils/ResolveServicesCapableTraitTest.php new file mode 100644 index 0000000..71cc5a0 --- /dev/null +++ b/tests/functional/Utils/ResolveServicesCapableTraitTest.php @@ -0,0 +1,48 @@ +subject = $this->getMockForTrait(ResolveServicesCapableTrait::class); + } + + public function testResolve() + { + $services = [ + 'a' => $a = $this->createMock(ServiceInterface::class), + 'b' => $b = $this->createMock(ServiceInterface::class), + 'c' => $c = $this->createMock(ServiceInterface::class), + 'd' => $d = $this->createMock(ServiceInterface::class), + ]; + + $expected = compact('b', 'c'); + $actual = $this->subject->resolveServices($services, ['b', 'c']); + + $this->assertSame($expected, $actual); + } + + public function testResolveKeysDontExist() + { + $services = [ + 'a' => $a = $this->createMock(ServiceInterface::class), + 'b' => $b = $this->createMock(ServiceInterface::class), + 'c' => $c = $this->createMock(ServiceInterface::class), + 'd' => $d = $this->createMock(ServiceInterface::class), + ]; + + $expected = compact('b'); + $actual = $this->subject->resolveServices($services, ['b', 'g', 'f']); + + $this->assertSame($expected, $actual); + } +} From 4e56b38cc1118d142a70a525c3ad603761c75d99 Mon Sep 17 00:00:00 2001 From: Miguel Date: Tue, 16 Mar 2021 23:01:02 +0100 Subject: [PATCH 05/12] Improved issue messages --- src/Analyzers/InvalidExtensionAnalyzer.php | 6 +++++- src/Analyzers/UnfulfilledDependencyAnalyzer.php | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Analyzers/InvalidExtensionAnalyzer.php b/src/Analyzers/InvalidExtensionAnalyzer.php index b50a2e5..012b8d3 100644 --- a/src/Analyzers/InvalidExtensionAnalyzer.php +++ b/src/Analyzers/InvalidExtensionAnalyzer.php @@ -16,7 +16,11 @@ public function analyze(array $factories, array $extensions): iterable { foreach ($extensions as $key => $extension) { if (!array_key_exists($key, $factories)) { - yield new ServiceIssue(Issue::WARNING, 'Extension extends unknown service', $key); + yield new ServiceIssue( + Issue::WARNING, + "Extension \"$key\" does not correspond to a known service", + $key + ); } } } diff --git a/src/Analyzers/UnfulfilledDependencyAnalyzer.php b/src/Analyzers/UnfulfilledDependencyAnalyzer.php index 494ba86..1bb8249 100644 --- a/src/Analyzers/UnfulfilledDependencyAnalyzer.php +++ b/src/Analyzers/UnfulfilledDependencyAnalyzer.php @@ -28,7 +28,12 @@ public function scan(array $factories, array $services): iterable foreach ($deps as $dep) { if (!array_key_exists($dep, $factories)) { - yield new DependencyIssue(Issue::WARNING, 'Unfulfilled dependency', $key, $dep); + yield new DependencyIssue( + Issue::WARNING, + "Service \"$key\" has an unfulfilled dependency: \"$dep\"", + $key, + $dep + ); } } } From b76f69e696bf17af8060931883bd9cbfdd1ff1cb Mon Sep 17 00:00:00 2001 From: Miguel Date: Tue, 16 Mar 2021 23:16:45 +0100 Subject: [PATCH 06/12] Added analysis usage in readme --- README.md | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6d98618..4f68c59 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,42 @@ # Dhii - Service tools -A set of tools for working with service providers. +A set of tools for working with service providers that use [`ServiceInterface`](service-interface). + +## Analysis + +This package provides a set of analyzers that can detect various kinds of service-related issues, such as: + +* Circular dependencies +* Dependencies that don't resolve to known services +* Extensions that don't extend a known service + +**Usage**: + +``` +$analyzer = new CircularDependencyAnalyzer(); +$issues = $analyzer->analyze($factories, $extensions); + +foreach ($issues as $issue) { + $severity = $issue->getSeverity(); + $message = $issue->getMessage(); + + switch ($severity) { + case Issue::WARNING: /* do something */ + case Issue::ERROR: /* do something */ + } +} +``` + +All the analyzers implement an [`AnalyzerInterface`](analyzer-interface). Container implementations can choose to +accept an analyzer instance during construction or setup using this interface, and then perform analysis on the +finished state of the container, after all [`ServiceProviderInterface`](service-provider) instances have been accepted. + +Whether a container takes action on the reported issues or not is left up to the implementation. However, it is +strongly recommended that implementations take action according to the severity of the reported issues: + +* `Issue::WARNING` - these issues should be reported to the developer; execution may resume. +* `Issue::ERROR` - these issues represent a fatal error waiting to happen; execution should probably stop. + +[service-interface]: https://github.com/Dhii/services-interface +[analyzer-interface]: https://github.com/Dhii/service-tools/blob/initial/src/AnalyzerInterface.php +[service-provider]: https://github.com/container-interop/service-provider/blob/master/src/ServiceProviderInterface.php From 9a03fc5d27cb3f8ccfb80edaeb3e3f035e28d25a Mon Sep 17 00:00:00 2001 From: Miguel Date: Tue, 16 Mar 2021 23:17:06 +0100 Subject: [PATCH 07/12] Removed SMELL issue severity --- src/Issue.php | 5 ++--- tests/functional/Analyzers/AggregateAnalyzerTest.php | 2 +- tests/functional/IssueTest.php | 4 +--- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Issue.php b/src/Issue.php index 27633d5..552906e 100644 --- a/src/Issue.php +++ b/src/Issue.php @@ -4,9 +4,8 @@ class Issue { - const SMELL = 0; - const WARNING = 1; - const ERROR = 2; + const WARNING = 0; + const ERROR = 1; /** @var int */ protected $severity; diff --git a/tests/functional/Analyzers/AggregateAnalyzerTest.php b/tests/functional/Analyzers/AggregateAnalyzerTest.php index ae0b63c..c90ff6c 100644 --- a/tests/functional/Analyzers/AggregateAnalyzerTest.php +++ b/tests/functional/Analyzers/AggregateAnalyzerTest.php @@ -20,7 +20,7 @@ public function testAnalyze() { $issues1 = [ new Issue(Issue::WARNING, 'Oh no'), - new Issue(Issue::SMELL, 'Your code smells bad'), + new Issue(Issue::WARNING, 'Your code smells bad'), ]; $issues2 = [ new Issue(Issue::ERROR, 'This needs fixing'), diff --git a/tests/functional/IssueTest.php b/tests/functional/IssueTest.php index d7952e4..2bfa1e5 100644 --- a/tests/functional/IssueTest.php +++ b/tests/functional/IssueTest.php @@ -9,7 +9,7 @@ class IssueTest extends TestCase { public function testCreate() { - $severity = Issue::SMELL; + $severity = Issue::WARNING; $message = 'A disturbance in the force'; $subject = new Issue($severity, $message); @@ -20,8 +20,6 @@ public function testCreate() public function testSeverityValues() { - $this->assertTrue(Issue::SMELL < Issue::WARNING); - $this->assertTrue(Issue::SMELL < Issue::ERROR); $this->assertTrue(Issue::WARNING < Issue::ERROR); } } From e9286f87b8f8170ad61a559cfacf9d4bd88f2cfb Mon Sep 17 00:00:00 2001 From: Miguel Date: Tue, 16 Mar 2021 23:21:54 +0100 Subject: [PATCH 08/12] Added missing phpdoc tag --- src/Issues/CircularDepIssue.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Issues/CircularDepIssue.php b/src/Issues/CircularDepIssue.php index f9e11c8..52b0de6 100644 --- a/src/Issues/CircularDepIssue.php +++ b/src/Issues/CircularDepIssue.php @@ -13,6 +13,8 @@ class CircularDepIssue extends ServiceIssue * Constructor. * * @param string[] $depChain + * + * @throws UnexpectedValueException If the given dependency chain is empty. */ public function __construct(array $depChain) { From 0eb5586d564b262a68d7808a74afadb5685e230c Mon Sep 17 00:00:00 2001 From: Miguel Date: Tue, 16 Mar 2021 23:22:08 +0100 Subject: [PATCH 09/12] Fixed reading of first dep in chain --- src/Issues/CircularDepIssue.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Issues/CircularDepIssue.php b/src/Issues/CircularDepIssue.php index 52b0de6..64bf1bf 100644 --- a/src/Issues/CircularDepIssue.php +++ b/src/Issues/CircularDepIssue.php @@ -22,7 +22,8 @@ public function __construct(array $depChain) throw new UnexpectedValueException('Dependency chain cannot be empty'); } - $service = reset($depChain); + $depChain = array_values($depChain); + $service = $depChain[0]; $message = 'Circular dependency: ' . implode(' -> ', $depChain) . ' -> ' . $service; parent::__construct(static::ERROR, $message, $service); From 702c8c1a1129b2aa58c0bb7c8d360f7570fd74e9 Mon Sep 17 00:00:00 2001 From: Miguel Date: Tue, 16 Mar 2021 23:47:36 +0100 Subject: [PATCH 10/12] Composer scripts for PHPCS and Psalm --- composer.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/composer.json b/composer.json index 741caf3..5de6fd4 100644 --- a/composer.json +++ b/composer.json @@ -40,5 +40,9 @@ "branch-alias": { "dev-initial": "0.1.x-dev" } + }, + "scripts": { + "phpcs": "vendor/bin/phpcs -s --runtime-set ignore_warnings_on_exit 1", + "psalm": "vendor/bin/psalm --show-info=true --threads=8 --diff" } } From b48e5061b0fb1cd2b6ecc079659a7624f6dd3d9e Mon Sep 17 00:00:00 2001 From: Miguel Date: Tue, 16 Mar 2021 23:49:04 +0100 Subject: [PATCH 11/12] Added strict types --- src/AnalyzerInterface.php | 2 ++ src/Analyzers/AggregateAnalyzer.php | 2 ++ src/Analyzers/CircularDependencyAnalyzer.php | 2 ++ src/Analyzers/InvalidExtensionAnalyzer.php | 2 ++ src/Analyzers/UnfulfilledDependencyAnalyzer.php | 2 ++ src/Issue.php | 2 ++ src/Issues/CircularDepIssue.php | 2 ++ src/Issues/DependencyIssue.php | 2 ++ src/Issues/ServiceIssue.php | 2 ++ src/Utils/GetServiceDepsCapableTrait.php | 2 ++ src/Utils/ResolveServicesCapableTrait.php | 2 ++ 11 files changed, 22 insertions(+) diff --git a/src/AnalyzerInterface.php b/src/AnalyzerInterface.php index 2b9aaa6..f5ff5ae 100644 --- a/src/AnalyzerInterface.php +++ b/src/AnalyzerInterface.php @@ -1,5 +1,7 @@ Date: Tue, 16 Mar 2021 23:49:44 +0100 Subject: [PATCH 12/12] Updated phpdoc blocks --- src/AnalyzerInterface.php | 10 ++++++--- src/Analyzers/CircularDependencyAnalyzer.php | 18 +++++++++------- .../UnfulfilledDependencyAnalyzer.php | 10 +++++++++ src/Issue.php | 21 +++++++++++++------ src/Issues/CircularDepIssue.php | 5 ++++- src/Issues/DependencyIssue.php | 9 ++++++-- src/Issues/ServiceIssue.php | 9 ++++++-- src/Utils/GetServiceDepsCapableTrait.php | 12 ++++++----- src/Utils/ResolveServicesCapableTrait.php | 12 ++++++++--- 9 files changed, 77 insertions(+), 29 deletions(-) diff --git a/src/AnalyzerInterface.php b/src/AnalyzerInterface.php index f5ff5ae..a870bff 100644 --- a/src/AnalyzerInterface.php +++ b/src/AnalyzerInterface.php @@ -4,13 +4,17 @@ namespace Dhii\Services\Tools; +use Dhii\Services\ServiceInterface; + interface AnalyzerInterface { /** - * @param array $factories - * @param array $extensions + * Analyzes a set of factories and extensions. + * + * @param array $factories The list of factories to analyze. + * @param array $extensions The list of extensions to analyze. * - * @return iterable + * @return iterable A list of issues found during analysis. */ public function analyze(array $factories, array $extensions): iterable; } diff --git a/src/Analyzers/CircularDependencyAnalyzer.php b/src/Analyzers/CircularDependencyAnalyzer.php index 65afcb3..b587518 100644 --- a/src/Analyzers/CircularDependencyAnalyzer.php +++ b/src/Analyzers/CircularDependencyAnalyzer.php @@ -4,6 +4,7 @@ namespace Dhii\Services\Tools\Analyzers; +use Dhii\Services\ServiceInterface; use Dhii\Services\Tools\AnalyzerInterface; use Dhii\Services\Tools\Issue; use Dhii\Services\Tools\Issues\CircularDepIssue; @@ -29,13 +30,16 @@ public function analyze(array $factories, array $extensions): iterable * Walks a list of services, recursively walking their dependencies while keeping track of what services have * already been visited. If a service is visited twice, an {@link Issue} is yielded. * - * @param array $factories The entire list of factories. Used to resolve service/dependency keys. - * @param array $toWalk The list of services to walk. - * @param array $visited A dictionary of keys for the services that have been "walked". This acts as a buffer - * stack and is used to detect when the walk re-visits an already visited service. - * @param array $skip A dictionary of keys for services that should be skipped. Services that were detected as - * being a part of a circular dependency are added to this list to prevent reporting the - * same circular chain multiple times. + * @param array $factories The entire list of factories. Used to resolve + * service/dependency keys. + * @param array $toWalk The list of services to walk. + * @param string[] $visited A dictionary of keys for the services that have been + * "walked". This acts as a buffer stack and is used to detect + * when the walk re-visits an already visited service. + * @param string[] $skip A dictionary of keys for services that should be skipped. + * Services that were detected as being a part of a circular + * dependency are added to this list to prevent reporting the + * same circular chain multiple times. * * @return iterable */ diff --git a/src/Analyzers/UnfulfilledDependencyAnalyzer.php b/src/Analyzers/UnfulfilledDependencyAnalyzer.php index eb90340..de080ed 100644 --- a/src/Analyzers/UnfulfilledDependencyAnalyzer.php +++ b/src/Analyzers/UnfulfilledDependencyAnalyzer.php @@ -4,6 +4,7 @@ namespace Dhii\Services\Tools\Analyzers; +use Dhii\Services\ServiceInterface; use Dhii\Services\Tools\AnalyzerInterface; use Dhii\Services\Tools\Issue; use Dhii\Services\Tools\Issues\DependencyIssue; @@ -23,6 +24,15 @@ public function analyze(array $factories, array $extensions): iterable yield from $this->scan($factories, $extensions); } + /** + * Scans a list of services to find if any of them have dependencies that don't resolve to existing services. + * + * @param array $factories The entire list of factories against which dependency + * existence will be determined. + * @param array $services The list of services to scan. + * + * @return iterable A list of found issues. + */ public function scan(array $factories, array $services): iterable { foreach ($services as $key => $service) { diff --git a/src/Issue.php b/src/Issue.php index ac5ba8e..0973c2d 100644 --- a/src/Issue.php +++ b/src/Issue.php @@ -6,8 +6,10 @@ class Issue { - const WARNING = 0; - const ERROR = 1; + /** Tame severity for an issue that deserves attention but might not require termination. */ + public const WARNING = 0; + /** High severity for an issue that should be reported and is likely to require terminating. */ + public const ERROR = 1; /** @var int */ protected $severity; @@ -18,8 +20,8 @@ class Issue /** * Constructor. * - * @param int $severity - * @param string $message + * @param int $severity The severity of the issue. See {@link Issue::WARNING} and {@link Issue::ERROR}. + * @param string $message A human-friendly message describing the issue. */ public function __construct(int $severity, string $message) { @@ -28,7 +30,12 @@ public function __construct(int $severity, string $message) } /** - * @return int + * Retrieves the severity of the issue. + * + * @see Issue::WARNING + * @see Issue::ERROR + * + * @return int See the constants in {@link Issue}. */ public function getSeverity(): int { @@ -36,7 +43,9 @@ public function getSeverity(): int } /** - * @return string + * Retrieves a message that describes the issue in a human-friendly manner. + * + * @return string The message. */ public function getMessage(): string { diff --git a/src/Issues/CircularDepIssue.php b/src/Issues/CircularDepIssue.php index 5a618f2..5317563 100644 --- a/src/Issues/CircularDepIssue.php +++ b/src/Issues/CircularDepIssue.php @@ -6,6 +6,9 @@ use UnexpectedValueException; +/** + * An circular dependency issue. + */ class CircularDepIssue extends ServiceIssue { /** @var string[] */ @@ -14,7 +17,7 @@ class CircularDepIssue extends ServiceIssue /** * Constructor. * - * @param string[] $depChain + * @param string[] $depChain The list of dependency keys in order of requirement (dependents first). * * @throws UnexpectedValueException If the given dependency chain is empty. */ diff --git a/src/Issues/DependencyIssue.php b/src/Issues/DependencyIssue.php index be62c0b..4831cea 100644 --- a/src/Issues/DependencyIssue.php +++ b/src/Issues/DependencyIssue.php @@ -4,6 +4,9 @@ namespace Dhii\Services\Tools\Issues; +/** + * An issue related to a specific service and one of its dependencies. + */ class DependencyIssue extends ServiceIssue { /** @var string */ @@ -12,7 +15,7 @@ class DependencyIssue extends ServiceIssue /** * @inheritDoc * - * @param string $dependency + * @param string $dependency The key of the dependency that is relevant to the issue. */ public function __construct(int $severity, string $message, string $service, string $dependency) { @@ -21,7 +24,9 @@ public function __construct(int $severity, string $message, string $service, str } /** - * @return string + * Retrieves the key of the dependency that is relevant to the issue. + * + * @return string A dependency key. */ public function getDependency(): string { diff --git a/src/Issues/ServiceIssue.php b/src/Issues/ServiceIssue.php index 735ab17..1a295a2 100644 --- a/src/Issues/ServiceIssue.php +++ b/src/Issues/ServiceIssue.php @@ -6,6 +6,9 @@ use Dhii\Services\Tools\Issue; +/** + * An issue related to a specific service. + */ class ServiceIssue extends Issue { /** @var string */ @@ -14,7 +17,7 @@ class ServiceIssue extends Issue /** * @inheritDoc * - * @param string $service + * @param string $service The key of the service that is related to the issue. */ public function __construct(int $severity, string $message, string $service) { @@ -23,7 +26,9 @@ public function __construct(int $severity, string $message, string $service) } /** - * @return string + * Retrieves the key of the service that is related to the issue. + * + * @return string A service key. */ public function getService(): string { diff --git a/src/Utils/GetServiceDepsCapableTrait.php b/src/Utils/GetServiceDepsCapableTrait.php index 93bef34..6a4e63e 100644 --- a/src/Utils/GetServiceDepsCapableTrait.php +++ b/src/Utils/GetServiceDepsCapableTrait.php @@ -9,14 +9,16 @@ trait GetServiceDepsCapableTrait { /** - * @param callable|ServiceInterface $keys + * Retrieves a service's dependency keys. * - * @return string[] + * @param callable|ServiceInterface $service The service whose dependencies to retrieve. + * + * @return string[] The keys of the service's dependencies. */ - public function getServiceDeps($keys): array + public function getServiceDeps($service): array { - return ($keys instanceof ServiceInterface) - ? $keys->getDependencies() + return ($service instanceof ServiceInterface) + ? $service->getDependencies() : []; } } diff --git a/src/Utils/ResolveServicesCapableTrait.php b/src/Utils/ResolveServicesCapableTrait.php index de1e033..284e0c0 100644 --- a/src/Utils/ResolveServicesCapableTrait.php +++ b/src/Utils/ResolveServicesCapableTrait.php @@ -4,13 +4,19 @@ namespace Dhii\Services\Tools\Utils; +use Dhii\Services\ServiceInterface; + trait ResolveServicesCapableTrait { /** - * @param array $services - * @param array $keys + * Resolves a list of keys into services. + * + * Keys that do not correspond to an existing service will be ignored. + * + * @param array $services The list of services to resolve from. + * @param string[] $keys The list of keys to resolve. * - * @return string[] + * @return array A mapping of keys to resolved services. */ public function resolveServices(array $services, array $keys): array {