Skip to content

Commit 9ab14f8

Browse files
authored
Improve ServiceWiringObserver (#259)
* Improve ServiceWiringObserver * Fix psalm inference
1 parent 1987488 commit 9ab14f8

File tree

5 files changed

+222
-16
lines changed

5 files changed

+222
-16
lines changed

CHANGELOG.md

+16
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased Changes
99

10+
## [v2.0.0-beta3](https://github.com/cspray/annotated-container/tree/v2.0.0-beta3) - 2022-09-05
11+
12+
### Added
13+
14+
- Added a new `Bootstrap\ServiceGatherer::getServicesWithAttribute` method that allows retrieving all services and definitions that have an Attribute of a given type.
15+
16+
### Fixed
17+
18+
- Fixed a bug where the `Bootstrap\ServiceWiringObserver` was not profile aware and could result in attempting to make a service the Container was unaware of.
19+
20+
## [v2.0.0-beta2](https://github.com/cspray/annotated-container/tree/v2.0.0-beta2) - 2022-09-04
21+
22+
### Changed
23+
24+
- The provided `Bootstrap\ServiceWiringObserver` now provides an array of `Bootstrap\ServiceFromServiceDefinition` which provides the `Definition\ServiceDefinition` as well as the corresponding service. This provides access to the new `Definition\ServiceDefinition::getAttribute` method which could be useful in contexts where service wiring occurs.
25+
1026
## [v2.0.0-beta1](https://github.com/cspray/annotated-container/tree/v2.0.0-beta1) - 2022-09-04
1127

1228
Version 2 represents significant improvements but includes backwards-breaking changes. If you run into problems migrating from v1 to v2 please [submit an Issue](https://github.com/cspray/annotated-container/issues/new).

VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
dev-main
1+
v2.0.0-beta3

src/Bootstrap/ServiceGatherer.php

+6
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,10 @@ interface ServiceGatherer {
1313
*/
1414
public function getServicesForType(string $type) : array;
1515

16+
/**
17+
* @param class-string<T> $attributeType
18+
* @return ServiceFromServiceDefinition<T>[]
19+
*/
20+
public function getServicesWithAttribute(string $attributeType) : array;
21+
1622
}

src/Bootstrap/ServiceWiringObserver.php

+46-15
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
use Cspray\AnnotatedContainer\AnnotatedContainer;
66
use Cspray\AnnotatedContainer\Definition\ContainerDefinition;
7+
use Cspray\AnnotatedContainer\Definition\ProfilesAwareContainerDefinition;
78
use Cspray\AnnotatedContainer\Definition\ServiceDefinition;
9+
use Cspray\AnnotatedContainer\Profiles\ActiveProfiles;
810

911
abstract class ServiceWiringObserver implements Observer {
1012

@@ -23,10 +25,16 @@ final public function beforeContainerCreation(ContainerDefinition $containerDefi
2325
final public function afterContainerCreation(ContainerDefinition $containerDefinition, AnnotatedContainer $container) : void {
2426
$serviceGatherer = new class($containerDefinition, $container) implements ServiceGatherer {
2527

28+
private readonly ContainerDefinition $containerDefinition;
29+
2630
public function __construct(
27-
private readonly ContainerDefinition $containerDefinition,
31+
ContainerDefinition $containerDefinition,
2832
private readonly AnnotatedContainer $container
29-
) {}
33+
) {
34+
$activeProfiles = $container->get(ActiveProfiles::class);
35+
assert($activeProfiles instanceof ActiveProfiles);
36+
$this->containerDefinition = new ProfilesAwareContainerDefinition($containerDefinition, $activeProfiles->getProfiles());
37+
}
3038

3139
public function getServicesForType(string $type) : array {
3240
/** @var array<array-key, object> $services */
@@ -41,26 +49,49 @@ public function getServicesForType(string $type) : array {
4149
if (is_a($serviceType, $type, true)) {
4250
$service = $this->container->get($serviceType);
4351
assert($service instanceof $type);
44-
$services[] = new class($service, $serviceDefinition) implements ServiceFromServiceDefinition {
52+
$services[] = $this->createServiceFromServiceDefinition($service, $serviceDefinition);
53+
}
54+
}
4555

46-
public function __construct(
47-
private readonly object $service,
48-
private readonly ServiceDefinition $definition
49-
) {}
56+
return $services;
57+
}
5058

51-
public function getService() : object {
52-
return $this->service;
53-
}
59+
public function getServicesWithAttribute(string $attributeType) : array {
60+
$services = [];
61+
foreach ($this->containerDefinition->getServiceDefinitions() as $serviceDefinition) {
62+
if ($serviceDefinition->isAbstract()) {
63+
continue;
64+
}
5465

55-
public function getDefinition() : ServiceDefinition {
56-
return $this->definition;
57-
}
58-
};
66+
$serviceAttribute = $serviceDefinition->getAttribute();
67+
if (!($serviceAttribute instanceof $attributeType)) {
68+
continue;
5969
}
60-
}
6170

71+
$service = $this->container->get($serviceDefinition->getType()->getName());
72+
assert(is_object($service));
73+
$services[] = $this->createServiceFromServiceDefinition($service, $serviceDefinition);
74+
}
6275
return $services;
6376
}
77+
78+
private function createServiceFromServiceDefinition(object $service, ServiceDefinition $serviceDefinition) : ServiceFromServiceDefinition {
79+
return new class($service, $serviceDefinition) implements ServiceFromServiceDefinition {
80+
public function __construct(
81+
private readonly object $service,
82+
private readonly ServiceDefinition $definition
83+
) {}
84+
85+
public function getService() : object {
86+
return $this->service;
87+
}
88+
89+
public function getDefinition() : ServiceDefinition {
90+
return $this->definition;
91+
}
92+
};
93+
94+
}
6495
};
6596
$this->wireServices($container, $serviceGatherer);
6697
}

test/BootstrapTest.php

+153
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Cspray\AnnotatedContainer\Compile\ContainerDefinitionBuilderContextConsumer;
1616
use Cspray\AnnotatedContainer\ContainerFactory\ParameterStore;
1717
use Cspray\AnnotatedContainer\Helper\TestLogger;
18+
use Cspray\AnnotatedContainerFixture\CustomServiceAttribute\Repository;
1819
use Cspray\AnnotatedContainerFixture\Fixtures;
1920
use PHPUnit\Framework\TestCase;
2021
use org\bovigo\vfs\vfsStream as VirtualFilesystem;
@@ -519,4 +520,156 @@ protected function wireServices(AnnotatedContainer $container, ServiceGatherer $
519520
], $actualServices);
520521
}
521522

