Issues (126)

src/Helpers/EdmElementComparer.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AlgoWeb\ODataMetadata\Helpers;
6
7
use AlgoWeb\ODataMetadata\Enums\PrimitiveTypeKind;
8
use AlgoWeb\ODataMetadata\Enums\TypeKind;
9
use AlgoWeb\ODataMetadata\Exception\InvalidOperationException;
10
use AlgoWeb\ODataMetadata\Interfaces\IBinaryTypeReference;
11
use AlgoWeb\ODataMetadata\Interfaces\ICollectionType;
12
use AlgoWeb\ODataMetadata\Interfaces\IDecimalTypeReference;
13
use AlgoWeb\ODataMetadata\Interfaces\IEdmElement;
14
use AlgoWeb\ODataMetadata\Interfaces\IEntityReferenceType;
15
use AlgoWeb\ODataMetadata\Interfaces\IFunctionBase;
16
use AlgoWeb\ODataMetadata\Interfaces\IFunctionParameter;
17
use AlgoWeb\ODataMetadata\Interfaces\IPrimitiveType;
18
use AlgoWeb\ODataMetadata\Interfaces\IPrimitiveTypeReference;
19
use AlgoWeb\ODataMetadata\Interfaces\IRowType;
20
use AlgoWeb\ODataMetadata\Interfaces\ISchemaType;
21
use AlgoWeb\ODataMetadata\Interfaces\ISpatialTypeReference;
22
use AlgoWeb\ODataMetadata\Interfaces\IStringTypeReference;
23
use AlgoWeb\ODataMetadata\Interfaces\IStructuralProperty;
24
use AlgoWeb\ODataMetadata\Interfaces\ITemporalTypeReference;
25
use AlgoWeb\ODataMetadata\Interfaces\IType;
26
use AlgoWeb\ODataMetadata\Interfaces\ITypeReference;
27
use AlgoWeb\ODataMetadata\StringConst;
28
29
abstract class EdmElementComparer
30
{
31
    /**
32
     * Returns true if the compared type is semantically equivalent to this type.
33
     *
34
     * @param  IEdmElement|null $thisType  type being compared
35
     * @param  IEdmElement|null $otherType type being compared to
36
     * @return bool             equivalence of the two types
37
     */
38
    public static function isEquivalentTo(?IEdmElement $thisType, ?IEdmElement $otherType): bool
39
    {
40
        if (null === $thisType || null === $otherType) {
41
            return false;
42
        }
43
44
        $equivalent = true;
45
        $interfaces = class_implements($thisType);
46
        $interfaces = array_filter($interfaces, function ($value) {
47
            return false !== strpos($value, 'AlgoWeb\\ODataMetadata');
48
        });
49
50
        foreach ($interfaces as $rawInterface) {
51
            $bitz       = explode('\\', $rawInterface);
52
            $interface  = end($bitz);
53
            $methodName = 'is' . $interface . 'EquivalentTo';
54
            if (!method_exists(self::class, $methodName)) {
55
                continue;
56
            }
57
            if (!in_array($rawInterface, class_implements($otherType))) {
58
                return false;
59
            }
60
            $equivalent &= self::{$methodName}($thisType, $otherType);
61
        }
62
        return boolval($equivalent);
63
    }
64
65
    /**
66
     * Returns true if the compared type is semantically equivalent to this type.
67
     * Schema types (ISchemaType) are compared by their object refs.
68
     *
69
     * @param  IType $thisType  type being compared
70
     * @param  IType $otherType type being compared to
71
     * @return bool  equivalence of the two types
72
     */
73
    protected static function isITypeEquivalentTo(IType $thisType, IType $otherType): bool
74
    {
75
        if ($thisType === $otherType) {
76
            return true;
77
        }
78
79
        if (!$thisType->getTypeKind()->equals($otherType->getTypeKind())) {
80
            return false;
81
        }
82
83
        if (!$thisType->getTypeKind()->isAnyOf(
84
            TypeKind::Primitive(),
85
            TypeKind::Complex(),
86
            TypeKind::Entity(),
87
            TypeKind::Enum(),
88
            TypeKind::Collection(),
89
            TypeKind::EntityReference(),
90
            TypeKind::Row(),
91
            TypeKind::None()
92
        )) {
93
            throw new InvalidOperationException(
94
                StringConst::UnknownEnumVal_TypeKind($thisType->getTypeKind()->getKey())
95
            );
96
        }
97
        return true;
98
    }
99
100
    protected static function isITypeReferenceEquivalentTo(ITypeReference $thisType, ITypeReference $otherType): bool
101
    {
102
        if ($thisType === $otherType) {
103
            return true;
104
        }
105
106
        $typeKind = $thisType->typeKind();
107
        if (!$typeKind->equals($otherType->typeKind())) {
108
            return false;
109
        }
110
111
        if (!$typeKind->isPrimitive()) {
112
            return $thisType->getNullable() === $otherType->getNullable() &&
113
                self::isEquivalentTo($thisType->getDefinition(), $otherType->getDefinition());
114
        }
115
        return true;
116
    }
117
118
    /**
119
     * Returns true if function signatures are semantically equivalent.
120
     * Signature includes function name (INamedElement) and its parameter types.
121
     *
122
     * @param  IFunctionBase|null $thisFunction  reference to the calling object
123
     * @param  IFunctionBase|null $otherFunction function being compared to
124
     * @return bool               equivalence of signatures of the two functions
125
     */
126
    public static function isFunctionSignatureEquivalentTo(?IFunctionBase $thisFunction, ?IFunctionBase $otherFunction): bool
127
    {
128
        if (null === $thisFunction || null === $otherFunction) {
129
            return false;
130
        }
131
132
        if ($thisFunction === $otherFunction) {
133
            return true;
134
        }
135
136
        if ($thisFunction->getName() != $otherFunction->getName()) {
137
            return false;
138
        }
139
140
        if (!self::isEquivalentTo($thisFunction->getReturnType(), $otherFunction->getReturnType())) {
141
            return false;
142
        }
143
144
        $thisTypeKeys  = array_keys($thisFunction->getParameters());
0 ignored issues
show
It seems like $thisFunction->getParameters() can also be of type null; however, parameter $array of array_keys() does only seem to accept array, 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

144
        $thisTypeKeys  = array_keys(/** @scrutinizer ignore-type */ $thisFunction->getParameters());
Loading history...
145
        $otherTypeKeys = array_keys($otherFunction->getParameters());
146
        $keyCount      =  count($thisTypeKeys);
147
        for ($i = 0; $i < $keyCount; ++$i) {
148
            if (!self::isEquivalentTo(
149
                $thisFunction->getParameters()[$thisTypeKeys[$i]],
150
                $otherFunction->getParameters()[$otherTypeKeys[$i]]
151
            )
152
            ) {
153
                return false;
154
            }
155
        }
156
157
        return true;
158
    }
159
160
    /**
161
     * Returns true if the compared function parameter is semantically equivalent to this function parameter.
162
     *
163
     * @param  IFunctionParameter $thisParameter  reference to the calling object
164
     * @param  IFunctionParameter $otherParameter function parameter being compared to
165
     * @return bool               equivalence of the two function parameters
166
     */
167
    protected static function isIFunctionParameterEquivalentTo(IFunctionParameter $thisParameter, IFunctionParameter $otherParameter): bool
168
    {
169
        if ($thisParameter === $otherParameter) {
170
            return true;
171
        }
172
173
        return $thisParameter->getName() == $otherParameter->getName() &&
174
            $thisParameter->getMode()->equals($otherParameter->getMode()) &&
175
            self::isEquivalentTo($thisParameter->getType(), $otherParameter->getType());
176
    }
177
178
    protected static function isIPrimitiveTypeEquivalentTo(IPrimitiveType $thisType, IPrimitiveType $otherType): bool
179
    {
180
        // OdataMetadata creates one-off instances of primitive type definitions that match by name and kind, but have
181
        // different object refs. So we can't use object ref comparison here like for other ISchemaType objects.
182
        return $thisType->getPrimitiveKind() === $otherType->getPrimitiveKind() &&
183
               $thisType->fullName() === $otherType->fullName();
184
    }
185
186
    protected static function isISchemaTypeEquivalentTo(ISchemaType $thisType, ISchemaType $otherType): bool
187
    {
188
        return $thisType === $otherType;
189
    }
190
191
    protected static function isICollectionTypeEquivalentTo(ICollectionType $thisType, ICollectionType $otherType): bool
192
    {
193
        return self::isEquivalentTo($thisType->getElementType(), $otherType->getElementType());
194
    }
195
196
    protected static function isIEntityReferenceTypeEquivalentTo(IEntityReferenceType $thisType, IEntityReferenceType $otherType): bool
197
    {
198
        return self::isEquivalentTo($thisType->getEntityType(), $otherType->getEntityType());
199
    }
200
201
    protected static function isIRowTypeEquivalentTo(IRowType $thisType, IRowType $otherType): bool
202
    {
203
        if (count($thisType->getDeclaredProperties()) != count($otherType->getDeclaredProperties())) {
204
            return false;
205
        }
206
207
        $thisTypeKeys  = array_keys($thisType->getDeclaredProperties());
208
        $otherTypeKeys = array_keys($otherType->getDeclaredProperties());
209
        $keyCount      =  count($thisTypeKeys);
210
        for ($i = 0; $i < $keyCount; ++$i) {
211
            if (!self::isEquivalentTo(
212
                $thisType->getDeclaredProperties()[$thisTypeKeys[$i]],
213
                $thisType->getDeclaredProperties()[$otherTypeKeys[$i]]
214
            )
215
            ) {
216
                return false;
217
            }
218
        }
219
        return true;
220
    }
221
222
    protected static function isIStructuralPropertyEquivalentTo(IStructuralProperty $thisProp, IStructuralProperty $otherProp): bool
223
    {
224
        if ($thisProp === $otherProp) {
225
            return true;
226
        }
227
228
        return $thisProp->getName() == $otherProp->getName() &&
229
            self::isEquivalentTo($thisProp->getType(), $otherProp->getType());
230
    }
231
232
233
    protected static function isIPrimitiveTypeReferenceEquivalentTo(IPrimitiveTypeReference $thisType, IPrimitiveTypeReference $otherType): bool
234
    {
235
        $thisTypePrimitiveKind = $thisType->primitiveKind();
236
        if (!$thisTypePrimitiveKind->equals($otherType->primitiveKind())) {
237
            return false;
238
        }
239
240
        if ($thisTypePrimitiveKind->isAnyOf(
241
            PrimitiveTypeKind::Binary(),
242
            PrimitiveTypeKind::Decimal(),
243
            PrimitiveTypeKind::String(),
244
            PrimitiveTypeKind::Time(),
245
            PrimitiveTypeKind::DateTime(),
246
            PrimitiveTypeKind::DateTimeOffset()
247
        ) ||
248
            $thisTypePrimitiveKind->IsSpatial()) {
249
            return $thisType->getNullable() === $otherType->getNullable() &&
250
                self::isEquivalentTo($thisType->getDefinition(), $otherType->getDefinition());
251
        }
252
        return true;
253
    }
254
255
    protected static function isIBinaryTypeReferenceEquivalentTo(IBinaryTypeReference $thisType, IBinaryTypeReference $otherType): bool
256
    {
257
        return $thisType->getNullable() === $otherType->getNullable() &&
258
            $thisType->isFixedLength() === $otherType->isFixedLength() &&
259
            $thisType->isUnBounded() === $otherType->isUnBounded() &&
260
            $thisType->getMaxLength() === $otherType->getMaxLength();
261
    }
262
263
    protected static function isIDecimalTypeReferenceEquivalentTo(IDecimalTypeReference $thisType, IDecimalTypeReference $otherType): bool
264
    {
265
        return $thisType->getNullable() === $otherType->getNullable() &&
266
            $thisType->getPrecision() === $otherType->getPrecision() &&
267
            $thisType->getScale() === $otherType->getScale();
268
    }
269
270
    protected static function isITemporalTypeReferenceEquivalentTo(ITemporalTypeReference $thisType, ITemporalTypeReference $otherType): bool
271
    {
272
        return $thisType->typeKind()->equals($otherType->typeKind()) &&
273
            $thisType->getNullable() == $otherType->getNullable() &&
274
            $thisType->getPrecision() == $otherType->getPrecision();
275
    }
276
277
    protected static function isIStringTypeReferenceEquivalentTo(IStringTypeReference $thisType, IStringTypeReference $otherType): bool
278
    {
279
        return $thisType->getNullable() === $otherType->getNullable() &&
280
            $thisType->isFixedLength() === $otherType->isFixedLength() &&
281
            $thisType->isUnbounded() === $otherType->isUnbounded() &&
282
            $thisType->getMaxLength() === $otherType->getMaxLength() &&
283
            $thisType->isUnicode() === $otherType->isUnicode() &&
284
            $thisType->getCollation() === $otherType->getCollation();
285
    }
286
287
    protected static function isISpatialTypeReferenceEquivalentTo(ISpatialTypeReference $thisType, ISpatialTypeReference $otherType): bool
288
    {
289
        return $thisType->getNullable() === $otherType->getNullable() &&
290
            $thisType->getSpatialReferenceIdentifier() === $otherType->getSpatialReferenceIdentifier();
291
    }
292
}
293