ParentsService::filterParents()   A
last analyzed

Complexity

Conditions 5
Paths 8

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 9.2248
c 0
b 0
f 0
cc 5
nc 8
nop 1
1
<?php
2
/*
3
 * 2018 Romain CANON <[email protected]>
4
 *
5
 * This file is part of the TYPO3 Configuration Object project.
6
 * It is free software; you can redistribute it and/or modify it
7
 * under the terms of the GNU General Public License, either
8
 * version 3 of the License, or any later version.
9
 *
10
 * For the full copyright and license information, see:
11
 * http://www.gnu.org/licenses/gpl-3.0.html
12
 */
13
14
namespace Romm\ConfigurationObject\Service\Items\Parents;
15
16
use Romm\ConfigurationObject\Core\Core;
17
use Romm\ConfigurationObject\Service\AbstractService;
18
use Romm\ConfigurationObject\Service\DataTransferObject\ConfigurationObjectConversionDTO;
19
use Romm\ConfigurationObject\Service\DataTransferObject\GetConfigurationObjectDTO;
20
use Romm\ConfigurationObject\Service\Event\ConfigurationObjectAfterServiceEventInterface;
21
use Romm\ConfigurationObject\Service\Event\ObjectConversionAfterServiceEventInterface;
22
use TYPO3\CMS\Core\SingletonInterface;
23
24
/**
25
 * This service will take care of saving the parent classes of the configuration
26
 * objects which use the trait `ParentsTrait`.
27
 */