523+
public function testServiceWiringObserverByAttributes() : void {
524+
$directoryResolver = new FixtureBootstrappingDirectoryResolver();
525+
526+
$goodXml = <<<XML
527+
<?xml version="1.0" encoding="UTF-8" ?>
528+
<annotatedContainer xmlns="https://annotated-container.cspray.io/schema/annotated-container.xsd">
529+
<scanDirectories>
530+
<source>
531+
<dir>CustomServiceAttribute</dir>
532+
</source>
533+
</scanDirectories>
534+
</annotatedContainer>
535+
XML;
536+
537+
VirtualFilesystem::newFile('annotated-container.xml')
538+
->withContent($goodXml)
539+
->at($this->vfs);
540+
541+
$bootstrap = new Bootstrap(directoryResolver: $directoryResolver);
542+
543+
$observer = new class extends ServiceWiringObserver {
544+
545+
private ?AnnotatedContainer $container = null;
546+
private array $services = [];
547+
548+
public function getAnnotatedContainer() : ?AnnotatedContainer {
549+
return $this->container;
550+
}
551+
552+
public function getServices() : array {
553+
return $this->services;
554+
}
555+
556+
protected function wireServices(AnnotatedContainer $container, ServiceGatherer $gatherer) : void {
557+
$this->container = $container;
558+
$this->services = $gatherer->getServicesWithAttribute(Repository::class);
559+
}
560+
};
561+
$bootstrap->addObserver($observer);
562+
563+
$container = $bootstrap->bootstrapContainer(['default', 'test']);
564+
565+
$actual = $observer->getServices();
566+
$actualServices = array_map(fn(ServiceFromServiceDefinition $fromServiceDefinition) => $fromServiceDefinition->getService(), $actual);
567+
568+
self::assertSame($container, $observer->getAnnotatedContainer());
569+
self::assertSame([
570+
$container->get(Fixtures::customServiceAttribute()->myRepo()->getName()),
571+
], $actualServices);
572+
}
573+
574+
public function testServiceWiringObserverByTypeProfileAware() : void {
575+
$directoryResolver = new FixtureBootstrappingDirectoryResolver();
576+
577+
$goodXml = <<<XML
578+
<?xml version="1.0" encoding="UTF-8" ?>
579+
<annotatedContainer xmlns="https://annotated-container.cspray.io/schema/annotated-container.xsd">
580+
<scanDirectories>
581+
<source>
582+
<dir>ProfileResolvedServices</dir>
583+
</source>
584+
</scanDirectories>
585+
</annotatedContainer>
586+
XML;
587+
588+
VirtualFilesystem::newFile('annotated-container.xml')
589+
->withContent($goodXml)
590+
->at($this->vfs);
591+
592+
$bootstrap = new Bootstrap(directoryResolver: $directoryResolver);
593+
594+
$observer = new class extends ServiceWiringObserver {
595+
596+
private ?AnnotatedContainer $container = null;
597+
private array $services = [];
598+
599+
public function getAnnotatedContainer() : ?AnnotatedContainer {
600+
return $this->container;
601+
}
602+
603+
public function getServices() : array {
604+
return $this->services;
605+
}
606+
607+
protected function wireServices(AnnotatedContainer $container, ServiceGatherer $gatherer) : void {
608+
$this->container = $container;
609+
$this->services = $gatherer->getServicesForType(Fixtures::profileResolvedServices()->fooInterface()->getName());
610+
}
611+
};
612+
$bootstrap->addObserver($observer);
613+
614+
$container = $bootstrap->bootstrapContainer(['default', 'prod']);
615+
616+
$actual = $observer->getServices();
617+
618+
$actualServices = array_map(fn(ServiceFromServiceDefinition $fromServiceDefinition) => $fromServiceDefinition->getService(), $actual);
619+
620+
usort($actualServices, fn($a, $b) => $a::class <=> $b::class);
621+
622+
self::assertSame($container, $observer->getAnnotatedContainer());
623+
self::assertSame([
624+
$container->get(Fixtures::profileResolvedServices()->prodImplementation()->getName()),
625+
], $actualServices);
626+
}
627+
628+
public function testServiceWiringObserverByAttributesProfileAware() : void {
629+
$directoryResolver = new FixtureBootstrappingDirectoryResolver();
630+
631+
$goodXml = <<<XML
632+
<?xml version="1.0" encoding="UTF-8" ?>
633+
<annotatedContainer xmlns="https://annotated-container.cspray.io/schema/annotated-container.xsd">
634+
<scanDirectories>
635+
<source>
636+
<dir>CustomServiceAttribute</dir>
637+
</source>
638+
</scanDirectories>
639+
</annotatedContainer>
640+
XML;
641+
642+
VirtualFilesystem::newFile('annotated-container.xml')
643+
->withContent($goodXml)
644+
->at($this->vfs);
645+
646+
$bootstrap = new Bootstrap(directoryResolver: $directoryResolver);
647+
648+
$observer = new class extends ServiceWiringObserver {
649+
650+
private ?AnnotatedContainer $container = null;
651+
private array $services = [];
652+
653+
public function getAnnotatedContainer() : ?AnnotatedContainer {
654+
return $this->container;
655+
}
656+
657+
public function getServices() : array {
658+
return $this->services;
659+
}
660+
661+
protected function wireServices(AnnotatedContainer $container, ServiceGatherer $gatherer) : void {
662+
$this->container = $container;
663+
$this->services = $gatherer->getServicesWithAttribute(Repository::class);
664+
}
665+
};
666+
$bootstrap->addObserver($observer);
667+
668+
// The Repository is only active under 'test' profile and should not be included
669+
$container = $bootstrap->bootstrapContainer(['default', 'dev']);
670+
671+
self::assertSame($container, $observer->getAnnotatedContainer());
672+
self::assertEmpty($observer->getServices());
673+
}
674+
522675
}

0 commit comments

Comments
 (0)