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