28
class ParentsService extends AbstractService implements ObjectConversionAfterServiceEventInterface, ConfigurationObjectAfterServiceEventInterface, SingletonInterface
29
{
30
31
    /**
32
     * This event needs to be called before the object is stored in cache, as it
33
     * will add internal variables to the object which are used further to fill
34
     * the parents variables.
35
     */
36
    const PRIORITY_SAVE_OBJECTS_WITH_PARENTS_PATHS = -500;
37
38
    /**
39
     * This event needs to be called after the object is obtained from cache.
40
     */
41
    const PRIORITY_FILL_PARENTS = -10000;
42
43
    /**
44
     * @var array
45
     */
46
    protected $objectsWithParentsPaths = [];
47
48
    /**
49
     * If the converted item is an object, its properties will be checked: if
50
     * they do use the trait `ParentsTrait`, then the full path to this property
51
     * is saved for further usage.
52
     *
53
     * @param ConfigurationObjectConversionDTO $serviceDataTransferObject
54
     */
55
    public function objectConversionAfter(ConfigurationObjectConversionDTO $serviceDataTransferObject)
56
    {
57
        $result = $serviceDataTransferObject->getResult();
58
59
        if (is_object($result)) {
60
            foreach (Core::get()->getGettablePropertiesOfObject($result) as $propertyName) {
61
                $property = Core::get()->getObjectService()->getObjectProperty($result, $propertyName);
62
63
                $this->checkProperty($serviceDataTransferObject, $property, $propertyName);
64
            }
65
        }
66
    }
67
68
    /**
69
     * Will check the given property: it will recursively go through arrays
70
     * which can contains objects using the trait `ParentsTrait`.
71
     *
72
     * @param ConfigurationObjectConversionDTO $serviceDataTransferObject
73
     * @param array|object                     $property
74
     * @param string                           $propertyPath
75
     */
76
    protected function checkProperty(ConfigurationObjectConversionDTO $serviceDataTransferObject, $property, $propertyPath)
77
    {
78
        if (is_array($property)) {
79
            foreach ($property as $key => $value) {
80
                $this->checkProperty($serviceDataTransferObject, $value, $propertyPath . '.' . $key);
81
            }
82
        } else {
83
            $this->checkObjectProperty($serviceDataTransferObject, $property, $propertyPath);
84
        }
85
    }
86
87
    /**
88
     * Will check the given property and see if it is an object which uses the
89
     * trait `ParentsTrait`: if it does, it will store it in the local array
90
     * containing all the paths to these objects.
91
     *
92
     * @param ConfigurationObjectConversionDTO $serviceDataTransferObject
93
     * @param object                           $property
94
     * @param string                           $pathSuffix
95
     */
96
    protected function checkObjectProperty(ConfigurationObjectConversionDTO $serviceDataTransferObject, $property, $pathSuffix)
97
    {
98
        if (is_object($property)) {
99
            $path = (false === empty($serviceDataTransferObject->getCurrentPropertyPath()))
100
                ? implode('.', $serviceDataTransferObject->getCurrentPropertyPath()) . '.'
101
                : '';
102
            $path .= $pathSuffix;
103
104
            if (true === Core::get()->getParentsUtility()->classUsesParentsTrait($property)) {
105
                $this->objectsWithParentsPaths[] = $path;
106
            }
107
        }
108
    }
109
110
    /**
111
     * This function will first save the entire storage of properties paths set
112
     * in the function `objectConversionAfter`.
113
     *
114
     * Then, each of the properties above will be processed to fill their parent
115
     * objects.
116
     *
117
     * @param GetConfigurationObjectDTO $serviceDataTransferObject
118
     */
119
    public function configurationObjectAfter(GetConfigurationObjectDTO $serviceDataTransferObject)
120
    {
121
        // Will save the paths of the properties which need their parent objects.
122
        $this->delay(
123
            self::PRIORITY_SAVE_OBJECTS_WITH_PARENTS_PATHS,
124
            function (GetConfigurationObjectDTO $serviceDataTransferObject) {
125
                if (false === empty($this->objectsWithParentsPaths)) {
126
                    $serviceDataTransferObject->getResult()
127
                        ->setInternalVar('objectsWithParentsPaths', $this->objectsWithParentsPaths);
128
129
                    $this->objectsWithParentsPaths = [];
130
                }
131
            }
132
        );
133
134
        // Will fill all the parents.
135
        $this->delay(
136
            self::PRIORITY_FILL_PARENTS,
137
            function (GetConfigurationObjectDTO $serviceDataTransferObject) {
138
                $objectsWithParentsPaths = $serviceDataTransferObject->getResult()->getInternalVar('objectsWithParentsPaths');
139
140
                if (false === empty($objectsWithParentsPaths)) {
141
                    $object = $serviceDataTransferObject->getResult()->getObject(true);
142
143
                    foreach ($objectsWithParentsPaths as $path) {
144
                        $this->insertParents($object, explode('.', $path), [$object]);
145
                    }
146
                }
147
            }
148
        );
149
    }
150
151
    /**
152
     * Internal function to fill the parents.
153
     *
154
     * @param mixed    $entity
155
     * @param array    $path
156
     * @param object[] $parents
157
     */
158
    protected function insertParents($entity, array $path, array $parents)
159
    {
160
        $propertyName = reset($path);
161
        $propertyValue = Core::get()->getObjectService()->getObjectProperty($entity, $propertyName);
162
163
        if (1 === count($path)) {
164
            if (is_object($propertyValue)
165
                && Core::get()->getParentsUtility()->classUsesParentsTrait($propertyValue)
166
            ) {
167
                /** @var ParentsTrait $propertyValue */
168
                $propertyValue->attachParents($this->filterParents($parents));
169
            }
170
        } else {
171
            if (is_object($propertyValue)) {
172
                $parents[] = $propertyValue;
173
            }
174
175
            array_shift($path);
176
            $this->insertParents($propertyValue, $path, $parents);
177
        }
178
    }
179
180
    /**
181
     * This function will filter a given array of objects, by removing
182
     * unnecessary parents that use `ParentsTrait` and are followed by another
183
     * parent that does the same: there is no need to have the full list of
184
     * chained parents
185
     *
186
     * @param object[] $parents
187
     * @return object[]
188
     */
189
    protected function filterParents(array $parents)
190
    {
191
        $filteredParents = [];
192
        $lastParentWithTrait = null;
193
194
        foreach ($parents as $parent) {
195
            if (Core::get()->getParentsUtility()->classUsesParentsTrait($parent)) {
196
                $lastParentWithTrait = $parent;
197
            } else {
198
                if (null !== $lastParentWithTrait) {
199
                    $filteredParents[] = $lastParentWithTrait;
200
                    $lastParentWithTrait = null;
201
                }
202
203
                $filteredParents[] = $parent;
204
            }
205
        }
206
207
        if (null !== $lastParentWithTrait) {
208
            $filteredParents[] = $lastParentWithTrait;
209
        }
210
211
        return $filteredParents;
212
    }
213
}
214