Passed
Pull Request — master (#47)
by Romain
03:39
created

DefinitionBuilder   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 132
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 132
rs 10
c 0
b 0
f 0
wmc 13

5 Methods

Rating   Name   Duplication   Size   Complexity  
B buildDefinition() 0 27 6
A __construct() 0 5 1
A sendComponentsSignal() 0 6 1
A buildDefinitionInternal() 0 15 2
A runProcessors() 0 8 3
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
109
    /**
110
     * @var DefinitionComponents
111
     */
112
    protected $components;
113
114
    /**
115
     * @var ConfigurationObjectInstance
116
     */
117
    protected $definitionObject;
118
119
    /**
120
     * @var CacheService
121
     */
122
    protected $cacheService;
123
124
    /**
125
     * @var Dispatcher
126
     */
127
    protected $dispatcher;
128
129
    /**
130
     * @param DefinitionComponents $components
131
     * @param CacheService $cacheService
132
     * @param Dispatcher $dispatcher
133
     */
134
    public function __construct(DefinitionComponents $components, CacheService $cacheService, Dispatcher $dispatcher)
135
    {
136
        $this->components = $components;
137
        $this->cacheService = $cacheService;
138
        $this->dispatcher = $dispatcher;
139
    }
140
141
    /**
142
     * Builds a complete definition object, using registered sources and
143
     * processors.
144
     *
145
     * If no error occurred during the build, the instance is put in cache so it
146
     * can be retrieved during future request.
147
     *
148
     * @internal do not use in your own API!
149
     *
150
     * @return ConfigurationObjectInstance
151
     */
152
    public function buildDefinition()
153
    {
154
        if (null === $this->definitionObject) {
155
            if ($this->cacheService->has(NotizConstants::CACHE_KEY_DEFINITION_OBJECT)) {
156
                $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...
157
            }
158
159
            if (false === $this->definitionObject instanceof ConfigurationObjectInstance) {
160
                $this->definitionObject = $this->buildDefinitionInternal();
161
                $validationResult = $this->definitionObject->getValidationResult();
162
163
                if (false === $validationResult->hasErrors()) {
164
                    /** @var DefinitionValidator $definitionValidator */
165
                    $definitionValidator = GeneralUtility::makeInstance(DefinitionValidator::class);
166
167
                    $result = $definitionValidator->validate($this->definitionObject->getObject());
168
169
                    if ($result->hasErrors()) {
170
                        $validationResult->merge($result);
171
                    } else {
172
                        $this->cacheService->set(NotizConstants::CACHE_KEY_DEFINITION_OBJECT, $this->definitionObject);
173
                    }
174
                }
175
            }
176
        }
177
178
        return $this->definitionObject;
179
    }
180
181
    /**
182
     * Runs the registered source components to get a definition array, then use
183
     * this array to create a definition object.
184
     *
185
     * This object is then passed to each registered processor component, that
186
     * is used to modify the object data.
187
     *
188
     * @return ConfigurationObjectInstance
189
     */
190
    protected function buildDefinitionInternal()
191
    {
192
        $arrayDefinition = [];
193
194
        $this->sendComponentsSignal();
195
196
        foreach ($this->components->getSources() as $source) {
197
            ArrayUtility::mergeRecursiveWithOverrule($arrayDefinition, $source->getDefinitionArray());
198
        }
199
200
        $definitionObject = ConfigurationObjectFactory::convert(Definition::class, $arrayDefinition);
201
202
        $this->runProcessors($definitionObject);
203
204
        return $definitionObject;
205
    }
206
207
    /**
208
     * Runs the registered processors, by giving them the previously created
209
     * definition object that they can modify like they need to.
210
     *
211
     * @param ConfigurationObjectInstance $definitionObject
212
     */
213
    protected function runProcessors(ConfigurationObjectInstance $definitionObject)
214
    {
215
        if (false === $definitionObject->getValidationResult()->hasErrors()) {
216
            /** @var Definition $definition */
217
            $definition = $definitionObject->getObject();
218
219
            foreach ($this->components->getProcessors() as $processor) {
220
                $processor->process($definition);
221
            }
222
        }
223
    }
224
225
    /**
226
     * Sends a signal to allow external API to manage their own definition
227
     * components.
228
     */
229
    protected function sendComponentsSignal()
230
    {
231
        $this->dispatcher->dispatch(
232
            self::class,
233
            self::COMPONENTS_SIGNAL,
234
            [$this->components]
235
        );
236
    }
237
}
238