Skip to content

Commit a116486

Browse files
committed
Introduce definition factory
1 parent 6810587 commit a116486

27 files changed

+1453
-106
lines changed

docs/how-to/01-add-third-party-services.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class ThirdPartyServicesProvider implements DefinitionProvider {
4949
public function consume(DefinitionProviderContext $context) : void {
5050
$context->addServiceDefinition(service($loggerType = objectType(LoggerInterface::class)));
5151
$context->addServiceDelegateDefinition(
52-
serviceDelegate($loggerType, objectType(MonologLoggerFactory::class), 'createLogger')
52+
serviceDelegate(objectType(MonologLoggerFactory::class), 'createLogger')
5353
);
5454
$context->addServicePrepareDefinition(
5555
servicePrepare(

docs/references/02-functional-api.md

-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ This document lists the functions for each purpose.
2323
) : \Cspray\AnnotatedContainer\Definition\AliasDefinition;
2424

2525
\Cspray\AnnotatedContainer\serviceDelegate(
26-
\Cspray\Typiphy\ObjectType $service,
2726
\Cspray\Typiphy\ObjectType $factoryClass,
2827
string $factoryMethod
2928
) : \Cspray\AnnotatedContainer\Definition\ServiceDelegateDefinition;

phpunit.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
</testsuites>
2121
<coverage>
2222
<report>
23-
<text outputFile="php://stdout" showOnlySummary="true"/>
2423
<!--
24+
<text outputFile="php://stdout" showOnlySummary="true"/>
2525
<html outputDirectory="build/code-coverage/html" />
2626
-->
2727
</report>

src/Definition/DefinitionFactory.php

+339
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Cspray\AnnotatedContainer\Definition;
4+
5+
use Cspray\AnnotatedContainer\Attribute\InjectAttribute;
6+
use Cspray\AnnotatedContainer\Attribute\ServiceAttribute;
7+
use Cspray\AnnotatedContainer\Attribute\ServiceDelegateAttribute;
8+
use Cspray\AnnotatedContainer\Attribute\ServicePrepareAttribute;
9+
use Cspray\AnnotatedContainer\Exception\InjectAttributeRequired;
10+
use Cspray\AnnotatedContainer\Exception\ServiceAttributeRequired;
11+
use Cspray\AnnotatedContainer\Exception\ServiceDelegateAttributeRequired;
12+
use Cspray\AnnotatedContainer\Exception\ServiceDelegateReturnsIntersectionType;
13+
use Cspray\AnnotatedContainer\Exception\ServiceDelegateReturnsScalarType;
14+
use Cspray\AnnotatedContainer\Exception\ServiceDelegateReturnsUnionType;
15+
use Cspray\AnnotatedContainer\Exception\ServiceDelegateReturnsUnknownType;
16+
use Cspray\AnnotatedContainer\Exception\ServicePrepareAttributeRequired;
17+
use Cspray\AnnotatedContainer\Exception\WrongTargetForInjectAttribute;
18+
use Cspray\AnnotatedContainer\Exception\WrongTargetForServiceAttribute;
19+
use Cspray\AnnotatedContainer\Exception\WrongTargetForServiceDelegateAttribute;
20+
use Cspray\AnnotatedContainer\Exception\WrongTargetForServicePrepareAttribute;
21+
use Cspray\AnnotatedTarget\AnnotatedTarget;
22+
use Cspray\Typiphy\ObjectType;
23+
use Cspray\Typiphy\Type;
24+
use Cspray\Typiphy\TypeIntersect;
25+
use Cspray\Typiphy\TypeUnion;
26+
use ReflectionClass;
27+
use ReflectionIntersectionType;
28+
use ReflectionMethod;
29+
use ReflectionNamedType;
30+
use ReflectionParameter;
31+
use ReflectionUnionType;
32+
use function Cspray\Typiphy\arrayType;
33+
use function Cspray\Typiphy\boolType;
34+
use function Cspray\Typiphy\floatType;
35+
use function Cspray\Typiphy\intType;
36+
use function Cspray\Typiphy\objectType;
37+
use function Cspray\Typiphy\mixedType;
38+
use function Cspray\Typiphy\stringType;
39+
40+
final class DefinitionFactory {
41+
42+
public function serviceDefinitionFromAnnotatedTarget(AnnotatedTarget $annotatedTarget) : ServiceDefinition {
43+
$attribute = $annotatedTarget->attributeInstance();
44+
if (!$attribute instanceof ServiceAttribute) {
45+
throw ServiceAttributeRequired::fromNotServiceAttributeProvidedInAnnotatedTarget(
46+
$attribute::class
47+
);
48+
}
49+
50+
$reflection = $annotatedTarget->targetReflection();
51+
if (!$reflection instanceof ReflectionClass) {
52+
throw WrongTargetForServiceAttribute::fromServiceAttributeNotTargetingClass($reflection);
53+
}
54+
55+
return $this->serviceDefinitionFromReflectionClassAndAttribute($reflection, $attribute);
56+
}
57+
58+
public function serviceDefinitionFromObjectTypeAndAttribute(
59+
ObjectType $objectType,
60+
ServiceAttribute $serviceAttribute
61+
) : ServiceDefinition {
62+
$reflection = new ReflectionClass($objectType->name());
63+
64+
return $this->serviceDefinitionFromReflectionClassAndAttribute($reflection, $serviceAttribute);
65+
}
66+
67+
private function serviceDefinitionFromReflectionClassAndAttribute(
68+
ReflectionClass $reflection,
69+
ServiceAttribute $attribute,
70+
) : ServiceDefinition {
71+
$objectType = objectType($reflection->getName());
72+
$isAbstract = $reflection->isInterface() || $reflection->isAbstract();
73+
return new class($objectType, $attribute, $isAbstract) implements ServiceDefinition {
74+
75+
public function __construct(
76+
private readonly ObjectType $type,
77+
private readonly ServiceAttribute $attribute,
78+
private readonly bool $isAbstract,
79+
) {
80+
}
81+
82+
public function type() : ObjectType {
83+
return $this->type;
84+
}
85+
86+
public function name() : ?string {
87+
return $this->attribute->name();
88+
}
89+
90+
public function profiles() : array {
91+
$profiles = $this->attribute->profiles();
92+
if ($profiles === []) {
93+
$profiles = ['default'];
94+
}
95+
96+
return $profiles;
97+
}
98+
99+
public function isPrimary() : bool {
100+
return $this->attribute->isPrimary();
101+
}
102+
103+
public function isConcrete() : bool {
104+
return $this->isAbstract === false;
105+
}
106+
107+
public function isAbstract() : bool {
108+
return $this->isAbstract;
109+
}
110+
111+
public function attribute() : ?ServiceAttribute {
112+
return $this->attribute;
113+
}
114+
};
115+
}
116+
117+
public function servicePrepareDefinitionFromAnnotatedTarget(AnnotatedTarget $annotatedTarget) : ServicePrepareDefinition {
118+
$attribute = $annotatedTarget->attributeInstance();
119+
if (!$attribute instanceof ServicePrepareAttribute) {
120+
throw ServicePrepareAttributeRequired::fromNotServicePrepareAttributeInAnnotatedTarget($attribute::class);
121+
}
122+
123+
$reflection = $annotatedTarget->targetReflection();
124+
if (!$reflection instanceof ReflectionMethod) {
125+
throw WrongTargetForServicePrepareAttribute::fromServicePrepareAttributeNotTargetingMethod($reflection);
126+
}
127+
128+
return $this->servicePrepareDefinitionFromReflectionMethodAndAttribute($reflection, $attribute);
129+
}
130+
131+
public function servicePrepareDefinitionFromClassMethodAndAttribute(
132+
ObjectType $objectType,
133+
string $method,
134+
ServicePrepareAttribute $attribute,
135+
) : ServicePrepareDefinition {
136+
return $this->servicePrepareDefinitionFromReflectionMethodAndAttribute(
137+
new ReflectionMethod($objectType->name(), $method),
138+
$attribute
139+
);
140+
}
141+
142+
private function servicePrepareDefinitionFromReflectionMethodAndAttribute(
143+
ReflectionMethod $reflection,
144+
ServicePrepareAttribute $attribute
145+
) : ServicePrepareDefinition {
146+
return new class(objectType($reflection->getDeclaringClass()->getName()), $reflection->getName(), $attribute) implements ServicePrepareDefinition {
147+
public function __construct(
148+
private readonly ObjectType $service,
149+
private readonly string $method,
150+
private readonly ServicePrepareAttribute $attribute,
151+
) {
152+
}
153+
154+
public function service() : ObjectType {
155+
return $this->service;
156+
}
157+
158+
public function methodName() : string {
159+
return $this->method;
160+
}
161+
162+
public function attribute() : ?ServicePrepareAttribute {
163+
return $this->attribute;
164+
}
165+
};
166+
}
167+
168+
public function serviceDelegateDefinitionFromAnnotatedTarget(AnnotatedTarget $target) : ServiceDelegateDefinition {
169+
$attribute = $target->attributeInstance();
170+
if (!$attribute instanceof ServiceDelegateAttribute) {
171+
throw ServiceDelegateAttributeRequired::fromNotServiceDelegateAttributeInAnnotatedTarget($attribute::class);
172+
}
173+
174+
$reflection = $target->targetReflection();
175+
if (!$reflection instanceof ReflectionMethod) {
176+
throw WrongTargetForServiceDelegateAttribute::fromServiceDelegateAttributeNotTargetingMethod($reflection);
177+
}
178+
179+
return $this->serviceDelegateDefinitionFromReflectionMethodAndAttribute($reflection, $attribute);
180+
}
181+
182+
public function serviceDelegateDefinitionFromClassMethodAndAttribute(
183+
ObjectType $delegateType,
184+
string $delegateMethod,
185+
ServiceDelegateAttribute $attribute,
186+
) : ServiceDelegateDefinition {
187+
$reflection = new ReflectionMethod($delegateType->name(), $delegateMethod);
188+
189+
return $this->serviceDelegateDefinitionFromReflectionMethodAndAttribute($reflection, $attribute);
190+
}
191+
192+
private function serviceDelegateDefinitionFromReflectionMethodAndAttribute(
193+
ReflectionMethod $reflection,
194+
ServiceDelegateAttribute $attribute
195+
) : ServiceDelegateDefinition {
196+
$delegateType = objectType($reflection->getDeclaringClass()->getName());
197+
$delegateMethod = $reflection->getName();
198+
$returnType = $reflection->getReturnType();
199+
200+
if ($returnType === null) {
201+
throw ServiceDelegateReturnsUnknownType::fromServiceDelegateHasNoReturnType($delegateType);
202+
}
203+
204+
if ($returnType instanceof ReflectionIntersectionType) {
205+
throw ServiceDelegateReturnsIntersectionType::fromServiceDelegateCreatesIntersectionType($delegateType);
206+
}
207+
208+
if ($returnType instanceof ReflectionUnionType) {
209+
throw ServiceDelegateReturnsUnionType::fromServiceDelegateReturnsUnionType($delegateType);
210+
}
211+
212+
$returnTypeName = $returnType->getName();
213+
if ($returnTypeName === 'self') {
214+
$returnTypeName = $delegateType->name();
215+
}
216+
217+
if (!interface_exists($returnTypeName) && !class_exists($returnTypeName)) {
218+
throw ServiceDelegateReturnsScalarType::fromServiceDelegateCreatesScalarType($delegateType);
219+
}
220+
221+
$serviceType = objectType($returnTypeName);
222+
223+
return new class(
224+
$delegateType,
225+
$delegateMethod,
226+
$serviceType,
227+
$attribute
228+
) implements ServiceDelegateDefinition {
229+
230+
public function __construct(
231+
private readonly ObjectType $delegateType,
232+
private readonly string $delegateMethod,
233+
private readonly ObjectType $serviceType,
234+
private readonly ServiceDelegateAttribute $attribute,
235+
) {
236+
}
237+
238+
public function delegateType() : ObjectType {
239+
return $this->delegateType;
240+
}
241+
242+
public function delegateMethod() : string {
243+
return $this->delegateMethod;
244+
}
245+
246+
public function serviceType() : ObjectType {
247+
return $this->serviceType;
248+
}
249+
250+
public function attribute() : ?ServiceDelegateAttribute {
251+
return $this->attribute;
252+
}
253+
};
254+
}
255+
256+
public function injectDefinitionFromAnnotatedTarget(AnnotatedTarget $target) : InjectDefinition {
257+
$attribute = $target->attributeInstance();
258+
if (!$attribute instanceof InjectAttribute) {
259+
throw InjectAttributeRequired::fromNotInjectAttributeProvidedInAnnotatedTarget($attribute::class);
260+
}
261+
262+
$reflection = $target->targetReflection();
263+
if (!$reflection instanceof ReflectionParameter) {
264+
throw WrongTargetForInjectAttribute::fromInjectAttributeNotTargetMethodParameter($reflection);
265+
}
266+
267+
$class = objectType($reflection->getDeclaringClass()->getName());
268+
$method = $reflection->getDeclaringFunction()->getName();
269+
$type = $this->typiphyTypeFromReflectionNamedType($reflection->getType());
270+
$parameter = $reflection->getName();
271+
$value = $attribute->value();
272+
$store = $attribute->from();
273+
$profiles = $attribute->profiles();
274+
275+
return new class(
276+
$class,
277+
$method,
278+
$type,
279+
$parameter,
280+
$value,
281+
$attribute
282+
) implements InjectDefinition {
283+
284+
public function __construct(
285+
private readonly ObjectType $class,
286+
private readonly string $method,
287+
private readonly Type $type,
288+
private readonly string $parameter,
289+
private readonly mixed $value,
290+
private readonly InjectAttribute $attribute,
291+
) {}
292+
293+
public function class() : ObjectType {
294+
return $this->class;
295+
}
296+
297+
public function methodName() : string {
298+
return $this->method;
299+
}
300+
301+
public function type() : Type|TypeUnion|TypeIntersect {
302+
return $this->type;
303+
}
304+
305+
public function parameterName() : string {
306+
return $this->parameter;
307+
}
308+
309+
public function value() : mixed {
310+
return $this->value;
311+
}
312+
313+
public function profiles() : array {
314+
return ['default'];
315+
}
316+
317+
public function storeName() : ?string {
318+
return null;
319+
}
320+
321+
public function attribute() : ?InjectAttribute {
322+
return $this->attribute;
323+
}
324+
};
325+
}
326+
327+
private function typiphyTypeFromReflectionNamedType(?ReflectionNamedType $type) : Type {
328+
$name = $type?->getName();
329+
return match ($name) {
330+
'array' => arrayType(),
331+
'bool' => boolType(),
332+
'int' => intType(),
333+
'float' => floatType(),
334+
'mixed', null => mixedType(),
335+
'string' => stringType(),
336+
default => objectType($name)
337+
};
338+
}
339+
}

src/Definition/ServiceDefinition.php

+7-8
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,23 @@
66
use Cspray\Typiphy\ObjectType;
77

88
/**
9-
* Defines a Service, a class or interface that should be shared or aliased on the wired Injector.
10-
*
11-
* @see ServiceDefinitionBuilder
9+
* Defines an object that will be shared in the created AnnotatedContainer.
1210
*/
1311
interface ServiceDefinition {
1412

15-
/**
16-
* @return non-empty-string|null
17-
*/
18-
public function name() : ?string;
19-
2013
/**
2114
* Returns the fully-qualified class/interface name for the given Service.
2215
*
2316
* @return ObjectType
2417
*/
2518
public function type() : ObjectType;
2619

20+
/**
21+
* @return non-empty-string|null
22+
*/
23+
public function name() : ?string;
24+
25+
2726
/**
2827
* Returns an array of profiles that this service is attached to.
2928
*

0 commit comments

Comments
 (0)