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