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
|
|
|
|