Skip to content

Commit 64e9a08

Browse files
committed
ContainerBuilder: better services completion
1 parent e552834 commit 64e9a08

File tree

3 files changed

+103
-27
lines changed

3 files changed

+103
-27
lines changed

src/DI/ContainerBuilder.php

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -523,17 +523,14 @@ private function resolveEntityClass($entity, $recursive = [])
523523
if (Strings::contains($service, '\\')) { // @\Class
524524
return ltrim($service, '\\');
525525
}
526-
return $this->definitions[$service]->getImplement() ?: $this->resolveServiceClass($service, $recursive);
526+
return $this->definitions[$service]->getImplement()
527+
?: $this->definitions[$service]->getClass()
528+
?: $this->resolveServiceClass($service, $recursive);
527529

528530
} elseif (is_string($entity)) {
529531
$name = array_slice(array_keys($recursive), -1);
530532
if (!class_exists($entity)) {
531533
throw new ServiceCreationException("Class $entity used in service '$name[0]' not found.");
532-
} elseif ((new ReflectionClass($entity))->isAbstract()) {
533-
throw new ServiceCreationException("Class $entity used in service '$name[0]' is abstract.");
534-
} elseif (($rm = (new ReflectionClass($entity))->getConstructor()) !== NULL && !$rm->isPublic()) {
535-
$visibility = $rm->isProtected() ? 'protected' : 'private';
536-
throw new ServiceCreationException("Class $entity used in service '$name[0]' has $visibility constructor.");
537534
}
538535
return ltrim($entity, '\\');
539536
}
@@ -605,7 +602,14 @@ public function completeStatement(Statement $statement)
605602
} elseif ($entity === 'not') { // operator
606603

607604
} elseif (is_string($entity)) { // class name
608-
if ($constructor = (new ReflectionClass($entity))->getConstructor()) {
605+
if (!class_exists($entity)) {
606+
throw new ServiceCreationException("Class $entity not found.");
607+
} elseif ((new ReflectionClass($entity))->isAbstract()) {
608+
throw new ServiceCreationException("Class $entity is abstract.");
609+
} elseif (($rm = (new ReflectionClass($entity))->getConstructor()) !== NULL && !$rm->isPublic()) {
610+
$visibility = $rm->isProtected() ? 'protected' : 'private';
611+
throw new ServiceCreationException("Class $entity has $visibility constructor.");
612+
} elseif ($constructor = (new ReflectionClass($entity))->getConstructor()) {
609613
$this->addDependency((string) $constructor->getFileName());
610614
$arguments = Helpers::autowireArguments($constructor, $arguments, $this);
611615
} elseif ($arguments) {
@@ -619,31 +623,32 @@ public function completeStatement(Statement $statement)
619623
throw new ServiceCreationException("Expected function, method or property name, '$entity[1]' given.");
620624

621625
} elseif ($entity[0] === '') { // globalFunc
626+
if (!Nette\Utils\Arrays::isList($arguments)) {
627+
throw new ServiceCreationException("Unable to pass specified arguments to $entity[0].");
628+
} elseif (!function_exists($entity[1])) {
629+
throw new ServiceCreationException("Function $entity[1] doesn't exist.");
630+
}
622631

623-
} elseif ($entity[0] instanceof Statement) {
624-
$entity[0] = $this->completeStatement($entity[0]);
632+
$rf = new \ReflectionFunction($entity[1]);
633+
$this->addDependency((string) $rf->getFileName());
634+
$arguments = Helpers::autowireArguments($rf, $arguments, $this);
625635

626-
} elseif ($entity[1][0] === '$') { // property getter, setter or appender
627-
Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Nette\Utils\Callback::toString($entity) . "'");
628-
if (!$arguments && substr($entity[1], -2) === '[]') {
629-
throw new ServiceCreationException("Missing argument for $entity[1].");
630-
}
631-
if ($service = $this->getServiceName($entity[0])) {
636+
} else {
637+
if ($entity[0] instanceof Statement) {
638+
$entity[0] = $this->completeStatement($entity[0]);
639+
} elseif ($service = $this->getServiceName($entity[0])) { // service method
632640
$entity[0] = '@' . $service;
633641
}
634642

635-
} elseif ($service = $this->getServiceName($entity[0])) { // service method
636-
$class = $this->definitions[$service]->getImplement();
637-
if (!$class || !method_exists($class, $entity[1])) {
638-
$class = $this->definitions[$service]->getClass();
639-
}
640-
if ($class) {
643+
if ($entity[1][0] === '$') { // property getter, setter or appender
644+
Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Nette\Utils\Callback::toString($entity) . "'");
645+
if (!$arguments && substr($entity[1], -2) === '[]') {
646+
throw new ServiceCreationException("Missing argument for $entity[1].");
647+
}
648+
} else {
649+
$class = $this->resolveEntityClass($entity[0]);
641650
$arguments = $this->autowireArguments($class, $entity[1], $arguments);
642651
}
643-
$entity[0] = '@' . $service;
644-
645-
} else { // static method
646-
$arguments = $this->autowireArguments($entity[0], $entity[1], $arguments);
647652
}
648653

649654
array_walk_recursive($arguments, function (& $val) {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
use Nette\DI;
4+
use Tester\Assert;
5+
6+
7+
require __DIR__ . '/../bootstrap.php';
8+
9+
10+
/** @return ClassA */
11+
function func()
12+
{
13+
return new ClassA;
14+
}
15+
16+
class ClassA
17+
{
18+
/** @return ClassB */
19+
function funcA(stdClass $arg)
20+
{
21+
return new ClassB;
22+
}
23+
}
24+
25+
class ClassB
26+
{
27+
/** @return ClassC */
28+
function funcB(stdClass $arg)
29+
{
30+
return new ClassC;
31+
}
32+
}
33+
34+
class ClassC
35+
{}
36+
37+
38+
$compiler = new DI\Compiler;
39+
$container = createContainer($compiler, '
40+
services:
41+
std: stdClass
42+
classA: ::func()
43+
classB1: @classA::funcA()
44+
classB2: ::func()::funcA()
45+
classC: ClassA()::funcA()::funcB()
46+
', 'neon');
47+
48+
Assert::type('ClassA', $container->getService('classA'));
49+
Assert::type('ClassB', $container->getService('classB1'));
50+
Assert::type('ClassB', $container->getService('classB2'));
51+
Assert::type('ClassC', $container->getService('classC'));

tests/DI/ContainerBuilder.factory.error.phpt

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
use Nette\DI;
8+
use Nette\DI\Statement;
89
use Tester\Assert;
910

1011

@@ -144,7 +145,26 @@ Assert::exception(function () {
144145
$builder = new DI\ContainerBuilder;
145146
$builder->addDefinition('one')->setClass('Bad8');
146147
$builder->generateClasses();
147-
}, Nette\InvalidStateException::class, "Class Bad8 used in service 'one' has private constructor.");
148+
}, Nette\InvalidStateException::class, "Service 'one': Class Bad8 has private constructor.");
149+
150+
151+
class Good
152+
{
153+
function __construct()
154+
{}
155+
}
156+
157+
Assert::exception(function () {
158+
$builder = new DI\ContainerBuilder;
159+
$builder->addDefinition('one')->setFactory('Good', [new Statement('Bad')]);
160+
$builder->generateClasses();
161+
}, Nette\InvalidStateException::class, "Service 'one': Class Bad not found.");
162+
163+
Assert::exception(function () {
164+
$builder = new DI\ContainerBuilder;
165+
$builder->addDefinition('one')->setFactory('Good', [new Statement('Bad8')]);
166+
$builder->generateClasses();
167+
}, Nette\InvalidStateException::class, "Service 'one': Class Bad8 has private constructor.");
148168

149169

150170
abstract class Bad9
@@ -157,4 +177,4 @@ Assert::exception(function () {
157177
$builder = new DI\ContainerBuilder;
158178
$builder->addDefinition('one')->setClass('Bad9');
159179
$builder->generateClasses();
160-
}, Nette\InvalidStateException::class, "Class Bad9 used in service 'one' is abstract.");
180+
}, Nette\InvalidStateException::class, "Service 'one': Class Bad9 is abstract.");

0 commit comments

Comments
 (0)