| 1 |
<?php |
| 2 |
|
| 3 |
/** |
| 4 |
* This file is part of the Nette Framework (https://nette.org) |
| 5 |
* Copyright (c) 2004 David Grudl (https://davidgrudl.com) |
| 6 |
*/ |
| 7 |
declare (strict_types=1); |
| 8 |
namespace Packetery\Nette\DI; |
| 9 |
|
| 10 |
use Packetery\Nette; |
| 11 |
/** |
| 12 |
* Autowiring. |
| 13 |
*/ |
| 14 |
class Autowiring |
| 15 |
{ |
| 16 |
use \Packetery\Nette\SmartObject; |
| 17 |
/** @var ContainerBuilder */ |
| 18 |
private $builder; |
| 19 |
/** @var array[] type => services, used by getByType() */ |
| 20 |
private $highPriority = []; |
| 21 |
/** @var array[] type => services, used by findByType() */ |
| 22 |
private $lowPriority = []; |
| 23 |
/** @var string[] of classes excluded from autowiring */ |
| 24 |
private $excludedClasses = []; |
| 25 |
public function __construct(ContainerBuilder $builder) |
| 26 |
{ |
| 27 |
$this->builder = $builder; |
| 28 |
} |
| 29 |
/** |
| 30 |
* Resolves service name by type. |
| 31 |
* @param bool $throw exception if service not found? |
| 32 |
* @return ($throw is true ? string : ?string) |
| 33 |
* @throws MissingServiceException when not found |
| 34 |
* @throws ServiceCreationException when multiple found |
| 35 |
*/ |
| 36 |
public function getByType(string $type, bool $throw = \false) : ?string |
| 37 |
{ |
| 38 |
$type = Helpers::normalizeClass($type); |
| 39 |
$types = $this->highPriority; |
| 40 |
if (empty($types[$type])) { |
| 41 |
if ($throw) { |
| 42 |
if (!\class_exists($type) && !\interface_exists($type)) { |
| 43 |
throw new MissingServiceException(\sprintf("Service of type '%s' not found. Check the class name because it cannot be found.", $type)); |
| 44 |
} |
| 45 |
throw new MissingServiceException(\sprintf('Service of type %s not found. Did you add it to configuration file?', $type)); |
| 46 |
} |
| 47 |
return null; |
| 48 |
} elseif (\count($types[$type]) === 1) { |
| 49 |
return $types[$type][0]; |
| 50 |
} else { |
| 51 |
$list = $types[$type]; |
| 52 |
\natsort($list); |
| 53 |
$hint = \count($list) === 2 && ($tmp = \strpos($list[0], '.') xor \strpos($list[1], '.')) ? '. If you want to overwrite service ' . $list[$tmp ? 0 : 1] . ', give it proper name.' : ''; |
| 54 |
throw new ServiceCreationException(\sprintf("Multiple services of type {$type} found: %s%s", \implode(', ', $list), $hint)); |
| 55 |
} |
| 56 |
} |
| 57 |
/** |
| 58 |
* Gets the service names and definitions of the specified type. |
| 59 |
* @return Definitions\Definition[] service name is key |
| 60 |
*/ |
| 61 |
public function findByType(string $type) : array |
| 62 |
{ |
| 63 |
$type = Helpers::normalizeClass($type); |
| 64 |
$definitions = $this->builder->getDefinitions(); |
| 65 |
$names = \array_merge($this->highPriority[$type] ?? [], $this->lowPriority[$type] ?? []); |
| 66 |
$res = []; |
| 67 |
foreach ($names as $name) { |
| 68 |
$res[$name] = $definitions[$name]; |
| 69 |
} |
| 70 |
return $res; |
| 71 |
} |
| 72 |
/** |
| 73 |
* @param string[] $types |
| 74 |
*/ |
| 75 |
public function addExcludedClasses(array $types) : void |
| 76 |
{ |
| 77 |
foreach ($types as $type) { |
| 78 |
if (\class_exists($type) || \interface_exists($type)) { |
| 79 |
$type = Helpers::normalizeClass($type); |
| 80 |
$this->excludedClasses += \class_parents($type) + \class_implements($type) + [$type => $type]; |
| 81 |
} |
| 82 |
} |
| 83 |
} |
| 84 |
public function getClassList() : array |
| 85 |
{ |
| 86 |
return [$this->lowPriority, $this->highPriority]; |
| 87 |
} |
| 88 |
public function rebuild() : void |
| 89 |
{ |
| 90 |
$this->lowPriority = $this->highPriority = $preferred = []; |
| 91 |
foreach ($this->builder->getDefinitions() as $name => $def) { |
| 92 |
if (!($type = $def->getType())) { |
| 93 |
continue; |
| 94 |
} |
| 95 |
$autowired = $def->getAutowired(); |
| 96 |
if (\is_array($autowired)) { |
| 97 |
foreach ($autowired as $k => $autowiredType) { |
| 98 |
if ($autowiredType === ContainerBuilder::ThisService) { |
| 99 |
$autowired[$k] = $type; |
| 100 |
} elseif (!\is_a($type, $autowiredType, \true)) { |
| 101 |
throw new ServiceCreationException(\sprintf("Incompatible class %s in autowiring definition of service '%s'.", $autowiredType, $name)); |
| 102 |
} |
| 103 |
} |
| 104 |
} |
| 105 |
foreach (\class_parents($type) + \class_implements($type) + [$type] as $parent) { |
| 106 |
if (!$autowired || isset($this->excludedClasses[$parent])) { |
| 107 |
continue; |
| 108 |
} elseif (\is_array($autowired)) { |
| 109 |
$priority = \false; |
| 110 |
foreach ($autowired as $autowiredType) { |
| 111 |
if (\is_a($parent, $autowiredType, \true)) { |
| 112 |
if (empty($preferred[$parent]) && isset($this->highPriority[$parent])) { |
| 113 |
$this->lowPriority[$parent] = \array_merge($this->lowPriority[$parent] ?? [], $this->highPriority[$parent]); |
| 114 |
$this->highPriority[$parent] = []; |
| 115 |
} |
| 116 |
$preferred[$parent] = $priority = \true; |
| 117 |
break; |
| 118 |
} |
| 119 |
} |
| 120 |
} else { |
| 121 |
$priority = empty($preferred[$parent]); |
| 122 |
} |
| 123 |
$list = $priority ? 'highPriority' : 'lowPriority'; |
| 124 |
$this->{$list}[$parent][] = $name; |
| 125 |
} |
| 126 |
} |
| 127 |
} |
| 128 |
} |
| 129 |
|