Packages

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

Classes

  • DIContainer
  • DIContainerBuilder
  • DIHelpers
  • DIServiceDefinition
  • DIStatement

Interfaces

  • IDIContainer

Exceptions

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