ValidatorResolver::buildBaseValidatorConjunction()   B
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 42
rs 8.9368
c 0
b 0
f 0
cc 5
nc 5
nop 3
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\Validation;
15
16
use Romm\ConfigurationObject\Core\Core;
17
use Romm\ConfigurationObject\Core\Service\ObjectService;
18
use Romm\ConfigurationObject\Legacy\Reflection\ReflectionService;
19
use Romm\ConfigurationObject\Service\Items\MixedTypes\MixedTypesInterface;
20
use Romm\ConfigurationObject\Service\Items\MixedTypes\MixedTypesService;
21
use Romm\ConfigurationObject\Validation\Validator\Internal\ConfigurationObjectValidator;
22
use Romm\ConfigurationObject\Validation\Validator\Internal\MixedTypeCollectionValidator;
23
use Romm\ConfigurationObject\Validation\Validator\Internal\MixedTypeValidator;
24
use TYPO3\CMS\Extbase\Validation\Validator\CollectionValidator;
25
use TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator;
26
use TYPO3\CMS\Extbase\Validation\Validator\GenericObjectValidator;
27
use TYPO3\CMS\Extbase\Validation\Validator\ObjectValidatorInterface;
28
29
/**
30
 * Customized validator resolver, which it mostly used to support the mixed
31
 * types.
32
 *
33
 * When an instance of validator is created, we check if the type of this
34
 * validator is `CollectionValidator`: in this case we use a custom one instead:
35
 * `MixedTypeCollectionValidator` which will support the mixed types feature.
36
 */
