-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added
HandleExceptions::class
for handle php error (#368)
* feat: add `HandleProviders::class` for handle php error * test 1 of 3 * test 2 of 3 (1 is skipped) * formatting * revent: skip cli error render * rename file name
- Loading branch information
1 parent
caa1f51
commit 939e0f7
Showing
3 changed files
with
216 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace System\Integrate\Bootstrap; | ||
|
||
use System\Integrate\Application; | ||
use System\Integrate\Exceptions\Handler; | ||
|
||
class HandleExceptions | ||
{ | ||
private Application $app; | ||
public static ?string $reserveMemory = null; | ||
|
||
public function bootstrap(Application $app): void | ||
{ | ||
self::$reserveMemory = str_repeat('x', 32_768); | ||
|
||
$this->app = $app; | ||
|
||
error_reporting(E_ALL); | ||
|
||
/* @phpstan-ignore-next-line */ | ||
set_error_handler([$this, 'handleError']); | ||
|
||
set_exception_handler([$this, 'handleException']); | ||
|
||
register_shutdown_function([$this, 'handleShutdown']); | ||
|
||
if ('testing' !== $app->environment()) { | ||
ini_set('display_errors', 'Off'); | ||
} | ||
} | ||
|
||
public function handleError(int $level, string $message, string $file = '', ?int $line = 0): void | ||
{ | ||
if ($this->isDeprecation($level)) { | ||
$this->handleDeprecationError($message, $file, $line, $level); | ||
} | ||
|
||
if (error_reporting() & $level) { | ||
throw new \ErrorException($message, 0, $level, $file, $line); | ||
} | ||
} | ||
|
||
private function handleDeprecationError(string $message, string $file, int $line, int $level): void | ||
{ | ||
$this->log($level, $message); | ||
} | ||
|
||
public function handleException(\Throwable $th): void | ||
{ | ||
self::$reserveMemory = null; | ||
|
||
$handler = $this->getHandler(); | ||
$handler->report($th); | ||
if (php_sapi_name() !== 'cli') { | ||
$handler->render($this->app['request'], $th)->send(); | ||
} | ||
} | ||
|
||
public function handleShutdown(): void | ||
{ | ||
self::$reserveMemory = null; | ||
$error = error_get_last(); | ||
if ($error && $this->isFatal($error['type'])) { | ||
$this->handleException(new \ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line'])); | ||
} | ||
} | ||
|
||
private function log(int $level, string $message): bool | ||
{ | ||
if ($this->app->has('log')) { | ||
$this->app['log']->log($level, $message); | ||
|
||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
private function getHandler(): Handler | ||
{ | ||
return $this->app[Handler::class]; | ||
} | ||
|
||
private function isDeprecation(int $level): bool | ||
{ | ||
return in_array($level, [E_DEPRECATED, E_USER_DEPRECATED]); | ||
} | ||
|
||
private function isFatal(int $level): bool | ||
{ | ||
return in_array($level, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace System\Integrate\Bootstrap; | ||
|
||
use PHPUnit\Framework\Assert; | ||
use PHPUnit\Framework\TestCase; | ||
use System\Http\Request; | ||
use System\Integrate\Application; | ||
use System\Integrate\Exceptions\Handler; | ||
|
||
class HandleExceptionsTest extends TestCase | ||
{ | ||
/** | ||
* @test | ||
*/ | ||
public function itCanHandleError() | ||
{ | ||
$app = new Application(dirname(__DIR__) . '/assets/app2'); | ||
$app->set('environment', 'testing'); | ||
|
||
$handle = new HandleExceptions(); | ||
$handle->bootstrap($app); | ||
|
||
$this->expectException(\ErrorException::class); | ||
$this->expectExceptionMessage(__CLASS__); | ||
$handle->handleError(E_ERROR, __CLASS__, __FILE__, __LINE__); | ||
|
||
$app->flush(); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function itCanHandleErrorDeprecation() | ||
{ | ||
$app = new Application(dirname(__DIR__) . '/assets/app2'); | ||
$app->set('environment', 'testing'); | ||
$app->set(Handler::class, fn () => new TestHandleExceptions($app)); | ||
$app->set('log', fn () => new TestLog()); | ||
|
||
$handle = new HandleExceptions(); | ||
$handle->bootstrap($app); | ||
|
||
$app[Handler::class]->deprecated(); | ||
$this->expectException(\ErrorException::class); | ||
$this->expectExceptionMessage('deprecation'); | ||
$handle->handleError(E_USER_DEPRECATED, 'deprecation', __FILE__, __LINE__); | ||
|
||
$app->flush(); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function itCanHandleException() | ||
{ | ||
$app = new Application(dirname(__DIR__) . '/assets/app2'); | ||
$app->set('request', fn (): Request => new Request('/')); | ||
$app->set('environment', 'testing'); | ||
$app->set(Handler::class, fn () => new TestHandleExceptions($app)); | ||
|
||
$handle = new HandleExceptions(); | ||
$handle->bootstrap($app); | ||
|
||
try { | ||
throw new \ErrorException('testing'); | ||
} catch (\Throwable $th) { | ||
$handle->handleException($th); | ||
} | ||
$app->flush(); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function itCanHandleShutdown() | ||
{ | ||
$this->markTestSkipped('dont how to test, but its work'); | ||
|
||
$app = new Application(dirname(__DIR__) . '/assets/app2'); | ||
$app->set('environment', 'testing'); | ||
$app->set(Handler::class, fn () => new TestHandleExceptions($app)); | ||
|
||
$handle = new HandleExceptions(); | ||
$handle->bootstrap($app); | ||
$handle->handleShutdown(); | ||
|
||
$app->flush(); | ||
} | ||
} | ||
|
||
class TestHandleExceptions extends Handler | ||
{ | ||
public function report(\Throwable $th): void | ||
{ | ||
Assert::assertTrue($th->getMessage() === 'testing', 'tesing helper'); | ||
} | ||
|
||
/** | ||
* Summary of deprecated. | ||
* | ||
* @deprecated message | ||
*/ | ||
public function deprecated(): void | ||
{ | ||
} | ||
} | ||
|
||
class TestLog | ||
{ | ||
public function log(int $level, string $message): void | ||
{ | ||
Assert::assertEquals($level, 16384); | ||
Assert::assertEquals($message, 'deprecation'); | ||
} | ||
} |