Skip to content

Commit 9c8f2ac

Browse files
committed
LocatorDefinition: supports more than one method
1 parent 752fb7c commit 9c8f2ac

File tree

4 files changed

+194
-31
lines changed

4 files changed

+194
-31
lines changed

src/DI/Definitions/LocatorDefinition.php

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,31 @@ final class LocatorDefinition extends Definition
2727
/** @var string|null */
2828
private $tagged;
2929

30-
/** @var bool */
31-
private $methodName;
32-
3330

3431
/**
3532
* @return static
3633
*/
3734
public function setImplement(string $type)
3835
{
3936
if (!interface_exists($type)) {
40-
throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Interface '$type' not found.");
37+
throw new Nette\InvalidArgumentException(sprintf("Service '%s': Interface '%s' not found.", $this->getName(), $type));
38+
}
39+
$methods = (new \ReflectionClass($type))->getMethods();
40+
if (!$methods) {
41+
throw new Nette\InvalidArgumentException(sprintf("Service '%s': Interface %s must have at least one method.", $this->getName(), $type));
4142
}
42-
$rc = new \ReflectionClass($type);
43-
$method = $rc->getMethods()[0] ?? null;
44-
if (!$method || $method->isStatic() || !in_array($method->getName(), [self::METHOD_GET, self::METHOD_CREATE], true) || count($rc->getMethods()) > 1) {
45-
throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Interface $type must have just one non-static method create() or get().");
46-
} elseif ($method->getNumberOfParameters() !== 1) {
47-
throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Method $type::{$method->getName()}() must have one parameter.");
43+
44+
foreach ($methods as $method) {
45+
if ($method->isStatic() || !(
46+
(preg_match('#^(get|create)$#', $method->getName()) && $method->getNumberOfParameters() === 1)
47+
|| (preg_match('#^(get|create)[A-Z]#', $method->getName()) && $method->getNumberOfParameters() === 0)
48+
)) {
49+
throw new Nette\InvalidArgumentException(sprintf(
50+
"Service '%s': Method %s::%s() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static.",
51+
$this->getName(), $type, $method->getName()
52+
));
53+
}
4854
}
49-
$this->methodName = $method->getName();
5055
return parent::setType($type);
5156
}
5257

@@ -122,33 +127,47 @@ public function complete(Nette\DI\Resolver $resolver): void
122127

123128
public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
124129
{
125-
$rm = new \ReflectionMethod($this->getType(), $this->methodName);
126-
$nullable = $rm->getReturnType()->allowsNull();
127-
128130
$class = (new Nette\PhpGenerator\ClassType)
129131
->addImplement($this->getType());
130132

131133
$class->addProperty('container')
132134
->setVisibility('private');
133135

134-
$class->addProperty('mapping', array_map(function ($item) { return $item->getValue(); }, $this->references))
135-
->setVisibility('private');
136-
137136
$class->addMethod('__construct')
138137
->addBody('$this->container = $container;')
139138
->addParameter('container')
140139
->setTypeHint($generator->getClassName());
141140

142-
$body = 'if (!isset($this->mapping[$name])) {
141+
foreach ((new \ReflectionClass($this->getType()))->getMethods() as $rm) {
142+
preg_match('#^(get|create)(.*)#', $rm->getName(), $m);
143+
$name = lcfirst($m[2]);
144+
$nullable = $rm->getReturnType()->allowsNull();
145+
146+
$methodInner = $class->addMethod($rm->getName())
147+
->setReturnType(Reflection::getReturnType($rm))
148+
->setReturnNullable($nullable);
149+
150+
if (!$name) {
151+
$class->addProperty('mapping', array_map(function ($item) { return $item->getValue(); }, $this->references))
152+
->setVisibility('private');
153+
154+
$methodInner->setBody('if (!isset($this->mapping[$name])) {
143155
' . ($nullable ? 'return null;' : 'throw new Nette\DI\MissingServiceException("Service \'$name\' is not defined.");') . '
144156
}
145-
return $this->container->' . $this->methodName . 'Service($this->mapping[$name]);';
146-
147-
$class->addMethod($this->methodName)
148-
->setReturnType(Reflection::getReturnType($rm))
149-
->setReturnNullable($nullable)
150-
->setBody($body)
151-
->addParameter('name');
157+
return $this->container->' . $m[1] . 'Service($this->mapping[$name]);')
158+
->addParameter('name');
159+
160+
} elseif (isset($this->references[$name])) {
161+
$ref = $this->references[$name]->getValue();
162+
if ($m[1] === 'get') {
163+
$methodInner->setBody('return $this->container->getService(?);', [$ref]);
164+
} else {
165+
$methodInner->setBody('return $this->container->?();', [Nette\DI\Container::getMethodName($ref)]);
166+
}
167+
} else {
168+
$methodInner->setBody($nullable ? 'return null;' : 'throw new Nette\DI\MissingServiceException("Service is not defined.");');
169+
}
170+
}
152171

153172
$method->setBody('return new class ($this) ' . $class . ';');
154173
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\DI\Compiler: generated services locators.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\DI;
10+
use Tester\Assert;
11+
12+
13+
require __DIR__ . '/../bootstrap.php';
14+
15+
16+
class Lorem
17+
{
18+
}
19+
20+
interface Locator
21+
{
22+
public function getA(): Lorem;
23+
24+
public function getB(): ?Lorem;
25+
26+
public function createC(): Lorem;
27+
28+
public function createD(): ?Lorem;
29+
}
30+
31+
32+
$container = createContainer(new DI\Compiler, '
33+
services:
34+
lorem1: Lorem
35+
36+
one: Locator(a: Lorem, b: Lorem, c: Lorem, d: Lorem)
37+
two: Locator(tagged: a)
38+
');
39+
40+
41+
// accessor
42+
$one = $container->getService('one');
43+
Assert::type(Lorem::class, $one->getA());
44+
Assert::type(Lorem::class, $one->getB());
45+
Assert::same($one->getA(), $one->getB());
46+
47+
// factory
48+
Assert::type(Lorem::class, $one->createC());
49+
Assert::type(Lorem::class, $one->createD());
50+
Assert::notSame($one->createC(), $one->createD());
51+
52+
// nullable
53+
$two = $container->getService('two');
54+
Assert::null($two->getB());
55+
Assert::null($two->createD());
56+
57+
// undefined
58+
Assert::exception(function () use ($two) {
59+
$two->getA();
60+
}, Nette\DI\MissingServiceException::class, 'Service is not defined.');
61+
62+
Assert::exception(function () use ($two) {
63+
$two->createC();
64+
}, Nette\DI\MissingServiceException::class, 'Service is not defined.');

tests/DI/Definitions.LocatorDefinition.api.phpt

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ interface Good2
5555
public function create($name);
5656
}
5757

58+
interface Good3
59+
{
60+
public function createA();
61+
62+
public function getB();
63+
}
64+
5865

5966
Assert::exception(function () {
6067
$def = new LocatorDefinition;
@@ -77,37 +84,37 @@ Assert::exception(function () {
7784
Assert::exception(function () {
7885
$def = new LocatorDefinition;
7986
$def->setImplement('Bad1');
80-
}, Nette\InvalidArgumentException::class, "Service '': Interface Bad1 must have just one non-static method create() or get().");
87+
}, Nette\InvalidArgumentException::class, "Service '': Interface Bad1 must have at least one method.");
8188

8289

8390
Assert::exception(function () {
8491
$def = new LocatorDefinition;
8592
$def->setImplement('Bad2');
86-
}, Nette\InvalidArgumentException::class, "Service '': Method Bad2::create() must have one parameter.");
93+
}, Nette\InvalidArgumentException::class, "Service '': Method Bad2::create() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static.");
8794

8895

8996
Assert::exception(function () {
9097
$def = new LocatorDefinition;
9198
$def->setImplement('Bad3');
92-
}, Nette\InvalidArgumentException::class, "Service '': Method Bad3::get() must have one parameter.");
99+
}, Nette\InvalidArgumentException::class, "Service '': Method Bad3::get() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static.");
93100

