Skip to content

Commit e552834

Browse files
committed
ContainerBuilder: class generator split into complete() and generateClasses()
1 parent 83b6a27 commit e552834

File tree

3 files changed

+167
-87
lines changed

3 files changed

+167
-87
lines changed

src/DI/ContainerBuilder.php

Lines changed: 145 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,140 @@ private function resolveEntityClass($entity, $recursive = [])
540540
}
541541

542542

543+
/**
544+
* @return void
545+
*/
546+
public function complete()
547+
{
548+
$this->prepareClassList();
549+
550+
foreach ($this->definitions as $name => $def) {
551+
if ($def->isDynamic()) {
552+
continue;
553+
}
554+
555+
$this->currentService = NULL;
556+
$entity = $def->getFactory()->getEntity();
557+
$serviceRef = $this->getServiceName($entity);
558+
$factory = $serviceRef && !$def->getFactory()->arguments && !$def->getSetup() && $def->getImplementType() !== 'create'
559+
? new Statement(['@' . self::THIS_CONTAINER, 'getService'], [$serviceRef])
560+
: $def->getFactory();
561+
562+
try {
563+
$def->setFactory($this->completeStatement($factory));
564+
$this->classListNeedsRefresh = FALSE;
565+
566+
$this->currentService = $name;
567+
$setups = $def->getSetup();
568+
foreach ($setups as & $setup) {
569+
if (is_string($setup->getEntity()) && strpbrk($setup->getEntity(), ':@?\\') === FALSE) { // auto-prepend @self
570+
$setup = new Statement(['@' . $name, $setup->getEntity()], $setup->arguments);
571+
}
572+
$setup = $this->completeStatement($setup);
573+
}
574+
$def->setSetup($setups);
575+
576+
} catch (\Exception $e) {
577+
throw new ServiceCreationException("Service '$name': " . $e->getMessage(), 0, $e);
578+
579+
} finally {
580+
$this->currentService = NULL;
581+
}
582+
}
583+
}
584+
585+
586+
/**
587+
* @return void
588+
*/
589+
public function completeStatement(Statement $statement)
590+
{
591+
$entity = $this->normalizeEntity($statement->getEntity());
592+
$arguments = $statement->arguments;
593+
594+
if (is_string($entity) && Strings::contains($entity, '?')) { // PHP literal
595+
596+
} elseif ($service = $this->getServiceName($entity)) { // factory calling
597+
$params = [];
598+
foreach ($this->definitions[$service]->parameters as $k => $v) {
599+
$params[] = preg_replace('#\w+\z#', '\$$0', (is_int($k) ? $v : $k)) . (is_int($k) ? '' : ' = ' . PhpHelpers::dump($v));
600+
}
601+
$rm = new \ReflectionFunction(create_function(implode(', ', $params), ''));
602+
$arguments = Helpers::autowireArguments($rm, $arguments, $this);
603+
$entity = '@' . $service;
604+
605+
} elseif ($entity === 'not') { // operator
606+
607+
} elseif (is_string($entity)) { // class name
608+
if ($constructor = (new ReflectionClass($entity))->getConstructor()) {
609+
$this->addDependency((string) $constructor->getFileName());
610+
$arguments = Helpers::autowireArguments($constructor, $arguments, $this);
611+
} elseif ($arguments) {
612+
throw new ServiceCreationException("Unable to pass arguments, class $entity has no constructor.");
613+
}
614+
615+
} elseif (!Nette\Utils\Arrays::isList($entity) || count($entity) !== 2) {
616+
throw new ServiceCreationException(sprintf('Expected class, method or property, %s given.', PhpHelpers::dump($entity)));
617+
618+
} elseif (!preg_match('#^\$?' . PhpHelpers::PHP_IDENT . '(\[\])?\z#', $entity[1])) {
619+
throw new ServiceCreationException("Expected function, method or property name, '$entity[1]' given.");
620+
621+
} elseif ($entity[0] === '') { // globalFunc
622+
623+
} elseif ($entity[0] instanceof Statement) {
624+
$entity[0] = $this->completeStatement($entity[0]);
625+
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])) {
632+
$entity[0] = '@' . $service;
633+
}
634+
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) {
641+
$arguments = $this->autowireArguments($class, $entity[1], $arguments);
642+
}
643+
$entity[0] = '@' . $service;
644+
645+
} else { // static method
646+
$arguments = $this->autowireArguments($entity[0], $entity[1], $arguments);
647+
}
648+
649+
array_walk_recursive($arguments, function (& $val) {
650+
if ($val instanceof Statement) {
651+
$val = $this->completeStatement($val);
652+
653+
} elseif ($val === $this) {
654+
trigger_error("Replace object ContainerBuilder in Statement arguments with '@container'.", E_USER_DEPRECATED);
655+
$val = self::literal('$this');
656+
657+
} elseif ($val instanceof ServiceDefinition) {
658+
$val = '@' . current(array_keys($this->getDefinitions(), $val, TRUE));
659+
660+
} elseif (is_string($val) && strlen($val) > 1 && $val[0] === '@' && $val[1] !== '@') {
661+
$pair = explode('::', $val, 2);
662+
$name = $this->getServiceName($pair[0]);
663+
if (!isset($pair[1])) { // @service
664+
$val = '@' . $name;
665+
} elseif (preg_match('#^[A-Z][A-Z0-9_]*\z#', $pair[1], $m)) { // @service::CONSTANT
666+
$val = self::literal($this->getDefinition($name)->getClass() . '::' . $pair[1]);
667+
} else { // @service::property
668+
$val = new Statement(['@' . $name, '$' . $pair[1]]);
669+
}
670+
}
671+
});
672+
673+
return new Statement($entity, $arguments);
674+
}
675+
676+
543677
private function checkCase($class)
544678
{
545679
if ((class_exists($class) || interface_exists($class)) && $class !== ($name = (new ReflectionClass($class))->getName())) {
@@ -596,7 +730,7 @@ public function getDependencies()
596730
*/
597731
public function generateClasses($className = NULL, $parentName = NULL)
598732
{
599-
$this->prepareClassList();
733+
$this->complete();
600734

601735
$this->generatedClasses = [];
602736
$this->className = $className ?: $this->className;
@@ -676,14 +810,9 @@ private function generateService($name)
676810
);
677811
}
678812

679-
$setups = $def->getSetup();
680-
foreach ($setups as & $setup) {
681-
if (is_string($setup->getEntity()) && strpbrk($setup->getEntity(), ':@?\\') === FALSE) { // auto-prepend @self
682-
$setup = new Statement(['@self', $setup->getEntity()], $setup->arguments);
683-
}
813+
foreach ($def->getSetup() as $setup) {
684814
$code .= $this->formatStatement($setup) . ";\n";
685815
}
686-
$def->setSetup($setups);
687816
$this->currentService = NULL;
688817

689818
$code .= 'return $service;';
@@ -743,39 +872,21 @@ private function convertParameters(array $parameters)
743872
*/
744873
public function formatStatement(Statement $statement)
745874
{
746-
$entity = $this->normalizeEntity($statement->getEntity());
875+
$entity = $statement->getEntity();
747876
$arguments = $statement->arguments;
748877

749878
if (is_string($entity) && Strings::contains($entity, '?')) { // PHP literal
750879
return $this->formatPhp($entity, $arguments);
751880

752881
} elseif ($service = $this->getServiceName($entity)) { // factory calling
753-
$params = [];
754-
foreach ($this->definitions[$service]->parameters as $k => $v) {
755-
$params[] = preg_replace('#\w+\z#', '\$$0', (is_int($k) ? $v : $k)) . (is_int($k) ? '' : ' = ' . PhpHelpers::dump($v));
756-
}
757-
$rm = new \ReflectionFunction(create_function(implode(', ', $params), ''));
758-
$arguments = Helpers::autowireArguments($rm, $arguments, $this);
759882
return $this->formatPhp('$this->?(?*)', [Container::getMethodName($service), $arguments]);
760883

761884
} elseif ($entity === 'not') { // operator
762885
return $this->formatPhp('!?', [$arguments[0]]);
763886

764887
} elseif (is_string($entity)) { // class name
765-
if ($constructor = (new ReflectionClass($entity))->getConstructor()) {
766-
$this->addDependency((string) $constructor->getFileName());
767-
$arguments = Helpers::autowireArguments($constructor, $arguments, $this);
768-
} elseif ($arguments) {
769-
throw new ServiceCreationException("Unable to pass arguments, class $entity has no constructor.");
770-
}
771888
return $this->formatPhp("new $entity" . ($arguments ? '(?*)' : ''), [$arguments]);
772889

773-
} elseif (!Nette\Utils\Arrays::isList($entity) || count($entity) !== 2) {
774-
throw new ServiceCreationException(sprintf('Expected class, method or property, %s given.', PhpHelpers::dump($entity)));
775-
776-
} elseif (!preg_match('#^\$?' . PhpHelpers::PHP_IDENT . '(\[\])?\z#', $entity[1])) {
777-
throw new ServiceCreationException("Expected function, method or property name, '$entity[1]' given.");
778-
779890
} elseif ($entity[0] === '') { // globalFunc
780891
return $this->formatPhp("$entity[1](?*)", [$arguments]);
781892

@@ -787,12 +898,8 @@ public function formatStatement(Statement $statement)
787898
return $this->formatPhp("$inner->?(?*)", [$entity[1], $arguments]);
788899

789900
} elseif ($entity[1][0] === '$') { // property getter, setter or appender
790-
Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Nette\Utils\Callback::toString($entity) . "'");
791901
$name = substr($entity[1], 1);
792902
if ($append = (substr($name, -2) === '[]')) {
793-
if (!$arguments) {
794-
throw new ServiceCreationException("Missing argument for $entity[1].");
795-
}
796903
$name = substr($name, 0, -2);
797904
}
798905
if ($this->getServiceName($entity[0])) {
@@ -805,17 +912,9 @@ public function formatStatement(Statement $statement)
805912
: $prop;
806913

807914
} elseif ($service = $this->getServiceName($entity[0])) { // service method
808-
$class = $this->definitions[$service]->getImplement();
809-
if (!$class || !method_exists($class, $entity[1])) {
810-
$class = $this->definitions[$service]->getClass();
811-
}
812-
if ($class) {
813-
$arguments = $this->autowireArguments($class, $entity[1], $arguments);
814-
}
815915
return $this->formatPhp('?->?(?*)', [$entity[0], $entity[1], $arguments]);
816916

817917
} else { // static method
818-
$arguments = $this->autowireArguments($entity[0], $entity[1], $arguments);
819918
return $this->formatPhp("$entity[0]::$entity[1](?*)", [$arguments]);
820919
}
821920
}
@@ -832,37 +931,18 @@ public function formatPhp($statement, $args)
832931
if ($val instanceof Statement) {
833932
$val = self::literal($this->formatStatement($val));
834933

835-
} elseif ($val === $this) {
836-
trigger_error("Replace object ContainerBuilder in Statement arguments with '@container'.", E_USER_DEPRECATED);
837-
$val = self::literal('$this');
838-
839-
} elseif ($val instanceof ServiceDefinition) {
840-
$val = '@' . current(array_keys($this->getDefinitions(), $val, TRUE));
841-
}
842-
843-
if (!is_string($val)) {
844-
return;
845-
846-
} elseif (substr($val, 0, 2) === '@@') {
934+
} elseif (is_string($val) && substr($val, 0, 2) === '@@') { // escaped text @@
847935
$val = substr($val, 1);
848936

849-
} elseif (substr($val, 0, 1) === '@' && strlen($val) > 1) {
850-
$pair = explode('::', $val, 2);
851-
$name = $this->getServiceName($pair[0]);
852-
if (isset($pair[1]) && preg_match('#^[A-Z][A-Z0-9_]*\z#', $pair[1])) {
853-
$val = $this->getDefinition($name)->getClass() . '::' . $pair[1];
854-
} elseif (isset($pair[1])) {
855-
$val = $this->formatStatement(new Statement(['@' . $name, '$' . $pair[1]]));
937+
} elseif (is_string($val) && substr($val, 0, 1) === '@' && strlen($val) > 1) { // service reference
938+
$name = substr($val, 1);
939+
if ($name === self::THIS_CONTAINER) {
940+
$val = self::literal('$this');
941+
} elseif ($name === $this->currentService) {
942+
$val = self::literal('$service');
856943
} else {
857-
if ($name === self::THIS_CONTAINER) {
858-
$val = '$this';
859-
} elseif ($name === $this->currentService) {
860-
$val = '$service';
861-
} else {
862-
$val = $this->formatStatement(new Statement(['@' . self::THIS_CONTAINER, 'getService'], [$name]));
863-
}
944+
$val = self::literal($this->formatStatement(new Statement(['@' . self::THIS_CONTAINER, 'getService'], [$name])));
864945
}
865-
$val = self::literal($val);
866946
}
867947
});
868948
return PhpHelpers::formatArgs($statement, $args);

tests/DI/DecoratorExtension.basic.phpt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ Assert::same(
7070
Assert::true($builder->getDefinition('one')->getTag('inject'));
7171

7272
Assert::equal([
73-
new Statement(['@self', 'setup'], ['Service']),
74-
new Statement(['@self', 'setup'], ['Object']),
75-
new Statement(['@self', 'setup'], ['Iface']),
76-
new Statement(['@self', 'setup']),
73+
new Statement(['@one', 'setup'], ['Service']),
74+
new Statement(['@one', 'setup'], ['Object']),
75+
new Statement(['@one', 'setup'], ['Iface']),
76+
new Statement(['@one', 'setup']),
7777
], $builder->getDefinition('one')->getSetup());

tests/DI/InjectExtension.basic.phpt

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -73,28 +73,28 @@ services:
7373
$builder = $compiler->getContainerBuilder();
7474

7575
Assert::equal([
76-
new Statement(['@self', 'injectB']),
77-
new Statement(['@self', 'injectA']),
78-
new Statement(['@self', 'injectD']),
79-
new Statement(['@self', 'injectC']),
80-
new Statement(['@self', '$c'], ['@\\stdClass']),
81-
new Statement(['@self', '$a'], ['@\\stdClass']),
76+
new Statement(['@last.one', 'injectB']),
77+
new Statement(['@last.one', 'injectA']),
78+
new Statement(['@last.one', 'injectD']),
79+
new Statement(['@last.one', 'injectC']),
80+
new Statement(['@last.one', '$c'], ['@1_stdClass']),
81+
new Statement(['@last.one', '$a'], ['@1_stdClass']),
8282
], $builder->getDefinition('last.one')->getSetup());
8383

8484
Assert::equal([
85-
new Statement(['@self', 'injectB']),
86-
new Statement(['@self', 'injectA']),
87-
new Statement(['@self', 'injectD']),
88-
new Statement(['@self', 'injectC']),
89-
new Statement(['@self', '$c'], ['@\\stdClass']),
90-
new Statement(['@self', '$a'], ['@\\stdClass']),
85+
new Statement(['@ext.one', 'injectB']),
86+
new Statement(['@ext.one', 'injectA']),
87+
new Statement(['@ext.one', 'injectD']),
88+
new Statement(['@ext.one', 'injectC']),
89+
new Statement(['@ext.one', '$c'], ['@1_stdClass']),
90+
new Statement(['@ext.one', '$a'], ['@1_stdClass']),
9191
], $builder->getDefinition('ext.one')->getSetup());
9292

9393
Assert::equal([
94-
new Statement(['@self', 'injectB'], [1]),
95-
new Statement(['@self', 'injectA']),
96-
new Statement(['@self', 'injectD']),
97-
new Statement(['@self', 'injectC']),
98-
new Statement(['@self', '$c'], ['@\\stdClass']),
99-
new Statement(['@self', '$a'], ['@\\stdClass']),
94+
new Statement(['@two', 'injectB'], [1]),
95+
new Statement(['@two', 'injectA']),
96+
new Statement(['@two', 'injectD']),
97+
new Statement(['@two', 'injectC']),
98+
new Statement(['@two', '$c'], ['@1_stdClass']),
99+
new Statement(['@two', '$a'], ['@1_stdClass']),
100100
], $builder->getDefinition('two')->getSetup());

0 commit comments

Comments
 (0)