Skip to content

Commit 8b553b7

Browse files
authored
Merge pull request #2 from SDPM-lab/fix_exception
debug bar fix
2 parents ebf9c2c + f42e7b6 commit 8b553b7

4 files changed

Lines changed: 301 additions & 3 deletions

File tree

src/Ci4UriBridge.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ private function transferPath(){
3434
unset($pathArr[1]);
3535
array_values($pathArr);
3636
}
37+
if($pathArr[count($pathArr)-1] == ""){
38+
unset($pathArr[count($pathArr)-1]);
39+
array_values($pathArr);
40+
}
3741
$path = "/".implode("/",$pathArr);
3842
$this->_cURI->setPath($path);
3943
}

src/Commands/file/psr-worker.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use SDPMlab\Ci4Roadrunner\Ci4ResponseBridge;
1414
use SDPMlab\Ci4Roadrunner\Ci4RequestBridge;
1515
use SDPMlab\Ci4Roadrunner\Debug\Exceptions;
16+
use SDPMlab\Ci4Roadrunner\Debug\Toolbar;
1617
// codeigniter4 public/index.php
1718
$minPHPVersion = '7.2';
1819
if (phpversion() < $minPHPVersion)
@@ -56,6 +57,21 @@ function is_cli(){
5657
$psr7->getWorker()->error((string)$e);
5758
}
5859

60+
//處理除錯工具列
61+
try{
62+
if(ENVIRONMENT === 'development'){
63+
$toolbar = new Toolbar(config('Toolbar'),$ci4Req);
64+
if($barResponse = $toolbar->respond()){
65+
$psr7->respond($barResponse);
66+
init();
67+
continue;
68+
}
69+
}
70+
} catch (\Throwable $e){
71+
$dumper->dump((string)$e, Debug\Dumper::ERROR_LOG);
72+
$psr7->getWorker()->error((string)$e);
73+
}
74+
5975
//執行框架邏輯與錯誤處理
6076
try{
6177
$ci4Response = $app->run();
@@ -91,7 +107,9 @@ function init()
91107
$input = fopen("php://input", "w");
92108
fwrite($input, "");
93109
fclose($input);
94-
ob_end_clean();
110+
try {
111+
ob_end_clean();
112+
} catch (\Throwable $th) {}
95113
\CodeIgniter\Config\Services::reset(true);
96114
$appConfig = config(\Config\App::class);
97115
$app = new \CodeIgniter\CodeIgniter($appConfig);

src/Debug/Exceptions.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,13 @@ public function exceptionHandler($exception)
114114
header($header, true, $statusCode);
115115
if (strpos($this->rRequest->getHeaderLine('accept'), 'text/html') === false)
116116
{
117-
$res = $this->respond(ENVIRONMENT === 'development' ? $this->collectVars($exception, $statusCode) : '', $statusCode)->send();
118-
return $res;
117+
$msg = $this->collectVars($exception, $statusCode);
118+
if(ENVIRONMENT === 'development'){
119+
$this->response->setBody(json_encode($msg));
120+
}
121+
$this->response->setStatusCode($statusCode);
122+
$response = new Ci4ResponseBridge($this->response->send(),$this->rRequest);
123+
return $response;
119124
}
120125
}
121126

