Namespaces

  • 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

  • Container
  • ContainerBuilder
  • Helpers
  • ServiceDefinition
  • Statement

Interfaces

  • IContainer

Exceptions

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