Passed
Push — master ( 687d8f...c1d3e7 )
by Romain
03:27
created

DefinitionBuilder::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 3
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
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);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->cacheService->get..._KEY_DEFINITION_OBJECT) can also be of type false. However, the property $definitionObject is declared as type Romm\ConfigurationObject...igurationObjectInstance. Maybe add an additional type check?

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 $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. 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.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
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