ValidationHelper::isInterfaceCritical()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AlgoWeb\ODataMetadata\Edm\Validation\Internal;
6
7
use AlgoWeb\ODataMetadata\Edm\Validation\EdmError;
8
use AlgoWeb\ODataMetadata\Edm\Validation\EdmErrorCode;
9
use AlgoWeb\ODataMetadata\Edm\Validation\ValidationContext;
10
use AlgoWeb\ODataMetadata\EdmConstants;
11
use AlgoWeb\ODataMetadata\EdmUtil;
12
use AlgoWeb\ODataMetadata\Exception\InvalidOperationException;
13
use AlgoWeb\ODataMetadata\Interfaces\IEntityType;
14
use AlgoWeb\ODataMetadata\Interfaces\IFunction;
15
use AlgoWeb\ODataMetadata\Interfaces\IModel;
16
use AlgoWeb\ODataMetadata\Interfaces\INamedElement;
17
use AlgoWeb\ODataMetadata\Interfaces\ISchemaElement;
18
use AlgoWeb\ODataMetadata\Interfaces\IStructuralProperty;
19
use AlgoWeb\ODataMetadata\Interfaces\Values\IStringValue;
20
use AlgoWeb\ODataMetadata\Interfaces\Values\IValue;
21
use AlgoWeb\ODataMetadata\StringConst;
22
use AlgoWeb\ODataMetadata\Structure\HashSetInternal;
23
use Exception;
24
use SplObjectStorage;
25
use Throwable;
26
use XMLReader;
27
28
abstract class ValidationHelper
29
{
30
    public static function isEdmSystemNamespace(string $namespaceName): bool
31
    {
32
        return ($namespaceName == EdmConstants::TransientNamespace ||
33
        $namespaceName == EdmConstants::EdmNamespace);
34
    }
35
36
    public static function addMemberNameToHashSet(
37
        INamedElement $item,
38
        HashSetInternal $memberNameList,
39
        ValidationContext $context,
40
        EdmErrorCode $errorCode,
41
        string $errorString,
42
        bool $suppressError
43
    ): bool {
44
        $name = $item instanceof ISchemaElement ? $item->fullName() : $item->getName();
45
        if (!$memberNameList->add($name)) {
46
            if (!$suppressError) {
47
                EdmUtil::checkArgumentNull($item->location(), 'item->Location');
48
                $context->addError($item->location(), $errorCode, $errorString);
49
            }
50
            return false;
51
        }
52
53
        return true;
54
    }
55
56
    /**
57
     * @param  IStructuralProperty[] $properties
58
     * @return bool
59
     */
60
    public static function allPropertiesAreNullable(array $properties): bool
61
    {
62
        return count(array_filter($properties, function (IStructuralProperty $item) {
63
            try {
64
                return $item->getType()->getNullable();
65
            } catch (Throwable $e) {
66
                throw new InvalidOperationException($e->getMessage());
67
            }
68
        })) === count($properties);
69
    }
70
71
    /**
72
     * @param  IStructuralProperty[] $properties
73
     * @return bool
74
     */
75
    public static function hasNullableProperty(array $properties): bool
76
    {
77
        return count(array_filter($properties, function (IStructuralProperty $item) {
78
            try {
79
                return $item->getType()->getNullable();
80
            } catch (Throwable $e) {
81
                throw new InvalidOperationException($e->getMessage());
82
            }
83
        })) > 0;
84
    }
85
86
    /**
87
     * @param  IStructuralProperty[] $set
88
     * @param  IStructuralProperty[] $subset
89
     * @return bool
90
     */
91
    public static function propertySetIsSubset(array $set, array $subset): bool
92
    {
93
        return count(array_diff($subset, $set)) === 0;
94
    }
95
96
    /**
97
     * @param  IStructuralProperty[] $set1
98
     * @param  IStructuralProperty[] $set2
99
     * @return bool
100
     */
101
    public static function propertySetsAreEquivalent(array $set1, array $set2): bool
102
    {
103
        if (count($set1) != count($set2)) {
104
            return false;
105
        }
106
        for ($i = count($set1) -1; $i > -1; --$i) {
107
            if ($set1[$i] !== $set2[$i]) {
108
                return false;
109
            }
110
        }
111
        return true;
112
    }
113
114
    public static function validateValueCanBeWrittenAsXmlElementAnnotation(
115
        IValue $value,
116
        ?string $annotationNamespace,
117
        ?string $annotationName,
118
        ?EdmError &$error
119
    ): bool {
120
        if (!($value instanceof IStringValue)) {
121
            $error = new EdmError(
122
                $value->location(),
123
                EdmErrorCode::InvalidElementAnnotation(),
124
                StringConst::EdmModel_Validator_Semantic_InvalidElementAnnotationNotIEdmStringValue()
125
            );
126
            return false;
127
        }
128
129
        $rawString = $value->getValue();
130
        $reader    = new XMLReader();
131
        $reader->XML($rawString);
132
        try {
133
            $eof = true;
134
            // Skip to root element.
135
            if ($reader->nodeType != XMLReader::ELEMENT) {
136
                while ($reader->read() && $reader->nodeType != XMLReader::ELEMENT) {
137
                    if ($reader->nodeType == XMLReader::ELEMENT) {
138
                        $eof = false;
139
                        break;
140
                    }
141
                }
142
            }
143
144
            // The annotation must be an element.
145
            if ($eof) {
146
                $error = new EdmError(
147
                    $value->location(),
148
                    EdmErrorCode::InvalidElementAnnotation(),
149
                    StringConst::EdmModel_Validator_Semantic_InvalidElementAnnotationValueInvalidXml()
150
                );
151
                return false;
152
            }
153
154
            // The root element must correspond to the term of the annotation
155
            $elementNamespace = $reader->namespaceURI;
156
            $elementName      = $reader->localName;
157
158
            if (EdmUtil::isNullOrWhiteSpaceInternal($elementNamespace) ||
159
                EdmUtil::isNullOrWhiteSpaceInternal($elementName)) {
160
                $error = new EdmError(
161
                    $value->location(),
162
                    EdmErrorCode::InvalidElementAnnotation(),
163
                    StringConst::EdmModel_Validator_Semantic_InvalidElementAnnotationNullNamespaceOrName()
164
                );
165
                return false;
166
            }
167
168
            if (!((null === $annotationNamespace || $elementNamespace == $annotationNamespace) &&
169
                  (null === $annotationName || $elementName == $annotationName))) {
170
                $error = new EdmError(
171
                    $value->location(),
172
                    EdmErrorCode::InvalidElementAnnotation(),
173
                    StringConst::EdmModel_Validator_Semantic_InvalidElementAnnotationMismatchedTerm()
174
                );
175
                return false;
176
            }
177
178
            // Parse the entire fragment to determine if the XML is valid
179
            /* @noinspection PhpStatementHasEmptyBodyInspection */
180
            while ($reader->read()) {
181
            }
182
183
            $error = null;
184
            return true;
185
        } catch (Exception $e) {
186
            $error = new EdmError(
187
                $value->location(),
188
                EdmErrorCode::InvalidElementAnnotation(),
189
                StringConst::EdmModel_Validator_Semantic_InvalidElementAnnotationValueInvalidXml()
190
            );
191
            return false;
192
        }
193
    }
194
195
    public static function isInterfaceCritical(EdmError $error): bool
196
    {
197
        $errVal = $error->getErrorCode()->getValue();
198
199
        return $errVal >= EdmErrorCode::InterfaceCriticalPropertyValueMustNotBeNull()->getValue() &&
200
               $errVal <= EdmErrorCode::InterfaceCriticalCycleInTypeHierarchy()->getValue();
201
    }
202
203
    public static function itemExistsInReferencedModel(
204
        IModel $model,
205
        string $fullName,
206
        bool $checkEntityContainer
207
    ): bool {
208
        foreach ($model->getReferencedModels() as $referenced) {
209
            if (self::checkItemReference($fullName, $checkEntityContainer, $referenced)) {
210
                return true;
211
            }
212
        }
213
214
        return false;
215
    }
216
217
    // Take function name to avoid recomputing it
218
    public static function functionOrNameExistsInReferencedModel(
219
        IModel $model,
220
        IFunction $function,
221
        string $functionFullName,
222
        bool $checkEntityContainer
223
    ): bool {
224
        foreach ($model->getReferencedModels() as $referenced) {
225
            if (self::checkFunctionOrNameReference($function, $functionFullName, $checkEntityContainer, $referenced)) {
226
                return true;
227
            }
228
        }
229
230
        return false;
231
    }
232
233
    public static function typeIndirectlyContainsTarget(
234
        IEntityType $source,
235
        IEntityType $target,
236
        HashSetInternal $visited,
237
        IModel $context
238
    ): bool {
239
        if ($visited->add($source)) {
240
            if ($source->isOrInheritsFrom($target)) {
241
                return true;
242
            }
243
244
            foreach ($source->navigationProperties() as $navProp) {
245
                if ($navProp->containsTarget() && self::typeIndirectlyContainsTarget(
246
                    $navProp->toEntityType(),
247
                    $target,
248
                    $visited,
249
                    $context
250
                )) {
251
                    return true;
252
                }
253
            }
254
255
            foreach ($context->findAllDerivedTypes($source) as $derived) {
256
                if ($derived instanceof IEntityType && self::typeIndirectlyContainsTarget(
257
                    $derived,
258
                    $target,
259
                    $visited,
260
                    $context
261
                )) {
262
                    return true;
263
                }
264
            }
265
        }
266
267
        return false;
268
    }
269
270
    /**
271
     * @param  string $fullName
272
     * @param  bool   $checkEntityContainer
273
     * @param  IModel $referenced
274
     * @return bool
275
     */
276
    protected static function checkItemReference(string $fullName, bool $checkEntityContainer, IModel $referenced): bool
277
    {
278
        if (self::checkExistsCore($referenced, $fullName, $checkEntityContainer)) {
279
            return true;
280
        }
281
        $functionList = $referenced->findDeclaredFunctions($fullName) ?? [];
282
        return 0 != count($functionList);
283
    }
284
285
    /**
286
     * @param  IFunction $function
287
     * @param  string    $functionFullName
288
     * @param  bool      $checkEntityContainer
289
     * @param  IModel    $referenced
290
     * @return bool
291
     */
292
    protected static function checkFunctionOrNameReference(
293
        IFunction $function,
294
        string $functionFullName,
295
        bool $checkEntityContainer,
296
        IModel $referenced
297
    ): bool {
298
        if (self::checkExistsCore($referenced, $functionFullName, $checkEntityContainer)) {
299
            return true;
300
        }
301
        $functionList = $referenced->findDeclaredFunctions($functionFullName) ?? [];
302
        return 0 < count(array_filter($functionList, [$function, 'IsFunctionSignatureEquivalentTo']));
303
    }
304
305
    protected static function checkExistsCore(IModel $referenced, string $fullName, bool $checkEntityContainer): bool
306
    {
307
        return $referenced->findDeclaredType($fullName) != null ||
308
               $referenced->findDeclaredValueTerm($fullName) != null ||
309
               ($checkEntityContainer && $referenced->findDeclaredEntityContainer($fullName) != null);
310
    }
311
}
312