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 = key(array_diff_key($this->config, self::$reserved, $this->extensions))) {
186: $hint = Nette\Utils\ObjectMixin::getSuggestion(array_keys(self::$reserved + $this->extensions), $extra);
187: throw new Nette\InvalidStateException(
188: "Found section '$extra' in configuration, but corresponding extension is missing"
189: . ($hint ? ", did you mean '$hint'?" : '.')
190: );
191: }
192: }
193:
194:
195:
196: public function processServices()
197: {
198: $this->parseServices($this->builder, $this->config);
199: }
200:
201:
202:
203: public function generateCode($className, $parentName = NULL)
204: {
205: $this->builder->prepareClassList();
206:
207: foreach ($this->extensions as $extension) {
208: $extension->beforeCompile();
209: $rc = new \ReflectionClass($extension);
210: $this->dependencies[] = $rc->getFileName();
211: }
212:
213: $classes = $this->builder->generateClasses($className, $parentName);
214: $classes[0]->addMethod('initialize');
215: $this->addDependencies($this->builder->getDependencies());
216:
217: foreach ($this->extensions as $extension) {
218: $extension->afterCompile($classes[0]);
219: }
220: return $classes;
221: }
222:
223:
224:
225:
226:
227: 228: 229: 230:
231: public static function parseServices(ContainerBuilder $builder, array $config, $namespace = NULL)
232: {
233: if (!empty($config['factories'])) {
234: throw new Nette\DeprecatedException("Section 'factories' is deprecated, move definitions to section 'services' and append key 'autowired: no'.");
235: }
236:
237: $services = isset($config['services']) ? $config['services'] : array();
238: $depths = array();
239: foreach ($services as $name => $def) {
240: $path = array();
241: while (Config\Helpers::isInheriting($def)) {
242: $path[] = $def;
243: $def = isset($services[$def[Config\Helpers::EXTENDS_KEY]]) ? $services[$def[Config\Helpers::EXTENDS_KEY]] : array();
244: if (in_array($def, $path, TRUE)) {
245: throw new ServiceCreationException("Circular reference detected for service '$name'.");
246: }
247: }
248: $depths[$name] = count($path);
249: }
250: array_multisort($depths, $services);
251:
252: foreach ($services as $origName => $def) {
253: if ((string) (int) $origName === (string) $origName) {
254: $postfix = $def instanceof Statement && is_string($def->getEntity()) ? '.' . $def->getEntity() : (is_scalar($def) ? ".$def" : '');
255: $name = (count($builder->getDefinitions()) + 1) . preg_replace('#\W+#', '_', $postfix);
256: } else {
257: $name = ($namespace ? $namespace . '.' : '') . strtr($origName, '\\', '_');
258: }
259:
260: $params = $builder->parameters;
261: if (is_array($def) && isset($def['parameters'])) {
262: foreach ((array) $def['parameters'] as $k => $v) {
263: $v = explode(' ', is_int($k) ? $v : $k);
264: $params[end($v)] = $builder::literal('$' . end($v));
265: }
266: }
267: $def = Helpers::expand($def, $params);
268:
269: if (($parent = Config\Helpers::takeParent($def)) && $parent !== $name) {
270: $builder->removeDefinition($name);
271: $definition = $builder->addDefinition(
272: $name,
273: $parent === Config\Helpers::OVERWRITE ? NULL : unserialize(serialize($builder->getDefinition($parent)))
274: );
275: } elseif ($builder->hasDefinition($name)) {
276: $definition = $builder->getDefinition($name);
277: } else {
278: $definition = $builder->addDefinition($name);
279: }
280:
281: try {
282: static::parseService($definition, $def);
283: } catch (\Exception $e) {
284: throw new ServiceCreationException("Service '$name': " . $e->getMessage(), NULL, $e);
285: }
286:
287: if ($definition->getClass() === 'self' || ($definition->getFactory() && $definition->getFactory()->getEntity() === 'self')) {
288: throw new Nette\DeprecatedException("Replace service definition '$origName: self' with '- $origName'.");
289: }
290: }
291: }
292:
293:
294: 295: 296: 297:
298: public static function parseService(ServiceDefinition $definition, $config)
299: {
300: if ($config === NULL) {
301: return;
302:
303: } elseif (is_string($config) && interface_exists($config)) {
304: $config = array('class' => NULL, 'implement' => $config);
305:
306: } elseif ($config instanceof Statement && is_string($config->getEntity()) && interface_exists($config->getEntity())) {
307: $config = array('class' => NULL, 'implement' => $config->getEntity(), 'factory' => array_shift($config->arguments));
308:
309: } elseif (!is_array($config) || isset($config[0], $config[1])) {
310: $config = array('class' => NULL, 'create' => $config);
311: }
312:
313: if (array_key_exists('factory', $config)) {
314: $config['create'] = $config['factory'];
315: unset($config['factory']);
316: };
317:
318: $known = array('class', 'create', 'arguments', 'setup', 'autowired', 'dynamic', 'inject', 'parameters', 'implement', 'run', 'tags');
319: if ($error = array_diff(array_keys($config), $known)) {
320: throw new Nette\InvalidStateException(sprintf("Unknown or deprecated key '%s' in definition of service.", implode("', '", $error)));
321: }
322:
323: $config = self::filterArguments($config);
324:
325: $arguments = array();
326: if (array_key_exists('arguments', $config)) {
327: Validators::assertField($config, 'arguments', 'array');
328: $arguments = $config['arguments'];
329: $definition->setArguments($arguments);
330: }
331:
332: if (array_key_exists('class', $config) || array_key_exists('create', $config)) {
333: $definition->setClass(NULL);
334: $definition->setFactory(NULL);
335: }
336:
337: if (array_key_exists('class', $config)) {
338: Validators::assertField($config, 'class', 'string|Nette\DI\Statement|null');
339: if (!$config['class'] instanceof Statement) {
340: $definition->setClass($config['class']);
341: }
342: $definition->setFactory($config['class'], $arguments);
343: }
344:
345: if (array_key_exists('create', $config)) {
346: Validators::assertField($config, 'create', 'callable|Nette\DI\Statement|null');
347: $definition->setFactory($config['create'], $arguments);
348: }
349:
350: if (isset($config['setup'])) {
351: if (Config\Helpers::takeParent($config['setup'])) {
352: $definition->setSetup(array());
353: }
354: Validators::assertField($config, 'setup', 'list');
355: foreach ($config['setup'] as $id => $setup) {
356: Validators::assert($setup, 'callable|Nette\DI\Statement', "setup item #$id");
357: $definition->addSetup($setup);
358: }
359: }
360:
361: if (isset($config['parameters'])) {
362: Validators::assertField($config, 'parameters', 'array');
363: $definition->setParameters($config['parameters']);
364: }
365:
366: if (isset($config['implement'])) {
367: Validators::assertField($config, 'implement', 'string');
368: $definition->setImplement($config['implement']);
369: $definition->setAutowired(TRUE);
370: }
371:
372: if (isset($config['autowired'])) {
373: Validators::assertField($config, 'autowired', 'bool');
374: $definition->setAutowired($config['autowired']);
375: }
376:
377: if (isset($config['dynamic'])) {
378: Validators::assertField($config, 'dynamic', 'bool');
379: $definition->setDynamic($config['dynamic']);
380: }
381:
382: if (isset($config['inject'])) {
383: Validators::assertField($config, 'inject', 'bool');
384: $definition->addTag(Extensions\InjectExtension::TAG_INJECT, $config['inject']);
385: }
386:
387: if (isset($config['run'])) {
388: $config['tags']['run'] = (bool) $config['run'];
389: }
390:
391: if (isset($config['tags'])) {
392: Validators::assertField($config, 'tags', 'array');
393: if (Config\Helpers::takeParent($config['tags'])) {
394: $definition->setTags(array());
395: }
396: foreach ($config['tags'] as $tag => $attrs) {
397: if (is_int($tag) && is_string($attrs)) {
398: $definition->addTag($attrs);
399: } else {
400: $definition->addTag($tag, $attrs);
401: }
402: }
403: }
404: }
405:
406:
407: 408: 409: 410:
411: public static function filterArguments(array $args)
412: {
413: foreach ($args as $k => $v) {
414: if ($v === '...') {
415: unset($args[$k]);
416: } elseif (is_string($v) && preg_match('#^[\w\\\\]*::[A-Z][A-Z0-9_]*\z#', $v, $m)) {
417: $args[$k] = ContainerBuilder::literal(ltrim($v, ':'));
418: } elseif (is_array($v)) {
419: $args[$k] = self::filterArguments($v);
420: } elseif ($v instanceof Statement) {
421: $tmp = self::filterArguments(array($v->getEntity()));
422: $args[$k] = new Statement($tmp[0], self::filterArguments($v->arguments));
423: }
424: }
425: return $args;
426: }
427:
428: }
429: