Completed
Push — v2 ( 357b8a...035655 )
by Peter
13:42 queued 09:14
created

Addendum::getPaths()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
ccs 0
cts 2
cp 0
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
3
/**
4
 * This software package is licensed under `AGPL, Commercial` license[s].
5
 *
6
 * @package maslosoft/signals
7
 * @license AGPL, Commercial
8
 *
9
 * @copyright Copyright (c) Peter Maselkowski <[email protected]>
10
 *
11
 */
12
13
namespace Maslosoft\Signals\Builder;
14
15
use Maslosoft\Addendum\Interfaces\AnnotatedInterface;
16
use Maslosoft\Addendum\Utilities\AnnotationUtility;
17
use Maslosoft\Addendum\Utilities\ClassChecker;
18
use Maslosoft\Addendum\Utilities\FileWalker;
19
use Maslosoft\Signals\Helpers\DataSorter;
20
use Maslosoft\Signals\Helpers\NameNormalizer;
21
use Maslosoft\Signals\Interfaces\ExtractorInterface;
22
use Maslosoft\Signals\Meta\DocumentMethodMeta;
23
use Maslosoft\Signals\Meta\DocumentPropertyMeta;
24
use Maslosoft\Signals\Meta\DocumentTypeMeta;
25
use Maslosoft\Signals\Meta\SignalsMeta;
26
use Maslosoft\Signals\Signal;
27
use ReflectionClass;
28
use UnexpectedValueException;
29
30
/**
31
 * Addendum extractor
32
 *
33
 * @author Piotr Maselkowski <pmaselkowski at gmail.com>
34
 */
35
class Addendum implements ExtractorInterface
36
{
37
38
	// Data keys for annotations extraction
39
	const SlotFor = 'SlotFor';
40
	const SignalFor = 'SignalFor';
41
	// Default annotation names
42
	const SlotName = 'SlotFor';
43
	const SignalName = 'SignalFor';
44
45
	/**
46
	 * Signal instance
47
	 * @var Signal
48
	 */
49
	private $signal = null;
50
51
	/**
52
	 * Signals and slots data
53
	 * @var mixed
54
	 */
55
	private $data = [
56
		Signal::Slots => [
57
		],
58
		Signal::Signals => [
59
		]
60
	];
61
62
	/**
63
	 * Scanned file paths
64
	 * @var string[]
65
	 */
66
	private $paths = [];
67
68
	/**
69
	 * Annotations mathing patterns
70
	 * @var string[]
71
	 */
72
	private $patterns = [];
73
74 15
	public function __construct()
75
	{
76
		$annotations = [
77 15
			self::SlotFor,
78 15
			self::SignalFor
79
		];
80 15
		foreach ($annotations as $annotation)
81
		{
82 15
			$annotation = preg_replace('~^@~', '', $annotation);
83 15
			$this->patterns[] = sprintf('~@%s~', $annotation);
84
		}
85 15
	}
86
87
	/**
88
	 * Get signals and slots data
89
	 * @return mixed
90
	 */
91
	public function getData()
92
	{
93
		(new FileWalker([], [$this, 'processFile'], $this->signal->paths))->walk();
94
		DataSorter::sort($this->data);
95
		return $this->data;
96
	}
97
98
	/**
99
	 * Get scanned paths. This is available only after getData call.
100
	 * @return string[]
101
	 */
102
	public function getPaths()
103
	{
104
		return $this->paths;
105
	}
106
107
	/**
108
	 * Set signal instance
109
	 * @param Signal $signal
110
	 */
111
	public function setSignal(Signal $signal)
112
	{
113
		$this->signal = $signal;
114
	}
115
116
	/**
117
	 * @param string $file
118
	 */
119
	public function processFile($file, $contents)
120
	{
121
		$file = realpath($file);
122
		$this->paths[] = $file;
123
		// Remove initial `\` from namespace
124
		$annotated = AnnotationUtility::rawAnnotate($file);
125
		$namespace = preg_replace('~^\\\\+~', '', $annotated['namespace']);
126
		$className = $annotated['className'];
127
128
129
		// Use fully qualified name, class must autoload
130
		$fqn = $namespace . '\\' . $className;
131
		NameNormalizer::normalize($fqn);
132
		$info = new ReflectionClass($fqn);
133
		$isAnnotated = $info->implementsInterface(AnnotatedInterface::class);
134
		$hasSignals = $this->hasSignals($contents);
135
		$isAbstract = $info->isAbstract() || $info->isInterface();
136
137
		// Old classes must now implement interface
138
		// Brake BC!
139
		if ($hasSignals && !$isAnnotated && !$isAbstract)
140
		{
141
			throw new UnexpectedValueException(sprintf('Class %s must implement %s to use signals', $fqn, AnnotatedInterface::class));
142
		}
143
144
		// Skip not annotated class
145
		if (!$isAnnotated)
146
		{
147
			return;
148
		}
149
150
		// Skip abstract classes
151
		if ($isAbstract)
152
		{
153
			return;
154
		}
155
		$meta = SignalsMeta::create($fqn);
156
		/* @var $typeMeta DocumentTypeMeta */
157
		$typeMeta = $meta->type();
158
159
		// Signals
160
		foreach ($typeMeta->signalFor as $slot)
161
		{
162
			$this->data[Signal::Slots][$slot][$fqn] = true;
163
		}
164
165
		// Slots
166
		// For constructor injection
167
		foreach ($typeMeta->slotFor as $slot)
168
		{
169
			$key = implode('@', [$fqn, '__construct', '()']);
170
			$this->data[Signal::Signals][$slot][$fqn][$key] = true;
171
		}
172
173
		// For method injection
174
		foreach ($meta->methods() as $methodName => $method)
175
		{
176
			/* @var $method DocumentMethodMeta */
177
			foreach ($method->slotFor as $slot)
178
			{
179
				$key = implode('@', [$fqn, $methodName, '()']);
180
				$this->data[Signal::Signals][$slot][$fqn][$key] = sprintf('%s()', $methodName);
181
			}
182
		}
183
184
		// For property injection
185
		foreach ($meta->fields() as $fieldName => $field)
186
		{
187
			/* @var $field DocumentPropertyMeta */
188
			foreach ($field->slotFor as $slot)
189
			{
190
				$key = implode('@', [$fqn, $fieldName]);
191
				$this->data[Signal::Signals][$slot][$fqn][$key] = sprintf('%s', $fieldName);
192
			}
193
		}
194
	}
195
196
	private function hasSignals($contents)
197
	{
198
		foreach ($this->patterns as $pattern)
199
		{
200
			if (preg_match($pattern, $contents))
201
			{
202
				return true;
203
			}
204
		}
205
		return false;
206
	}
207
208
}
209