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