resultDefinition = new ServiceDefinition(); } /** @return static */ public function setImplement(string $interface) { if (!\interface_exists($interface)) { throw new \Packetery\Nette\InvalidArgumentException(\sprintf("Service '%s': Interface '%s' not found.", $this->getName(), $interface)); } $rc = new \ReflectionClass($interface); $method = $rc->getMethods()[0] ?? null; if (!$method || $method->isStatic() || $method->name !== self::MethodCreate || \count($rc->getMethods()) > 1) { throw new \Packetery\Nette\InvalidArgumentException(\sprintf("Service '%s': Interface %s must have just one non-static method create().", $this->getName(), $interface)); } try { Helpers::ensureClassType(Type::fromReflection($method), "return type of {$interface}::create()"); } catch (\Packetery\Nette\DI\ServiceCreationException $e) { \trigger_error($e->getMessage(), \E_USER_DEPRECATED); } return parent::setType($interface); } public function getImplement() : ?string { return $this->getType(); } public final function getResultType() : ?string { return $this->resultDefinition->getType(); } /** @return static */ public function setResultDefinition(Definition $definition) { $this->resultDefinition = $definition; return $this; } /** @return ServiceDefinition */ public function getResultDefinition() : Definition { return $this->resultDefinition; } /** @deprecated */ public function setParameters(array $params) { if ($params) { $old = $new = []; foreach ($params as $k => $v) { $tmp = \explode(' ', \is_int($k) ? $v : $k); $old[] = '%' . \end($tmp) . '%'; $new[] = '$' . \end($tmp); } \trigger_error(\sprintf("Service '%s': Option 'parameters' is deprecated and should be removed. The %s should be replaced with %s in configuration.", $this->getName(), \implode(', ', $old), \implode(', ', $new)), \E_USER_DEPRECATED); } $this->parameters = $params; return $this; } /** @deprecated */ public function getParameters() : array { return $this->parameters; } public function resolveType(\Packetery\Nette\DI\Resolver $resolver) : void { $interface = $this->getType(); if (!$interface) { throw new ServiceCreationException('Type is missing in definition of service.'); } $method = new \ReflectionMethod($interface, self::MethodCreate); $type = Type::fromReflection($method) ?? Helpers::getReturnTypeAnnotation($method); $resultDef = $this->resultDefinition; try { $resolver->resolveDefinition($resultDef); } catch (ServiceCreationException $e) { if ($resultDef->getType()) { throw $e; } $resultDef->setType(Helpers::ensureClassType($type, "return type of {$interface}::create()")); $resolver->resolveDefinition($resultDef); } if ($type && !$type->allows($resultDef->getType())) { throw new ServiceCreationException(\sprintf('Factory for %s cannot create incompatible %s type.', $type, $resultDef->getType())); } } public function complete(\Packetery\Nette\DI\Resolver $resolver) : void { $resultDef = $this->resultDefinition; if ($resultDef instanceof ServiceDefinition) { if (!$this->parameters) { $this->completeParameters($resolver); } $this->convertArguments($resultDef->getCreator()->arguments); foreach ($resultDef->getSetup() as $setup) { $this->convertArguments($setup->arguments); } if ($resultDef->getEntity() instanceof Reference && !$resultDef->getCreator()->arguments) { $resultDef->setCreator([ // render as $container->createMethod() new Reference(\Packetery\Nette\DI\ContainerBuilder::ThisContainer), \Packetery\Nette\DI\Container::getMethodName($resultDef->getEntity()->getValue()), ]); } } $resolver->completeDefinition($resultDef); } private function completeParameters(\Packetery\Nette\DI\Resolver $resolver) : void { $interface = $this->getType(); $method = new \ReflectionMethod($interface, self::MethodCreate); $ctorParams = []; if (($class = $resolver->resolveEntityType($this->resultDefinition->getCreator())) && ($ctor = (new \ReflectionClass($class))->getConstructor())) { foreach ($ctor->getParameters() as $param) { $ctorParams[$param->name] = $param; } } foreach ($method->getParameters() as $param) { $methodType = Type::fromReflection($param); if (isset($ctorParams[$param->name])) { $ctorParam = $ctorParams[$param->name]; $ctorType = Type::fromReflection($ctorParam); if ($ctorType && !$ctorType->allows((string) $methodType)) { throw new ServiceCreationException(\sprintf("Type of \$%s in %s::create() doesn't match type in %s constructor.", $param->name, $interface, $class)); } $this->resultDefinition->getCreator()->arguments[$ctorParam->getPosition()] = new Php\Literal('$' . $ctorParam->name); } elseif (!$this->resultDefinition->getSetup()) { // [param1, param2] => '$param1, $param2' $stringifyParams = function (array $params) : string { return \implode(', ', \array_map(function (string $param) { return '$' . $param; }, $params)); }; $ctorParamsKeys = \array_keys($ctorParams); $hint = \Packetery\Nette\Utils\Helpers::getSuggestion($ctorParamsKeys, $param->name); throw new ServiceCreationException(\sprintf('Cannot implement %s::create(): factory method parameters (%s) are not matching %s::__construct() parameters (%s).', $interface, $stringifyParams(\array_map(function (\ReflectionParameter $param) { return $param->name; }, $method->getParameters())), $class, $stringifyParams($ctorParamsKeys)) . ($hint ? " Did you mean to use '\${$hint}' in factory method?" : '')); } $paramDef = $methodType . ' ' . $param->name; if ($param->isDefaultValueAvailable()) { $this->parameters[$paramDef] = Reflection::getParameterDefaultValue($param); } else { $this->parameters[] = $paramDef; } } } public function convertArguments(array &$args) : void { foreach ($args as &$v) { if (\is_string($v) && $v && $v[0] === '$') { $v = new Php\Literal($v); } } } public function generateMethod(Php\Method $method, \Packetery\Nette\DI\PhpGenerator $generator) : void { $class = (new Php\ClassType())->addImplement($this->getType()); $class->addProperty('container')->setPrivate(); $class->addMethod('__construct')->addBody('$this->container = $container;')->addParameter('container')->setType($generator->getClassName()); $methodCreate = $class->addMethod(self::MethodCreate); $this->resultDefinition->generateMethod($methodCreate, $generator); $body = $methodCreate->getBody(); $body = \str_replace('$this', '$this->container', $body); $body = \str_replace('$this->container->container', '$this->container', $body); $rm = new \ReflectionMethod($this->getType(), self::MethodCreate); $methodCreate->setParameters($generator->convertParameters($this->parameters))->setReturnType((string) (Type::fromReflection($rm) ?? $this->getResultType()))->setBody($body); $method->setBody('return new class ($this) ' . $class . ';'); } public function __clone() { parent::__clone(); $this->resultDefinition = \unserialize(\serialize($this->resultDefinition)); } }