Completed
Pull Request — master (#6)
by Jakub
03:28
created

AutoscanCompilerPass   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 154
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 17.02%

Importance

Changes 4
Bugs 2 Features 0
Metric Value
wmc 30
c 4
b 2
f 0
lcom 1
cbo 7
dl 0
loc 154
ccs 16
cts 94
cp 0.1702
rs 10

2 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
F process() 0 137 29
1
<?php
2
namespace Skrz\Bundle\AutowiringBundle\DependencyInjection\Compiler;
3
4
use Doctrine\Common\Annotations\AnnotationReader;
5
use Skrz\Bundle\AutowiringBundle\Annotation\Component;
6
use Skrz\Bundle\AutowiringBundle\DependencyInjection\ClassMultiMap;
7
use Skrz\Bundle\AutowiringBundle\Exception\AutowiringException;
8
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
9
use Symfony\Component\DependencyInjection\ContainerBuilder;
10
use Symfony\Component\DependencyInjection\Definition;
11
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
12
13
/**
14
 * @author Jakub Kulhan <[email protected]>
15
 */
16
class AutoscanCompilerPass implements CompilerPassInterface
17
{
18
19
	/** @var ClassMultiMap */
20
	private $classMap;
21
22
	/** @var AnnotationReader */
23
	private $annotationReader;
24
25 2
	public function __construct(ClassMultiMap $classMap, AnnotationReader $annotationReader)
26
	{
27 2
		$this->classMap = $classMap;
28 2
		$this->annotationReader = $annotationReader;
29 2
	}
30
31 1
	public function process(ContainerBuilder $container)
32
	{
33 1
		$parameterBag = $container->getParameterBag();
34
		try {
35 1
			$autoscanPsr4 = (array)$parameterBag->resolveValue("%autowiring.autoscan_psr4%");
36 1
		} catch (ParameterNotFoundException $e) {
37 1
			$autoscanPsr4 = [];
38
		}
39
40
		try {
41 1
			$fastAnnotationChecksRegex = implode("|", array_map(function ($s) {
42
				return preg_quote($s);
43 1
			}, (array)$parameterBag->resolveValue("%autowiring.fast_annotation_checks%")));
44 1
		} catch (ParameterNotFoundException $e) {
45 1
			$fastAnnotationChecksRegex = null;
46
		}
47
48 1
		$env = $parameterBag->resolveValue("%kernel.environment%");
49
50
		// TODO: better error state handling
51 1
		if (empty($autoscanPsr4) || empty($fastAnnotationChecksRegex)) {
52 1
			return;
53
		}
54
55
		// TODO: more find methods than grep
56
		$grep = "egrep -lir " . escapeshellarg($fastAnnotationChecksRegex);
57
		foreach ($autoscanPsr4 as $ns => $dir) {
58
			if (!is_dir($dir)) {
59
				throw new AutowiringException(
60
					sprintf("Autoscan directory '%s' does not exits.", $dir)
61
				);
62
			}
63
64
			$autoscanPsr4[$ns] = $dir = realpath($dir);
65
			$grep .= " " . escapeshellarg($dir);
66
		}
67
68
		if (($files = shell_exec($grep)) === null) {
69
			throw new AutowiringException("Autoscan grep failed.");
70
		}
71
72
		$classNames = [];
73
		foreach (explode("\n", trim($files)) as $file) {
74
			if (substr($file, -4) !== ".php") {
75
				continue;
76
			}
77
78
			foreach ($autoscanPsr4 as $ns => $dir) {
79
				if (strncmp($file, $dir, strlen($dir)) === 0) {
80
					$fileWithoutDir = substr($file, strlen($dir), strlen($file) - strlen($dir) - 4);
81
					$className = $ns . str_replace("/", "\\", $fileWithoutDir);
82
					$classNames[$className] = $file;
83
					break;
84
				}
85
			}
86
		}
87
88
		foreach ($classNames as $className => $file) {
89
			try {
90
				new \ReflectionClass($className);
91
			} catch (\ReflectionException $e) {
92
				throw new AutowiringException(
93
					sprintf(
94
						"File '%s' does not contain class '%s', or class is not autoload-able. " .
95
						"Check 'autowiring.autoscan_psr4' configuration if you specified the path correctly.",
96
						$file,
97
						$className
98
					)
99
				);
100
			}
101
		}
102
103
		$classFiles = array_flip($classNames);
104
105
		foreach (get_declared_classes() as $className) {
106
			$serviceIds = $this->classMap->getMulti($className);
107
			if (!empty($serviceIds)) {
108
				continue;
109
			}
110
111
			$rc = new \ReflectionClass($className);
112
113
			if (!isset($classFiles[$rc->getFileName()])) {
114
				continue;
115
			}
116
117
			$annotations = $this->annotationReader->getClassAnnotations($rc);
118
119
			foreach ($annotations as $annotation) {
120
				if ($annotation instanceof Component && ($annotation->env === $env || $annotation->env === null)) {
121
					$serviceId = $annotation->name;
122
123
					if ($serviceId === null) {
124
						$annotationClassName = get_class($annotation);
125
						$annotationSimpleName = substr($annotationClassName, strrpos($annotationClassName, "\\") + 1);
126
						$classNameParts = explode("\\", $className);
127
						$classSimpleName = array_pop($classNameParts);
128
						$annotationLen = strlen($annotationSimpleName);
129
						if (substr($classSimpleName, -$annotationLen) === $annotationSimpleName) {
130
							$classSimpleName = substr($classSimpleName, 0, strlen($classSimpleName) - $annotationLen);
131
						}
132
133
						$middle = ".";
134
135
						do {
136
							$serviceId = lcfirst($annotationSimpleName) . $middle . lcfirst($classSimpleName);
137
138
							do {
139
								$part = array_pop($classNameParts);
140
							} while ($part === $annotationSimpleName && !empty($classNameParts));
141
142
							$middle = "." . lcfirst($part) . $middle;
143
144
						} while ($container->hasDefinition($serviceId) && !empty($classNameParts));
145
					}
146
147
					if ($container->hasDefinition($serviceId)) {
148
						throw new AutowiringException(
149
							sprintf(
150
								"Class '%s' cannot be added as service '%s', service ID already exists.",
151
								$className,
152
								$serviceId
153
							)
154
						);
155
					}
156
157
					$definition = new Definition($className);
158
159
					if ($classFiles[$rc->getFileName()] !== $className) {
160
						$definition->setFile($rc->getFileName());
161
					}
162
163
					$container->setDefinition($serviceId, $definition);
164
				}
165
			}
166
		}
167
	}
168
169
}
170