Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
      • Traits
    • Reflection
    • Security
    • Tokenizer
    • Utils
  • Tracy
    • Bridges
      • Nette
  • none

Classes

  • Compiler
  • CompilerExtension
  • Container
  • ContainerBuilder
  • ContainerLoader
  • DependencyChecker
  • Helpers
  • PhpGenerator
  • PhpReflection
  • ServiceDefinition
  • Statement

Exceptions

  • MissingServiceException
  • ServiceCreationException
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Other releases
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (https://nette.org)
  5:  * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  6:  */
  7: 
  8: namespace Nette\DI;
  9: 
 10: use Nette;
 11: use Nette\PhpGenerator\Helpers as PhpHelpers;
 12: use Nette\PhpGenerator\PhpLiteral;
 13: use Nette\Utils\Reflection;
 14: use Nette\Utils\Strings;
 15: 
 16: 
 17: /**
 18:  * Container PHP code generator.
 19:  */
 20: class PhpGenerator
 21: {
 22:     /** @var ContainerBuilder */
 23:     private $builder;
 24: 
 25:     /** @var string */
 26:     private $className;
 27: 
 28:     /** @var Nette\PhpGenerator\ClassType[] */
 29:     private $generatedClasses = [];
 30: 
 31:     /** @var string */
 32:     private $currentService;
 33: 
 34: 
 35:     public function __construct(ContainerBuilder $builder)
 36:     {
 37:         $this->builder = $builder;
 38:     }
 39: 
 40: 
 41:     /**
 42:      * Generates PHP classes. First class is the container.
 43:      * @return Nette\PhpGenerator\ClassType[]
 44:      */
 45:     public function generate($className)
 46:     {
 47:         $this->builder->complete();
 48: 
 49:         $this->generatedClasses = [];
 50:         $this->className = $className;
 51:         $containerClass = $this->generatedClasses[] = new Nette\PhpGenerator\ClassType($this->className);
 52:         $containerClass->setExtends(Container::class);
 53:         $containerClass->addMethod('__construct')
 54:             ->addBody('$this->parameters = $params;')
 55:             ->addBody('$this->parameters += ?;', [$this->builder->parameters])
 56:             ->addParameter('params', [])
 57:                 ->setTypeHint('array');
 58: 
 59:         $definitions = $this->builder->getDefinitions();
 60:         ksort($definitions);
 61: 
 62:         $meta = $containerClass->addProperty('meta')
 63:             ->setVisibility('protected')
 64:             ->setValue([Container::TYPES => $this->builder->getClassList()]);
 65: 
 66:         foreach ($definitions as $name => $def) {
 67:             $meta->value[Container::SERVICES][$name] = $def->getType() ?: null;
 68:             foreach ($def->getTags() as $tag => $value) {
 69:                 $meta->value[Container::TAGS][$tag][$name] = $value;
 70:             }
 71:         }
 72: 
 73:         foreach ($definitions as $name => $def) {
 74:             try {
 75:                 $name = (string) $name;
 76:                 $methodName = Container::getMethodName($name);
 77:                 if (!PhpHelpers::isIdentifier($methodName)) {
 78:                     throw new ServiceCreationException('Name contains invalid characters.');
 79:                 }
 80:                 $containerClass->addMethod($methodName)
 81:                     ->addComment(PHP_VERSION_ID < 70000 ? '@return ' . ($def->getImplement() ?: $def->getType()) : '')
 82:                     ->setReturnType(PHP_VERSION_ID >= 70000 ? ($def->getImplement() ?: $def->getType()) : null)
 83:                     ->setBody($name === ContainerBuilder::THIS_CONTAINER ? 'return $this;' : $this->generateService($name))
 84:                     ->setParameters($def->getImplement() ? [] : $this->convertParameters($def->parameters));
 85:             } catch (\Exception $e) {
 86:                 throw new ServiceCreationException("Service '$name': " . $e->getMessage(), 0, $e);
 87:             }
 88:         }
 89: 
 90:         $aliases = $this->builder->getAliases();
 91:         ksort($aliases);
 92:         $meta->value[Container::ALIASES] = $aliases;
 93: 
 94:         return $this->generatedClasses;
 95:     }
 96: 
 97: 
 98:     /**
 99:      * Generates body of service method.
100:      * @return string
101:      */
102:     private function generateService($name)
103:     {
104:         $def = $this->builder->getDefinition($name);
105: 
106:         if ($def->isDynamic()) {
107:             return PhpHelpers::formatArgs('throw new Nette\\DI\\ServiceCreationException(?);',
108:                 ["Unable to create dynamic service '$name', it must be added using addService()"]
109:             );
110:         }
111: 
112:         $entity = $def->getFactory()->getEntity();
113:         $serviceRef = $this->builder->getServiceName($entity);
114:         $factory = $serviceRef && !$def->getFactory()->arguments && !$def->getSetup() && $def->getImplementMode() !== $def::IMPLEMENT_MODE_CREATE
115:             ? new Statement(['@' . ContainerBuilder::THIS_CONTAINER, 'getService'], [$serviceRef])
116:             : $def->getFactory();
117: 
118:         $this->currentService = null;
119:         $code = '$service = ' . $this->formatStatement($factory) . ";\n";
120: 
121:         if (
122:             (PHP_VERSION_ID < 70000 || $def->getSetup())
123:             && ($type = $def->getType())
124:             && !$serviceRef && $type !== $entity
125:             && !(is_string($entity) && preg_match('#^[\w\\\\]+\z#', $entity) && is_subclass_of($entity, $type))
126:         ) {
127:             $code .= PhpHelpers::formatArgs("if (!\$service instanceof $type) {\n"
128:                 . "\tthrow new Nette\\UnexpectedValueException(?);\n}\n",
129:                 ["Unable to create service '$name', value returned by factory is not $type type."]
130:             );
131:         }
132: 
133:         $this->currentService = $name;
134:         foreach ($def->getSetup() as $setup) {
135:             $code .= $this->formatStatement($setup) . ";\n";
136:         }
137: 
138:         $code .= 'return $service;';
139: 
140:         if (!$def->getImplement()) {
141:             return $code;
142:         }
143: 
144:         $factoryClass = (new Nette\PhpGenerator\ClassType)
145:             ->addImplement($def->getImplement());
146: 
147:         $factoryClass->addProperty('container')
148:             ->setVisibility('private');
149: 
150:         $factoryClass->addMethod('__construct')
151:             ->addBody('$this->container = $container;')
152:             ->addParameter('container')
153:                 ->setTypeHint($this->className);
154: 
155:         $rm = new \ReflectionMethod($def->getImplement(), $def->getImplementMode());
156: 
157:         $factoryClass->addMethod($def->getImplementMode())
158:             ->setParameters($this->convertParameters($def->parameters))
159:             ->setBody(str_replace('$this', '$this->container', $code))
160:             ->setReturnType(PHP_VERSION_ID >= 70000 ? (Reflection::getReturnType($rm) ?: $def->getType()) : null);
161: 
162:         if (PHP_VERSION_ID < 70000) {
163:             $this->generatedClasses[] = $factoryClass;
164:             $factoryClass->setName(str_replace(['\\', '.'], '_', "{$this->className}_{$def->getImplement()}Impl_{$name}"));
165:             return "return new {$factoryClass->getName()}(\$this);";
166:         }
167: 
168:         return 'return new class ($this) ' . $factoryClass . ';';
169:     }
170: 
171: 
172:     /**
173:      * Formats PHP code for class instantiating, function calling or property setting in PHP.
174:      * @return string
175:      */
176:     private function formatStatement(Statement $statement)
177:     {
178:         $entity = $statement->getEntity();
179:         $arguments = $statement->arguments;
180: 
181:         if (is_string($entity) && Strings::contains($entity, '?')) { // PHP literal
182:             return $this->formatPhp($entity, $arguments);
183: 
184:         } elseif ($service = $this->builder->getServiceName($entity)) { // factory calling
185:             return $this->formatPhp('$this->?(...?)', [Container::getMethodName($service), $arguments]);
186: 
187:         } elseif ($entity === 'not') { // operator
188:             return $this->formatPhp('!?', [$arguments[0]]);
189: 
190:         } elseif (is_string($entity)) { // class name
191:             return $this->formatPhp("new $entity" . ($arguments ? '(...?)' : ''), $arguments ? [$arguments] : []);
192: 
193:         } elseif ($entity[0] === '') { // globalFunc
194:             return $this->formatPhp("$entity[1](...?)", [$arguments]);
195: 
196:         } elseif ($entity[0] instanceof Statement) {
197:             $inner = $this->formatPhp('?', [$entity[0]]);
198:             if (substr($inner, 0, 4) === 'new ') {
199:                 $inner = "($inner)";
200:             }
201:             return $this->formatPhp("$inner->?(...?)", [$entity[1], $arguments]);
202: 
203:         } elseif ($entity[1][0] === '$') { // property getter, setter or appender
204:             $name = substr($entity[1], 1);
205:             if ($append = (substr($name, -2) === '[]')) {
206:                 $name = substr($name, 0, -2);
207:             }
208:             if ($this->builder->getServiceName($entity[0])) {
209:                 $prop = $this->formatPhp('?->?', [$entity[0], $name]);
210:             } else {
211:                 $prop = $this->formatPhp($entity[0] . '::$?', [$name]);
212:             }
213:             return $arguments
214:                 ? $this->formatPhp($prop . ($append ? '[]' : '') . ' = ?', [$arguments[0]])
215:                 : $prop;
216: 
217:         } elseif ($service = $this->builder->getServiceName($entity[0])) { // service method
218:             return $this->formatPhp('?->?(...?)', [$entity[0], $entity[1], $arguments]);
219: 
220:         } else { // static method
221:             return $this->formatPhp("$entity[0]::$entity[1](...?)", [$arguments]);
222:         }
223:     }
224: 
225: 
226:     /**
227:      * Formats PHP statement.
228:      * @return string
229:      * @internal
230:      */
231:     public function formatPhp($statement, $args)
232:     {
233:         array_walk_recursive($args, function (&$val) {
234:             if ($val instanceof Statement) {
235:                 $val = new PhpLiteral($this->formatStatement($val));
236: 
237:             } elseif (is_string($val) && substr($val, 0, 2) === '@@') { // escaped text @@
238:                 $val = substr($val, 1);
239: 
240:             } elseif (is_string($val) && substr($val, 0, 1) === '@' && strlen($val) > 1) { // service reference
241:                 $name = substr($val, 1);
242:                 if ($name === ContainerBuilder::THIS_CONTAINER) {
243:                     $val = new PhpLiteral('$this');
244:                 } elseif ($name === $this->currentService) {
245:                     $val = new PhpLiteral('$service');
246:                 } else {
247:                     $val = new PhpLiteral($this->formatStatement(new Statement(['@' . ContainerBuilder::THIS_CONTAINER, 'getService'], [$name])));
248:                 }
249:             }
250:         });
251:         return PhpHelpers::formatArgs($statement, $args);
252:     }
253: 
254: 
255:     /**
256:      * Converts parameters from ServiceDefinition to PhpGenerator.
257:      * @return Nette\PhpGenerator\Parameter[]
258:      */
259:     private function convertParameters(array $parameters)
260:     {
261:         $res = [];
262:         foreach ($parameters as $k => $v) {
263:             $tmp = explode(' ', is_int($k) ? $v : $k);
264:             $param = $res[] = new Nette\PhpGenerator\Parameter(end($tmp));
265:             if (!is_int($k)) {
266:                 $param->setOptional(true)->setDefaultValue($v);
267:             }
268:             if (isset($tmp[1])) {
269:                 $param->setTypeHint($tmp[0]);
270:             }
271:         }
272:         return $res;
273:     }
274: }
275: 
Nette 2.4-20180918 API API documentation generated by ApiGen 2.8.0