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