94101

95102
Assert::exception(function () {
96103
$def = new LocatorDefinition;
97104
$def->setImplement('Bad4');
98-
}, Nette\InvalidArgumentException::class, "Service '': Interface Bad4 must have just one non-static method create() or get().");
105+
}, Nette\InvalidArgumentException::class, "Service '': Method Bad4::foo() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static.");
99106

100107

101108
Assert::exception(function () {
102109
$def = new LocatorDefinition;
103110
$def->setImplement('Bad5');
104-
}, Nette\InvalidArgumentException::class, "Service '': Interface Bad5 must have just one non-static method create() or get().");
111+
}, Nette\InvalidArgumentException::class, "Service '': Method Bad5::get() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static.");
105112

106113

107114
Assert::exception(function () {
108115
$def = new LocatorDefinition;
109116
$def->setImplement('Bad6');
110-
}, Nette\InvalidArgumentException::class, "Service '': Method Bad6::get() must have one parameter.");
117+
}, Nette\InvalidArgumentException::class, "Service '': Method Bad6::get() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static.");
111118

112119

113120
Assert::noError(function () {
@@ -126,6 +133,14 @@ Assert::noError(function () {
126133
});
127134

128135

136+
Assert::noError(function () {
137+
$def = new LocatorDefinition;
138+
$def->setImplement('Good3');
139+
Assert::same('Good3', $def->getImplement());
140+
Assert::same('Good3', $def->getType());
141+
});
142+
143+
129144
test(function () {
130145
$def = new LocatorDefinition;
131146
$def->setImplement('Good1');
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
/**
4+
* Test: LocatorDefinition
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\DI\Definitions\LocatorDefinition;
10+
use Tester\Assert;
11+
12+
13+
require __DIR__ . '/../bootstrap.php';
14+
15+
16+
interface Good
17+
{
18+
public function createFirst(): stdClass;
19+
20+
public function getSecond(): ?stdClass;
21+
}
22+
23+
24+
test(function () {
25+
$def = new LocatorDefinition;
26+
$def->setName('abc');
27+
$def->setImplement('Good');
28+
$def->setReferences(['first' => '@a', 'second' => 'stdClass']);
29+
30+
$builder = new Nette\DI\ContainerBuilder;
31+
$builder->addDefinition('a')->setType('stdClass');
32+
$resolver = new Nette\DI\Resolver($builder);
33+
34+
$resolver->resolveDefinition($def);
35+
$resolver->completeDefinition($def);
36+
37+
$phpGenerator = new Nette\DI\PhpGenerator($builder);
38+
$method = $phpGenerator->generateMethod($def);
39+
40+
Assert::match(
41+
'public function createServiceAbc(): Good
42+
{
43+
return new class ($this) implements Good {
44+
private $container;
45+
46+
47+
public function __construct($container)
48+
{
49+
$this->container = $container;
50+
}
51+
52+
53+
public function createFirst(): stdClass
54+
{
55+
return $this->container->createServiceA();
56+
}
57+
58+
59+
public function getSecond(): ?stdClass
60+
{
61+
return $this->container->getService(\'a\');
62+
}
63+
};
64+
}', $method->__toString());
65+
});

0 commit comments

Comments
 (0)