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