Passed
Push — master ( e92a23...70bf0a )
by Marwan
08:52
created

App::__get()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 2
c 2
b 0
f 0
nc 1
nop 1
dl 0
loc 5
ccs 3
cts 3
cp 1
cc 1
crap 1
rs 10
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;
13
14
use MAKS\Velox\Backend\Config;
15
use MAKS\Velox\Backend\Router;
16
use MAKS\Velox\Backend\Globals;
17
use MAKS\Velox\Frontend\Data;
18
use MAKS\Velox\Frontend\View;
19
use MAKS\Velox\Frontend\HTML;
20
use MAKS\Velox\Frontend\Path;
21
use MAKS\Velox\Helper\Dumper;
22
use MAKS\Velox\Helper\Misc;
23
24
/**
25
 * A class that serves as a basic service-container for VELOX.
26
 * This class has all VELOX classes as public properties:
27
 *      - `$config`  = `Config::class`
28
 *      - `$router`  = `Router::class`
29
 *      - `$globals` = `Globals::class`
30
 *      - `$data`    = `Data::class`
31
 *      - `$view`    = `View::class`
32
 *      - `$html`    = `HTML::class`
33
 *      - `$path`    = `Path::class`
34
 *      - `$dumper`  = `Dumper::class`
35
 *      - `$misc`    = `Misc::class`
36
 *
37
 * Example:
38
 * ```
39
 * // create an instance
40
 * $app = new App();
41
 * // get an instance of the `Router` class via public property access notation
42
 * $app->router->handle('/dump', 'dd');
43
 * // or via calling a method with the same name
44
 * $app->router()->handle('/dump', 'dd');
45
 * ```
46
 *
47
 * @since 1.0.0
48
 */
