Issues (126)

src/Asserts.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AlgoWeb\ODataMetadata;
6
7
use AlgoWeb\ODataMetadata\Exception\ArgumentException;
8
use ReflectionException;
9
use ReflectionFunction;
10
use ReflectionFunctionAbstract;
11
use ReflectionMethod;
12
use ReflectionNamedType;
13
14
abstract class Asserts
15
{
16
    private static function performAsserts(): bool
17
    {
18
        return assert_options(ASSERT_ACTIVE) === 1;
19
    }
20
21
    public static function assertSignatureMatches(callable $expected, callable $actual, string $callablePropName): bool
22
    {
23
        if (!self::performAsserts()) {
24
            return true;
25
        }
26
        $expectedReflection = self::getReflection($expected);
27
        $actualReflection   = self::getReflection($actual);
28
        $messageBuilder     = function (string $messagePrefix = '') use ($callablePropName, $expectedReflection, $actualReflection): string {
29
            return sprintf(
30
                '%s%s should be a callable with signature %s, however callable with signature %s provided',
31
                empty($messagePrefix) ? $messagePrefix : trim($messagePrefix) . ' ',
32
                $callablePropName,
33
                self::getSignatureFromReflection($expectedReflection),
34
                self::getSignatureFromReflection($actualReflection)
35
            );
36
        };
37
        assert(
38
            $expectedReflection->getNumberOfRequiredParameters() ===  $actualReflection->getNumberOfRequiredParameters(),
39
            $messageBuilder('Incorrect Parameter Count')
40
        );
41
        if ($expectedReflection->hasReturnType()) {
42
            assert(
43
                $actualReflection->hasReturnType(),
44
                $messageBuilder('Missing Return Type')
45
            );
46
            //TODO: improve this to check that the actual type does not return a childType;
47
            $expectedReturnType = $expectedReflection->getReturnType();
48
            $actualReturnType   = $actualReflection->getReturnType();
49
            $name               = $expectedReturnType instanceof ReflectionNamedType ?
50
                $expectedReturnType->getName() :
51
                strval($expectedReturnType);
52
            $actName = $actualReturnType instanceof ReflectionNamedType ?
53
                $actualReturnType->getName() :
54
                strval($actualReturnType);
55
56
            assert(
57
                $name === $actName,
58
                $messageBuilder('IncorrectOrInvalid ReturnType')
59
            );
60
            if (!$expectedReturnType->allowsNull()) {
61
                assert(
62
                    !$actualReturnType->allowsNull(),
63
                    $messageBuilder('Nullable ReturnType Not allowed')
64
                );
65
            }
66
        }
67
68
        for ($i = 0; $i < $expectedReflection->getNumberOfParameters(); $i++) {
69
            $expectedParm = $expectedReflection->getParameters()[$i];
70
            if ($expectedParm->hasType()) {
71
                $actualParm = $actualReflection->getParameters()[$i];
72
                assert(
73
                    $actualParm->hasType(),
74
                    $messageBuilder(sprintf('Parameter %s Is missing TypeHint', $i))
75
                );
76
                $expectedParmType = $expectedParm->getType();
77
                $actualParmType   = $actualParm->getType();
78
                $name             = $expectedParmType instanceof ReflectionNamedType ?
79
                    $expectedParmType->getName() :
80
                    strval($expectedParmType);
81
                $actName = $actualParmType instanceof ReflectionNamedType ?
82
                    $actualParmType->getName() :
83
                    strval($actualParmType);
84
85
                //TODO: improve this to check that the actual type does not return a childType;
86
                assert(
87
                    $name === $actName,
88
                    $messageBuilder(sprintf('Parameter %s has Incorrect Type', $i))
89
                );
90
                if (!$expectedParm->allowsNull()) {
91
                    assert(
92
                        !$actualParm->allowsNull(),
93
                        $messageBuilder(sprintf('Parameter %s should disallow Nulls', $i))
94
                    );
95
                }
96
            }
97
        }
98
        return true;
99
    }
100
101
    private static function getSignatureFromReflection(ReflectionFunctionAbstract $reflection): string
102
    {
103
        $parameters = [];
104
        foreach ($reflection->getParameters() as $parameter) {
105
            $parameterString = '';
106
            if ($parameter->hasType()) {
107
                $parmType        = $parameter->getType();
108
                $parmName        = $parmType instanceof ReflectionNamedType ?
109
                                    $parmType->getName() :
110
                                    strval($parmType);
111
                $parameterString .= $parmType->allowsNull() ? '?' : '';
112
                $parameterString .= $parmName . ' ';
113
            }
114
            $parameterString .= $parameter->isVariadic() ? '...$' : '$';
115
            $parameterString .= $parameter->getName();
116
            if ($parameter->isOptional()) {
117
                try {
118
                    $parameterString .= ' = ' . strval($parameter->getDefaultValue());
119
                } catch (ReflectionException $e) {
120
                    // Keep on trucking
121
                }
122
            }
123
            $parameters[] = $parameterString;
124
        }
125
        $return = '';
126
        if ($reflection->hasReturnType()) {
127
            $returnType = $reflection->getReturnType();
128
            $name       = $returnType instanceof ReflectionNamedType ?
129
                $returnType->getName() :
130
                strval($returnType);
131
            $return .= ': ' . $name;
132
        }
133
        return sprintf('function(%s)%s', implode(',', $parameters), $return);
134
    }
135
136
    private static function getReflection(callable $method): ReflectionFunctionAbstract
137
    {
138
        try {
139
            return is_array($method) ? new ReflectionMethod(...$method) : new ReflectionFunction($method);
0 ignored issues
show
$method is expanded, but the parameter $objectOrMethod of ReflectionMethod::__construct() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

139
            return is_array($method) ? new ReflectionMethod(/** @scrutinizer ignore-type */ ...$method) : new ReflectionFunction($method);
Loading history...
140
        } catch (ReflectionException $e) {
141
            throw new ArgumentException($e->getMessage());
142
        }
143
    }
144
}
145