Passed
Push — master ( 766bf6...808da0 )
by Marwan
07:32
created

Globals::__callStatic()

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 11
c 4
b 0
f 0
nc 4
nop 2
dl 0
loc 15
ccs 9
cts 9
cp 1
1
<?php
2
3
/**
4
 * @author Marwan Al-Soltany <[email protected]>
5
 * @copyright Marwan Al-Soltany 2021
6
 * For the full copyright and license information, please view
7
 * the LICENSE file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace MAKS\Velox\Backend;
13
14
use MAKS\Velox\Backend\Exception;
15
use MAKS\Velox\Backend\Session;
16
use MAKS\Velox\Helper\Misc;
17
18
/**
19
 * A class that serves as an abstraction/wrapper to work with superglobals.
20
 *
21
 * @package Velox\Backend
22
 * @since 1.0.0
23
 * @api
24
 *
25
 * @method static mixed getGet(string $key = null) Gets a value from $_GET. Dot-notation can be used for nested values.
26
 * @method static mixed getPost(string $key = null) Gets a value from $_POST. Dot-notation can be used for nested values.
27
 * @method static mixed getFiles(string $key = null) Gets a value from $_FILES. Dot-notation can be used for nested values.
28
 * @method static mixed getCookie(string $key = null) Gets a value from $_COOKIE. Dot-notation can be used for nested values.
29
 * @method static mixed getSession(string $key = null) Gets a value from $_SESSION. Dot-notation can be used for nested values.
30
 * @method static mixed getRequest(string $key = null) Gets a value from $_REQUEST. Dot-notation can be used for nested values.
31
 * @method static mixed getServer(string $key = null) Gets a value from $_SERVER. Dot-notation can be used for nested values.
32
 * @method static mixed getEnv(string $key = null) Gets a value from $_ENV. Dot-notation can be used for nested values.
33
 * @method static static setGet(string $key, $value) Sets a value in $_GET. Dot-notation can be used for nested values.
34
 * @method static static setPost(string $key, $value) Sets a value in $_POST. Dot-notation can be used for nested values.
35
 * @method static static setFiles(string $key, $value) Sets a value in $_FILES. Dot-notation can be used for nested values.
36
 * @method static static setCookie(string $key, $value) Sets a value in $_COOKIE. Dot-notation can be used for nested values.
37
 * @method static static setSession(string $key, $value) Sets a value in $_SESSION. Dot-notation can be used for nested values.
38
 * @method static static setRequest(string $key, $value) Sets a value in $_REQUEST. Dot-notation can be used for nested values.
39
 * @method static static setServer(string $key, $value) Sets a value in $_SERVER. Dot-notation can be used for nested values.
40
 * @method static static setEnv(string $key, $value) Sets a value in $_ENV. Dot-notation can be used for nested values.
41
 * @method static mixed cutGet(string $key = null) Cuts a value from $_GET. Dot-notation can be used for nested values.
42
 * @method static mixed cutPost(string $key = null) Cuts a value from $_POST. Dot-notation can be used for nested values.
43
 * @method static mixed cutFiles(string $key = null) Cuts a value from $_FILES. Dot-notation can be used for nested values.
44
 * @method static mixed cutCookie(string $key = null) Cuts a value from $_COOKIE. Dot-notation can be used for nested values.
45
 * @method static mixed cutSession(string $key = null) Cuts a value from $_SESSION. Dot-notation can be used for nested values.
46
 * @method static mixed cutRequest(string $key = null) Cuts a value from $_REQUEST. Dot-notation can be used for nested values.
47
 * @method static mixed cutServer(string $key = null) Cuts a value from $_SERVER. Dot-notation can be used for nested values.
48
 * @method static mixed cutEnv(string $key = null) Cuts a value from $_ENV. Dot-notation can be used for nested values.
49
 *
50
 * @property object $get A class around the superglobal `$_GET` that has the methods `has($key)`, `get($key, $default)`, `set($key, $value)`, and `getAll()`.
51
 * @property object $post A class around the superglobal `$_POST` that has the methods `has($key)`, `get($key, $default)`, `set($key, $value)`, and `getAll()`.
52
 * @property object $files A class around the superglobal `$_FILES` that has the methods `has($key)`, `get($key, $default)`, `set($key, $value)`, and `getAll()`.
53
 * @property object $cookie A class around the superglobal `$_COOKIE` that has the methods `has($key)`, `get($key, $default)`, `set($key, $value)`, and `getAll()`.
54
 * @property object $session A class around the superglobal `$_SESSION` that has the methods `has($key)`, `get($key, $default)`, `set($key, $value)`, and `getAll()`.
55
 * @property object $request A class around the superglobal `$_REQUEST` that has the methods `has($key)`, `get($key, $default)`, `set($key, $value)`, and `getAll()`.
56
 * @property object $server A class around the superglobal `$_SERVER` that has the methods `has($key)`, `get($key, $default)`, `set($key, $value)`, and `getAll()`.
57
 * @property object $env A class around the superglobal `$_ENV` that has the methods `has($key)`, `get($key, $default)`, `set($key, $value)`, and `getAll()`.
58
 */
