|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/* |
|
4
|
|
|
* Copyright (C) 2018 |
|
5
|
|
|
* Nathan Boiron <[email protected]> |
|
6
|
|
|
* Romain Canon <[email protected]> |
|
7
|
|
|
* |
|
8
|
|
|
* This file is part of the TYPO3 NotiZ project. |
|
9
|
|
|
* It is free software; you can redistribute it and/or modify it |
|
10
|
|
|
* under the terms of the GNU General Public License, either |
|
11
|
|
|
* version 3 of the License, or any later version. |
|
12
|
|
|
* |
|
13
|
|
|
* For the full copyright and license information, see: |
|
14
|
|
|
* http://www.gnu.org/licenses/gpl-3.0.html |
|
15
|
|
|
*/ |
|
16
|
|
|
|
|
17
|
|
|
namespace CuyZ\Notiz\Core\Definition\Builder; |
|
18
|
|
|
|
|
19
|
|
|
use CuyZ\Notiz\Core\Definition\Builder\Component\DefinitionComponents; |
|
20
|
|
|
use CuyZ\Notiz\Core\Definition\Tree\Definition; |
|
21
|
|
|
use CuyZ\Notiz\Core\Support\NotizConstants; |
|
22
|
|
|
use CuyZ\Notiz\Service\CacheService; |
|
23
|
|
|
use CuyZ\Notiz\Service\Traits\ExtendedSelfInstantiateTrait; |
|
24
|
|
|
use CuyZ\Notiz\Validation\Validator\DefinitionValidator; |
|
25
|
|
|
use Romm\ConfigurationObject\ConfigurationObjectFactory; |
|
26
|
|
|
use Romm\ConfigurationObject\ConfigurationObjectInstance; |
|
27
|
|
|
use TYPO3\CMS\Core\SingletonInterface; |
|
28
|
|
|
use TYPO3\CMS\Core\Utility\ArrayUtility; |
|
29
|
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility; |
|
30
|
|
|
use TYPO3\CMS\Extbase\SignalSlot\Dispatcher; |
|
31
|
|
|
|
|
32
|
|
|
/** |
|
33
|
|
|
* This class is responsible for building a whole PHP definition object that can |
|
34
|
|
|
* then be used everywhere in the API. |
|
35
|
|
|
* |
|
36
|
|
|
* It works with two types of components: |
|
37
|
|
|
* |
|
38
|
|
|
* Source components |
|
39
|
|
|
* ----------------- |
|
40
|
|
|
* |
|
41
|
|
|
* @see \CuyZ\Notiz\Core\Definition\Builder\Component\Source\DefinitionSource |
|
42
|
|
|
* |
|
43
|
|
|
* They are used to fetch a definition array from any origin, like TypoScript, |
|
44
|
|
|
* YAML or others. This array must be a representation of the definition object |
|
45
|
|
|
* used in this extension. |
|
46
|
|
|
* |
|
47
|
|
|
* The final definition array will be a merge of all the results of the source |
|
48
|
|
|
* components. |
|
49
|
|
|
* |
|
50
|
|
|
* Processor components |
|
51
|
|
|
* -------------------- |
|
52
|
|
|
* |
|
53
|
|
|
* @see \CuyZ\Notiz\Core\Definition\Builder\Component\Processor\DefinitionProcessor |
|
54
|
|
|
* |
|
55
|
|
|
* Once the array definition has been calculated by calling all the source |
|
56
|
|
|
* components, a definition object is created. This object can be modified after |
|
57
|
|
|
* its creation, by adding so-called "processors" to the builder components. |
|
58
|
|
|
* |
|
59
|
|
|
* These processor components will have access to the definition object, and can |
|
60
|
|
|
* basically use any public method available to add/remove/modify any data. |
|
61
|
|
|
* |
|
62
|
|
|
* --- |
|
63
|
|
|
* |
|
64
|
|
|
* Register new components |
|
65
|
|
|
* ----------------------- |
|
66
|
|
|
* |
|
67
|
|
|
* To register new components in your own API, you first need to connect a class |
|
68
|
|
|
* on a signal. Add this code to your `ext_localconf.php` file: |
|
69
|
|
|
* |
|
70
|
|
|
* ``` |
|
71
|
|
|
* $dispatcher = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance( |
|
72
|
|
|
* \TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class |
|
73
|
|
|
* ); |
|
74
|
|
|
* |
|
75
|
|
|
* $dispatcher->connect( |
|
76
|
|
|
* \CuyZ\Notiz\Core\Definition\Builder\DefinitionBuilder::class, |
|
77
|
|
|
* \CuyZ\Notiz\Core\Definition\Builder\DefinitionBuilder::COMPONENTS_SIGNAL, |
|
78
|
|
|
* \Vendor\MyExtension\Domain\Definition\Builder\Component\MyCustomComponents::class, |
|
79
|
|
|
* 'register' |
|
80
|
|
|
* ); |
|
81
|
|
|
* ``` |
|
82
|
|
|
* |
|
83
|
|
|
* The registration class should then look like this: |
|
84
|
|
|
* |
|
85
|
|
|
* ``` |
|
86
|
|
|
* class MyCustomComponents |
|
87
|
|
|
* { |
|
88
|
|
|
* public function register(\CuyZ\Notiz\Core\Definition\Builder\Component\DefinitionComponents $components) |
|
89
|
|
|
* { |
|
90
|
|
|
* $components->addSource( |
|
91
|
|
|
* 'mySourceIdentifier', |
|
92
|
|
|
* \Vendor\MyExtension\Domain\Definition\Builder\Source\MySource::class |
|
93
|
|
|
* ); |
|
94
|
|
|
* |
|
95
|
|
|
* $components->addProcessor( |
|
96
|
|
|
* 'myProcessorIdentifier', |
|
97
|
|
|
* \Vendor\MyExtension\Domain\Definition\Builder\Processor\MyProcessor::class |
|
98
|
|
|
* ); |
|
99
|
|
|
* } |
|
100
|
|
|
* } |
|
101
|
|
|
* ``` |
|
102
|
|
|
*/ |
|
103
|
|
|
class DefinitionBuilder implements SingletonInterface |
|
104
|
|
|
{ |
|
105
|
|
|
use ExtendedSelfInstantiateTrait; |
|
106
|
|
|
|
|
107
|
|
|
const COMPONENTS_SIGNAL = 'manageDefinitionComponents'; |
|
108
|
|
|
const DEFINITION_BUILT_SIGNAL = 'definitionBuilt'; |
|
109
|
|
|
|
|
110
|
|
|
/** |
|
111
|
|
|
* @var DefinitionComponents |
|
112
|
|
|
*/ |
|
113
|
|
|
protected $components; |
|
114
|
|
|
|
|
115
|
|
|
/** |
|
116
|
|
|
* @var ConfigurationObjectInstance |
|
117
|
|
|
*/ |
|
118
|
|
|
protected $definitionObject; |
|
119
|
|
|
|
|
120
|
|
|
/** |
|
121
|
|
|
* @var CacheService |
|
122
|
|
|
*/ |
|
123
|
|
|
protected $cacheService; |
|
124
|
|
|
|
|
125
|
|
|
/** |
|
126
|
|
|
* @var Dispatcher |
|
127
|
|
|
*/ |
|
128
|
|
|
protected $dispatcher; |
|
129
|
|
|
|
|
130
|
|
|
/** |
|
131
|
|
|
* @param DefinitionComponents $components |
|
132
|
|
|
* @param CacheService $cacheService |
|
133
|
|
|
* @param Dispatcher $dispatcher |
|
134
|
|
|
*/ |
|
135
|
|
|
public function __construct(DefinitionComponents $components, CacheService $cacheService, Dispatcher $dispatcher) |
|
136
|
|
|
{ |
|
137
|
|
|
$this->components = $components; |
|
138
|
|
|
$this->cacheService = $cacheService; |
|
139
|
|
|
$this->dispatcher = $dispatcher; |
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
|
|
/** |
|
143
|
|
|
* Builds a complete definition object, using registered sources and |
|
144
|
|
|
* processors. |
|
145
|
|
|
* |
|
146
|
|
|
* If no error occurred during the build, the instance is put in cache so it |
|
147
|
|
|
* can be retrieved during future request. |
|
148
|
|
|
* |
|
149
|
|
|
* @internal do not use in your own API! |
|
150
|
|
|
* |
|
151
|
|
|
* @return ConfigurationObjectInstance |
|
152
|
|
|
*/ |
|
153
|
|
|
public function buildDefinition() |
|
154
|
|
|
{ |
|
155
|
|
|
if (null === $this->definitionObject) { |
|
156
|
|
|
if ($this->cacheService->has(NotizConstants::CACHE_KEY_DEFINITION_OBJECT)) { |
|
157
|
|
|
$this->definitionObject = $this->cacheService->get(NotizConstants::CACHE_KEY_DEFINITION_OBJECT); |
|
|
|
|
|
|
158
|
|
|
} |
|
159
|
|
|
|
|
160
|
|
|
if (false === $this->definitionObject instanceof ConfigurationObjectInstance) { |
|
161
|
|
|
$this->definitionObject = $this->buildDefinitionInternal(); |
|
162
|
|
|
$validationResult = $this->definitionObject->getValidationResult(); |
|
163
|
|
|
|
|
164
|
|
|
if (false === $validationResult->hasErrors()) { |
|
165
|
|
|
/** @var DefinitionValidator $definitionValidator */ |
|
166
|
|
|
$definitionValidator = GeneralUtility::makeInstance(DefinitionValidator::class); |
|
167
|
|
|
|
|
168
|
|
|
$result = $definitionValidator->validate($this->definitionObject->getObject()); |
|
169
|
|
|
|
|
170
|
|
|
if ($result->hasErrors()) { |
|
171
|
|
|
$validationResult->merge($result); |
|
172
|
|
|
} else { |
|
173
|
|
|
$this->cacheService->set(NotizConstants::CACHE_KEY_DEFINITION_OBJECT, $this->definitionObject); |
|
174
|
|
|
} |
|
175
|
|
|
} |
|
176
|
|
|
} |
|
177
|
|
|
|
|
178
|
|
|
$this->sendDefinitionBuiltSignal(); |
|
179
|
|
|
} |
|
180
|
|
|
|
|
181
|
|
|
return $this->definitionObject; |
|
182
|
|
|
} |
|
183
|
|
|
|
|
184
|
|
|
/** |
|
185
|
|
|
* Runs the registered source components to get a definition array, then use |
|
186
|
|
|
* this array to create a definition object. |
|
187
|
|
|
* |
|
188
|
|
|
* This object is then passed to each registered processor component, that |
|
189
|
|
|
* is used to modify the object data. |
|
190
|
|
|
* |
|
191
|
|
|
* @return ConfigurationObjectInstance |
|
192
|
|
|
*/ |
|
193
|
|
|
protected function buildDefinitionInternal() |
|
194
|
|
|
{ |
|
195
|
|
|
$arrayDefinition = []; |
|
196
|
|
|
|
|
197
|
|
|
$this->sendComponentsSignal(); |
|
198
|
|
|
|
|
199
|
|
|
foreach ($this->components->getSources() as $source) { |
|
200
|
|
|
ArrayUtility::mergeRecursiveWithOverrule($arrayDefinition, $source->getDefinitionArray()); |
|
201
|
|
|
} |
|
202
|
|
|
|
|
203
|
|
|
$definitionObject = ConfigurationObjectFactory::convert(Definition::class, $arrayDefinition); |
|
204
|
|
|
|
|
205
|
|
|
$this->runProcessors($definitionObject); |
|
206
|
|
|
|
|
207
|
|
|
return $definitionObject; |
|
208
|
|
|
} |
|
209
|
|
|
|
|
210
|
|
|
/** |
|
211
|
|
|
* Runs the registered processors, by giving them the previously created |
|
212
|
|
|
* definition object that they can modify like they need to. |
|
213
|
|
|
* |
|
214
|
|
|
* @param ConfigurationObjectInstance $definitionObject |
|
215
|
|
|
*/ |
|
216
|
|
|
protected function runProcessors(ConfigurationObjectInstance $definitionObject) |
|
217
|
|
|
{ |
|
218
|
|
|
if (false === $definitionObject->getValidationResult()->hasErrors()) { |
|
219
|
|
|
/** @var Definition $definition */ |
|
220
|
|
|
$definition = $definitionObject->getObject(); |
|
221
|
|
|
|
|
222
|
|
|
foreach ($this->components->getProcessors() as $processor) { |
|
223
|
|
|
$processor->process($definition); |
|
224
|
|
|
} |
|
225
|
|
|
} |
|
226
|
|
|
} |
|
227
|
|
|
|
|
228
|
|
|
/** |
|
229
|
|
|
* Sends a signal to allow external API to manage their own definition |
|
230
|
|
|
* components. |
|
231
|
|
|
*/ |
|
232
|
|
|
protected function sendComponentsSignal() |
|
233
|
|
|
{ |
|
234
|
|
|
$this->dispatcher->dispatch( |
|
235
|
|
|
self::class, |
|
236
|
|
|
self::COMPONENTS_SIGNAL, |
|
237
|
|
|
[$this->components] |
|
238
|
|
|
); |
|
239
|
|
|
} |
|
240
|
|
|
|
|
241
|
|
|
/** |
|
242
|
|
|
* Sends a signal when the definition object is complete. |
|
243
|
|
|
* |
|
244
|
|
|
* Please be aware that this signal is sent only if no error was found when |
|
245
|
|
|
* the definition was built. |
|
246
|
|
|
*/ |
|
247
|
|
|
protected function sendDefinitionBuiltSignal() |
|
248
|
|
|
{ |
|
249
|
|
|
if (!$this->definitionObject->getValidationResult()->hasErrors()) { |
|
250
|
|
|
$this->dispatcher->dispatch( |
|
251
|
|
|
self::class, |
|
252
|
|
|
self::DEFINITION_BUILT_SIGNAL, |
|
253
|
|
|
[$this->definitionObject->getObject()] |
|
254
|
|
|
); |
|
255
|
|
|
} |
|
256
|
|
|
} |
|
257
|
|
|
} |
|
258
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountIdthat can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theidproperty of an instance of theAccountclass. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.