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