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: 18: 19: 20: 21: 22:
23: class Compiler extends Nette\Object
24: {
25:
26: private $extensions = array();
27:
28:
29: private $builder;
30:
31:
32: private $config;
33:
34:
35: private static $reserved = array('services' => 1, 'factories' => 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(self::$reserved[$name])) {
51: throw new Nette\InvalidArgumentException("Name '$name' is 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:
82: public function getConfig()
83: {
84: return $this->config;
85: }
86:
87:
88: 89: 90:
91: public function compile(array $config, $className, $parentName)
92: {
93: $this->config = $config;
94: $this->processParameters();
95: $this->processExtensions();
96: $this->processServices();
97: return $this->generateCode($className, $parentName);
98: }
99:
100:
101: public function processParameters()
102: {
103: if (isset($this->config['parameters'])) {
104: $this->builder->parameters = Helpers::expand($this->config['parameters'], $this->config['parameters'], TRUE);
105: }
106: }
107:
108:
109: public function processExtensions()
110: {
111: for ($i = 0; $slice = array_slice($this->extensions, $i, 1, TRUE); $i++) {
112: $name = key($slice);
113: if (isset($this->config[$name])) {
114: $this->config[$name] = $this->builder->expand($this->config[$name]);
115: }
116: $this->extensions[$name]->loadConfiguration();
117: }
118:
119: if ($extra = array_diff_key($this->config, self::$reserved, $this->extensions)) {
120: $extra = implode("', '", array_keys($extra));
121: throw new Nette\InvalidStateException("Found sections '$extra' in configuration, but corresponding extensions are missing.");
122: }
123: }
124:
125:
126: public function processServices()
127: {
128: $this->parseServices($this->builder, $this->config);
129:
130: foreach ($this->extensions as $name => $extension) {
131: if (isset($this->config[$name])) {
132: $this->parseServices($this->builder, $this->config[$name], $name);
133: }
134: }
135: }
136:
137:
138: public function generateCode($className, $parentName)
139: {
140: foreach ($this->extensions as $extension) {
141: $extension->beforeCompile();
142: $this->builder->addDependency(Nette\Reflection\ClassType::from($extension)->getFileName());
143: }
144:
145: $classes = $this->builder->generateClasses($className, $parentName);
146: $classes[0]->addMethod('initialize');
147:
148: foreach ($this->extensions as $extension) {
149: $extension->afterCompile($classes[0]);
150: }
151: return implode("\n\n\n", $classes);
152: }
153:
154:
155:
156:
157:
158: 159: 160: 161:
162: public static function parseServices(ContainerBuilder $builder, array $config, $namespace = NULL)
163: {
164: if (!empty($config['factories'])) {
165: trigger_error("Section 'factories' is deprecated, move definitions to section 'services' and append key 'autowired: no'.", E_USER_DEPRECATED);
166: }
167:
168: $services = isset($config['services']) ? $config['services'] : array();
169: $factories = isset($config['factories']) ? $config['factories'] : array();
170: $all = array_merge($services, $factories);
171:
172: $depths = array();
173: foreach ($all as $name => $def) {
174: $path = array();
175: while (Config\Helpers::isInheriting($def)) {
176: $path[] = $def;
177: $def = isset($all[$def[Config\Helpers::EXTENDS_KEY]]) ? $all[$def[Config\Helpers::EXTENDS_KEY]] : array();
178: if (in_array($def, $path, TRUE)) {
179: throw new ServiceCreationException("Circular reference detected for service '$name'.");
180: }
181: }
182: $depths[$name] = count($path);
183: }
184: array_multisort($depths, $all);
185:
186: foreach ($all as $origName => $def) {
187: if ((string) (int) $origName === (string) $origName) {
188: $name = (count($builder->getDefinitions()) + 1)
189: . preg_replace('#\W+#', '_', $def instanceof \stdClass ? ".$def->value" : (is_scalar($def) ? ".$def" : ''));
190: } elseif (array_key_exists($origName, $services) && array_key_exists($origName, $factories)) {
191: throw new ServiceCreationException("It is not allowed to use services and factories with the same name: '$origName'.");
192: } else {
193: $name = ($namespace ? $namespace . '.' : '') . strtr($origName, '\\', '_');
194: }
195:
196: $params = $builder->parameters;
197: if (is_array($def) && isset($def['parameters'])) {
198: foreach ((array) $def['parameters'] as $k => $v) {
199: $v = explode(' ', is_int($k) ? $v : $k);
200: $params[end($v)] = $builder::literal('$' . end($v));
201: }
202: }
203: $def = Helpers::expand($def, $params);
204:
205: if (($parent = Config\Helpers::takeParent($def)) && $parent !== $name) {
206: $builder->removeDefinition($name);
207: $definition = $builder->addDefinition(
208: $name,
209: $parent === Config\Helpers::OVERWRITE ? NULL : unserialize(serialize($builder->getDefinition($parent)))
210: );
211: } elseif ($builder->hasDefinition($name)) {
212: $definition = $builder->getDefinition($name);
213: } else {
214: $definition = $builder->addDefinition($name);
215: }
216:
217: try {
218: static::parseService($definition, $def);
219: } catch (\Exception $e) {
220: throw new ServiceCreationException("Service '$name': " . $e->getMessage(), NULL, $e);
221: }
222:
223: if ($definition->class === 'self') {
224: $definition->class = $origName;
225: trigger_error("Replace service definition '$origName: self' with '- $origName'.", E_USER_DEPRECATED);
226: }
227: if ($definition->factory && $definition->factory->entity === 'self') {
228: $definition->factory->entity = $origName;
229: trigger_error("Replace service definition '$origName: self' with '- $origName'.", E_USER_DEPRECATED);
230: }
231: }
232: }
233:
234:
235: 236: 237: 238:
239: public static function parseService(ServiceDefinition $definition, $config)
240: {
241: if ($config === NULL) {
242: return;
243:
244: } elseif (is_string($config) && interface_exists($config)) {
245: $config = array('class' => NULL, 'implement' => $config);
246:
247: } elseif ($config instanceof \stdClass && interface_exists($config->value)) {
248: $config = array('class' => NULL, 'implement' => $config->value, 'factory' => array_shift($config->attributes));
249:
250: } elseif (!is_array($config)) {
251: $config = array('class' => NULL, 'create' => $config);
252: }
253:
254: if (array_key_exists('factory', $config)) {
255: $config['create'] = $config['factory'];
256: unset($config['factory']);
257: };
258:
259: $known = array('class', 'create', 'arguments', 'setup', 'autowired', 'inject', 'parameters', 'implement', 'run', 'tags');
260: if ($error = array_diff(array_keys($config), $known)) {
261: throw new Nette\InvalidStateException(sprintf("Unknown or deprecated key '%s' in definition of service.", implode("', '", $error)));
262: }
263:
264: $arguments = array();
265: if (array_key_exists('arguments', $config)) {
266: Validators::assertField($config, 'arguments', 'array');
267: $arguments = self::filterArguments($config['arguments']);
268: $definition->setArguments($arguments);
269: }
270:
271: if (array_key_exists('class', $config) || array_key_exists('create', $config)) {
272: $definition->class = NULL;
273: $definition->factory = NULL;
274: }
275:
276: if (array_key_exists('class', $config)) {
277: Validators::assertField($config, 'class', 'string|stdClass|null');
278: if ($config['class'] instanceof \stdClass) {
279: $definition->setClass($config['class']->value, self::filterArguments($config['class']->attributes));
280: } else {
281: $definition->setClass($config['class'], $arguments);
282: }
283: }
284:
285: if (array_key_exists('create', $config)) {
286: Validators::assertField($config, 'create', 'callable|stdClass|null');
287: if ($config['create'] instanceof \stdClass) {
288: $definition->setFactory($config['create']->value, self::filterArguments($config['create']->attributes));
289: } else {
290: $definition->setFactory($config['create'], $arguments);
291: }
292: }
293:
294: if (isset($config['setup'])) {
295: if (Config\Helpers::takeParent($config['setup'])) {
296: $definition->setup = array();
297: }
298: Validators::assertField($config, 'setup', 'list');
299: foreach ($config['setup'] as $id => $setup) {
300: Validators::assert($setup, 'callable|stdClass', "setup item #$id");
301: if ($setup instanceof \stdClass) {
302: Validators::assert($setup->value, 'callable', "setup item #$id");
303: $definition->addSetup($setup->value, self::filterArguments($setup->attributes));
304: } else {
305: $definition->addSetup($setup);
306: }
307: }
308: }
309:
310: if (isset($config['parameters'])) {
311: Validators::assertField($config, 'parameters', 'array');
312: $definition->setParameters($config['parameters']);
313: }
314:
315: if (isset($config['implement'])) {
316: Validators::assertField($config, 'implement', 'string');
317: $definition->setImplement($config['implement']);
318: $definition->setAutowired(TRUE);
319: }
320:
321: if (isset($config['autowired'])) {
322: Validators::assertField($config, 'autowired', 'bool');
323: $definition->setAutowired($config['autowired']);
324: }
325:
326: if (isset($config['inject'])) {
327: Validators::assertField($config, 'inject', 'bool');
328: $definition->setInject($config['inject']);
329: }
330:
331: if (isset($config['run'])) {
332: $config['tags']['run'] = (bool) $config['run'];
333: }
334:
335: if (isset($config['tags'])) {
336: Validators::assertField($config, 'tags', 'array');
337: if (Config\Helpers::takeParent($config['tags'])) {
338: $definition->tags = array();
339: }
340: foreach ($config['tags'] as $tag => $attrs) {
341: if (is_int($tag) && is_string($attrs)) {
342: $definition->addTag($attrs);
343: } else {
344: $definition->addTag($tag, $attrs);
345: }
346: }
347: }
348: }
349:
350:
351: 352: 353: 354:
355: public static function filterArguments(array $args)
356: {
357: foreach ($args as $k => $v) {
358: if ($v === '...') {
359: unset($args[$k]);
360: } elseif (is_array($v)) {
361: $args[$k] = self::filterArguments($v);
362: } elseif ($v instanceof \stdClass && isset($v->value, $v->attributes)) {
363: $args[$k] = new Statement($v->value, self::filterArguments($v->attributes));
364: }
365: }
366: return $args;
367: }
368:
369: }
370: