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