Packages

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Config
      • Adapters
      • Extensions
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Diagnostics
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
      • PhpGenerator
  • NetteModule
  • None
  • PHP

Classes

  • NDIContainer
  • NDIContainerBuilder
  • NDIHelpers
  • NDIServiceDefinition
  • NDIStatement

Interfaces

  • IDIContainer

Exceptions

  • NMissingServiceException
  • NServiceCreationException
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (http://nette.org)
  5:  *
  6:  * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  7:  *
  8:  * For the full copyright and license information, please view
  9:  * the file license.txt that was distributed with this source code.
 10:  * @package Nette\DI
 11:  */
 12: 
 13: 
 14: 
 15: /**
 16:  * Basic container builder.
 17:  *
 18:  * @author     David Grudl
 19:  * @property-read array $definitions
 20:  * @property-read array $dependencies
 21:  * @package Nette\DI
 22:  */
 23: class NDIContainerBuilder extends NObject
 24: {
 25:     const CREATED_SERVICE = 'self',
 26:         THIS_CONTAINER = 'container';
 27: 
 28:     /** @var array  %param% will be expanded */
 29:     public $parameters = array();
 30: 
 31:     /** @var array of NDIServiceDefinition */
 32:     private $definitions = array();
 33: 
 34:     /** @var array for auto-wiring */
 35:     private $classes;
 36: 
 37:     /** @var array of file names */
 38:     private $dependencies = array();
 39: 
 40: 
 41: 
 42:     /**
 43:      * Adds new service definition. The expressions %param% and @service will be expanded.
 44:      * @param  string
 45:      * @return NDIServiceDefinition
 46:      */
 47:     public function addDefinition($name)
 48:     {
 49:         if (isset($this->definitions[$name])) {
 50:             throw new InvalidStateException("Service '$name' has already been added.");
 51:         }
 52:         return $this->definitions[$name] = new NDIServiceDefinition;
 53:     }
 54: 
 55: 
 56: 
 57:     /**
 58:      * Removes the specified service definition.
 59:      * @param  string
 60:      * @return void
 61:      */
 62:     public function removeDefinition($name)
 63:     {
 64:         unset($this->definitions[$name]);
 65:     }
 66: 
 67: 
 68: 
 69:     /**
 70:      * Gets the service definition.
 71:      * @param  string
 72:      * @return NDIServiceDefinition
 73:      */
 74:     public function getDefinition($name)
 75:     {
 76:         if (!isset($this->definitions[$name])) {
 77:             throw new NMissingServiceException("Service '$name' not found.");
 78:         }
 79:         return $this->definitions[$name];
 80:     }
 81: 
 82: 
 83: 
 84:     /**
 85:      * Gets all service definitions.
 86:      * @return array
 87:      */
 88:     public function getDefinitions()
 89:     {
 90:         return $this->definitions;
 91:     }
 92: 
 93: 
 94: 
 95:     /**
 96:      * Does the service definition exist?
 97:      * @param  string
 98:      * @return bool
 99:      */
100:     public function hasDefinition($name)
101:     {
102:         return isset($this->definitions[$name]);
103:     }
104: 
105: 
106: 
107:     /********************* class resolving ****************d*g**/
108: 
109: 
110: 
111:     /**
112:      * Resolves service name by type.
113:      * @param  string  class or interface
114:      * @return string  service name or NULL
115:      * @throws NServiceCreationException
116:      */
117:     public function getByType($class)
118:     {
119:         $lower = ltrim(strtolower($class), '\\');
120:         if (!isset($this->classes[$lower])) {
121:             return;
122: 
123:         } elseif (count($this->classes[$lower]) === 1) {
124:             return $this->classes[$lower][0];
125: 
126:         } else {
127:             throw new NServiceCreationException("Multiple services of type $class found: " . implode(', ', $this->classes[$lower]));
128:         }
129:     }
130: 
131: 
132: 
133:     /**
134:      * Gets the service objects of the specified tag.
135:      * @param  string
136:      * @return array of [service name => tag attributes]
137:      */
138:     public function findByTag($tag)
139:     {
140:         $found = array();
141:         foreach ($this->definitions as $name => $def) {
142:             if (isset($def->tags[$tag]) && $def->shared) {
143:                 $found[$name] = $def->tags[$tag];
144:             }
145:         }
146:         return $found;
147:     }
148: 
149: 
150: 
151:     /**
152:      * Creates a list of arguments using autowiring.
153:      * @return array
154:      */
155:     public function autowireArguments($class, $method, array $arguments)
156:     {
157:         $rc = NClassReflection::from($class);
158:         if (!$rc->hasMethod($method)) {
159:             if (!NValidators::isList($arguments)) {
160:                 throw new NServiceCreationException("Unable to pass specified arguments to $class::$method().");
161:             }
162:             return $arguments;
163:         }
164: 
165:         $rm = $rc->getMethod($method);
166:         if ($rm->isAbstract() || !$rm->isPublic()) {
167:             throw new NServiceCreationException("$rm is not callable.");
168:         }
169:         $this->addDependency($rm->getFileName());
170:         return NDIHelpers::autowireArguments($rm, $arguments, $this);
171:     }
172: 
173: 
174: 
175:     /**
176:      * Generates $dependencies, $classes and expands and normalize class names.
177:      * @return array
178:      */
179:     public function prepareClassList()
180:     {
181:         // complete class-factory pairs; expand classes
182:         foreach ($this->definitions as $name => $def) {
183:             if ($def->class === self::CREATED_SERVICE || ($def->factory && $def->factory->entity === self::CREATED_SERVICE)) {
184:                 $def->class = $name;
185:                 $def->internal = TRUE;
186:                 if ($def->factory && $def->factory->entity === self::CREATED_SERVICE) {
187:                     $def->factory->entity = $def->class;
188:                 }
189:                 unset($this->definitions[$name]);
190:                 $this->definitions['_anonymous_' . str_replace('\\', '_', strtolower(trim($name, '\\')))] = $def;
191:             }
192: 
193:             if ($def->class) {
194:                 $def->class = $this->expand($def->class);
195:                 if (!$def->factory) {
196:                     $def->factory = new NDIStatement($def->class);
197:                 }
198:             } elseif (!$def->factory) {
199:                 throw new NServiceCreationException("Class and factory are missing in service '$name' definition.");
200:             }
201:         }
202: 
203:         // complete classes
204:         $this->classes = FALSE;
205:         foreach ($this->definitions as $name => $def) {
206:             $this->resolveClass($name);
207:         }
208: 
209:         //  build auto-wiring list
210:         $this->classes = array();
211:         foreach ($this->definitions as $name => $def) {
212:             if (!$def->class) {
213:                 continue;
214:             }
215:             if (!class_exists($def->class) && !interface_exists($def->class)) {
216:                 throw new InvalidStateException("Class $def->class has not been found.");
217:             }
218:             $def->class = NClassReflection::from($def->class)->getName();
219:             if ($def->autowired) {
220:                 foreach (class_parents($def->class) + class_implements($def->class) + array($def->class) as $parent) {
221:                     $this->classes[strtolower($parent)][] = $name;
222:                 }
223:             }
224:         }
225: 
226:         foreach ($this->classes as $class => $foo) {
227:             $this->addDependency(NClassReflection::from($class)->getFileName());
228:         }
229:     }
230: 
231: 
232: 
233:     private function resolveClass($name, $recursive = array())
234:     {
235:         if (isset($recursive[$name])) {
236:             throw new InvalidArgumentException('Circular reference detected for services: ' . implode(', ', array_keys($recursive)) . '.');
237:         }
238:         $recursive[$name] = TRUE;
239: 
240:         $def = $this->definitions[$name];
241:         $factory = $this->normalizeEntity($this->expand($def->factory->entity));
242: 
243:         if ($def->class) {
244:             return $def->class;
245: 
246:         } elseif (is_array($factory)) { // method calling
247:             if ($service = $this->getServiceName($factory[0])) {
248:                 if (NStrings::contains($service, '\\')) { // @Class
249:                     throw new NServiceCreationException("Unable resolve class name for service '$name'.");
250:                 }
251:                 $factory[0] = $this->resolveClass($service, $recursive);
252:                 if (!$factory[0]) {
253:                     return;
254:                 }
255:             }
256:             $factory = callback($factory);
257:             if (!$factory->isCallable()) {
258:                 throw new InvalidStateException("Factory '$factory' is not callable.");
259:             }
260:             try {
261:                 $reflection = $factory->toReflection();
262:                 $def->class = preg_replace('#[|\s].*#', '', $reflection->getAnnotation('return'));
263:                 if ($def->class && !class_exists($def->class) && $def->class[0] !== '\\' && $reflection instanceof ReflectionMethod) {
264:                     }
265:             } catch (ReflectionException $e) {
266:             }
267: 
268:         } elseif ($service = $this->getServiceName($factory)) { // alias or factory
269:             if (NStrings::contains($service, '\\')) { // @Class
270:                 $service = ltrim($service, '\\');
271:                 $def->autowired = FALSE;
272:                 return $def->class = $service;
273:             }
274:             if ($this->definitions[$service]->shared) {
275:                 $def->autowired = FALSE;
276:             }
277:             return $def->class = $this->resolveClass($service, $recursive);
278: 
279:         } else {
280:             return $def->class = $factory; // class name
281:         }
282:     }
283: 
284: 
285: 
286:     /**
287:      * Adds a file to the list of dependencies.
288:      * @return NDIContainerBuilder  provides a fluent interface
289:      */
290:     public function addDependency($file)
291:     {
292:         $this->dependencies[$file] = TRUE;
293:         return $this;
294:     }
295: 
296: 
297: 
298:     /**
299:      * Returns the list of dependent files.
300:      * @return array
301:      */
302:     public function getDependencies()
303:     {
304:         unset($this->dependencies[FALSE]);
305:         return array_keys($this->dependencies);
306:     }
307: 
308: 
309: 
310:     /********************* code generator ****************d*g**/
311: 
312: 
313: 
314:     /**
315:      * Generates PHP class.
316:      * @return NPhpClassType
317:      */
318:     public function generateClass($parentClass = 'NDIContainer')
319:     {
320:         unset($this->definitions[self::THIS_CONTAINER]);
321:         $this->addDefinition(self::THIS_CONTAINER)->setClass($parentClass);
322: 
323:         $this->prepareClassList();
324: 
325:         $class = new NPhpClassType('Container');
326:         $class->addExtend($parentClass);
327:         $class->addMethod('__construct')
328:             ->addBody('parent::__construct(?);', array($this->expand($this->parameters)));
329: 
330:         $classes = $class->addProperty('classes', array());
331:         foreach ($this->classes as $name => $foo) {
332:             try {
333:                 $classes->value[$name] = $this->getByType($name);
334:             } catch (NServiceCreationException $e) {
335:                 $classes->value[$name] = new NPhpLiteral('FALSE, //' . strstr($e->getMessage(), ':'));
336:             }
337:         }
338: 
339:         $definitions = $this->definitions;
340:         ksort($definitions);
341: 
342:         $meta = $class->addProperty('meta', array());
343:         foreach ($definitions as $name => $def) {
344:             if ($def->shared) {
345:                 foreach ($this->expand($def->tags) as $tag => $value) {
346:                     $meta->value[$name][NDIContainer::TAGS][$tag] = $value;
347:                 }
348:             }
349:         }
350: 
351:         foreach ($definitions as $name => $def) {
352:             try {
353:                 $type = ($tmp=$def->class) ? $tmp : 'object';
354:                 $methodName = NDIContainer::getMethodName($name, $def->shared);
355:                 if (!NPhpHelpers::isIdentifier($methodName)) {
356:                     throw new NServiceCreationException('Name contains invalid characters.');
357:                 }
358:                 if ($def->shared && !$def->internal && NPhpHelpers::isIdentifier($name)) {
359:                     $class->addDocument("@property $type \$$name");
360:                 }
361:                 $method = $class->addMethod($methodName)
362:                     ->addDocument("@return $type")
363:                     ->setVisibility($def->shared || $def->internal ? 'protected' : 'public')
364:                     ->setBody($name === self::THIS_CONTAINER ? 'return $this;' : $this->generateService($name));
365: 
366:                 foreach ($this->expand($def->parameters) as $k => $v) {
367:                     $tmp = explode(' ', is_int($k) ? $v : $k);
368:                     $param = is_int($k) ? $method->addParameter(end($tmp)) : $method->addParameter(end($tmp), $v);
369:                     if (isset($tmp[1])) {
370:                         $param->setTypeHint($tmp[0]);
371:                     }
372:                 }
373:             } catch (Exception $e) {
374:                 throw new NServiceCreationException("Service '$name': " . $e->getMessage());
375:             }
376:         }
377: 
378:         return $class;
379:     }
380: 
381: 
382: 
383:     /**
384:      * Generates body of service method.
385:      * @return string
386:      */
387:     private function generateService($name)
388:     {
389:         $def = $this->definitions[$name];
390:         $parameters = $this->parameters;
391:         foreach ($this->expand($def->parameters) as $k => $v) {
392:             $v = explode(' ', is_int($k) ? $v : $k);
393:             $parameters[end($v)] = new NPhpLiteral('$' . end($v));
394:         }
395: 
396:         $code = '$service = ' . $this->formatStatement(NDIHelpers::expand($def->factory, $parameters, TRUE)) . ";\n";
397: 
398:         $entity = $this->normalizeEntity($def->factory->entity);
399:         if ($def->class && $def->class !== $entity && !$this->getServiceName($entity)) {
400:             $code .= NPhpHelpers::formatArgs("if (!\$service instanceof $def->class) {\n"
401:                 . "\tthrow new UnexpectedValueException(?);\n}\n",
402:                 array("Unable to create service '$name', value returned by factory is not $def->class type.")
403:             );
404:         }
405: 
406:         foreach ((array) $def->setup as $setup) {
407:             $setup = NDIHelpers::expand($setup, $parameters, TRUE);
408:             if (is_string($setup->entity) && strpbrk($setup->entity, ':@?') === FALSE) { // auto-prepend @self
409:                 $setup->entity = array("@$name", $setup->entity);
410:             }
411:             $code .= $this->formatStatement($setup, $name) . ";\n";
412:         }
413: 
414:         return $code .= 'return $service;';
415:     }
416: 
417: 
418: 
419:     /**
420:      * Formats PHP code for class instantiating, function calling or property setting in PHP.
421:      * @return string
422:      * @internal
423:      */
424:     public function formatStatement(NDIStatement $statement, $self = NULL)
425:     {
426:         $entity = $this->normalizeEntity($statement->entity);
427:         $arguments = $statement->arguments;
428: 
429:         if (is_string($entity) && NStrings::contains($entity, '?')) { // PHP literal
430:             return $this->formatPhp($entity, $arguments, $self);
431: 
432:         } elseif ($service = $this->getServiceName($entity)) { // factory calling or service retrieving
433:             if ($this->definitions[$service]->shared) {
434:                 if ($arguments) {
435:                     throw new NServiceCreationException("Unable to call service '$entity'.");
436:                 }
437:                 return $this->formatPhp('$this->?', array($service));
438:             }
439:             $params = array();
440:             foreach ($this->definitions[$service]->parameters as $k => $v) {
441:                 $params[] = preg_replace('#\w+$#', '\$$0', (is_int($k) ? $v : $k)) . (is_int($k) ? '' : ' = ' . NPhpHelpers::dump($v));
442:             }
443:             $rm = new NFunctionReflection(create_function(implode(', ', $params), ''));
444:             $arguments = NDIHelpers::autowireArguments($rm, $arguments, $this);
445:             return $this->formatPhp('$this->?(?*)', array(NDIContainer::getMethodName($service, FALSE), $arguments), $self);
446: 
447:         } elseif ($entity === 'not') { // operator
448:             return $this->formatPhp('!?', array($arguments[0]));
449: 
450:         } elseif (is_string($entity)) { // class name
451:             if ($constructor = NClassReflection::from($entity)->getConstructor()) {
452:                 $this->addDependency($constructor->getFileName());
453:                 $arguments = NDIHelpers::autowireArguments($constructor, $arguments, $this);
454:             } elseif ($arguments) {
455:                 throw new NServiceCreationException("Unable to pass arguments, class $entity has no constructor.");
456:             }
457:             return $this->formatPhp("new $entity" . ($arguments ? '(?*)' : ''), array($arguments), $self);
458: 
459:         } elseif (!NValidators::isList($entity) || count($entity) !== 2) {
460:             throw new InvalidStateException("Expected class, method or property, " . NPhpHelpers::dump($entity) . " given.");
461: 
462:         } elseif ($entity[0] === '') { // globalFunc
463:             return $this->formatPhp("$entity[1](?*)", array($arguments), $self);
464: 
465:         } elseif (NStrings::contains($entity[1], '$')) { // property setter
466:             NValidators::assert($arguments, 'list:1', "setup arguments for '" . callback($entity) . "'");
467:             if ($this->getServiceName($entity[0], $self)) {
468:                 return $this->formatPhp('?->? = ?', array($entity[0], substr($entity[1], 1), $arguments[0]), $self);
469:             } else {
470:                 return $this->formatPhp($entity[0] . '::$? = ?', array(substr($entity[1], 1), $arguments[0]), $self);
471:             }
472: 
473:         } elseif ($service = $this->getServiceName($entity[0], $self)) { // service method
474:             if ($this->definitions[$service]->class) {
475:                 $arguments = $this->autowireArguments($this->definitions[$service]->class, $entity[1], $arguments);
476:             }
477:             return $this->formatPhp('?->?(?*)', array($entity[0], $entity[1], $arguments), $self);
478: 
479:         } else { // static method
480:             $arguments = $this->autowireArguments($entity[0], $entity[1], $arguments);
481:             return $this->formatPhp("$entity[0]::$entity[1](?*)", array($arguments), $self);
482:         }
483:     }
484: 
485: 
486: 
487:     /**
488:      * Formats PHP statement.
489:      * @return string
490:      */
491:     public function formatPhp($statement, $args, $self = NULL)
492:     {
493:         $that = $this;
494:         array_walk_recursive($args, create_function('&$val', 'extract(NCFix::$vars['.NCFix::uses(array('self'=>$self,'that'=> $that)).'], EXTR_REFS);
495:             list($val) = $that->normalizeEntity(array($val));
496: 
497:             if ($val instanceof NDIStatement) {
498:                 $val = new NPhpLiteral($that->formatStatement($val, $self));
499: 
500:             } elseif ($val === \'@\' . NDIContainerBuilder::THIS_CONTAINER) {
501:                 $val = new NPhpLiteral(\'$this\');
502: 
503:             } elseif ($service = $that->getServiceName($val, $self)) {
504:                 $val = $service === $self ? \'$service\' : $that->formatStatement(new NDIStatement($val));
505:                 $val = new NPhpLiteral($val, $self);
506:             }
507:         '));
508:         return NPhpHelpers::formatArgs($statement, $args);
509:     }
510: 
511: 
512: 
513:     /**
514:      * Expands %placeholders% in strings (recursive).
515:      * @param  mixed
516:      * @return mixed
517:      */
518:     public function expand($value)
519:     {
520:         return NDIHelpers::expand($value, $this->parameters, TRUE);
521:     }
522: 
523: 
524: 
525:     /** @internal */
526:     public function normalizeEntity($entity)
527:     {
528:         if (is_string($entity) && NStrings::contains($entity, '::') && !NStrings::contains($entity, '?')) { // NClass::method -> [Class, method]
529:             $entity = explode('::', $entity);
530:         }
531: 
532:         if (is_array($entity) && $entity[0] instanceof NDIServiceDefinition) { // [ServiceDefinition, ...] -> [@serviceName, ...]
533:             $tmp = array_keys($this->definitions, $entity[0], TRUE);
534:             $entity[0] = "@$tmp[0]";
535: 
536:         } elseif ($entity instanceof NDIServiceDefinition) { // ServiceDefinition -> @serviceName
537:             $tmp = array_keys($this->definitions, $entity, TRUE);
538:             $entity = "@$tmp[0]";
539: 
540:         } elseif (is_array($entity) && $entity[0] === $this) { // [$this, ...] -> [@container, ...]
541:             $entity[0] = '@' . NDIContainerBuilder::THIS_CONTAINER;
542:         }
543:         return $entity; // Class, @service, [Class, member], [@service, member], [, globalFunc]
544:     }
545: 
546: 
547: 
548:     /**
549:      * Converts @service or @Class -> service name and checks its existence.
550:      * @param  mixed
551:      * @return string  of FALSE, if argument is not service name
552:      */
553:     public function getServiceName($arg, $self = NULL)
554:     {
555:         if (!is_string($arg) || !preg_match('#^@[\w\\\\.].+$#', $arg)) {
556:             return FALSE;
557:         }
558:         $service = substr($arg, 1);
559:         if ($service === self::CREATED_SERVICE) {
560:             $service = $self;
561:         }
562:         if (NStrings::contains($service, '\\')) {
563:             if ($this->classes === FALSE) { // may be disabled by prepareClassList
564:                 return $service;
565:             }
566:             $res = $this->getByType($service);
567:             if (!$res) {
568:                 throw new NServiceCreationException("Reference to missing service of type $service.");
569:             }
570:             return $res;
571:         }
572:         if (!isset($this->definitions[$service])) {
573:             throw new NServiceCreationException("Reference to missing service '$service'.");
574:         }
575:         return $service;
576:     }
577: 
578: }
579: 
Nette Framework 2.0.1 (for PHP 5.2, prefixed) API API documentation generated by ApiGen 2.7.0