59
final class Globals
60
{
61
    public const GLOBALS = [
62
        '_GET'     => '_GET',
63
        '_POST'    => '_POST',
64
        '_FILES'   => '_FILES',
65
        '_COOKIE'  => '_COOKIE',
66
        '_SESSION' => '_SESSION',
67
        '_REQUEST' => '_REQUEST',
68
        '_SERVER'  => '_SERVER',
69
        '_ENV'     => '_ENV',
70
    ];
71
72
73
    /**
74
     * This array holds an anonymous class that acts as a wrapper for each superglobal.
75
     */
76
    protected static array $globals;
77
78
    protected static bool $isInitialized = false;
79
80
    private static array $_GET;
81
    private static array $_POST;
82
    private static array $_FILES;
83
    private static array $_COOKIE;
84
    private static array $_SESSION;
85
    private static array $_REQUEST;
86
    private static array $_SERVER;
87
    private static array $_ENV;
88
89
90
    /**
91
     * Initializes class internal state from superglobals and returns an instance o it.
92
     *
93
     * @return static
94
     */
95 1
    public static function instance()
96
    {
97 1
        static::initialize();
98
99 1
        return new static();
100
    }
101
102
    /**
103
     * Initializes class internal state from superglobals.
104
     *
105
     * @return void
106
     */
107 59
    public static function initialize(): void
108
    {
109 59
        if (!static::$isInitialized) {
110
            // $_SESSION is a special case, unlike other superglobals which are arrays by default,
111
            // $_SESSION has the value null, we need to start it first in order to be able to reference it
112
            // otherwise we will be referencing an array that is not referencing the actual $_SESSION superglobal.
113 1
            Session::start();
114
115 1
            foreach (self::GLOBALS as $global) {
116
                global $$global;
117 1
                self::$$global = isset($$global) ? self::$$global = &$$global : [];
118
            }
119
120 1
            static::$isInitialized = true;
121
        }
122
    }
123
124
    /**
125
     * Gets a value from the specified superglobal.
126
     *
127
     * @param string $name The superglobal name to get the value from. Can be written in any case with or without the leading underscore.
128
     * @param string $key The array element to get from the superglobal. Dot-notation can be used with nested arrays.
129
     *
130
     * @return mixed
131
     *
132
     * @throws \OutOfBoundsException If the passed name is not a superglobal.
133
     */
134 30
    public static function get(string $name, string $key = null)
135
    {
136 30
        static::initialize();
137
138 30
        $name = static::getValidNameOrFail($name);
139
140 29
        if ($key !== null) {
141 28
            return Misc::getArrayValueByKey(self::$$name, $key, null);
142
        }
143
144 2
        return self::$$name;
145
    }
146
147
    /**
148
     * Sets a value in the specified superglobal.
149
     *
150
     * @param string $name The superglobal name to set the value in. Can be written in any case with or without the leading underscore.
151
     * @param string $key The array element to set in the superglobal. Dot-notation can be used with nested arrays.
152
     * @param mixed $value The value to set.
153
     *
154
     * @return static
155
     *
156
     * @throws \OutOfBoundsException If the passed name is not a superglobal.
157
     */
158 32
    public static function set(string $name, string $key, $value)
159
    {
160 32
        static::initialize();
161
162 32
        $name = static::getValidNameOrFail($name);
163
164 31
        Misc::setArrayValueByKey(self::$$name, $key, $value);
165
166 31
        return new static();
167
    }
168
169
    /**
170
     * Cuts a value from the specified superglobal. The value will be returned and the key will be unset from the superglobal.
171
     *
172
     * @param string $name The superglobal name to get the value from. Can be written in any case with or without the leading underscore.
173
     * @param string $key The array element to get from the superglobal. Dot-notation can be used with nested arrays.
174
     *
175
     * @return static
176
     *
177
     * @throws \OutOfBoundsException If the passed name is not a superglobal.
178
     */
179 17
    public static function cut(string $name, string $key)
180
    {
181 17
        static::initialize();
182
183 17
        $name = static::getValidNameOrFail($name);
184
185 17
        return Misc::cutArrayValueByKey(self::$$name, $key, null);
186
    }
187
188
    /**
189
     * Returns all superglobals.
190
     *
191
     * @return array
192
     */
193 1
    public static function getAll(): array
194
    {
195 1
        static::initialize();
196
197 1
        $globals = [];
198 1
        foreach (self::GLOBALS as $global) {
199 1
            $globals[$global] = self::$$global;
200
        }
201
202 1
        return $globals;
203
    }
204
205
    /**
206
     * Returns a valid superglobal name from the passed name.
207
     *
208
     * @param string $name
209
     *
210
     * @return string
211
     *
212
     * @throws \OutOfBoundsException
213
     */
214 48
    private static function getValidNameOrFail(string $name): string
215
    {
216 48
        $variable = '_' . trim(strtoupper($name), '_');
217
218 48
        if (!in_array($variable, self::GLOBALS)) {
219 6
            $available = implode(', ', self::GLOBALS);
220
221 6
            Exception::throw(
222
                'UnknownSuperglobalException:OutOfBoundsException',
223 6
                "There is no PHP superglobal with the name '{$name}'. Available superglobals are: [{$available}]"
224
            );
225
        }
226
227 43
        return $variable;
228
    }
229
230
231
    /**
232
     * Class constructor.
233
     */
234 53
    final public function __construct()
235
    {
236
        // the constructor is final to allow to the use of
237
        // "return new static()" without caring about class dependencies.
238
239 53
        $this->initialize();
240
    }
241
242
    /**
243
     * Aliases getters and setter for class members.
244
     */
245 1
    public function __get(string $name)
246
    {
247
        try {
248 1
            $name   = static::getValidNameOrFail($name);
249 1
            $global = &self::$$name;
250
251 1
            if (isset(static::$globals[$name])) {
252 1
                return static::$globals[$name];
253
            }
254
255 1
            return static::$globals[$name] = new class ($global) {
256
                private $self;
257
                public function __construct(&$self)
258
                {
259 1
                    $this->self = &$self;
260
                }
261
                public function has(string $key)
262
                {
263 1
                    $value = Misc::getArrayValueByKey($this->self, $key, null);
264 1
                    return isset($value);
265
                }
266
                public function get(string $key, $default = null)
267
                {
268 1
                    return Misc::getArrayValueByKey($this->self, $key, $default);
269
                }
270
                public function set(string $key, $value)
271
                {
272 1
                    Misc::setArrayValueByKey($this->self, $key, $value);
273 1
                    return $this;
274
                }
275
                public function getAll(): array
276
                {
277 1
                    return $this->self;
278
                }
279
            };
280 1
        } catch (\Exception $error) {
281 1
            Exception::throw(
282
                'UndefinedPropertyException:OutOfBoundsException',
283 1
                sprintf('Call to undefined property %s::$%s', static::class, $name),
284 1
                $error->getCode(),
285
                $error
286
            );
287
        }
288
    }
289
290
    /**
291
     * Allows static methods handled by `self::__callStatic()` to be accessible via object operator `->`.
292
     */
293 7
    public function __call(string $method, array $arguments)
294
    {
295 7
        return static::__callStatic($method, $arguments);
296
    }
297
298
    /**
299
     * Aliases getters and setter for class members.
300
     */
301 42
    public static function __callStatic(string $name, array $arguments)
302
    {
303
        try {
304 42
            if (preg_match('/^([gs]et|cut)([_]{0,1}[a-z0-9]+)$/i', $name, $matches)) {
305 42
                return forward_static_call_array(
306 42
                    [static::class, $matches[1]],
307 42
                    [static::getValidNameOrFail($matches[2]), ...$arguments]
308
                );
309
            }
310 3
        } catch (\Exception $error) {
311 3
            Exception::throw(
312
                'UndefinedMethodException:BadMethodCallException',
313 3
                sprintf('Call to undefined method %s::%s', static::class, $name),
314 3
                $error->getCode(),
315
                $error
316
            );
317
        }
318
    }
319
}
320