1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Charcoal\App; |
4
|
|
|
|
5
|
|
|
// PHP Dependencies |
6
|
|
|
use Exception; |
7
|
|
|
use LogicException; |
8
|
|
|
use RuntimeException; |
9
|
|
|
|
10
|
|
|
// Dependency from 'Slim' |
11
|
|
|
use Slim\App as SlimApp; |
12
|
|
|
use Slim\Exception\ContainerValueNotFoundException; |
13
|
|
|
|
14
|
|
|
// Dependencies from 'PSR-3' (Logging) |
15
|
|
|
use Psr\Log\LoggerAwareInterface; |
16
|
|
|
use Psr\Log\LoggerAwareTrait; |
17
|
|
|
|
18
|
|
|
// Dependencies from 'PSR-7' (HTTP Messaging) |
19
|
|
|
use Psr\Http\Message\RequestInterface; |
20
|
|
|
use Psr\Http\Message\ResponseInterface; |
21
|
|
|
|
22
|
|
|
// Dependencies from 'charcoal-config' |
23
|
|
|
use Charcoal\Config\ConfigurableInterface; |
24
|
|
|
use Charcoal\Config\ConfigurableTrait; |
25
|
|
|
|
26
|
|
|
// Local namespace dependencies |
27
|
|
|
use Charcoal\App\AppConfig; |
28
|
|
|
use Charcoal\App\AppContainer; |
29
|
|
|
use Charcoal\App\Module\ModuleManager; |
30
|
|
|
use Charcoal\App\Route\RouteManager; |
31
|
|
|
use Charcoal\App\Route\RouteFactory; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* Charcoal App |
35
|
|
|
* |
36
|
|
|
* This is the primary class with which you instantiate, configure, |
37
|
|
|
* and run a Slim Framework application within Charcoal. |
38
|
|
|
*/ |
39
|
|
|
class App extends SlimApp implements |
40
|
|
|
ConfigurableInterface |
41
|
|
|
{ |
42
|
|
|
use ConfigurableTrait; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* Store the unique instance |
46
|
|
|
* |
47
|
|
|
* @var App $instance |
48
|
|
|
*/ |
49
|
|
|
protected static $instance; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* @var ModuleManager |
53
|
|
|
*/ |
54
|
|
|
private $moduleManager; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* @var RouteManager |
58
|
|
|
*/ |
59
|
|
|
private $routeManager; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* Getter for creating/returning the unique instance of this class. |
63
|
|
|
* |
64
|
|
|
* @param Container|array $container The application's settings. |
65
|
|
|
* @return self |
66
|
|
|
*/ |
67
|
|
|
public static function instance($container = []) |
68
|
|
|
{ |
69
|
|
|
if (!isset(static::$instance)) { |
70
|
|
|
$called_class = get_called_class(); |
71
|
|
|
|
72
|
|
|
static::$instance = new $called_class($container); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
return static::$instance; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Create new Charcoal application (and SlimApp). |
80
|
|
|
* |
81
|
|
|
* ### Dependencies |
82
|
|
|
* |
83
|
|
|
* **Required** |
84
|
|
|
* |
85
|
|
|
* - `config` — AppConfig |
86
|
|
|
* |
87
|
|
|
* **Optional** |
88
|
|
|
* |
89
|
|
|
* - `logger` — PSR-3 Logger |
90
|
|
|
* |
91
|
|
|
* @uses SlimApp::__construct() |
92
|
|
|
* @param ContainerInterface|array $container The application's settings. |
93
|
|
|
* @throws LogicException If trying to create a new instance of a singleton. |
94
|
|
|
*/ |
95
|
|
|
public function __construct($container = []) |
96
|
|
|
{ |
97
|
|
|
if (isset(static::$instance)) { |
98
|
|
|
throw new LogicException( |
99
|
|
|
sprintf( |
100
|
|
|
'"%s" is a singleton. Use static instance() method.', |
101
|
|
|
get_called_class() |
102
|
|
|
) |
103
|
|
|
); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
// Ensure the DI container is |
107
|
|
|
if (is_array($container)) { |
108
|
|
|
$container = new AppContainer($container); |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
// SlimApp constructor |
112
|
|
|
parent::__construct($container); |
113
|
|
|
|
114
|
|
|
if (isset($container['config'])) { |
115
|
|
|
$this->setConfig($container['config']); |
116
|
|
|
} |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Run application. |
121
|
|
|
* |
122
|
|
|
* Initialize the Charcoal application before running (with SlimApp). |
123
|
|
|
* |
124
|
|
|
* @uses self::setup() |
125
|
|
|
* @param boolean $silent If true, will run in silent mode (no response). |
126
|
|
|
* @return ResponseInterface The PSR7 HTTP response. |
127
|
|
|
*/ |
128
|
|
|
public function run($silent = false) |
129
|
|
|
{ |
130
|
|
|
$this->setup(); |
131
|
|
|
|
132
|
|
|
return parent::run($silent); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* @throws LogicException If trying to clone an instance of a singleton. |
137
|
|
|
* @return void |
138
|
|
|
*/ |
139
|
|
|
final private function __clone() |
140
|
|
|
{ |
141
|
|
|
throw new LogicException( |
142
|
|
|
sprintf( |
143
|
|
|
'Cloning "%s" is not allowed.', |
144
|
|
|
get_called_class() |
145
|
|
|
) |
146
|
|
|
); |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* @throws LogicException If trying to unserialize an instance of a singleton. |
151
|
|
|
* @return void |
152
|
|
|
*/ |
153
|
|
|
final private function __wakeup() |
154
|
|
|
{ |
155
|
|
|
throw new LogicException( |
156
|
|
|
sprintf( |
157
|
|
|
'Unserializing "%s" is not allowed.', |
158
|
|
|
get_called_class() |
159
|
|
|
) |
160
|
|
|
); |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Retrieve the application's module manager. |
165
|
|
|
* |
166
|
|
|
* @return ModuleManager |
167
|
|
|
*/ |
168
|
|
|
protected function moduleManager() |
169
|
|
|
{ |
170
|
|
|
if (!isset($this->moduleManager)) { |
171
|
|
|
$config = $this->config(); |
172
|
|
|
$modules = (isset($config['modules']) ? $config['modules'] : [] ); |
173
|
|
|
|
174
|
|
|
$container = $this->getContainer(); |
175
|
|
|
$this->moduleManager = new ModuleManager([ |
176
|
|
|
'config' => $modules, |
177
|
|
|
'app' => $this, |
178
|
|
|
'logger' => $container['logger'], |
179
|
|
|
'module_factory' => $container['module/factory'] |
180
|
|
|
]); |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
return $this->moduleManager; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* Retrieve the application's route manager. |
188
|
|
|
* |
189
|
|
|
* @return RouteManager |
190
|
|
|
*/ |
191
|
|
|
protected function routeManager() |
192
|
|
|
{ |
193
|
|
|
if (!isset($this->routeManager)) { |
194
|
|
|
$config = $this->config(); |
195
|
|
|
$routes = (isset($config['routes']) ? $config['routes'] : [] ); |
196
|
|
|
|
197
|
|
|
$this->routeManager = new RouteManager([ |
198
|
|
|
'config' => $routes, |
199
|
|
|
'app' => $this |
200
|
|
|
]); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
return $this->routeManager; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Registers the default services and features that Charcoal needs to work. |
208
|
|
|
* |
209
|
|
|
* @return void |
210
|
|
|
*/ |
211
|
|
|
private function setup() |
212
|
|
|
{ |
213
|
|
|
$config = $this->config(); |
214
|
|
|
date_default_timezone_set($config['timezone']); |
215
|
|
|
|
216
|
|
|
// Setup routes |
217
|
|
|
$this->routeManager()->setupRoutes(); |
218
|
|
|
|
219
|
|
|
// Setup modules |
220
|
|
|
$this->moduleManager()->setupModules($this); |
|
|
|
|
221
|
|
|
|
222
|
|
|
// Setup routable |
223
|
|
|
$this->setupRoutables(); |
224
|
|
|
|
225
|
|
|
// Setup middleware |
226
|
|
|
$this->setupMiddleware(); |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* Setup the application's "global" routables. |
232
|
|
|
* |
233
|
|
|
* Routables can only be defined globally (app-level) for now. |
234
|
|
|
* |
235
|
|
|
* @return void |
236
|
|
|
*/ |
237
|
|
|
protected function setupRoutables() |
238
|
|
|
{ |
239
|
|
|
$app = $this; |
240
|
|
|
// For now, need to rely on a catch-all... |
241
|
|
|
$this->get( |
242
|
|
|
'{catchall:.*}', |
243
|
|
|
function ( |
244
|
|
|
RequestInterface $request, |
245
|
|
|
ResponseInterface $response, |
246
|
|
|
array $args |
247
|
|
|
) use ($app) { |
248
|
|
|
$config = $app->config(); |
249
|
|
|
$routables = $config['routables']; |
250
|
|
|
if ($routables === null || count($routables) === 0) { |
251
|
|
|
return $this['notFoundHandler']($request, $response); |
252
|
|
|
} |
253
|
|
|
$routeFactory = $this['route/factory']; |
254
|
|
|
foreach ($routables as $routableType => $routableOptions) { |
255
|
|
|
$route = $routeFactory->create($routableType, [ |
256
|
|
|
'path' => $args['catchall'], |
257
|
|
|
'config' => $routableOptions |
258
|
|
|
]); |
259
|
|
|
if ($route->pathResolvable($this)) { |
260
|
|
|
$this['logger']->debug( |
261
|
|
|
sprintf('Loaded routable "%s" for path %s', $routableType, $args['catchall']) |
262
|
|
|
); |
263
|
|
|
return $route($this, $request, $response); |
264
|
|
|
} |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
// If this point is reached, no routable has provided a callback. 404. |
268
|
|
|
return $this['notFoundHandler']($request, $response); |
269
|
|
|
} |
270
|
|
|
); |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* @throws ContainerValueNotFoundException No container entry was found for the middleware. |
275
|
|
|
* @return void |
276
|
|
|
*/ |
277
|
|
|
protected function setupMiddleware() |
278
|
|
|
{ |
279
|
|
|
$container = $this->getContainer(); |
280
|
|
|
$middlewareConfig = $container['config']['middleware']; |
281
|
|
|
if (!$middlewareConfig) { |
282
|
|
|
return; |
283
|
|
|
} |
284
|
|
|
foreach ($middlewareConfig as $key => $opts) { |
285
|
|
|
if (isset($opts['active']) && $opts['active'] === true) { |
286
|
|
|
if (!isset($container[$key])) { |
287
|
|
|
throw new ContainerValueNotFoundException( |
288
|
|
|
sprintf('Middleware "%s" is not defined on the container.', $key) |
289
|
|
|
); |
290
|
|
|
} |
291
|
|
|
$this->add($container[$key]); |
292
|
|
|
} |
293
|
|
|
} |
294
|
|
|
} |
295
|
|
|
} |
296
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.