Namespaces

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Diagnostics
      • Extensions
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
      • Diagnostics
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • PhpGenerator
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
  • NetteModule
  • none

Classes

  • Compiler
  • CompilerExtension
  • Container
  • ContainerBuilder
  • 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:  * @author     David Grudl
 18:  *
 19:  * @property-read CompilerExtension[] $extensions
 20:  * @property-read ContainerBuilder $containerBuilder
 21:  * @property-read array $config
 22:  */
 23: class Compiler extends Nette\Object
 24: {
 25:     /** @var CompilerExtension[] */
 26:     private $extensions = array();
 27: 
 28:     /** @var ContainerBuilder */
 29:     private $builder;
 30: 
 31:     /** @var array */
 32:     private $config;
 33: 
 34:     /** @var array reserved section names */
 35:     private static $reserved = array('services' => 1, 'factories' => 1, 'parameters' => 1);
 36: 
 37: 
 38:     /**
 39:      * Add custom configurator extension.
 40:      * @return self
 41:      */
 42:     public function addExtension($name, CompilerExtension $extension)
 43:     {
 44:         if (isset(self::$reserved[$name])) {
 45:             throw new Nette\InvalidArgumentException("Name '$name' is reserved.");
 46:         }
 47:         $this->extensions[$name] = $extension->setCompiler($this, $name);
 48:         return $this;
 49:     }
 50: 
 51: 
 52:     /**
 53:      * @return array
 54:      */
 55:     public function getExtensions($type = NULL)
 56:     {
 57:         return $type
 58:             ? array_filter($this->extensions, function ($item) use ($type) { return $item instanceof $type; })
 59:             : $this->extensions;
 60:     }
 61: 
 62: 
 63:     /**
 64:      * @return ContainerBuilder
 65:      */
 66:     public function getContainerBuilder()
 67:     {
 68:         return $this->builder;
 69:     }
 70: 
 71: 
 72:     /**
 73:      * Returns configuration.
 74:      * @return array
 75:      */
 76:     public function getConfig()
 77:     {
 78:         return $this->config;
 79:     }
 80: 
 81: 
 82:     /**
 83:      * @return string
 84:      */
 85:     public function compile(array $config, $className, $parentName)
 86:     {
 87:         $this->config = $config;
 88:         $this->builder = new ContainerBuilder;
 89:         $this->processParameters();
 90:         $this->processExtensions();
 91:         $this->processServices();
 92:         return $this->generateCode($className, $parentName);
 93:     }
 94: 
 95: 
 96:     /** @internal */
 97:     public function processParameters()
 98:     {
 99:         if (isset($this->config['parameters'])) {
100:             $this->builder->parameters = Helpers::expand($this->config['parameters'], $this->config['parameters'], TRUE);
101:         }
102:     }
103: 
104: 
105:     /** @internal */
106:     public function processExtensions()
107:     {
108:         for ($i = 0; $slice = array_slice($this->extensions, $i, 1, TRUE); $i++) {
109:             $name = key($slice);
110:             if (isset($this->config[$name])) {
111:                 $this->config[$name] = $this->builder->expand($this->config[$name]);
112:             }
113:             $this->extensions[$name]->loadConfiguration();
114:         }
115: 
116:         if ($extra = array_diff_key($this->config, self::$reserved, $this->extensions)) {
117:             $extra = implode("', '", array_keys($extra));
118:             throw new Nette\InvalidStateException("Found sections '$extra' in configuration, but corresponding extensions are missing.");
119:         }
120:     }
121: 
122: 
123:     /** @internal */
124:     public function processServices()
125:     {
126:         $this->parseServices($this->builder, $this->config);
127: 
128:         foreach ($this->extensions as $name => $extension) {
129:             if (isset($this->config[$name])) {
130:                 $this->parseServices($this->builder, $this->config[$name], $name);
131:             }
132:         }
133:     }
134: 
135: 
136:     /** @internal */
137:     public function generateCode($className, $parentName)
138:     {
139:         foreach ($this->extensions as $extension) {
140:             $extension->beforeCompile();
141:             $this->builder->addDependency(Nette\Reflection\ClassType::from($extension)->getFileName());
142:         }
143: 
144:         $classes = $this->builder->generateClasses($className, $parentName);
145:         $classes[0]->addMethod('initialize');
146: 
147:         foreach ($this->extensions as $extension) {
148:             $extension->afterCompile($classes[0]);
149:         }
150:         return implode("\n\n\n", $classes);
151:     }
152: 
153: 
154:     /********************* tools ****************d*g**/
155: 
156: 
157:     /**
158:      * Parses section 'services' from (unexpanded) configuration file.
159:      * @return void
160:      */
161:     public static function parseServices(ContainerBuilder $builder, array $config, $namespace = NULL)
162:     {
163:         if (!empty($config['factories'])) {
164:             trigger_error("Section 'factories' is deprecated, move definitions to section 'services' and append key 'autowired: no'.", E_USER_DEPRECATED);
165:         }
166: 
167:         $services = isset($config['services']) ? $config['services'] : array();
168:         $factories = isset($config['factories']) ? $config['factories'] : array();
169:         $all = array_merge($services, $factories);
170: 
171:         $depths = array();
172:         foreach ($all as $name => $def) {
173:             $path = array();
174:             while (Config\Helpers::isInheriting($def)) {
175:                 $path[] = $def;
176:                 $def = isset($all[$def[Config\Helpers::EXTENDS_KEY]]) ? $all[$def[Config\Helpers::EXTENDS_KEY]] : array();
177:                 if (in_array($def, $path, TRUE)) {
178:                     throw new ServiceCreationException("Circular reference detected for service '$name'.");
179:                 }
180:             }
181:             $depths[$name] = count($path);
182:         }
183:         array_multisort($depths, $all);
184: 
185:         foreach ($all as $origName => $def) {
186:             if ((string) (int) $origName === (string) $origName) {
187:                 $name = count($builder->getDefinitions())
188:                     . preg_replace('#\W+#', '_', $def instanceof \stdClass ? ".$def->value" : (is_scalar($def) ? ".$def" : ''));
189:             } elseif (array_key_exists($origName, $services) && array_key_exists($origName, $factories)) {
190:                 throw new ServiceCreationException("It is not allowed to use services and factories with the same name: '$origName'.");
191:             } else {
192:                 $name = ($namespace ? $namespace . '.' : '') . strtr($origName, '\\', '_');
193:             }
194: 
195:             $params = $builder->parameters;
196:             if (is_array($def) && isset($def['parameters'])) {
197:                 foreach ((array) $def['parameters'] as $k => $v) {
198:                     $v = explode(' ', is_int($k) ? $v : $k);
199:                     $params[end($v)] = $builder::literal('$' . end($v));
200:                 }
201:             }
202:             $def = Helpers::expand($def, $params);
203: 
204:             if (($parent = Config\Helpers::takeParent($def)) && $parent !== $name) {
205:                 $builder->removeDefinition($name);
206:                 $definition = $builder->addDefinition(
207:                     $name,
208:                     $parent === Config\Helpers::OVERWRITE ? NULL : unserialize(serialize($builder->getDefinition($parent))) // deep clone
209:                 );
210:             } elseif ($builder->hasDefinition($name)) {
211:                 $definition = $builder->getDefinition($name);
212:             } else {
213:                 $definition = $builder->addDefinition($name);
214:             }
215: 
216:             try {
217:                 static::parseService($definition, $def);
218:             } catch (\Exception $e) {
219:                 throw new ServiceCreationException("Service '$name': " . $e->getMessage(), NULL, $e);
220:             }
221: 
222:             if (array_key_exists($origName, $factories)) {
223:                 $definition->setAutowired(FALSE);
224:             }
225: 
226:             if ($definition->class === 'self') {
227:                 $definition->class = $origName;
228:                 trigger_error("Replace service definition '$origName: self' with '- $origName'.", E_USER_DEPRECATED);
229:             }
230:             if ($definition->factory && $definition->factory->entity === 'self') {
231:                 $definition->factory->entity = $origName;
232:                 trigger_error("Replace service definition '$origName: self' with '- $origName'.", E_USER_DEPRECATED);
233:             }
234:         }
235:     }
236: 
237: 
238:     /**
239:      * Parses single service from configuration file.
240:      * @return void
241:      */
242:     public static function parseService(ServiceDefinition $definition, $config)
243:     {
244:         if ($config === NULL) {
245:             return;
246: 
247:         } elseif (is_string($config) && interface_exists($config)) {
248:             $config = array('class' => NULL, 'implement' => $config);
249: 
250:         } elseif ($config instanceof \stdClass && is_string($config->value) && interface_exists($config->value)) {
251:             $config = array('class' => NULL, 'implement' => $config->value, 'factory' => array_shift($config->attributes));
252: 
253:         } elseif (!is_array($config) || isset($config[0], $config[1])) {
254:             $config = array('class' => NULL, 'create' => $config);
255:         }
256: 
257:         if (array_key_exists('factory', $config)) {
258:             $config['create'] = $config['factory'];
259:             unset($config['factory']);
260:         };
261: 
262:         $known = array('class', 'create', 'arguments', 'setup', 'autowired', 'inject', 'parameters', 'implement', 'run', 'tags');
263:         if ($error = array_diff(array_keys($config), $known)) {
264:             throw new Nette\InvalidStateException(sprintf("Unknown or deprecated key '%s' in definition of service.", implode("', '", $error)));
265:         }
266: 
267:         $arguments = array();
268:         if (array_key_exists('arguments', $config)) {
269:             Validators::assertField($config, 'arguments', 'array');
270:             $arguments = self::filterArguments($config['arguments']);
271:             $definition->setArguments($arguments);
272:         }
273: 
274:         if (array_key_exists('class', $config) || array_key_exists('create', $config)) {
275:             $definition->class = NULL;
276:             $definition->factory = NULL;
277:         }
278: 
279:         if (array_key_exists('class', $config)) {
280:             Validators::assertField($config, 'class', 'string|stdClass|null');
281:             if ($config['class'] instanceof \stdClass) {
282:                 $definition->setClass($config['class']->value, self::filterArguments($config['class']->attributes));
283:             } else {
284:                 $definition->setClass($config['class'], $arguments);
285:             }
286:         }
287: 
288:         if (array_key_exists('create', $config)) {
289:             Validators::assertField($config, 'create', 'callable|stdClass|null');
290:             if ($config['create'] instanceof \stdClass) {
291:                 $definition->setFactory($config['create']->value, self::filterArguments($config['create']->attributes));
292:             } else {
293:                 $definition->setFactory($config['create'], $arguments);
294:             }
295:         }
296: 
297:         if (isset($config['setup'])) {
298:             if (Config\Helpers::takeParent($config['setup'])) {
299:                 $definition->setup = array();
300:             }
301:             Validators::assertField($config, 'setup', 'list');
302:             foreach ($config['setup'] as $id => $setup) {
303:                 Validators::assert($setup, 'callable|stdClass', "setup item #$id");
304:                 if ($setup instanceof \stdClass) {
305:                     Validators::assert($setup->value, 'callable', "setup item #$id");
306:                     $definition->addSetup($setup->value, self::filterArguments($setup->attributes));
307:                 } else {
308:                     $definition->addSetup($setup);
309:                 }
310:             }
311:         }
312: 
313:         if (isset($config['parameters'])) {
314:             Validators::assertField($config, 'parameters', 'array');
315:             $definition->setParameters($config['parameters']);
316:         }
317: 
318:         if (isset($config['implement'])) {
319:             Validators::assertField($config, 'implement', 'string');
320:             $definition->setImplement($config['implement']);
321:             $definition->setAutowired(TRUE);
322:         }
323: 
324:         if (isset($config['autowired'])) {
325:             Validators::assertField($config, 'autowired', 'bool');
326:             $definition->setAutowired($config['autowired']);
327:         }
328: 
329:         if (isset($config['inject'])) {
330:             Validators::assertField($config, 'inject', 'bool');
331:             $definition->setInject($config['inject']);
332:         }
333: 
334:         if (isset($config['run'])) {
335:             $config['tags']['run'] = (bool) $config['run'];
336:         }
337: 
338:         if (isset($config['tags'])) {
339:             Validators::assertField($config, 'tags', 'array');
340:             if (Config\Helpers::takeParent($config['tags'])) {
341:                 $definition->tags = array();
342:             }
343:             foreach ($config['tags'] as $tag => $attrs) {
344:                 if (is_int($tag) && is_string($attrs)) {
345:                     $definition->addTag($attrs);
346:                 } else {
347:                     $definition->addTag($tag, $attrs);
348:                 }
349:             }
350:         }
351:     }
352: 
353: 
354:     /**
355:      * Removes ... and replaces entities with Statement.
356:      * @return array
357:      */
358:     public static function filterArguments(array $args)
359:     {
360:         foreach ($args as $k => $v) {
361:             if ($v === '...') {
362:                 unset($args[$k]);
363:             } elseif ($v instanceof \stdClass && isset($v->value, $v->attributes)) {
364:                 $args[$k] = new Statement($v->value, self::filterArguments($v->attributes));
365:             }
366:         }
367:         return $args;
368:     }
369: 
370: }
371: 
Nette 2.1 API documentation generated by ApiGen 2.8.0