IsFinalizable::isOnlyInvokable()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
namespace Finalizer\Constraint;
4
5
final class IsFinalizable
6
{
7
    /**
8
     * @param \ReflectionClass $class
9
     * @param \ReflectionClass ...$definedClasses
10
     *
11
     * @return bool
12
     */
13 14
    public function __invoke(\ReflectionClass $class, \ReflectionClass ...$definedClasses)
14
    {
15 14
        return ! $class->isAbstract()
16 14
            && ! $this->hasChildClasses($class, $definedClasses)
17
            && (
18 13
                ($class->getInterfaces() && $this->implementsOnlyInterfaceMethods($class))
19 14
                || $this->isOnlyInvokable($class)
20
            );
21
    }
22
23
    /**
24
     * Checks whether a given class is an invokable, and whether no other methods are implemented on it
25
     *
26
     * @param \ReflectionClass $class
27
     *
28
     * @return bool
29
     */
30 5
    private function isOnlyInvokable(\ReflectionClass $class)
31
    {
32 5
        return ['__invoke'] === array_values(array_map('strtolower', $this->getNonConstructorMethodNames($class)))
33 5
            && ! $class->getMethod('__invoke')->isStatic();
34
    }
35
36
    /**
37
     * @param \ReflectionClass   $class
38
     * @param \ReflectionClass[] $definedClasses
39
     *
40
     * @return bool
41
     */
42 14
    private function hasChildClasses(\ReflectionClass $class, array $definedClasses)
43
    {
44 14
        return array_filter(
45 14
            $definedClasses,
46
            function (\ReflectionClass $childClassCandidate) use ($class) {
47 1
                $parentClass = $childClassCandidate->getParentClass();
48
49 1
                if (! $parentClass) {
50
                    return false;
51
                }
52
53 1
                return $parentClass->getName() === $class->getName();
54 14
            }
55
        );
56
    }
57
58
    /**
59
     * Checks whether all methods implemented by $class are defined in
60
     * interfaces implemented by $class
61
     *
62
     * @param \ReflectionClass $class
63
     *
64
     * @return bool
65
     */
66 9
    private function implementsOnlyInterfaceMethods(\ReflectionClass $class)
67
    {
68 9
        return ! array_diff(
69 9
            $this->getNonConstructorMethodNames($class),
70 9
            array_merge(
71 9
                [],
72 9
                [],
73 9
                ...array_values(array_map(
74 9
                    [$this, 'getNonConstructorMethodNames'],
75 9
                    $class->getInterfaces()
76
                ))
77
            )
78
        );
79
    }
80
81
    /**
82
     * @param \ReflectionClass $class
83
     *
84
     * @return string[] (indexed numerically)
85
     */
86 13
    private function getNonConstructorMethodNames(\ReflectionClass $class)
87
    {
88 13
        return array_values(array_map(
89
            function (\ReflectionMethod $method) {
90 7
                return $method->getName();
91 13
            },
92 13
            array_filter(
93 13
                $class->getMethods(),
94
                function (\ReflectionMethod $method) {
95 10
                    return $method->isPublic() && ! $method->isConstructor();
96 13
                }
97
            )
98
        ));
99
    }
100
}
101