1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Charcoal\App\Handler; |
4
|
|
|
|
5
|
|
|
// Dependencies from PSR-3 (logger) |
6
|
|
|
use Psr\Log\LoggerAwareInterface; |
7
|
|
|
use Psr\Log\LoggerAwareTrait; |
8
|
|
|
|
9
|
|
|
// Dependencies from PSR-7 (HTTP Messaging) |
10
|
|
|
use Psr\Http\Message\ResponseInterface; |
11
|
|
|
use Psr\Http\Message\ServerRequestInterface; |
12
|
|
|
use Psr\Http\Message\UriInterface; |
13
|
|
|
|
14
|
|
|
// Dependency from Pimple |
15
|
|
|
use Pimple\Container; |
16
|
|
|
|
17
|
|
|
// Dependency from 'charcoal-config' |
18
|
|
|
use Charcoal\Config\ConfigurableInterface; |
19
|
|
|
use Charcoal\Config\ConfigurableTrait; |
20
|
|
|
|
21
|
|
|
// Dependencies from 'charcoal-view' |
22
|
|
|
use Charcoal\View\ViewInterface; |
23
|
|
|
use Charcoal\View\ViewableInterface; |
24
|
|
|
use Charcoal\View\ViewableTrait; |
25
|
|
|
|
26
|
|
|
// Dependencies from 'charcoal-translator' |
27
|
|
|
use Charcoal\Translator\TranslatorAwareTrait; |
28
|
|
|
|
29
|
|
|
// Intra-module (`charcoal-app`) dependencies |
30
|
|
|
use Charcoal\App\AppConfig; |
31
|
|
|
use Charcoal\App\Template\TemplateInterface; |
32
|
|
|
use Charcoal\App\Handler\HandlerInterface; |
33
|
|
|
use Charcoal\App\Handler\HandlerConfig; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Base Error Handler |
37
|
|
|
* |
38
|
|
|
* Enhanced version Slim's error handlers. |
39
|
|
|
* |
40
|
|
|
* It outputs messages in either JSON, XML or HTML |
41
|
|
|
* based on the Accept header. |
42
|
|
|
*/ |
43
|
|
|
abstract class AbstractHandler implements |
44
|
|
|
ConfigurableInterface, |
45
|
|
|
HandlerInterface, |
46
|
|
|
LoggerAwareInterface, |
47
|
|
|
ViewableInterface |
48
|
|
|
{ |
49
|
|
|
use ConfigurableTrait; |
50
|
|
|
use LoggerAwareTrait; |
51
|
|
|
use TranslatorAwareTrait; |
52
|
|
|
use ViewableTrait; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Container |
56
|
|
|
* |
57
|
|
|
* @var Container |
58
|
|
|
*/ |
59
|
|
|
protected $container; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* URL for the home page |
63
|
|
|
* |
64
|
|
|
* @var string |
65
|
|
|
*/ |
66
|
|
|
protected $baseUrl; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Known handled content types |
70
|
|
|
* |
71
|
|
|
* @var array |
72
|
|
|
*/ |
73
|
|
|
protected $knownContentTypes = [ |
74
|
|
|
'application/json', |
75
|
|
|
'application/xml', |
76
|
|
|
'text/xml', |
77
|
|
|
'text/html', |
78
|
|
|
]; |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Return a new AbstractHandler object. |
82
|
|
|
* |
83
|
|
|
* @param Container $container A dependencies container instance. |
84
|
|
|
*/ |
85
|
|
|
public function __construct(Container $container) |
86
|
|
|
{ |
87
|
|
|
$this->setDependencies($container); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* Initialize the AbstractHandler object. |
92
|
|
|
* |
93
|
|
|
* @return AbstractHandler Chainable |
94
|
|
|
*/ |
95
|
|
|
public function init() |
96
|
|
|
{ |
97
|
|
|
return $this; |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* Inject dependencies from a Pimple Container. |
102
|
|
|
* |
103
|
|
|
* ## Dependencies |
104
|
|
|
* |
105
|
|
|
* - `AppConfig $appConfig` — The application's configuration. |
106
|
|
|
* - `UriInterface $baseUri` — A base URI. |
107
|
|
|
* - `ViewInterface $view` — A view instance. |
108
|
|
|
* |
109
|
|
|
* @param Container $container A dependencies container instance. |
110
|
|
|
* @return AbstractHandler Chainable |
111
|
|
|
*/ |
112
|
|
|
public function setDependencies(Container $container) |
113
|
|
|
{ |
114
|
|
|
$this->setLogger($container['logger']); |
115
|
|
|
$this->setContainer($container); |
116
|
|
|
$this->setBaseUrl($container['base-url']); |
117
|
|
|
$this->setView($container['view']); |
118
|
|
|
$this->setTranslator($container['translator']); |
119
|
|
|
|
120
|
|
|
if (isset($container['config']['handlers.defaults'])) { |
121
|
|
|
$this->setConfig($container['config']['handlers.defaults']); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
return $this; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* Set container for use with the template controller |
129
|
|
|
* |
130
|
|
|
* @param Container $container A dependencies container instance. |
131
|
|
|
* @return AbstractHandler Chainable |
132
|
|
|
*/ |
133
|
|
|
public function setContainer(Container $container) |
134
|
|
|
{ |
135
|
|
|
$this->container = $container; |
136
|
|
|
return $this; |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* ConfigurableTrait > createConfig() |
141
|
|
|
* |
142
|
|
|
* @see ConfigurableTrait::createConfig() |
143
|
|
|
* @param mixed|null $data Optional config data. |
144
|
|
|
* @return ConfigInterface |
145
|
|
|
*/ |
146
|
|
|
public function createConfig($data = null) |
147
|
|
|
{ |
148
|
|
|
return new HandlerConfig($data); |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* Determine which content type we know about is wanted using "Accept" header |
153
|
|
|
* |
154
|
|
|
* @param ServerRequestInterface $request The most recent Request object. |
155
|
|
|
* @return string |
156
|
|
|
*/ |
157
|
|
|
protected function determineContentType(ServerRequestInterface $request) |
158
|
|
|
{ |
159
|
|
|
$acceptHeader = $request->getHeaderLine('Accept'); |
160
|
|
|
$selectedContentTypes = array_intersect(explode(',', $acceptHeader), $this->knownContentTypes); |
161
|
|
|
|
162
|
|
|
if (count($selectedContentTypes)) { |
163
|
|
|
return reset($selectedContentTypes); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
// handle +json and +xml specially |
167
|
|
|
if (preg_match('/\+(json|xml)/', $acceptHeader, $matches)) { |
168
|
|
|
$mediaType = 'application/'.$matches[1]; |
169
|
|
|
if (in_array($mediaType, $this->knownContentTypes)) { |
170
|
|
|
return $mediaType; |
171
|
|
|
} |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
return 'text/html'; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* Render HTML Error Page |
179
|
|
|
* |
180
|
|
|
* @return string |
181
|
|
|
*/ |
182
|
|
|
protected function renderHtmlOutput() |
183
|
|
|
{ |
184
|
|
|
$config = $this->config(); |
185
|
|
|
$container = $this->container; |
186
|
|
|
|
187
|
|
|
$templateIdent = $config['template']; |
188
|
|
|
|
189
|
|
|
if ($config['cache']) { |
190
|
|
|
$cachePool = $container['cache']; |
191
|
|
|
$cacheKey = str_replace('/', '.', 'template/'.$templateIdent); |
192
|
|
|
$cacheItem = $cachePool->getItem($cacheKey); |
193
|
|
|
|
194
|
|
|
$output = $cacheItem->get(); |
195
|
|
|
if ($cacheItem->isMiss()) { |
196
|
|
|
$output = $this->renderHtmlTemplate(); |
197
|
|
|
|
198
|
|
|
$cacheItem->set($output, $config['cache_ttl']); |
199
|
|
|
} |
200
|
|
|
} else { |
201
|
|
|
$output = $this->renderHtmlTemplate(); |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
return $output; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* Render title of error |
209
|
|
|
* |
210
|
|
|
* @return string |
211
|
|
|
*/ |
212
|
|
|
public function messageTitle() |
213
|
|
|
{ |
214
|
|
|
return $this->translator()->translate('Application Error'); |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* Render body of HTML error |
219
|
|
|
* |
220
|
|
|
* @return string |
221
|
|
|
*/ |
222
|
|
|
abstract public function renderHtmlMessage(); |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* Render HTML Error Page |
226
|
|
|
* |
227
|
|
|
* @return string |
228
|
|
|
*/ |
229
|
|
|
protected function renderHtmlTemplate() |
230
|
|
|
{ |
231
|
|
|
$config = $this->config(); |
232
|
|
|
$container = $this->container; |
233
|
|
|
|
234
|
|
|
$templateIdent = $config['template']; |
235
|
|
|
$templateController = $config['controller']; |
236
|
|
|
$templateData = $config['template_data']; |
237
|
|
|
|
238
|
|
|
$templateFactory = $container['template/factory']; |
239
|
|
|
if (isset($config['default_controller'])) { |
240
|
|
|
$templateFactory->setDefaultClass($config['default_controller']); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
if (!$templateController) { |
244
|
|
|
return ''; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
$template = $templateFactory->create($templateController); |
248
|
|
|
|
249
|
|
|
if (!isset($templateData['error_title'])) { |
250
|
|
|
$templateData['error_title'] = $this->messageTitle(); |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
if (!isset($templateData['error_message'])) { |
254
|
|
|
$templateData['error_message'] = $this->renderHtmlMessage(); |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
$template->setData($templateData); |
258
|
|
|
return $container['view']->render($templateIdent, $template); |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* Set the base URL (home page). |
263
|
|
|
* |
264
|
|
|
* @param string|UriInterface $url A URL to the base URL. |
265
|
|
|
* @return AbstractHandler Chainable |
266
|
|
|
*/ |
267
|
|
|
public function setBaseUrl($url) |
268
|
|
|
{ |
269
|
|
|
if ($url instanceof UriInterface) { |
270
|
|
|
$url = $url->withPath('')->withQuery('')->withFragment(''); |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
$this->baseUrl = $url; |
|
|
|
|
274
|
|
|
|
275
|
|
|
return $this; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* Retrieves the URL for the home page. |
280
|
|
|
* |
281
|
|
|
* @return string A URL representing the home page. |
282
|
|
|
*/ |
283
|
|
|
public function baseUrl() |
284
|
|
|
{ |
285
|
|
|
return $this->baseUrl; |
286
|
|
|
} |
287
|
|
|
} |
288
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.