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
    • Reflection
    • Security
    • Utils
  • none
  • Tracy
    • Bridges
      • Nette

Classes

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

Exceptions

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