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