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