src/Debug/Toolbar.php

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
<?php
2+
3+
namespace SDPMlab\Ci4Roadrunner\Debug;
4+
5+
use CodeIgniter\CodeIgniter;
6+
use CodeIgniter\Config\BaseConfig;
7+
use CodeIgniter\Debug\Toolbar\Collectors\History;
8+
use CodeIgniter\Format\JSONFormatter;
9+
use CodeIgniter\Format\XMLFormatter;
10+
use Config\Services;
11+
use Laminas\Diactoros\Stream;
12+
use Psr\Http\Message\StreamInterface;
13+
14+
/**
15+
* Debug Toolbar
16+
*
17+
* Displays a toolbar with bits of stats to aid a developer in debugging.
18+
*
19+
* Inspiration: http://prophiler.fabfuel.de
20+
*
21+
* @package CodeIgniter\Debug
22+
*/
23+
class Toolbar
24+
{
25+
26+
/**
27+
* Toolbar configuration settings.
28+
*
29+
* @var BaseConfig
30+
*/
31+
protected $config;
32+
33+
/**
34+
* Collectors to be used and displayed.
35+
*
36+
* @var \CodeIgniter\Debug\Toolbar\Collectors\BaseCollector[]
37+
*/
38+
protected $collectors = [];
39+
40+
/**
41+
* The incoming request.
42+
*
43+
* @var \CodeIgniter\HTTP\IncomingRequest
44+
*/
45+
protected $request;
46+
//--------------------------------------------------------------------
47+
48+
/**
49+
* The outgoing response.
50+
*
51+
* @var \CodeIgniter\HTTP\Response
52+
*/
53+
protected $response;
54+
55+
/**
56+
* Constructor
57+
*
58+
* @param BaseConfig $config
59+
*/
60+
public function __construct(
61+
BaseConfig $config,
62+
\CodeIgniter\HTTP\IncomingRequest $request
63+
)
64+
{
65+
$this->config = $config;
66+
$this->request = $request;
67+
foreach ($config->collectors as $collector)
68+
{
69+
if (! class_exists($collector))
70+
{
71+
log_message('critical', 'Toolbar collector does not exists(' . $collector . ').' .
72+
'please check $collectors in the Config\Toolbar.php file.');
73+
continue;
74+
}
75+
76+
$this->collectors[] = new $collector();
77+
}
78+
}
79+
80+
//--------------------------------------------------------------------
81+
82+
/**
83+
* Inject debug toolbar into the response.
84+
*/
85+
public function respond()
86+
{
87+
if (ENVIRONMENT === 'testing')
88+
{
89+
return false;
90+
}
91+
92+
// @codeCoverageIgnoreStart
93+
$request = $this->request;
94+
95+
// If the request contains '?debugbar then we're
96+
// simply returning the loading script
97+
if ($request->getGet('debugbar') !== null)
98+
{
99+
ob_start();
100+
include($this->config->viewsPath . 'toolbarloader.js.php');
101+
$output = ob_get_clean();
102+
return $this->getResponse($output,200,"application/javascript");
103+
}
104+
105+
// Otherwise, if it includes ?debugbar_time, then
106+
// we should return the entire debugbar.
107+
if ($request->getGet('debugbar_time'))
108+
{
109+
helper('security');
110+
111+
// Negotiate the content-type to format the output
112+
$format = $request->negotiate('media', [
113+
'text/html',
114+
'application/json',
115+
'application/xml',
116+
]);
117+
$formatName = explode('/', $format)[1];
118+
119+
$file = sanitize_filename('debugbar_' . $request->getGet('debugbar_time'));
120+
$filename = WRITEPATH . 'debugbar/' . $file . '.json';
121+
122+
// Show the toolbar
123+
if (is_file($filename))
124+
{
125+
$contents = $this->format(file_get_contents($filename), $formatName);
126+
return $this->getResponse($contents,200,$format);
127+
}
128+
129+
// File was not written or do not exists
130+
return $this->getResponse("",404,"text/html");
131+
}
132+
return false;
133+
// @codeCoverageIgnoreEnd
134+
}
135+
136+
/**
137+
* Format output
138+
*
139+
* @param string $data JSON encoded Toolbar data
140+
* @param string $format html, json, xml
141+
*
142+
* @return string
143+
*/
144+
protected function format(string $data, string $format = 'html'): string
145+
{
146+
$data = json_decode($data, true);
147+
148+
if ($this->config->maxHistory !== 0)
149+
{
150+
$history = new History();
151+
$history->setFiles(
152+
$this->request->getGet('debugbar_time'),
153+
$this->config->maxHistory
154+
);
155+
156+
$data['collectors'][] = $history->getAsArray();
157+
}
158+
159+
$output = '';
160+
161+
switch ($format)
162+
{
163+
case 'html':
164+
$data['styles'] = [];
165+
extract($data);
166+
$parser = Services::parser($this->config->viewsPath, null, false);
167+
ob_start();
168+
include($this->config->viewsPath . 'toolbar.tpl.php');
169+
$output = ob_get_clean();
170+
break;
171+
case 'json':
172+
$formatter = new JSONFormatter();
173+
$output = $formatter->format($data);
174+
break;
175+
case 'xml':
176+
$formatter = new XMLFormatter;
177+
$output = $formatter->format($data);
178+
break;
179+
}
180+
181+
return $output;
182+
}
183+
184+
private function getResponse(string $body,int $code = 200,string $contentType){
185+
return new \Laminas\Diactoros\Response(
186+
$this->createBody($body),
187+
$code,
188+
["Content-Type" => $contentType]
189+
);
190+
}
191+
192+
private function createBody(string $bodyStr) : StreamInterface
193+
{
194+
$html = $bodyStr;
195+
if ($html instanceof StreamInterface){
196+
return $html;
197+
}
198+
$body = new Stream('php://temp', 'wb+');
199+
$body->write($html);
200+
$body->rewind();
201+
return $body;
202+
}
203+
204+
/**
205+
* Called within the view to display the timeline itself.
206+
*
207+
* @param array $collectors
208+
* @param float $startTime
209+
* @param integer $segmentCount
210+
* @param integer $segmentDuration
211+
* @param array $styles
212+
*
213+
* @return string
214+
*/
215+
protected function renderTimeline(array $collectors, float $startTime, int $segmentCount, int $segmentDuration, array &$styles): string
216+
{
217+
$displayTime = $segmentCount * $segmentDuration;
218+
$rows = $this->collectTimelineData($collectors);
219+
$output = '';
220+
$styleCount = 0;
221+
222+
foreach ($rows as $row)
223+
{
224+
$output .= '<tr>';
225+
$output .= "<td>{$row['name']}</td>";
226+
$output .= "<td>{$row['component']}</td>";
227+
$output .= "<td class='debug-bar-alignRight'>" . number_format($row['duration'] * 1000, 2) . ' ms</td>';
228+
$output .= "<td class='debug-bar-noverflow' colspan='{$segmentCount}'>";
229+
230+
$offset = ((((float) $row['start'] - $startTime) * 1000) / $displayTime) * 100;
231+
$length = (((float) $row['duration'] * 1000) / $displayTime) * 100;
232+
233+
$styles['debug-bar-timeline-' . $styleCount] = "left: {$offset}%; width: {$length}%;";
234+
$output .= "<span class='timer debug-bar-timeline-{$styleCount}' title='" . number_format($length, 2) . "%'></span>";
235+
$output .= '</td>';
236+
$output .= '</tr>';
237+
238+
$styleCount ++;
239+
}
240+
241+
return $output;
242+
}
243+
244+
/**
245+
* Returns a sorted array of timeline data arrays from the collectors.
246+
*
247+
* @param array $collectors
248+
*
249+
* @return array
250+
*/
251+
protected function collectTimelineData($collectors): array
252+
{
253+
$data = [];
254+
255+
// Collect it
256+
foreach ($collectors as $collector)
257+
{
258+
if (! $collector['hasTimelineData'])
259+
{
260+
continue;
261+
}
262+
263+
$data = array_merge($data, $collector['timelineData']);
264+
}
265+
266+
// Sort it
267+
268+
return $data;
269+
}
270+
271+
}

0 commit comments

Comments
 (0)