Passed
Pull Request — master (#44)
by Alex
03:33
created

EdmElementComparer::isITypeReferenceEquivalentTo()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 5
eloc 9
c 2
b 1
f 0
nc 5
nop 2
dl 0
loc 16
rs 9.6111
1
<?php
2
3
declare(strict_types=1);
4
5
6
namespace AlgoWeb\ODataMetadata\Helpers;
7
8
use AlgoWeb\ODataMetadata\Enums\PrimitiveTypeKind;
9
use AlgoWeb\ODataMetadata\Enums\TypeKind;
10
use AlgoWeb\ODataMetadata\Exception\InvalidOperationException;
11
use AlgoWeb\ODataMetadata\Interfaces\IBinaryTypeReference;
12
use AlgoWeb\ODataMetadata\Interfaces\ICollectionType;
13
use AlgoWeb\ODataMetadata\Interfaces\IDecimalTypeReference;
14
use AlgoWeb\ODataMetadata\Interfaces\IEdmElement;
15
use AlgoWeb\ODataMetadata\Interfaces\IEntityReferenceType;
16
use AlgoWeb\ODataMetadata\Interfaces\IFunctionBase;
17
use AlgoWeb\ODataMetadata\Interfaces\IFunctionParameter;
18
use AlgoWeb\ODataMetadata\Interfaces\IPrimitiveType;
19
use AlgoWeb\ODataMetadata\Interfaces\IPrimitiveTypeReference;
20
use AlgoWeb\ODataMetadata\Interfaces\IRowType;
21
use AlgoWeb\ODataMetadata\Interfaces\ISchemaType;
22
use AlgoWeb\ODataMetadata\Interfaces\ISpatialTypeReference;
23
use AlgoWeb\ODataMetadata\Interfaces\IStringTypeReference;
24
use AlgoWeb\ODataMetadata\Interfaces\IStructuralProperty;
25
use AlgoWeb\ODataMetadata\Interfaces\ITemporalTypeReference;
26
use AlgoWeb\ODataMetadata\Interfaces\IType;
27
use AlgoWeb\ODataMetadata\Interfaces\ITypeReference;
28
use AlgoWeb\ODataMetadata\StringConst;
29
30
abstract class EdmElementComparer
31
{
32
    /**
33
     * Returns true if the compared type is semantically equivalent to this type.
34
     *
35
     * @param  IEdmElement|null $thisType  type being compared
36
     * @param  IEdmElement|null $otherType type being compared to
37
     * @return bool             equivalence of the two types
38
     */
39
    public static function isEquivalentTo(?IEdmElement $thisType, ?IEdmElement $otherType): bool
40
    {
41
        if (null === $thisType || null === $otherType) {
42
            return false;
43
        }
44
45
        $equivalent = true;
46
        $interfaces = class_implements($thisType);
47
        $interfaces = array_filter($interfaces, function ($value) {
48
            return false !== strpos($value, 'AlgoWeb\\ODataMetadata');
49
        });
50
51
        foreach ($interfaces as $rawInterface) {
52
            $bitz       = explode('\\', $rawInterface);
53
            $interface  = end($bitz);
54
            $methodName = 'is' . $interface . 'EquivalentTo';
55
            if (!method_exists(self::class, $methodName)) {
56
                continue;
57
            }
58
            if (!in_array($rawInterface, class_implements($otherType))) {
59
                return false;
60
            }
61
            $equivalent &= self::{$methodName}($thisType, $otherType);
62
        }
63
        return boolval($equivalent);
64
    }
65
66
    /**
67
     * Returns true if the compared type is semantically equivalent to this type.
68
     * Schema types (ISchemaType) are compared by their object refs.
69
     *
70
     * @param  IType $thisType  type being compared
71
     * @param  IType $otherType type being compared to
72
     * @return bool  equivalence of the two types
73
     */
74
    protected static function isITypeEquivalentTo(IType $thisType, IType $otherType): bool
75
    {
76
        if ($thisType === $otherType) {
77
            return true;
78
        }
79
80
        if (!$thisType->getTypeKind()->equals($otherType->getTypeKind())) {
81
            return false;
82
        }
83
84
        if (!$thisType->getTypeKind()->isAnyOf(
85
            TypeKind::Primitive(),
86
            TypeKind::Complex(),
87
            TypeKind::Entity(),
88
            TypeKind::Enum(),
89
            TypeKind::Collection(),
90
            TypeKind::EntityReference(),
91
            TypeKind::Row(),
92
            TypeKind::None()
93
        )) {
94
            throw new InvalidOperationException(
95
                StringConst::UnknownEnumVal_TypeKind($thisType->getTypeKind()->getKey())
0 ignored issues
show
Bug introduced by
It seems like $thisType->getTypeKind()->getKey() can also be of type false; however, parameter $typeKind of AlgoWeb\ODataMetadata\St...knownEnumVal_TypeKind() does only seem to accept 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

95
                StringConst::UnknownEnumVal_TypeKind(/** @scrutinizer ignore-type */ $thisType->getTypeKind()->getKey())
Loading history...
96
            );
97
        }
98
        return true;
99
    }
100
101
    protected static function isITypeReferenceEquivalentTo(ITypeReference $thisType, ITypeReference $otherType): bool
102
    {
103
        if ($thisType === $otherType) {
104
            return true;
105
        }
106
107
        $typeKind = $thisType->TypeKind();
108
        if (!$typeKind->equals($otherType->TypeKind())) {
109
            return false;
110
        }
111
112
        if (!$typeKind->isPrimitive()) {
113
            return $thisType->getNullable() === $otherType->getNullable() &&
114
                self::isEquivalentTo($thisType->getDefinition(), $otherType->getDefinition());
115
        }
116
        return true;
117
    }
118
119
    /**
120
     * Returns true if function signatures are semantically equivalent.
121
     * Signature includes function name (INamedElement) and its parameter types.
122
     *
123
     * @param  IFunctionBase|null $thisFunction  reference to the calling object
124
     * @param  IFunctionBase|null $otherFunction function being compared to
125
     * @return bool               equivalence of signatures of the two functions
126
     */
127
    public static function isFunctionSignatureEquivalentTo(?IFunctionBase $thisFunction, ?IFunctionBase $otherFunction): bool
128
    {
129
        if (null === $thisFunction || null === $otherFunction) {
130
            return false;
131
        }
132
133
        if ($thisFunction === $otherFunction) {
134
            return true;
135
        }
136
137
        if ($thisFunction->getName() != $otherFunction->getName()) {
138
            return false;
139
        }
140
141
        if (!self::isEquivalentTo($thisFunction->getReturnType(), $otherFunction->getReturnType())) {
142
            return false;
143
        }
144
145
        $thisTypeKeys  = array_keys($thisFunction->getParameters());
0 ignored issues
show
Bug introduced by
It seems like $thisFunction->getParameters() can also be of type null; however, parameter $input 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

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