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 (array_key_exists($origName, $factories)) {
219: $definition->setAutowired(FALSE);
220: }
221:
222: if ($definition->class === 'self') {
223: $definition->class = $origName;
224: trigger_error("Replace service definition '$origName: self' with '- $origName'.", E_USER_DEPRECATED);
225: }
226: if ($definition->factory && $definition->factory->entity === 'self') {
227: $definition->factory->entity = $origName;
228: trigger_error("Replace service definition '$origName: self' with '- $origName'.", E_USER_DEPRECATED);
229: }
230: }
231: }
232:
233:
234: 235: 236: 237:
238: public static function parseService(ServiceDefinition $definition, $config)
239: {
240: if ($config === NULL) {
241: return;
242:
243: } elseif (is_string($config) && interface_exists($config)) {
244: $config = array('class' => NULL, 'implement' => $config);
245:
246: } elseif ($config instanceof \stdClass && interface_exists($config->value)) {
247: $config = array('class' => NULL, 'implement' => $config->value, 'factory' => array_shift($config->attributes));
248:
249: } elseif (!is_array($config)) {
250: $config = array('class' => NULL, 'create' => $config);
251: }
252:
253: if (array_key_exists('factory', $config)) {
254: $config['create'] = $config['factory'];
255: unset($config['factory']);
256: };
257:
258: $known = array('class', 'create', 'arguments', 'setup', 'autowired', 'inject', 'parameters', 'implement', 'run', 'tags');
259: if ($error = array_diff(array_keys($config), $known)) {
260: throw new Nette\InvalidStateException("Unknown or deprecated key '" . implode("', '", $error) . "' in definition of service.");
261: }
262:
263: $arguments = array();
264: if (array_key_exists('arguments', $config)) {
265: Validators::assertField($config, 'arguments', 'array');
266: $arguments = self::filterArguments($config['arguments']);
267: $definition->setArguments($arguments);
268: }
269:
270: if (array_key_exists('class', $config) || array_key_exists('create', $config)) {
271: $definition->class = NULL;
272: $definition->factory = NULL;
273: }
274:
275: if (array_key_exists('class', $config)) {
276: Validators::assertField($config, 'class', 'string|stdClass|null');
277: if ($config['class'] instanceof \stdClass) {
278: $definition->setClass($config['class']->value, self::filterArguments($config['class']->attributes));
279: } else {
280: $definition->setClass($config['class'], $arguments);
281: }
282: }
283:
284: if (array_key_exists('create', $config)) {
285: Validators::assertField($config, 'create', 'callable|stdClass|null');
286: if ($config['create'] instanceof \stdClass) {
287: $definition->setFactory($config['create']->value, self::filterArguments($config['create']->attributes));
288: } else {
289: $definition->setFactory($config['create'], $arguments);
290: }
291: }
292:
293: if (isset($config['setup'])) {
294: if (Config\Helpers::takeParent($config['setup'])) {
295: $definition->setup = array();
296: }
297: Validators::assertField($config, 'setup', 'list');
298: foreach ($config['setup'] as $id => $setup) {
299: Validators::assert($setup, 'callable|stdClass', "setup item #$id");
300: if ($setup instanceof \stdClass) {
301: Validators::assert($setup->value, 'callable', "setup item #$id");
302: $definition->addSetup($setup->value, self::filterArguments($setup->attributes));
303: } else {
304: $definition->addSetup($setup);
305: }
306: }
307: }
308:
309: if (isset($config['parameters'])) {
310: Validators::assertField($config, 'parameters', 'array');
311: $definition->setParameters($config['parameters']);
312: }
313:
314: if (isset($config['implement'])) {
315: Validators::assertField($config, 'implement', 'string');
316: $definition->setImplement($config['implement']);
317: $definition->setAutowired(TRUE);
318: }
319:
320: if (isset($config['autowired'])) {
321: Validators::assertField($config, 'autowired', 'bool');
322: $definition->setAutowired($config['autowired']);
323: }
324:
325: if (isset($config['inject'])) {
326: Validators::assertField($config, 'inject', 'bool');
327: $definition->setInject($config['inject']);
328: }
329:
330: if (isset($config['run'])) {
331: $config['tags']['run'] = (bool) $config['run'];
332: }
333:
334: if (isset($config['tags'])) {
335: Validators::assertField($config, 'tags', 'array');
336: if (Config\Helpers::takeParent($config['tags'])) {
337: $definition->tags = array();
338: }
339: foreach ($config['tags'] as $tag => $attrs) {
340: if (is_int($tag) && is_string($attrs)) {
341: $definition->addTag($attrs);
342: } else {
343: $definition->addTag($tag, $attrs);
344: }
345: }
346: }
347: }
348:
349:
350: 351: 352: 353:
354: public static function filterArguments(array $args)
355: {
356: foreach ($args as $k => $v) {
357: if ($v === '...') {
358: unset($args[$k]);
359: } elseif ($v instanceof \stdClass && isset($v->value, $v->attributes)) {
360: $args[$k] = new Statement($v->value, self::filterArguments($v->attributes));
361: }
362: }
363: return $args;
364: }
365:
366: }
367: