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