Namespaces

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