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