Issues (2)

src/Proxy.php (1 issue)

1
<?php
2
3
namespace Mpyw\Privator;
4
5
class Proxy
6
{
7
    /**
8
     * Create anonymous proxy class of your class.
9
     *
10
     * @param  string $classname
11
     * @return mixed
12
     */
13
    public static function get(string $classname)
14 27
    {
15
        return new class($classname) implements ClassProxyInterface {
16
            private static $rc;
17
18
            /**
19
             * @param string $classname
20
             */
21
            public function __construct(string $classname)
22 27
            {
23 27
                self::$rc = new \ReflectionClass($classname);
24
            }
25
26
            /**
27
             * Call static method of your class.
28
             *
29
             * @param  string $name
30
             * @param  array  $args
31
             * @return mixed
32
             */
33 6
            public static function __callStatic(string $name, array $args)
34 6
            {
35 2
                $rc = self::$rc;
36 2
                if (\method_exists($rc->name, $name)) {
37 1
                    $rm = $rc->getMethod($name);
38
                    if (!$rm->isStatic()) {
39 1
                        throw new ProxyException(
40
                            "Non-static method called statically: " .
41 1
                            "$rc->name::$name()"
42 1
                        );
43
                    }
44 4
                    $rm->setAccessible(true);
45 4
                    return $rm->invokeArgs(null, $args);
46
                }
47
                if (\method_exists($rc->name, '__callStatic')) {
48
                    return $rc->name::$name(...$args);
49
                }
50
                throw new ProxyException(
51
                    "Call to undefined method: $rc->name::$name()"
52
                );
53 6
            }
54 6
55 4
            private static function getStaticReflectionProperty(string $name) : \ReflectionProperty
56 4
            {
57 2
                $rc = self::$rc;
58
                if (\property_exists($rc->name, $name)) {
59 2
                    $rp = $rc->getProperty($name);
60
                    if (!$rp->isStatic()) {
61 2
                        throw new ProxyException(
62 2
                            "Access to undeclared static property: " .
63
                            "$rc->name::\$$name"
64 2
                        );
65
                    }
66 2
                    $rp->setAccessible(true);
67
                    return $rp;
68
                }
69
                throw new ProxyException(
70
                    "Access to undeclared static property: " .
71
                    "$rc->name::\$$name"
72
                );
73
            }
74
75
            /**
76
             * Get static property of your class.
77
             * If you want to call your own "static function getStatic()":
78 4
             *   $proxy->__callStatic('getStatic', $args)
79
             *
80
             * @param  string $name
81
             * @return mixed
82
             */
83
            public static function getStatic(string $name)
84
            {
85
                return self::getStaticReflectionProperty($name)->getValue();
86
            }
87
88
            /**
89
             * Set static property of your class.
90 3
             * If you want to call your own "static function setStatic()":
91 1
             *   $proxy->__callStatic('setStatic', $args)
92
             *
93
             * @param string $name
94
             * @param mixed  $value
95
             */
96
            public static function setStatic(string $name, $value)
97
            {
98
                self::getStaticReflectionProperty($name)->setValue($name, $value);
99
            }
100
101
            /**
102 1
             * Create anonymous proxy object of your class.
103
             * If you want to call your own "static function new()":
104
             *   $proxy->__callStatic('new', $args)
105
             *
106
             * @param  mixed ...$args
107
             * @return mixed|InstanceProxyInterface
108
             */
109
            public static function new(...$args)
110
            {
111
                return self::newInstance($args);
112
            }
113 14
114
            /**
115
             * Create anonymous proxy object of your class without constructor.
116
             * If you want to call your own "static function newWithoutConstructor()":
117
             *   $proxy->__callStatic('newWithoutConstructor', $args)
118
             *
119
             * @return mixed|InstanceProxyInterface
120
             */
121
            public static function newWithoutConstructor()
122
            {
123 15
                return self::newInstance();
124
            }
125 15
126 15
            private static function newInstance(array $args = null)
127 1
            {
128 1
                return new class(self::$rc, $args) implements InstanceProxyInterface {
129
                    private $ro;
130 14
                    private $ins;
131 14
132
                    public function __construct(\ReflectionClass $rc, array $args = null)
133
                    {
134
                        $this->ins = $rc->newInstanceWithoutConstructor();
135
                        if ($args !== null && $con = $rc->getConstructor()) {
136
                            $con->setAccessible(true);
137
                            $con->invokeArgs($this->ins, $args);
138
                        }
139 9
                        $this->ro = new \ReflectionObject($this->ins);
140
                    }
141 9
142 3
                    /**
143 3
                     * Call instance method of your class.
144 3
                     *
145
                     * @param  string $name
146 6
                     * @param  array  $args
147 4
                     * @return mixed
148
                     */
149 2
                    public function __call(string $name, array $args)
150
                    {
151 2
                        if (\method_exists($this->ro->name, $name)) {
152
                            $rm = $this->ro->getMethod($name);
153
                            $rm->setAccessible(true);
154 8
                            return $rm->invokeArgs($this->ins, $args);
155
                        }
156 8
                        if (\method_exists($this->ro->name, '__call')) {
157 6
                            return $this->ins->$name(...$args);
158 6
                        }
159 6
                        throw new ProxyException(
160
                            "Call to undefined method: " .
161 3
                            "{$this->ro->name}::$name()"
162 3
                        );
163
                    }
164
165
                    private function getReflectionProperty(string $name)
166
                    {
167
                        if (\property_exists($this->ins, $name)) {
168
                            $rp = $this->ro->getProperty($name);
169
                            $rp->setAccessible(true);
170 8
                            return $rp;
171
                        }
172
                        throw new ProxyException(
173 8
                            "Undefined property: {$this->ro->name}::\$$name"
174 6
                        );
175 2
                    }
176
177 2
                    /**
178 1
                     * Get property of your object.
179 1
                     *
180
                     * @param  string $name
181
                     * @return mixed
182
                     */
183
                    public function __get(string $name)
184
                    {
185
                        try {
186
                            return $this->getReflectionProperty($name)
187
                                        ->getValue($this->ins);
188
                        } catch (ProxyException $e) {
189 4
                            try {
190
                                return $this->__call('__get', [$name]);
191
                            } catch (ProxyException $_) {
192 4
                                throw $e;
193 2
                            }
194 2
                        }
195
                    }
196 2
197 1
                    /**
198 1
                     * Set property of your object.
199
                     *
200 1
                     * @param  string $name
201
                     * @param  mixed  $value
202
                     */
203
                    public function __set(string $name, $value)
204 1
                    {
205
                        try {
206
                            $property = $this->getReflectionProperty($name);
207
                            $property->setValue($this->ins, $value);
208 3
                        } catch (ProxyException $e) {
209
                            try {
210
                                $this->__call('__set', [$name, $value]);
211
                                return;
212
                            } catch (ProxyException $_) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
213
                            } // If __set() is undefined,
214
                            // fallback to the actual property.
215
                            if (isset($property)) {
216
                                throw $e; // Static property exists,
217
                                          // so you cannot create a new field.
218
                            } else {
219
                                $this->ins->$name = $value; // Property does not exists
220
                                                            // so you can create a new field.
221
                            }
222
                        }
223
                    }
224
                };
225
            }
226
        };
227
    }
228
}
229