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