1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\DI;
9:
10: use Nette;
11: use Nette\Utils\Validators;
12:
13:
14: 15: 16:
17: class Compiler extends Nette\Object
18: {
19:
20: private $extensions = array();
21:
22:
23: private $builder;
24:
25:
26: private $config = array();
27:
28:
29: private $dependencies = array();
30:
31:
32: private $className;
33:
34:
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: 46: 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: 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: 71:
72: public function getContainerBuilder()
73: {
74: return $this->builder;
75: }
76:
77:
78: 79: 80:
81: public function setClassName($className)
82: {
83: $this->className = $className;
84: return $this;
85: }
86:
87:
88: 89: 90: 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: 101: 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: 114: 115:
116: public function getConfig()
117: {
118: return $this->config;
119: }
120:
121:
122: 123: 124: 125:
126: public function addDependencies(array $files)
127: {
128: $this->dependencies = array_merge($this->dependencies, $files);
129: return $this;
130: }
131:
132:
133: 134: 135: 136:
137: public function getDependencies()
138: {
139: return array_values(array_unique(array_filter($this->dependencies)));
140: }
141:
142:
143: 144: 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)
155: : $classes;
156: }
157:
158:
159:
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:
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:
209: public function processServices()
210: {
211: $this->parseServices($this->builder, $this->config);
212: }
213:
214:
215:
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:
243:
244:
245: 246: 247: 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)))
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: 319: 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: 432: 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: