1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Nette\Config;
13:
14: use Nette,
15: Nette\Utils\Validators;
16:
17:
18: 19: 20: 21: 22: 23: 24: 25: 26:
27: class Compiler extends Nette\Object
28: {
29:
30: private $extensions = array();
31:
32:
33: private $container;
34:
35:
36: private $config;
37:
38:
39: private static $reserved = array('services' => 1, 'factories' => 1, 'parameters' => 1);
40:
41:
42: 43: 44: 45:
46: public function addExtension($name, CompilerExtension $extension)
47: {
48: if (isset(self::$reserved[$name])) {
49: throw new Nette\InvalidArgumentException("Name '$name' is reserved.");
50: }
51: $this->extensions[$name] = $extension->setCompiler($this, $name);
52: return $this;
53: }
54:
55:
56: 57: 58:
59: public function getExtensions()
60: {
61: return $this->extensions;
62: }
63:
64:
65: 66: 67:
68: public function getContainerBuilder()
69: {
70: return $this->container;
71: }
72:
73:
74: 75: 76: 77:
78: public function getConfig()
79: {
80: return $this->config;
81: }
82:
83:
84: 85: 86:
87: public function compile(array $config, $className, $parentName)
88: {
89: $this->config = $config;
90: $this->container = new Nette\DI\ContainerBuilder;
91: $this->processParameters();
92: $this->processExtensions();
93: $this->processServices();
94: return $this->generateCode($className, $parentName);
95: }
96:
97:
98: public function processParameters()
99: {
100: if (isset($this->config['parameters'])) {
101: $this->container->parameters = $this->config['parameters'];
102: }
103: }
104:
105:
106: public function processExtensions()
107: {
108: for ($i = 0; $slice = array_slice($this->extensions, $i, 1); $i++) {
109: reset($slice)->loadConfiguration();
110: }
111:
112: if ($extra = array_diff_key($this->config, self::$reserved, $this->extensions)) {
113: $extra = implode("', '", array_keys($extra));
114: throw new Nette\InvalidStateException("Found sections '$extra' in configuration, but corresponding extensions are missing.");
115: }
116: }
117:
118:
119: public function processServices()
120: {
121: $this->parseServices($this->container, $this->config);
122:
123: foreach ($this->extensions as $name => $extension) {
124: $this->container->addDefinition($name)
125: ->setClass('Nette\DI\NestedAccessor', array('@container', $name))
126: ->setAutowired(FALSE);
127:
128: if (isset($this->config[$name])) {
129: $this->parseServices($this->container, $this->config[$name], $name);
130: }
131: }
132:
133: foreach ($this->container->getDefinitions() as $name => $def) {
134: $factory = $name . 'Factory';
135: if (!$def->shared && !$def->internal && !$this->container->hasDefinition($factory)) {
136: $this->container->addDefinition($factory)
137: ->setClass('Nette\Callback', array('@container', Nette\DI\Container::getMethodName($name, FALSE)))
138: ->setAutowired(FALSE)
139: ->tags = $def->tags;
140: }
141: }
142: }
143:
144:
145: public function generateCode($className, $parentName)
146: {
147: foreach ($this->extensions as $extension) {
148: $extension->beforeCompile();
149: $this->container->addDependency(Nette\Reflection\ClassType::from($extension)->getFileName());
150: }
151:
152: $classes[] = $class = $this->container->generateClass($parentName);
153: $class->setName($className)
154: ->addMethod('initialize');
155:
156: foreach ($this->extensions as $extension) {
157: $extension->afterCompile($class);
158: }
159:
160: $defs = $this->container->getDefinitions();
161: ksort($defs);
162: $list = array_keys($defs);
163: foreach (array_reverse($defs, TRUE) as $name => $def) {
164: if ($def->class === 'Nette\DI\NestedAccessor' && ($found = preg_grep('#^'.$name.'\.#i', $list))) {
165: $list = array_diff($list, $found);
166: $def->class = $className . '_' . preg_replace('#\W+#', '_', $name);
167: $class->documents = preg_replace("#\\S+(?= \\$$name\\z)#", $def->class, $class->documents);
168: $classes[] = $accessor = new Nette\Utils\PhpGenerator\ClassType($def->class);
169: foreach ($found as $item) {
170: if ($defs[$item]->internal) {
171: continue;
172: }
173: $short = substr($item, strlen($name) + 1);
174: $accessor->addDocument($defs[$item]->shared
175: ? "@property {$defs[$item]->class} \$$short"
176: : "@method {$defs[$item]->class} create" . ucfirst("$short()"));
177: }
178: }
179: }
180:
181: return implode("\n\n\n", $classes);
182: }
183:
184:
185:
186:
187:
188: 189: 190: 191:
192: public static function parseServices(Nette\DI\ContainerBuilder $container, array $config, $namespace = NULL)
193: {
194: $services = isset($config['services']) ? $config['services'] : array();
195: $factories = isset($config['factories']) ? $config['factories'] : array();
196: $all = array_merge($services, $factories);
197:
198: uasort($all, function($a, $b) {
199: return strcmp(Helpers::isInheriting($a), Helpers::isInheriting($b));
200: });
201:
202: foreach ($all as $origName => $def) {
203: $shared = array_key_exists($origName, $services);
204: if ((string) (int) $origName === (string) $origName) {
205: $name = (string) (count($container->getDefinitions()) + 1);
206: } elseif ($shared && array_key_exists($origName, $factories)) {
207: throw new Nette\DI\ServiceCreationException("It is not allowed to use services and factories with the same name: '$origName'.");
208: } else {
209: $name = ($namespace ? $namespace . '.' : '') . strtr($origName, '\\', '_');
210: }
211:
212: if (($parent = Helpers::takeParent($def)) && $parent !== $name) {
213: $container->removeDefinition($name);
214: $definition = $container->addDefinition($name);
215: if ($parent !== Helpers::OVERWRITE) {
216: foreach ($container->getDefinition($parent) as $k => $v) {
217: $definition->$k = unserialize(serialize($v));
218: }
219: }
220: } elseif ($container->hasDefinition($name)) {
221: $definition = $container->getDefinition($name);
222: if ($definition->shared !== $shared) {
223: throw new Nette\DI\ServiceCreationException("It is not allowed to use service and factory with the same name '$name'.");
224: }
225: } else {
226: $definition = $container->addDefinition($name);
227: }
228: try {
229: static::parseService($definition, $def, $shared);
230: } catch (\Exception $e) {
231: throw new Nette\DI\ServiceCreationException("Service '$name': " . $e->getMessage(), NULL, $e);
232: }
233: }
234: }
235:
236:
237: 238: 239: 240:
241: public static function parseService(Nette\DI\ServiceDefinition $definition, $config, $shared = TRUE)
242: {
243: if ($config === NULL) {
244: return;
245: } elseif (!is_array($config)) {
246: $config = array('class' => NULL, 'factory' => $config);
247: }
248:
249: $known = $shared
250: ? array('class', 'factory', 'arguments', 'setup', 'autowired', 'run', 'tags')
251: : array('class', 'factory', 'arguments', 'setup', 'autowired', 'tags', 'internal', 'parameters');
252:
253: if ($error = array_diff(array_keys($config), $known)) {
254: throw new Nette\InvalidStateException("Unknown key '" . implode("', '", $error) . "' in definition of service.");
255: }
256:
257: $arguments = array();
258: if (array_key_exists('arguments', $config)) {
259: Validators::assertField($config, 'arguments', 'array');
260: $arguments = self::filterArguments($config['arguments']);
261: $definition->setArguments($arguments);
262: }
263:
264: if (array_key_exists('class', $config) || array_key_exists('factory', $config)) {
265: $definition->class = NULL;
266: $definition->factory = NULL;
267: }
268:
269: if (array_key_exists('class', $config)) {
270: Validators::assertField($config, 'class', 'string|stdClass|null');
271: if ($config['class'] instanceof \stdClass) {
272: $definition->setClass($config['class']->value, self::filterArguments($config['class']->attributes));
273: } else {
274: $definition->setClass($config['class'], $arguments);
275: }
276: }
277:
278: if (array_key_exists('factory', $config)) {
279: Validators::assertField($config, 'factory', 'callable|stdClass|null');
280: if ($config['factory'] instanceof \stdClass) {
281: $definition->setFactory($config['factory']->value, self::filterArguments($config['factory']->attributes));
282: } else {
283: $definition->setFactory($config['factory'], $arguments);
284: }
285: }
286:
287: if (isset($config['setup'])) {
288: if (Helpers::takeParent($config['setup'])) {
289: $definition->setup = array();
290: }
291: Validators::assertField($config, 'setup', 'list');
292: foreach ($config['setup'] as $id => $setup) {
293: Validators::assert($setup, 'callable|stdClass', "setup item #$id");
294: if ($setup instanceof \stdClass) {
295: Validators::assert($setup->value, 'callable', "setup item #$id");
296: $definition->addSetup($setup->value, self::filterArguments($setup->attributes));
297: } else {
298: $definition->addSetup($setup);
299: }
300: }
301: }
302:
303: $definition->setShared($shared);
304: if (isset($config['parameters'])) {
305: Validators::assertField($config, 'parameters', 'array');
306: $definition->setParameters($config['parameters']);
307: }
308:
309: if (isset($config['autowired'])) {
310: Validators::assertField($config, 'autowired', 'bool');
311: $definition->setAutowired($config['autowired']);
312: }
313:
314: if (isset($config['internal'])) {
315: Validators::assertField($config, 'internal', 'bool');
316: $definition->setInternal($config['internal']);
317: }
318:
319: if (isset($config['run'])) {
320: $config['tags']['run'] = (bool) $config['run'];
321: }
322:
323: if (isset($config['tags'])) {
324: Validators::assertField($config, 'tags', 'array');
325: if (Helpers::takeParent($config['tags'])) {
326: $definition->tags = array();
327: }
328: foreach ($config['tags'] as $tag => $attrs) {
329: if (is_int($tag) && is_string($attrs)) {
330: $definition->addTag($attrs);
331: } else {
332: $definition->addTag($tag, $attrs);
333: }
334: }
335: }
336: }
337:
338:
339: 340: 341: 342:
343: public static function filterArguments(array $args)
344: {
345: foreach ($args as $k => $v) {
346: if ($v === '...') {
347: unset($args[$k]);
348: } elseif ($v instanceof \stdClass && isset($v->value, $v->attributes)) {
349: $args[$k] = new Nette\DI\Statement($v->value, self::filterArguments($v->attributes));
350: }
351: }
352: return $args;
353: }
354:
355: }
356: