Prototypes::addClassStaticMethod()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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

178
    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...
179
    {
180
        throw new Exception('Adding normal method to '. static::class . ' does not make sense. Did you mean addStaticMethod?');
181
    }
182
183
}
184