Completed
Push — master ( b9f89d...74ba61 )
by Alex
29s queued 14s
created

isFunctionSignatureEquivalentTo()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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