Passed
Pull Request — master (#89)
by Romain
17:29 queued 11:32
created

PropertyFactory::getProperties()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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

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