dispatchPropertyBuildDefinitionSignal()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 3
dl 0
loc 12
rs 10
c 0
b 0
f 0
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\Property\Factory;
19
20
use CuyZ\Notiz\Core\Definition\Tree\EventGroup\Event\EventDefinition;
21
use CuyZ\Notiz\Core\Event\Event;
22
use CuyZ\Notiz\Core\Event\Support\HasProperties;
23
use CuyZ\Notiz\Core\Notification\Notification;
24
use CuyZ\Notiz\Core\Property\PropertyEntry;
25
use CuyZ\Notiz\Service\Traits\ExtendedSelfInstantiateTrait;
26
use TYPO3\CMS\Core\SingletonInterface;
27
use TYPO3\CMS\Core\Utility\GeneralUtility;
28
use TYPO3\CMS\Extbase\Object\Container\Container;
0 ignored issues
show
Bug introduced by
The type TYPO3\CMS\Extbase\Object\Container\Container 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...
29
use TYPO3\CMS\Extbase\Object\ObjectManager;
0 ignored issues
show
Bug introduced by
The type TYPO3\CMS\Extbase\Object\ObjectManager 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...
30
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...
31
32
/**
33
 * Factory for getting both properties definitions and values, that are defined
34
 * by events and used by notifications.
35
 *
36
 * Global properties manipulation
37
 * ------------------------------
38
 *
39
 * If you need to globally do things with properties (for instance markers), you
40
 * can use the signals below.
41
 *
42
 * In the example below, we add a new global marker `currentDate` that will be
43
 * accessible for every notification.
44
 *
45
 * > my_extension/ext_localconf.php
46
 * ```
47
 * $dispatcher = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
48
 *     \TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class
49
 * );
50
 *
51
 *  // We add a new entry to the definition of the markers: `currentDate` that will
52
 *  // be later filled with the date of the day.
53
 *  //
54
 *  // This marker will be accessible to every notification, regardless of the event
55
 *  // and other selected configuration.
56
 * $dispatcher->connect(
57
 *     \CuyZ\Notiz\Core\Property\Factory\PropertyFactory::class,
58
 *     \CuyZ\Notiz\Core\Property\Factory\PropertyFactory::SIGNAL_PROPERTY_BUILD_DEFINITION,
59
 *     function (
60
 *         \CuyZ\Notiz\Core\Property\Factory\PropertyDefinition $propertyDefinition,
61
 *         \CuyZ\Notiz\Core\Definition\Tree\EventGroup\Event\EventDefinition $eventDefinition,
62
 *         \CuyZ\Notiz\Core\Notification\Notification $notification
63
 *     ) {
64
 *         if ($propertyDefinition->getPropertyType() === \CuyZ\Notiz\Domain\Property\Marker::class) {
65
 *             $propertyDefinition->addEntry('currentDate')
66
 *                 ->setLabel('Formatted date of the day');
67
 *         }
68
 *     }
69
 * );
70
 *
71
 * // Manually filling the marker `currentDate` with the date of the day.
72
 * $dispatcher->connect(
73
 *     \CuyZ\Notiz\Core\Property\Factory\PropertyFactory::class,
74
 *     \CuyZ\Notiz\Core\Property\Factory\PropertyFactory::SIGNAL_PROPERTY_FILLING,
75
 *     function (
76
 *         \CuyZ\Notiz\Core\Property\Factory\PropertyContainer $propertyContainer,
77
 *         \CuyZ\Notiz\Core\Event\Event $event
78
 *     ) {
79
 *         if ($propertyContainer->getPropertyType() === \CuyZ\Notiz\Domain\Property\Marker::class) {
80
 *             $propertyContainer->getEntry('currentDate')
81
 *                 ->setValue(date('d/m/Y'));
82
 *         }
83
 *     }
84
 * );
85
 * ```
86
 */
87
class PropertyFactory implements SingletonInterface
88
{
89
    const SIGNAL_PROPERTY_BUILD_DEFINITION = 'propertyBuildDefinition';
90
91
    const SIGNAL_PROPERTY_FILLING = 'propertyFilling';
92
93
    use ExtendedSelfInstantiateTrait;
94
95
    /**
96
     * @var PropertyDefinition[]
97
     */
98
    protected $propertyDefinition = [];
99
100
    /**
101
     * @var PropertyContainer[]
102
     */
103
    protected $properties = [];
104
105
    /**
106
     * @var ObjectManager
107
     */
108
    protected $objectManager;
109
110
    /**
111
     * @var Container
112
     */
113
    protected $objectContainer;
114
115
    /**
116
     * @var Dispatcher
117
     */
118
    protected $slotDispatcher;
119
120
    /**
121
     * @param ObjectManager $objectManager
122
     * @param Container $objectContainer
123
     * @param Dispatcher $slotDispatcher
124
     */
125
    public function __construct(ObjectManager $objectManager, Container $objectContainer, Dispatcher $slotDispatcher)
126
    {
127
        $this->objectManager = $objectManager;
128
        $this->objectContainer = $objectContainer;
129
        $this->slotDispatcher = $slotDispatcher;
130
    }
131
132
    /**
133
     * Return property definition from given event definition and notification.
134
     *
135
     * The definition is built only once for the given parameters, memoization
136
     * is used to serve the same definition later in a same run time.
137
     *
138
     * Entries have not been processed by any event instance yet. This means all
139
     * their data can not be accessed yet (mainly their values, but also
140
     * arbitrary data the property can have).
141
     *
142
     * @param string $propertyClassName
143
     * @param EventDefinition $eventDefinition
144
     * @param Notification $notification
145
     * @return PropertyDefinition
146
     */
147
    public function getPropertyDefinition(string $propertyClassName, EventDefinition $eventDefinition, Notification $notification): PropertyDefinition
148
    {
149
        $propertyClassName = $this->objectContainer->getImplementationClassName($propertyClassName);
150
151
        $identifier = $eventDefinition->getClassName() . '::' . $propertyClassName;
152
153
        if (false === isset($this->propertyDefinition[$identifier])) {
154
            $this->propertyDefinition[$identifier] = $this->buildPropertyDefinition($propertyClassName, $eventDefinition, $notification);
155
        }
156
157
        return $this->propertyDefinition[$identifier];
158
    }
159
160
    /**
161
     * Returns a container of property entries that have been processed by the
162
     * given event. This means all their data can be accessed properly.
163
     *
164
     * Note that each property type for each event is processed only once,
165
     * memoization is used to serve the same properties later in a same run
166
     * time.
167
     *
168
     * @param string $propertyClassName
169
     * @param Event $event
170
     * @return PropertyContainer
171
     */
172
    public function getPropertyContainer(string $propertyClassName, Event $event): PropertyContainer
173
    {
174
        $propertyClassName = $this->objectContainer->getImplementationClassName($propertyClassName);
175
176
        $hash = spl_object_hash($event) . '::' . $propertyClassName;
177
178
        if (false === isset($this->properties[$hash])) {
179
            $this->properties[$hash] = $this->buildPropertyContainer($propertyClassName, $event);
180
        }
181
182
        return $this->properties[$hash];
183
    }
184
185
186
    /**
187
     * @param string $propertyClassName
188
     * @param Event $event
189
     * @return PropertyEntry[]
190
     */
191
    public function getProperties(string $propertyClassName, Event $event): array
192
    {
193
        return $this->getPropertyContainer($propertyClassName, $event)->getEntries();
194
    }
195
196
    /**
197
     * @param string $propertyClassName
198
     * @param EventDefinition $eventDefinition
199
     * @param Notification $notification
200
     * @return PropertyDefinition
201
     */
202
    protected function buildPropertyDefinition(string $propertyClassName, EventDefinition $eventDefinition, Notification $notification): PropertyDefinition
203
    {
204
        /** @var PropertyDefinition $propertyDefinition */
205
        $propertyDefinition = $this->objectManager->get(PropertyDefinition::class, $eventDefinition->getClassName(), $propertyClassName);
206
207
        if ($this->eventHasProperties($eventDefinition)) {
208
            /** @var HasProperties $eventClassName */
209
            $eventClassName = $eventDefinition->getClassName();
210
211
            $propertyBuilder = $eventClassName::getPropertyBuilder();
212
213
            $propertyBuilder->build($propertyDefinition, $notification);
214
        }
215
216
        $this->dispatchPropertyBuildDefinitionSignal($propertyDefinition, $eventDefinition, $notification);
217
218
        return $propertyDefinition;
219
    }
220
221
    /**
222
     * @param string $propertyClassName
223
     * @param Event|HasProperties $event
224
     * @return PropertyContainer
225
     */
226
    protected function buildPropertyContainer(string $propertyClassName, Event $event): PropertyContainer
227
    {
228
        $propertyDefinition = $this->getPropertyDefinition($propertyClassName, $event->getDefinition(), $event->getNotification());
229
230
        /** @var PropertyContainer $propertyContainer */
231
        $propertyContainer = GeneralUtility::makeInstance(PropertyContainer::class, $propertyDefinition);
232
233
        if ($this->eventHasProperties($event->getDefinition())) {
234
            $event->fillPropertyEntries($propertyContainer);
0 ignored issues
show
Bug introduced by
The method fillPropertyEntries() does not exist on CuyZ\Notiz\Core\Event\Event. Since it exists in all sub-types, consider adding an abstract or default implementation to CuyZ\Notiz\Core\Event\Event. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

234
            $event->/** @scrutinizer ignore-call */ 
235
                    fillPropertyEntries($propertyContainer);
Loading history...
235
        }
236
237
        $this->dispatchPropertyFillingSignal($propertyContainer, $event);
238
239
        $propertyContainer->freeze();
240
241
        return $propertyContainer;
242
    }
243
244
    /**
245
     * This signal is sent after the property definition was built. It can be
246
     * used to alter the definition depending on your needs.
247
     *
248
     * @param PropertyDefinition $propertyDefinition
249
     * @param EventDefinition $eventDefinition
250
     * @param Notification $notification
251
     */
252
    protected function dispatchPropertyBuildDefinitionSignal(
253
        PropertyDefinition $propertyDefinition,
254
        EventDefinition $eventDefinition,
255
        Notification $notification
256
    ) {
257
        $this->slotDispatcher->dispatch(
258
            self::class,
259
            self::SIGNAL_PROPERTY_BUILD_DEFINITION,
260
            [
261
                $propertyDefinition,
262
                $eventDefinition,
263
                $notification,
264
            ]
265
        );
266
    }
267
268
    /**
269
     * This signal is sent just after properties have been filled with their own
270
     * values. It can be used to manipulate the values of properties before they
271
     * are freezed.
272
     *
273
     * @param PropertyContainer $propertyContainer
274
     * @param Event $event
275
     */
276
    protected function dispatchPropertyFillingSignal(PropertyContainer $propertyContainer, Event $event)
277
    {
278
        $this->slotDispatcher->dispatch(
279
            self::class,
280
            self::SIGNAL_PROPERTY_FILLING,
281
            [
282
                $propertyContainer,
283
                $event,
284
            ]
285
        );
286
    }
287
288
    /**
289
     * @param EventDefinition $eventDefinition
290
     * @return bool
291
     */
292
    protected function eventHasProperties(EventDefinition $eventDefinition): bool
293
    {
294
        return in_array(HasProperties::class, class_implements($eventDefinition->getClassName()));
295
    }
296
}
297