DefinitionBuilder   A
last analyzed

Complexity

Total Complexity 15

Size/Duplication

Total Lines 151
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 46
dl 0
loc 151
rs 10
c 0
b 0
f 0
wmc 15

6 Methods

Rating   Name   Duplication   Size   Complexity  
A buildDefinition() 0 29 6
A __construct() 0 5 1
A sendDefinitionBuiltSignal() 0 7 2
A sendComponentsSignal() 0 6 1
A buildDefinitionInternal() 0 15 2
A runProcessors() 0 8 3
1
<?php
2
declare(strict_types=1);
3
4
/*
5
 * Copyright (C)
6
 * Nathan Boiron <[email protected]>
7
 * Romain Canon <[email protected]>
8
 *
9
 * This file is part of the TYPO3 NotiZ project.
10
 * It is free software; you can redistribute it and/or modify it
11
 * under the terms of the GNU General Public License, either
12
 * version 3 of the License, or any later version.
13
 *
14
 * For the full copyright and license information, see:
15
 * http://www.gnu.org/licenses/gpl-3.0.html
16
 */
17
18
namespace CuyZ\Notiz\Core\Definition\Builder;
19
20
use CuyZ\Notiz\Core\Definition\Builder\Component\DefinitionComponents;
21
use CuyZ\Notiz\Core\Definition\Tree\Definition;
22
use CuyZ\Notiz\Core\Support\NotizConstants;
23
use CuyZ\Notiz\Service\CacheService;
24
use CuyZ\Notiz\Service\Traits\ExtendedSelfInstantiateTrait;
25
use CuyZ\Notiz\Validation\Validator\DefinitionValidator;
26
use Romm\ConfigurationObject\ConfigurationObjectFactory;
27
use Romm\ConfigurationObject\ConfigurationObjectInstance;
28
use TYPO3\CMS\Core\SingletonInterface;
29
use TYPO3\CMS\Core\Utility\ArrayUtility;
30
use TYPO3\CMS\Core\Utility\GeneralUtility;
31
use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
0 ignored issues
show
Bug introduced by
The type TYPO3\CMS\Extbase\SignalSlot\Dispatcher was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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