1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\DI\Extensions;
9:
10: use Nette;
11: use Nette\DI;
12: use Nette\DI\PhpReflection;
13:
14:
15: 16: 17:
18: class InjectExtension extends DI\CompilerExtension
19: {
20: const TAG_INJECT = 'inject';
21:
22:
23: public function beforeCompile()
24: {
25: foreach ($this->getContainerBuilder()->getDefinitions() as $def) {
26: if ($def->getTag(self::TAG_INJECT) && $def->getClass()) {
27: $this->updateDefinition($def);
28: }
29: }
30: }
31:
32:
33: private function updateDefinition($def)
34: {
35: $class = $def->getClass();
36: $builder = $this->getContainerBuilder();
37: $injects = [];
38: foreach (self::getInjectProperties($class) as $property => $type) {
39: self::checkType($class, $property, $type, $builder);
40: $injects[] = new DI\Statement('$' . $property, ['@\\' . ltrim($type, '\\')]);
41: }
42:
43: foreach (array_reverse(self::getInjectMethods($def->getClass())) as $method) {
44: $injects[] = new DI\Statement($method);
45: }
46:
47: $setups = $def->getSetup();
48: foreach ($injects as $inject) {
49: foreach ($setups as $key => $setup) {
50: if ($setup->getEntity() === $inject->getEntity()) {
51: $inject = $setup;
52: unset($setups[$key]);
53: }
54: }
55: array_unshift($setups, $inject);
56: }
57: $def->setSetup($setups);
58: }
59:
60:
61: 62: 63: 64: 65:
66: public static function getInjectMethods($class)
67: {
68: $res = [];
69: foreach (get_class_methods($class) as $name) {
70: if (substr($name, 0, 6) === 'inject') {
71: $res[$name] = (new \ReflectionMethod($class, $name))->getDeclaringClass()->getName();
72: }
73: }
74: uksort($res, function ($a, $b) use ($res) {
75: return $res[$a] === $res[$b]
76: ? strcmp($a, $b)
77: : (is_a($res[$a], $res[$b], TRUE) ? 1 : -1);
78: });
79: return array_keys($res);
80: }
81:
82:
83: 84: 85: 86: 87:
88: public static function getInjectProperties($class)
89: {
90: $res = [];
91: foreach (get_class_vars($class) as $name => $foo) {
92: $rp = new \ReflectionProperty($class, $name);
93: if (PhpReflection::parseAnnotation($rp, 'inject') !== NULL) {
94: if ($type = PhpReflection::parseAnnotation($rp, 'var')) {
95: $type = PhpReflection::expandClassName($type, PhpReflection::getDeclaringClass($rp));
96: }
97: $res[$name] = $type;
98: }
99: }
100: ksort($res);
101: return $res;
102: }
103:
104:
105: 106: 107: 108:
109: public static function callInjects(DI\Container $container, $service)
110: {
111: if (!is_object($service)) {
112: throw new Nette\InvalidArgumentException(sprintf('Service must be object, %s given.', gettype($service)));
113: }
114:
115: foreach (self::getInjectMethods($service) as $method) {
116: $container->callMethod([$service, $method]);
117: }
118:
119: foreach (self::getInjectProperties(get_class($service)) as $property => $type) {
120: self::checkType($service, $property, $type, $container);
121: $service->$property = $container->getByType($type);
122: }
123: }
124:
125:
126:
127: private static function checkType($class, $name, $type, $container)
128: {
129: $rc = PhpReflection::getDeclaringClass(new \ReflectionProperty($class, $name));
130: $fullname = $rc->getName() . '::$' . $name;
131: if (!$type) {
132: throw new Nette\InvalidStateException("Property $fullname has no @var annotation.");
133: } elseif (!class_exists($type) && !interface_exists($type)) {
134: throw new Nette\InvalidStateException("Class or interface '$type' used in @var annotation at $fullname not found. Check annotation and 'use' statements.");
135: } elseif (!$container->getByType($type, FALSE)) {
136: throw new Nette\InvalidStateException("Service of type {$type} used in @var annotation at $fullname not found. Did you register it in configuration file?");
137: }
138: }
139:
140: }
141: