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