37
class ValidatorResolver extends \Romm\ConfigurationObject\Legacy\Validation\ValidatorResolver
38
{
39
40
    /**
41
     * @var array
42
     */
43
    protected $baseValidatorConjunctionsWithChecks = [];
44
45
    /**
46
     * @var ObjectService
47
     */
48
    protected $objectService;
49
50
    /**
51
     * @var \Romm\ConfigurationObject\Reflection\ReflectionService
52
     */
53
    protected $reflectionService;
54
55
    /**
56
     * @inheritdoc
57
     */
58
    public function createValidator($validatorType, array $validatorOptions = [])
59
    {
60
        return (CollectionValidator::class === $validatorType)
61
            ? parent::createValidator(MixedTypeCollectionValidator::class)
62
            : parent::createValidator($validatorType, $validatorOptions);
63
    }
64
65
    /**
66
     * @inheritdoc
67
     */
68
    protected function buildBaseValidatorConjunction($indexKey, $targetClassName, array $validationGroups = [])
69
    {
70
        parent::buildBaseValidatorConjunction($indexKey, $targetClassName, $validationGroups);
71
72
        /*
73
         * The code below is DIRTY: in order to use `SilentExceptionInterface`
74
         * feature we need an extended version of the `GenericObjectValidator`,
75
         * but this is hardcoded in:
76
         * \TYPO3\CMS\Extbase\Validation\ValidatorResolver::buildBaseValidatorConjunction()
77
         *
78
         * Here we replace every `GenericObjectValidator` by our own instance.
79
         *
80
         * Please, do not try this at home.
81
         */
82
        /** @var ConjunctionValidator $conjunctionValidator */
83
        $conjunctionValidator = $this->baseValidatorConjunctions[$indexKey];
84
85
        foreach ($conjunctionValidator->getValidators() as $validator) {
86
            if ($validator instanceof GenericObjectValidator) {
87
                /*
88
                 * A full check is processed on the properties to check for
89
                 * mixed types, in which case a validator is added to these
90
                 * properties.
91
                 */
92
                $this->addMixedTypeValidators($targetClassName, $validator);
93
94
                /** @var ConfigurationObjectValidator $newValidator */
95
                $newValidator = $this->objectManager->get(ConfigurationObjectValidator::class, []);
96
97
                foreach ($validator->getPropertyValidators() as $propertyName => $propertyValidators) {
98
                    foreach ($propertyValidators as $propertyValidator) {
99
                        $newValidator->addPropertyValidator($propertyName, $propertyValidator);
100
                    }
101
                }
102
103
                // Replacing the old validator with the new one...
104
                $conjunctionValidator->removeValidator($validator);
105
                unset($validator);
106
                $conjunctionValidator->addValidator($newValidator);
107
            }
108
        }
109
    }
110
111
    /**
112
     * This function will list the properties of the given class, and filter on
113
     * the ones that do not have a validator assigned yet.
114
     *
115
     * @param string                 $targetClassName
116
     * @param GenericObjectValidator $validator
117
     */
118
    protected function addMixedTypeValidators($targetClassName, GenericObjectValidator $validator)
119
    {
120
        foreach ($this->reflectionService->getClassPropertyNames($targetClassName) as $property) {
121
            /*
122
             * If the property already is already bound to an object validator,
123
             * there is no need to do further checks.
124
             */
125
            if ($this->propertyHasObjectValidator($validator, $property)) {
126
                continue;
127
            }
128
129
            if ($this->propertyIsMixedType($targetClassName, $property)) {
130
                /*
131
                 * The property is mixed, a validator with the `mixedTypes`
132
                 * option is added, to delegate the validator resolving to
133
                 * later (when the property is actually filled).
134
                 */
135
                $objectValidator = $this->createValidator(MixedTypeValidator::class);
136
137
                $validator->addPropertyValidator($property, $objectValidator);
0 ignored issues
show
Bug introduced by
It seems like $objectValidator defined by $this->createValidator(\...edTypeValidator::class) on line 135 can be null; however, TYPO3\CMS\Extbase\Valida...:addPropertyValidator() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
138
            }
139
        }
140
    }
141
142
    /**
143
     * Checks among the existing validators of the given property if it does
144
     * already has an object validator (can be several types, like the classes
145
     * `ObjectValidator` or `ConjunctionValidator`, as long as they implement
146
     * the interface `ObjectValidatorInterface`).
147
     *
148
     * If one is found, `true` is returned.
149
     *
150
     * @param GenericObjectValidator $validator
151
     * @param string                 $property
152
     * @return bool
153
     */
154
    protected function propertyHasObjectValidator(GenericObjectValidator $validator, $property)
155
    {
156
        $propertiesValidators = $validator->getPropertyValidators();
157
158
        if (isset($propertiesValidators[$property])) {
159
            foreach ($propertiesValidators[$property] as $validator) {
160
                if ($validator instanceof ObjectValidatorInterface) {
161
                    return true;
162
                }
163
            }
164
        }
165
166
        return false;
167
    }
168
169
    /**
170
     * Checks if the given property is a mixed type.
171
     *
172
     * First, we check if the type of the property is a class that implements
173
     * the interface `MixedTypesInterface`.
174
     *
175
     * If not, we check if the property has the annotation `@mixedTypesResolver`
176
     * with a class name that implements the interface `MixedTypesInterface`.
177
     *
178
     * If one was found, `true` is returned.
179
     *
180
     * @param string $className
181
     * @param string $property
182
     * @return bool
183
     */
184
    protected function propertyIsMixedType($className, $property)
185
    {
186
        $mixedType = false;
187
188
        $propertySchema = $this->reflectionService->getClassSchema($className)->getProperty($property);
189
190
        if ($this->classIsMixedType($propertySchema['type'])) {
191
            $mixedType = true;
192
        } else {
193
            if ($this->reflectionService->isPropertyTaggedWith($className, $property, MixedTypesService::PROPERTY_ANNOTATION_MIXED_TYPE)) {
194
                $tags = $this->reflectionService->getPropertyTagValues($className, $property, MixedTypesService::PROPERTY_ANNOTATION_MIXED_TYPE);
195
                $mixedTypeClassName = reset($tags);
196
197
                if ($this->classIsMixedType($mixedTypeClassName)) {
198
                    $mixedType = true;
199
                }
200
            }
201
        }
202
203
        return $mixedType;
204
    }
205
206
    /**
207
     * @param string $className
208
     * @return bool
209
     */
210
    protected function classIsMixedType($className)
211
    {
212
        return Core::get()->classExists($className)
213
            && array_key_exists(MixedTypesInterface::class, class_implements($className));
214
    }
215
216
    /**
217
     * @param ReflectionService $reflectionService
218
     */
219
    public function injectReflectionService(ReflectionService $reflectionService)
220
    {
221
        $this->reflectionService = Core::get()->getReflectionService();
222
    }
223
224
    /**
225
     * @param ObjectService $objectService
226
     */
227
    public function injectObjectService(ObjectService $objectService)
228
    {
229
        $this->objectService = $objectService;
230
    }
231
}
232