Completed
Push — master ( 08457c...216e12 )
by Ryosuke
07:50
created

Proxy.php$0 ➔ new()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
crap 2
1
<?php
2
3
namespace mpyw\Privator;
4
5
use mpyw\Privator\ProxyException;
6
7
class Proxy
8
{
9
    /**
10
     * Create anonymous proxy class of your class.
11
     * @param  string $classname
12
     * @return class@anonymous
0 ignored issues
show
Documentation introduced by
The doc-type class@anonymous could not be parsed: Unknown type name "class@anonymous" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
13
     */
14
    public static function get(string $classname)
15
    {
16
        return new class($classname)
17
        {
18
            private static $rc;
19
20
            public function __construct(string $classname)
21
            {
22
                self::$rc = new \ReflectionClass($classname);
23
            }
24
25
            /**
26
             * Call static method of your class.
27
             * @param  string $name
28
             * @param  array  $args
29
             * @return mixed
30
             */
31
            public static function __callStatic(string $name, array $args)
32
            {
33
                $rc = self::$rc;
34 View Code Duplication
                if (method_exists($rc->name, $name)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
35
                    $rm = $rc->getMethod($name);
36
                    if (!$rm->isStatic()) {
37
                        throw new ProxyException(
38
                            "Non-static method called statically: " .
39
                            "$rc->name::$name()");
40
                    }
41
                    $rm->setAccessible(true);
42
                    return $rm->invokeArgs(null, $args);
43
                }
44
                if (method_exists($rc->name, '__callStatic')) {
45
                    return $rc->name::$name(...$args);
46
                }
47
                throw new ProxyException(
48
                    "Call to undefined method: $rc->name::$name()");
49
            }
50
51
            private static function getStaticReflectionProperty(string $name) : \ReflectionProperty
52
            {
53
                $rc = self::$rc;
54 View Code Duplication
                if (property_exists($rc->name, $name)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
55
                    $rp = $rc->getProperty($name);
56
                    if (!$rp->isStatic()) {
57
                        throw new ProxyException(
58
                            "Access to undeclared static property: " .
59
                            "$rc->name::\$$name");
60
                    }
61
                    $rp->setAccessible(true);
62
                    return $rp;
63
                }
64
                throw new ProxyException(
65
                    "Access to undeclared static property: " .
66
                    "$rc->name::\$$name");
67
            }
68
69
            /**
70
             * Get static property of your class.
71
             * If you want to call your own "static function getStatic()":
72
             *   $proxy->__callStatic('getStatic', $args)
73
             * @param  string $name
74
             * @return mixed
75
             */
76
            public static function getStatic(string $name)
77
            {
78
                return self::getStaticReflectionProperty($name)->getValue();
79
            }
80
81
            /**
82
             * Set static property of your class.
83
             * If you want to call your own "static function setStatic()":
84
             *   $proxy->__callStatic('setStatic', $args)
85
             * @param string $name
86
             * @param mixed  $value
87
             */
88
            public static function setStatic(string $name, $value)
89
            {
90
                self::getStaticReflectionProperty($name)->setValue($name, $value);
91
            }
92
93
            /**
94
             * Create anonymous proxy object of your class.
95
             * If you want to call your own "static function new()":
96
             *   $proxy->__callStatic('new', $args)
97
             * @param  mixed ...$args
98
             * @return class@anonymous
0 ignored issues
show
Documentation introduced by
The doc-type class@anonymous could not be parsed: Unknown type name "class@anonymous" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
99
             */
100
            public static function new(...$args)
101
            {
102
                return self::newInstance($args);
103
            }
104
105
            /**
106
             * Create anonymous proxy object of your class without constructor.
107
             * If you want to call your own "static function newWithoutConstructor()":
108
             *   $proxy->__callStatic('newWithoutConstructor', $args)
109
             * @return class@anonymous
0 ignored issues
show
Documentation introduced by
The doc-type class@anonymous could not be parsed: Unknown type name "class@anonymous" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
110
             */
111
            public static function newWithoutConstructor()
112
            {
113
                return self::newInstance();
114
            }
115
116
            private static function newInstance(array $args = null)
117
            {
118
                return new class(self::$rc, $args)
119
                {
120
                    private $ro;
121
                    private $ins;
122
123
                    public function __construct(\ReflectionClass $rc, array $args = null)
124
                    {
125
                        $this->ins = $rc->newInstanceWithoutConstructor();
126
                        if ($args !== null && $con = $rc->getConstructor()) {
127
                            $con->setAccessible(true);
128
                            $con->invokeArgs($this->ins, $args);
129
                        }
130
                        $this->ro = new \ReflectionObject($this->ins);
131
                    }
132
133
                    /**
134
                     * Call instance method of your class.
135
                     * @param  string $name
136
                     * @param  array  $args
137
                     * @return mixed
138
                     */
139 9
                    public function __call(string $name, array $args)
140
                    {
141 9
                        if (method_exists($this->ro->name, $name)) {
142 3
                            $rm = $this->ro->getMethod($name);
143 3
                            $rm->setAccessible(true);
144 3
                            return $rm->invokeArgs($this->ins, $args);
145
                        }
146 6
                        if (method_exists($this->ro->name, '__call')) {
147 4
                            return $this->ins->$name(...$args);
148
                        }
149 2
                        throw new ProxyException(
150
                            "Call to undefined method: " .
151 2
                            "{$this->ro->name}::$name()");
152
                    }
153
154 8
                    private function getReflectionProperty(string $name)
155
                    {
156 8
                        if (property_exists($this->ins, $name)) {
157 6
                            $rp = $this->ro->getProperty($name);
158 6
                            $rp->setAccessible(true);
159 6
                            return $rp;
160
                        }
161 3
                        throw new ProxyException(
162 3
                            "Undefined property: {$this->ro->name}::\$$name");
163
                    }
164
165
                    /**
166
                     * Get property of your object.
167
                     * @param  string $name
168
                     * @return mixed
169
                     */
170 8
                    public function __get(string $name)
171
                    {
172
                        try {
173 8
                            return $this->getReflectionProperty($name)
174 6
                                        ->getValue($this->ins);
175 2
                        } catch (ProxyException $e) {
176
                            try {
177 2
                                return $this->__call('__get', [$name]);
178 1
                            } catch (ProxyException $_) {
179 1
                                throw $e;
180
                            }
181
                        }
182
                    }
183
184
                    /**
185
                     * Set property of your object.
186
                     * @param  string $name
187
                     * @param  mixed  $value
188
                     */
189
                    public function __set(string $name, $value)
190
                    {
191
                        try {
192
                            $property = $this->getReflectionProperty($name);
193
                            $property->setValue($this->ins, $value);
194
                        } catch (ProxyException $e) {
195
                            try {
196
                                $this->__call('__set', [$name, $value]);
197
                                return;
198
                            } catch (ProxyException $_) { } // If __set() is undefined,
199
                                                            // fallback to the actual property.
200
                            if (isset($property)) {
201
                                throw $e; // Static property exists,
202
                                          // so you cannot create a new field.
203
                            } else {
204
                                $this->ins->$name = $value; // Property does not exists
205
                                                            // so you can create a new field.
206
                            }
207
                        }
208
                    }
209
                };
210
            }
211
        };
212
    }
213
}
214