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