49
class App
50
{
51
    public Config $config;
52
53
    public Router $router;
54
55
    public Globals $globals;
56
57
    public Data $data;
58
59
    public View $view;
60
61
    public HTML $html;
62
63
    public Path $path;
64
65
    public Dumper $dumper;
66
67
    public Misc $misc;
68
69
    protected array $methods;
70
71
    protected static array $staticMethods;
72
73
74
    /**
75
     * Class constructor.
76
     */
77 7
    public function __construct()
78
    {
79 7
        $this->config  = new Config();
80 7
        $this->router  = new Router();
81 7
        $this->globals = new Globals();
82 7
        $this->data    = new Data();
83 7
        $this->view    = new View();
84 7
        $this->html    = new HTML();
85 7
        $this->path    = new Path();
86 7
        $this->dumper  = new Dumper();
87 7
        $this->misc    = new Misc();
88 7
        $this->methods = [];
89 7
    }
90
91 2
    public function __get(string $property)
92
    {
93 2
        $class = static::class;
94
95 2
        throw new \Exception("Call to undefined property {$class}::${$property}");
96
    }
97
98 3
    public function __call(string $method, array $arguments)
99
    {
100 3
        $class = static::class;
101
102
        try {
103 3
            return isset($this->methods[$method]) ? $this->methods[$method](...$arguments) : $this->{$method};
104 1
        } catch (\Exception $error) {
105 1
            throw new \Exception(
106 1
                "Call to undefined method {$class}::{$method}()",
107 1
                (int)$error->getCode(),
108
                $error
109
            );
110
        }
111
    }
112
113 2
    public static function __callStatic(string $method, array $arguments)
114
    {
115 2
        $class = static::class;
116
117 2
        if (!isset(static::$staticMethods[$method])) {
118 1
            throw new \Exception("Call to undefined static method {$class}::{$method}()");
119
        }
120
121 1
        return static::$staticMethods[$method](...$arguments);
122
    }
123
124
125
    /**
126
     * Extends the class using the passed callback.
127
     *
128
     * @param string $name Method name.
129
     * @param callable $callback The callback to use as method body.
130
     *
131
     * @return callable The created bound closure.
132
     */
133 1
    public function extend(string $name, callable $callback): callable
134
    {
135 1
        $method = \Closure::fromCallable($callback);
136 1
        $method = \Closure::bind($method, $this, $this);
137
138 1
        return $this->methods[$name] = $method;
139
    }
140
141
    /**
142
     * Extends the class using the passed callback.
143
     *
144
     * @param string $name Method name.
145
     * @param callable $callback The callback to use as method body.
146
     *
147
     * @return callable The created closure.
148
     */
149 1
    public static function extendStatic(string $name, callable $callback): callable
150
    {
151 1
        $method = \Closure::fromCallable($callback);
152 1
        $method = \Closure::bind($method, null, static::class);
153
154 1
        return static::$staticMethods[$name] = $method;
155
    }
156
157
158
    /**
159
     * Logs a message to a file and generates it if it does not exist.
160
     *
161
     * @param string $message The message wished to be logged.
162
     * @param array|null $context An associative array of values where array key = {key} in the message (context).
163
     * @param string|null $filename [optional] The name wished to be given to the file. If not provided `{global.logging.defaultFilename}` will be used instead.
164
     * @param string|null $directory [optional] The directory where the log file should be written. If not provided `{global.logging.defaultDirectory}` will be used instead.
165
     *
166
     * @return bool True on success (if the message was written).
167
     */
168 4
    public static function log(string $message, ?array $context = [], ?string $filename = null, ?string $directory = null): bool
169
    {
170 4
        if (!Config::get('global.logging.enabled', true)) {
171 1
            return true;
172
        }
173
174 4
        $hasPassed = false;
175
176 4
        if (!$filename) {
177 1
            $filename = Config::get('global.logging.defaultFilename', sprintf('autogenerated-%s', date('Ymd')));
178
        }
179
180 4
        if (!$directory) {
181 4
            $directory = Config::get('global.logging.defaultDirectory', BASE_PATH);
182
        }
183
184 4
        $file = Path::normalize($directory, $filename, '.log');
185
186 4
        if (!file_exists($directory)) {
187 1
            mkdir($directory, 0744, true);
188
        }
189
190
        // create log file if it does not exist
191 4
        if (!is_file($file) && is_writable($directory)) {
192 2
            $signature = 'Created by ' . __METHOD__ . date('() \o\\n l jS \of F Y h:i:s A (Ymdhis)') . PHP_EOL . PHP_EOL;
193 2
            file_put_contents($file, $signature, 0);
194 2
            chmod($file, 0775);
195
        }
196
197
        // write in the log file
198 4
        if (is_writable($file)) {
199 4
            clearstatcache(true, $file);
200
            // empty the file if it exceeds the configured file size
201 4
            $maxFileSize = Config::get('global.logging.maxFileSize', 6.4e+7);
202 4
            if (filesize($file) > $maxFileSize) {
203 1
                $stream = fopen($file, 'r');
204 1
                if (is_resource($stream)) {
205 1
                    $signature = fgets($stream) . 'For exceeding the configured {global.logging.maxFileSize}, it was overwritten on ' . date('l jS \of F Y h:i:s A (Ymdhis)') . PHP_EOL . PHP_EOL;
206 1
                    fclose($stream);
207 1
                    file_put_contents($file, $signature, 0);
208 1
                    chmod($file, 0775);
209
                }
210
            }
211
212 4
            $timestamp = (new \DateTime())->format(DATE_ISO8601);
213 4
            $message   = Misc::interpolate($message, $context ?? []);
214
215 4
            $log = "$timestamp\t$message\n";
216
217 4
            $stream = fopen($file, 'a+');
218 4
            if (is_resource($stream)) {
219 4
                fwrite($stream, $log);
220 4
                fclose($stream);
221 4
                $hasPassed = true;
222
            }
223
        }
224
225 4
        return $hasPassed;
226
    }
227
}
228