PersistentObjectConverter::convertFrom()   B
last analyzed

Complexity

Conditions 10
Paths 11

Size

Total Lines 35
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 22
nc 11
nop 4
dl 0
loc 35
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace TYPO3\CMS\Extbase\Property\TypeConverter;
19
20
use TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject;
21
use TYPO3\CMS\Extbase\DomainObject\AbstractValueObject;
22
use TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface;
23
use TYPO3\CMS\Extbase\Property\Exception\InvalidPropertyMappingConfigurationException;
24
use TYPO3\CMS\Extbase\Property\Exception\InvalidSourceException;
25
use TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException;
26
use TYPO3\CMS\Extbase\Property\Exception\TargetNotFoundException;
27
use TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface;
28
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
29
30
/**
31
 * This converter transforms arrays or strings to persistent objects. It does the following:
32
 *
33
 * - If the input is string, it is assumed to be a UID. Then, the object is fetched from persistence.
34
 * - If the input is array, we check if it has an identity property.
35
 *
36
 * - If the input has an identity property and NO additional properties, we fetch the object from persistence.
37
 * - If the input has an identity property AND additional properties, we fetch the object from persistence,
38
 *   and set the sub-properties. We only do this if the configuration option "CONFIGURATION_MODIFICATION_ALLOWED" is TRUE.
39
 * - If the input has NO identity property, but additional properties, we create a new object and return it.
40
 *   However, we only do this if the configuration option "CONFIGURATION_CREATION_ALLOWED" is TRUE.
41
 */
