Test Setup Failed
Branch master (17e5cc)
by Gaetano
01:56
created

Prototypes::callStatic()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 1
Metric Value
cc 2
eloc 6
c 1
b 1
f 1
nc 2
nop 3
dl 0
loc 9
rs 10
1
<?php
2
3
namespace skrtdev\Prototypes;
4
5
use Closure, Error;
6
7
class Prototypes {
8
9
    use Prototypeable;
10
11
    /**
12
     * @var array[]
13
     */
14
    protected static array $methods = [];
15
16
    /**
17
     * @var Closure[]
18
     */
19
    protected static array $static_methods = [];
20
21
    /**
22
     * @var Closure[]
23
     */
24
    protected static array $classes = [];
25
26
27
    /**
28
     * @param string $class_name
29
     * @param string $name
30
     * @param Closure $fun
31
     * @throws Exception
32
     */
33
    public static function addClassMethod(string $class_name, string $name, Closure $fun): void
34
    {
35
        if(self::isPrototypeable($class_name)){
36
            if(!method_exists($class_name, $name)){
37
                self::$methods[$class_name] ??= [];
38
                if(!self::classHasPrototypeMethod($class_name, $name)){
39
                    self::$methods[$class_name][$name] = $fun;
40
                }
41
                else{
42
                    throw new Exception("Invalid method name provided for class '$class_name': method '$name' is already a Prototype");
43
                }
44
            }
45
            else{
46
                throw new Exception("Invalid method name provided for class '$class_name': method '$name' already exists");
47
            }
48
        }
49
        else{
50
            throw new Exception("Invalid class provided: class '$class_name' is not Prototypeable");
51
        }
52
    }
53
54
    /**
55
     * @param string $class_name
56
     * @param string $name
57
     * @param Closure $fun
58
     * @throws Exception
59
     */
60
    public static function addClassStaticMethod(string $class_name, string $name, Closure $fun): void
61
    {
62
        if(self::isPrototypeable($class_name)){
63
            if(!method_exists($class_name, $name)){
64
                self::$static_methods[$class_name] ??= [];
65
                if(!self::classHasPrototypeMethod($class_name, $name)){
66
                    self::$static_methods[$class_name][$name] = $fun;
67
                }
68
                else{
69
                    throw new Exception("Invalid method name provided for class '$class_name': method '$name' is already a Prototype");
70
                }
71
            }
72
            else{
73
                throw new Exception("Invalid method name provided for class '$class_name': method '$name' already exists");
74
            }
75
        }
76
        else{
77
            throw new Exception("Invalid class provided: class '$class_name' is not Prototypeable");
78
        }
79
    }
80
81
    /**
82
     * @param object $obj
83
     * @param string $name
84
     * @param array $args
85
     * @return mixed
86
     */
87
    public static function call(object $obj, string $name, array $args)
88
    {
89
        $class_name = get_class($obj);
90
        self::$methods[$class_name] ??= [];
91
        if(isset(self::$methods[$class_name][$name])){
92
            $closure = self::$methods[$class_name][$name];
93
            return $closure->call($obj, ...$args);
94
        }
95
        else{
96
            throw new Error("Call to undefined method $class_name::$name()");
97
        }
98
    }
99
100
    /**
101
     * @param string $class_name
102
     * @param string $name
103
     * @param array $args
104
     * @return mixed
105
     */
106
    public static function callStatic(string $class_name, string $name, array $args)
107
    {
108
        self::$static_methods[$class_name] ??= [];
109
        if(isset(self::$static_methods[$class_name][$name])){
110
            $closure = self::$static_methods[$class_name][$name];
111
            return ($closure->bindTo(null, $class_name))(...$args);
112
        }
113
        else{
114
            throw new Error("Call to undefined static method $class_name::$name()");
115
        }
116
    }
117
118
    /**
119
     * @param string $class_name
120
     * @return bool
121
     * @throws Exception
122
     */
123
    public static function isPrototypeable(string $class_name): bool
124
    {
125
        return self::$classes[$class_name] ??= in_array(Prototypeable::class, self::getClassTraits($class_name));
126
    }
127
128
    /**
129
     * @param string $class_name
130
     * @param string $method_name
131
     * @return bool
132
     */
133
    public static function classHasPrototypeMethod(string $class_name, string $method_name): bool
134
    {
135
        return isset(self::$methods[$class_name][$method_name]) || isset(self::$static_methods[$class_name][$method_name]);
136
    }
137
138
    /**
139
     * @param string $class
140
     * @return array
141
     * @throws Exception
142
     */
143
    protected static function getClassTraits(string $class): array
144
    {
145
        if(!class_exists($class)){
146
            throw new Exception("Class $class does not exist");
147
        }
148
        $traits = [];
149
        do {
150
            $traits = array_merge(class_uses($class), $traits);
151
        }
152
        while($class = get_parent_class($class));
153
154
        foreach ($traits as $trait) {
155
            $traits = array_merge(class_uses($trait), $traits);
156
        }
157
158
        return array_unique(array_values($traits));
159
    }
160
161
162
    /**
163
     * Prototypes constructor that disallow instantiation
164
     * @throws Exception
165
     */
166
    public function __construct()
167
    {
168
        throw new Exception(static::class.' class can not be instantiated');
169
    }
170
171
    /**
172
     * @param mixed ...$_
173
     * @throws Exception
174
     */
175
    final public static function addMethod(...$_): void
0 ignored issues
show
Unused Code introduced by
The parameter $_ is not used and could be removed. ( Ignorable by Annotation )

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

175
    final public static function addMethod(/** @scrutinizer ignore-unused */ ...$_): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
176
    {
177
        throw new Exception('Adding normal method to '. static::class . ' does not make sense. Did you mean addStaticMethod?');
178
    }
179
180
}
181