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
      • Reflection
      • 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
  • ContainerFactory
  • ContainerLoader
  • ServiceDefinition
  • Statement

Exceptions

  • MissingServiceException
  • ServiceCreationException
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Other releases
  • Nette homepage
  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: 
 13: 
 14: /**
 15:  * DI container compiler.
 16:  */
 17: class Compiler extends Nette\Object
 18: {
 19:     /** @var CompilerExtension[] */
 20:     private $extensions = array();
 21: 
 22:     /** @var ContainerBuilder */
 23:     private $builder;
 24: 
 25:     /** @var array */
 26:     private $config = array();
 27: 
 28:     /** @var string[] of file names */
 29:     private $dependencies = array();
 30: 
 31:     /** @var string */
 32:     private $className;
 33: 
 34:     /** @var array reserved section names */
 35:     private static $reserved = array('services' => 1, 'parameters' => 1);
 36: 
 37: 
 38:     public function __construct(ContainerBuilder $builder = NULL)
 39:     {
 40:         $this->builder = $builder ?: new ContainerBuilder;
 41:     }
 42: 
 43: 
 44:     /**
 45:      * Add custom configurator extension.
 46:      * @return static
 47:      */
 48:     public function addExtension($name, CompilerExtension $extension)
 49:     {
 50:         if (isset($this->extensions[$name]) || isset(self::$reserved[$name])) {
 51:             throw new Nette\InvalidArgumentException("Name '$name' is already used or reserved.");
 52:         }
 53:         $this->extensions[$name] = $extension->setCompiler($this, $name);
 54:         return $this;
 55:     }
 56: 
 57: 
 58:     /**
 59:      * @return array
 60:      */
 61:     public function getExtensions($type = NULL)
 62:     {
 63:         return $type
 64:             ? array_filter($this->extensions, function ($item) use ($type) { return $item instanceof $type; })
 65:             : $this->extensions;
 66:     }
 67: 
 68: 
 69:     /**
 70:      * @return ContainerBuilder
 71:      */
 72:     public function getContainerBuilder()
 73:     {
 74:         return $this->builder;
 75:     }
 76: 
 77: 
 78:     /**
 79:      * @return static
 80:      */
 81:     public function setClassName($className)
 82:     {
 83:         $this->className = $className;
 84:         return $this;
 85:     }
 86: 
 87: 
 88:     /**
 89:      * Adds new configuration.
 90:      * @return static
 91:      */
 92:     public function addConfig(array $config)
 93:     {
 94:         $this->config = Config\Helpers::merge($config, $this->config);
 95:         return $this;
 96:     }
 97: 
 98: 
 99:     /**
100:      * Adds new configuration from file.
101:      * @return static
102:      */
103:     public function loadConfig($file)
104:     {
105:         $loader = new Config\Loader;
106:         $this->addConfig($loader->load($file));
107:         $this->addDependencies($loader->getDependencies());
108:         return $this;
109:     }
110: 
111: 
112:     /**
113:      * Returns configuration.
114:      * @return array
115:      */
116:     public function getConfig()
117:     {
118:         return $this->config;
119:     }
120: 
121: 
122:     /**
123:      * Adds a files to the list of dependencies.
124:      * @return static
125:      */
126:     public function addDependencies(array $files)
127:     {
128:         $this->dependencies = array_merge($this->dependencies, $files);
129:         return $this;
130:     }
131: 
132: 
133:     /**
134:      * Returns the unique list of dependent files.
135:      * @return array
136:      */
137:     public function getDependencies()
138:     {
139:         return array_values(array_unique(array_filter($this->dependencies)));
140:     }
141: 
142: 
143:     /**
144:      * @return Nette\PhpGenerator\ClassType[]|string
145:      */
146:     public function compile(array $config = NULL, $className = NULL, $parentName = NULL)
147:     {
148:         $this->config = $config ?: $this->config;
149:         $this->processParameters();
150:         $this->processExtensions();
151:         $this->processServices();
152:         $classes = $this->generateCode($className ?: $this->className, $parentName);
153:         return func_num_args()
154:             ? implode("\n\n\n", $classes) // back compatiblity
155:             : $classes;
156:     }
157: 
158: 
159:     /** @internal */
160:     public function processParameters()
161:     {
162:         if (isset($this->config['parameters'])) {
163:             $this->builder->parameters = Helpers::expand($this->config['parameters'], $this->config['parameters'], TRUE);
164:         }
165:     }
166: 
167: 
168:     /** @internal */
169:     public function processExtensions()
170:     {
171:         $this->config = Helpers::expand(array_diff_key($this->config, self::$reserved), $this->builder->parameters)
172:             + array_intersect_key($this->config, self::$reserved);
173: 
174:         foreach ($first = $this->getExtensions('Nette\DI\Extensions\ExtensionsExtension') as $name => $extension) {
175:             $extension->setConfig(isset($this->config[$name]) ? $this->config[$name] : array());
176:             $extension->loadConfiguration();
177:         }
178: 
179:         $last = $this->getExtensions('Nette\DI\Extensions\InjectExtension');
180:         $this->extensions = array_merge(array_diff_key($this->extensions, $last), $last);
181: 
182:         $extensions = array_diff_key($this->extensions, $first);
183:         foreach (array_intersect_key($extensions, $this->config) as $name => $extension) {
184:             if (isset($this->config[$name]['services'])) {
185:                 trigger_error("Support for inner section 'services' inside extension was removed (used in '$name').", E_USER_DEPRECATED);
186:             }
187:             $extension->setConfig($this->config[$name] ?: array());
188:         }
189: 
190:         foreach ($extensions as $extension) {
191:             $extension->loadConfiguration();
192:         }
193: 
194:         if ($extra = array_diff_key($this->extensions, $extensions, $first)) {
195:             $extra = implode("', '", array_keys($extra));
196:             throw new Nette\DeprecatedException("Extensions '$extra' were added while container was being compiled.");
197: 
198:         } elseif ($extra = key(array_diff_key($this->config, self::$reserved, $this->extensions))) {
199:             $hint = Nette\Utils\ObjectMixin::getSuggestion(array_keys(self::$reserved + $this->extensions), $extra);
200:             throw new Nette\InvalidStateException(
201:                 "Found section '$extra' in configuration, but corresponding extension is missing"
202:                 . ($hint ? ", did you mean '$hint'?" : '.')
203:             );
204:         }
205:     }
206: 
207: 
208:     /** @internal */
209:     public function processServices()
210:     {
211:         $this->parseServices($this->builder, $this->config);
212:     }
213: 
214: 
215:     /** @internal */
216:     public function generateCode($className, $parentName = NULL)
217:     {
218:         $this->builder->prepareClassList();
219:         $state = serialize($this->builder->getDefinitions());
220: 
221:         foreach ($this->extensions as $extension) {
222:             $extension->beforeCompile();
223:             $rc = new \ReflectionClass($extension);
224:             $this->dependencies[] = $rc->getFileName();
225:             if ($state !== serialize($this->builder->getDefinitions())) {
226:                 $this->builder->prepareClassList();
227:                 $state = serialize($this->builder->getDefinitions());
228:             }
229:         }
230: 
231:         $classes = $this->builder->generateClasses($className, $parentName);
232:         $classes[0]->addMethod('initialize');
233:         $this->addDependencies($this->builder->getDependencies());
234: 
235:         foreach ($this->extensions as $extension) {
236:             $extension->afterCompile($classes[0]);
237:         }
238:         return $classes;
239:     }
240: 
241: 
242:     /********************* tools ****************d*g**/
243: 
244: 
245:     /**
246:      * Parses section 'services' from (unexpanded) configuration file.
247:      * @return void
248:      */
249:     public static function parseServices(ContainerBuilder $builder, array $config, $namespace = NULL)
250:     {
251:         if (!empty($config['factories'])) {
252:             throw new Nette\DeprecatedException("Section 'factories' is deprecated, move definitions to section 'services' and append key 'autowired: no'.");
253:         }
254: 
255:         $services = isset($config['services']) ? $config['services'] : array();
256:         $depths = array();
257:         foreach ($services as $name => $def) {
258:             $path = array();
259:             while (Config\Helpers::isInheriting($def)) {
260:                 $path[] = $def;
261:                 $def = isset($services[$def[Config\Helpers::EXTENDS_KEY]]) ? $services[$def[Config\Helpers::EXTENDS_KEY]] : array();
262:                 if (in_array($def, $path, TRUE)) {
263:                     throw new ServiceCreationException("Circular reference detected for service '$name'.");
264:                 }
265:             }
266:             $depths[$name] = count($path);
267:         }
268:         array_multisort($depths, $services);
269: 
270:         foreach ($services as $origName => $def) {
271:             if ((string) (int) $origName === (string) $origName) {
272:                 $postfix = $def instanceof Statement && is_string($def->getEntity()) ? '.' . $def->getEntity() : (is_scalar($def) ? ".$def" : '');
273:                 $name = (count($builder->getDefinitions()) + 1) . preg_replace('#\W+#', '_', $postfix);
274:             } else {
275:                 $name = ($namespace ? $namespace . '.' : '') . strtr($origName, '\\', '_');
276:             }
277: 
278:             if ($def === FALSE) {
279:                 $builder->removeDefinition($name);
280:                 continue;
281:             }
282: 
283:             $params = $builder->parameters;
284:             if (is_array($def) && isset($def['parameters'])) {
285:                 foreach ((array) $def['parameters'] as $k => $v) {
286:                     $v = explode(' ', is_int($k) ? $v : $k);
287:                     $params[end($v)] = $builder::literal('$' . end($v));
288:                 }
289:             }
290:             $def = Helpers::expand($def, $params);
291: 
292:             if (($parent = Config\Helpers::takeParent($def)) && $parent !== $name) {
293:                 $builder->removeDefinition($name);
294:                 $definition = $builder->addDefinition(
295:                     $name,
296:                     $parent === Config\Helpers::OVERWRITE ? NULL : unserialize(serialize($builder->getDefinition($parent))) // deep clone
297:                 );
298:             } elseif ($builder->hasDefinition($name)) {
299:                 $definition = $builder->getDefinition($name);
300:             } else {
301:                 $definition = $builder->addDefinition($name);
302:             }
303: 
304:             try {
305:                 static::parseService($definition, $def);
306:             } catch (\Exception $e) {
307:                 throw new ServiceCreationException("Service '$name': " . $e->getMessage(), NULL, $e);
308:             }
309: 
310:             if ($definition->getClass() === 'self' || ($definition->getFactory() && $definition->getFactory()->getEntity() === 'self')) {
311:                 throw new Nette\DeprecatedException("Replace service definition '$origName: self' with '- $origName'.");
312:             }
313:         }
314:     }
315: 
316: 
317:     /**
318:      * Parses single service from configuration file.
319:      * @return void
320:      */
321:     public static function parseService(ServiceDefinition $definition, $config)
322:     {
323:         if ($config === NULL) {
324:             return;
325: 
326:         } elseif (is_string($config) && interface_exists($config)) {
327:             $config = array('class' => NULL, 'implement' => $config);
328: 
329:         } elseif ($config instanceof Statement && is_string($config->getEntity()) && interface_exists($config->getEntity())) {
330:             $config = array('class' => NULL, 'implement' => $config->getEntity(), 'factory' => array_shift($config->arguments));
331: 
332:         } elseif (!is_array($config) || isset($config[0], $config[1])) {
333:             $config = array('class' => NULL, 'create' => $config);
334:         }
335: 
336:         if (array_key_exists('factory', $config)) {
337:             $config['create'] = $config['factory'];
338:             unset($config['factory']);
339:         };
340: 
341:         $known = array('class', 'create', 'arguments', 'setup', 'autowired', 'dynamic', 'inject', 'parameters', 'implement', 'run', 'tags');
342:         if ($error = array_diff(array_keys($config), $known)) {
343:             throw new Nette\InvalidStateException(sprintf("Unknown or deprecated key '%s' in definition of service.", implode("', '", $error)));
344:         }
345: 
346:         $config = self::filterArguments($config);
347: 
348:         $arguments = array();
349:         if (array_key_exists('arguments', $config)) {
350:             Validators::assertField($config, 'arguments', 'array');
351:             $arguments = $config['arguments'];
352:             $definition->setArguments($arguments);
353:         }
354: 
355:         if (array_key_exists('class', $config) || array_key_exists('create', $config)) {
356:             $definition->setClass(NULL);
357:             $definition->setFactory(NULL);
358:         }
359: 
360:         if (array_key_exists('class', $config)) {
361:             Validators::assertField($config, 'class', 'string|Nette\DI\Statement|null');
362:             if (!$config['class'] instanceof Statement) {
363:                 $definition->setClass($config['class']);
364:             }
365:             $definition->setFactory($config['class'], $arguments);
366:         }
367: 
368:         if (array_key_exists('create', $config)) {
369:             Validators::assertField($config, 'create', 'callable|Nette\DI\Statement|null');
370:             $definition->setFactory($config['create'], $arguments);
371:         }
372: 
373:         if (isset($config['setup'])) {
374:             if (Config\Helpers::takeParent($config['setup'])) {
375:                 $definition->setSetup(array());
376:             }
377:             Validators::assertField($config, 'setup', 'list');
378:             foreach ($config['setup'] as $id => $setup) {
379:                 Validators::assert($setup, 'callable|Nette\DI\Statement', "setup item #$id");
380:                 $definition->addSetup($setup);
381:             }
382:         }
383: 
384:         if (isset($config['parameters'])) {
385:             Validators::assertField($config, 'parameters', 'array');
386:             $definition->setParameters($config['parameters']);
387:         }
388: 
389:         if (isset($config['implement'])) {
390:             Validators::assertField($config, 'implement', 'string');
391:             $definition->setImplement($config['implement']);
392:             $definition->setAutowired(TRUE);
393:         }
394: 
395:         if (isset($config['autowired'])) {
396:             Validators::assertField($config, 'autowired', 'bool');
397:             $definition->setAutowired($config['autowired']);
398:         }
399: 
400:         if (isset($config['dynamic'])) {
401:             Validators::assertField($config, 'dynamic', 'bool');
402:             $definition->setDynamic($config['dynamic']);
403:         }
404: 
405:         if (isset($config['inject'])) {
406:             Validators::assertField($config, 'inject', 'bool');
407:             $definition->addTag(Extensions\InjectExtension::TAG_INJECT, $config['inject']);
408:         }
409: 
410:         if (isset($config['run'])) {
411:             $config['tags']['run'] = (bool) $config['run'];
412:         }
413: 
414:         if (isset($config['tags'])) {
415:             Validators::assertField($config, 'tags', 'array');
416:             if (Config\Helpers::takeParent($config['tags'])) {
417:                 $definition->setTags(array());
418:             }
419:             foreach ($config['tags'] as $tag => $attrs) {
420:                 if (is_int($tag) && is_string($attrs)) {
421:                     $definition->addTag($attrs);
422:                 } else {
423:                     $definition->addTag($tag, $attrs);
424:                 }
425:             }
426:         }
427:     }
428: 
429: 
430:     /**
431:      * Removes ... and process constants recursively.
432:      * @return array
433:      */
434:     public static function filterArguments(array $args)
435:     {
436:         foreach ($args as $k => $v) {
437:             if ($v === '...') {
438:                 unset($args[$k]);
439:             } elseif (is_string($v) && preg_match('#^[\w\\\\]*::[A-Z][A-Z0-9_]*\z#', $v, $m)) {
440:                 $args[$k] = ContainerBuilder::literal(ltrim($v, ':'));
441:             } elseif (is_array($v)) {
442:                 $args[$k] = self::filterArguments($v);
443:             } elseif ($v instanceof Statement) {
444:                 $tmp = self::filterArguments(array($v->getEntity()));
445:                 $args[$k] = new Statement($tmp[0], self::filterArguments($v->arguments));
446:             }
447:         }
448:         return $args;
449:     }
450: 
451: }
452: 
Nette 2.3-20161221 API API documentation generated by ApiGen 2.8.0