42
class PersistentObjectConverter extends ObjectConverter
43
{
44
    /**
45
     * @var int
46
     */
47
    const CONFIGURATION_MODIFICATION_ALLOWED = 1;
48
49
    /**
50
     * @var int
51
     */
52
    const CONFIGURATION_CREATION_ALLOWED = 2;
53
54
    /**
55
     * @var array
56
     */
57
    protected $sourceTypes = ['integer', 'string', 'array'];
58
59
    /**
60
     * @var string
61
     */
62
    protected $targetType = 'object';
63
64
    /**
65
     * @var int
66
     */
67
    protected $priority = 20;
68
69
    /**
70
     * @var \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface
71
     */
72
    protected $persistenceManager;
73
74
    /**
75
     * @param \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface $persistenceManager
76
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
77
     */
78
    public function injectPersistenceManager(PersistenceManagerInterface $persistenceManager): void
79
    {
80
        $this->persistenceManager = $persistenceManager;
81
    }
82
83
    /**
84
     * We can only convert if the $targetType is either tagged with entity or value object.
85
     *
86
     * @param mixed $source
87
     * @param string $targetType
88
     * @return bool
89
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
90
     */
91
    public function canConvertFrom($source, string $targetType): bool
92
    {
93
        return is_subclass_of($targetType, AbstractDomainObject::class);
94
    }
95
96
    /**
97
     * All properties in the source array except __identity are sub-properties.
98
     *
99
     * @param mixed $source
100
     * @return array
101
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
102
     */
103
    public function getSourceChildPropertiesToBeConverted($source): array
104
    {
105
        if (is_string($source) || is_int($source)) {
106
            return [];
107
        }
108
        if (isset($source['__identity'])) {
109
            unset($source['__identity']);
110
        }
111
        return parent::getSourceChildPropertiesToBeConverted($source);
112
    }
113
114
    /**
115
     * The type of a property is determined by the reflection service.
116
     *
117
     * @param string $targetType
118
     * @param string $propertyName
119
     * @param \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface $configuration
120
     * @return string
121
     * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException
122
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
123
     */
124
    public function getTypeOfChildProperty($targetType, string $propertyName, PropertyMappingConfigurationInterface $configuration): string
125
    {
126
        $configuredTargetType = $configuration->getConfigurationFor($propertyName)->getConfigurationValue(\TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::class, self::CONFIGURATION_TARGET_TYPE);
127
        if ($configuredTargetType !== null) {
128
            return $configuredTargetType;
129
        }
130
131
        $specificTargetType = $this->objectContainer->getImplementationClassName($targetType);
132
        $schema = $this->reflectionService->getClassSchema($specificTargetType);
133
        if (!$schema->hasProperty($propertyName)) {
134
            throw new InvalidTargetException('Property "' . $propertyName . '" was not found in target object of type "' . $specificTargetType . '".', 1297978366);
135
        }
136
        $property = $schema->getProperty($propertyName);
137
        return $property->getType() . ($property->getElementType() !== null ? '<' . $property->getElementType() . '>' : '');
138
    }
139
140
    /**
141
     * Convert an object from $source to an entity or a value object.
142
     *
143
     * @param mixed $source
144
     * @param string $targetType
145
     * @param array $convertedChildProperties
146
     * @param \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface $configuration
147
     * @throws \InvalidArgumentException
148
     * @return object|null the target type
149
     * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException
150
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
151
     */
152
    public function convertFrom($source, string $targetType, array $convertedChildProperties = [], PropertyMappingConfigurationInterface $configuration = null): ?object
153
    {
154
        if (is_array($source)) {
155
            if (
156
                class_exists($targetType)
157
                && is_subclass_of($targetType, AbstractValueObject::class)
158
            ) {
159
                // Unset identity for valueobject to use constructor mapping, since the identity is determined from
160
                // constructor arguments
161
                unset($source['__identity']);
162
            }
163
            $object = $this->handleArrayData($source, $targetType, $convertedChildProperties, $configuration);
164
        } elseif (is_string($source) || is_int($source)) {
165
            if (empty($source)) {
166
                return null;
167
            }
168
            $object = $this->fetchObjectFromPersistence($source, $targetType);
169
        } else {
170
            // todo: this case is impossible as this converter is never called with a source that is not an integer, a string or an array
171
            throw new \InvalidArgumentException('Only integers, strings and arrays are accepted.', 1305630314);
172
        }
173
        foreach ($convertedChildProperties as $propertyName => $propertyValue) {
174
            $result = ObjectAccess::setProperty($object, $propertyName, $propertyValue);
175
            if ($result === false) {
176
                $exceptionMessage = sprintf(
177
                    'Property "%s" having a value of type "%s" could not be set in target object of type "%s". Make sure that the property is accessible properly, for example via an appropriate setter method.',
178
                    $propertyName,
179
                    (is_object($propertyValue) ? get_class($propertyValue) : gettype($propertyValue)),
180
                    $targetType
181
                );
182
                throw new InvalidTargetException($exceptionMessage, 1297935345);
183
            }
184
        }
185
186
        return $object;
187
    }
188
189
    /**
190
     * Handle the case if $source is an array.
191
     *
192
     * @param array $source
193
     * @param string $targetType
194
     * @param array $convertedChildProperties
195
     * @param \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface $configuration
196
     * @return object
197
     * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidPropertyMappingConfigurationException
198
     */
199
    protected function handleArrayData(array $source, string $targetType, array &$convertedChildProperties, PropertyMappingConfigurationInterface $configuration = null): object
200
    {
201
        if (isset($source['__identity'])) {
202
            $object = $this->fetchObjectFromPersistence($source['__identity'], $targetType);
203
204
            if (count($source) > 1 && ($configuration === null || $configuration->getConfigurationValue(\TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::class, self::CONFIGURATION_MODIFICATION_ALLOWED) !== true)) {
205
                throw new InvalidPropertyMappingConfigurationException('Modification of persistent objects not allowed. To enable this, you need to set the PropertyMappingConfiguration Value "CONFIGURATION_MODIFICATION_ALLOWED" to TRUE.', 1297932028);
206
            }
207
        } else {
208
            if ($configuration === null || $configuration->getConfigurationValue(\TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::class, self::CONFIGURATION_CREATION_ALLOWED) !== true) {
209
                throw new InvalidPropertyMappingConfigurationException(
210
                    'Creation of objects not allowed. To enable this, you need to set the PropertyMappingConfiguration Value "CONFIGURATION_CREATION_ALLOWED" to TRUE',
211
                    1476044961
212
                );
213
            }
214
            $object = $this->buildObject($convertedChildProperties, $targetType);
215
        }
216
        return $object;
217
    }
218
219
    /**
220
     * Fetch an object from persistence layer.
221
     *
222
     * @param mixed $identity
223
     * @param string $targetType
224
     * @throws \TYPO3\CMS\Extbase\Property\Exception\TargetNotFoundException
225
     * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidSourceException
226
     * @return object
227
     */
228
    protected function fetchObjectFromPersistence($identity, string $targetType): object
229
    {
230
        if (ctype_digit((string)$identity)) {
231
            $object = $this->persistenceManager->getObjectByIdentifier($identity, $targetType);
232
        } else {
233
            throw new InvalidSourceException('The identity property "' . $identity . '" is no UID.', 1297931020);
234
        }
235
236
        if ($object === null) {
237
            throw new TargetNotFoundException(sprintf('Object of type %s with identity "%s" not found.', $targetType, print_r($identity, true)), 1297933823);
0 ignored issues
show
Bug introduced by
It seems like print_r($identity, true) can also be of type true; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

237
            throw new TargetNotFoundException(sprintf('Object of type %s with identity "%s" not found.', $targetType, /** @scrutinizer ignore-type */ print_r($identity, true)), 1297933823);
Loading history...
238
        }
239
240
        return $object;
241
    }
242
}
243