I have the following (test) setup:
web.php
Route::get("test/test","TestController#test");
Route::get("test/numeric","TestController#numeric");
Route::get("forbidden", "TestController#exception")
TestController.php
use \Symfony\Component\HttpKernel\Exception\HTTPException;
public class TestController {
public function test() {
return redirect()->to("/forbidden")->with("exception",new HttpException(403));
}
public function numeric() {
return redirect()->to("/forbidden")->with("exception",403);
}
public function exception() {
if (\Session::get("exception") instanceof \Throwable) {
throw \Session::get("exception"); //Let the default handler handle it.
} else if (is_numeric(\Session::get("exception"))) {
throw new HttpException(\Session::get("exception"));
} else {
return "Empty exception";
}
}
}
When I navigate to /test/test I always get "Empty exception" to appear.
However /test/numeric shows the exception normally. Furthermore I've checked the contents of the session in both cases, in the first case the exception object is not passed at all.
Am I missing something obvious here?
After a lot more digging through I've realised that this is impossible to do because of the exception stack trace. There's a lot of functions which take in closures as parameters and because of that the exception stack trace is not serializable. Laravel seems to quietly drop any non-serializable variables from the session.
Related
From a Service class, how manage exceptions and return an error 500 ?
By example, I have a service class 'A' called from another service Class 'B'. The content of the service 'A' is :
namespace App\Service;
use ...
class A
{
...
public static function foo(){
$tmp = [];
// do some stuff
if(isOK($tmp)){
return $tmp;
}else{
// return 500 with message
}
}
private static function isOK($tmp){
// do some stuff
}
}
I tried this :
namespace App\Service;
use ...
class A
{
...
public static function foo(){
$tmp = [];
// do some stuff
if(isOK($tmp)){
return $tmp;
}else{
// return 500 with message
}
}
private static function isOK($tmp){
try{
if(...) throw new \Exception();
}catch (HttpException $e){
dump('not valid data $tmp var in ' . __FUNCTION__,500);
exit;
}
}
}
But I don't think I use a good way. If I deliberately set a wrong value to the $tmp var, the process is stopped (as I want) and, in a case where I use this service for build a symfony http web page, a blank page is displayed with my message but this page get a status 200 (not a 500 'internal server error').
What is the good/properly way for return an exception from a Service ?
Is there a global (symfony ? oop php?) way for manage properly errors exceptions in the 'service called from another service' context and/or in the 'service called from a controller used only for REST web service' context and/or , more conventionally, in the 'service called from a classical http controller' context ? (bonus : and/or in the "service called from a custom Command Class")
Maybe I completely misunderstand the question, but I'd say: throw an Exception from your Service.
But: you only catch an Exception, if you can properly handle it. In your case it looks as if you can't handle it in your Service, so you let it bubble up its way to the appropriate Symfony component (that differs between Console command, Controller or Rest endpoint).
The Service shouldn't set the 500 code, as it doesn't know in which context it is used. Therefor you might want to throw an explicit ServiceException and catch that in your controller and convert it to something more useful:
class A
{
public function foo(){
$tmp = [];
if($this->isOK($tmp)){
return $tmp;
}
throw new ServiceException('Failed checking $tmp');
}
private function isOK($tmp){
return false;
}
}
class TestController
{
/**
* #var A
*/
protected $a;
public function fooAction() {
try {
$this->a->foo();
} catch (ServiceException $e) {
throw new HttpException(500, $e->getMessage())
}
}
}
For web and rest you have to make sure that your Exception has the correct code, which will then be used to set the HTTP code.
Only the code that uses the service knows how to handle the Exception properly. As the status code doesn't matter in your console command, you could not catch it.
But in general you can say that this is not best practice, as you might have to do some cleanup (close connections, close file handles, write error log) before the Exception is passed to the next code level.
An example for the console:
class MyCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$a = new A();
try {
$this->a->foo();
} catch (ServiceException $e) {
// write to log file
$io->error('Service failed: ' . $e->getMessage());
return;
}
// do more stuff
}
}
How can I see if an exception is currently in flight, i.e. the stack is unwinding?
In the example below how would you implement isExceptionInFlight()?
<?php
class Destroyer
{
function __destruct() {
if (isExceptionInFlight()) {
echo 'failure';
} else {
echo 'success';
}
}
}
function isExceptionInFlight() {
// ?????
}
function createAndThrow()
{
$var = new Destroyer;
throw new \Exception;
}
createAndThrow();
The purpose of this would be to implement D's scope statement, which is available as a library in multiple other languages. This allows you to get rid of nested try-catch blocks, which in turn makes it easier to do transactions with rollbacks correctly.
Addendum1:
I've looked around in the Zend PHP Engine and executor_globals.exception seems to be what I'm looking for (https://github.com/php/php-src/blob/master/Zend/zend_globals.h). However this value is always nullptr when I inspect it during __destruct(). Any idea where I should look next?
Addendum2:
Inspecting executor_globals.opline_before_exception has led to some progress. However it is not reset to nullptr when the exception has been caught.
Addendum3:
I've found the following code (line 135)
/* Make sure that destructors are protected from previously thrown exceptions.
* For example, if an exception was thrown in a function and when the function's
* local variable destruction results in a destructor being called.
*/
old_exception = NULL;
if (EG(exception)) {
if (EG(exception) == object) {
zend_error_noreturn(E_CORE_ERROR, "Attempt to destruct pending exception");
} else {
old_exception = EG(exception);
EG(exception) = NULL;
}
}
zend_call_method_with_0_params(&obj, object->ce, &destructor, ZEND_DESTRUCTOR_FUNC_NAME, NULL);
if (old_exception) {
if (EG(exception)) {
zend_exception_set_previous(EG(exception), old_exception);
} else {
EG(exception) = old_exception;
}
}
This seems to actively PREVENT me from doing what I want, and explains why executor_globals.exception is always nullptr.
Although I don't recommend, I have implemented it in the past. My approach was (simply put) like this:
Implement custom Exception class
class MyException extends Exception {
public static $exceptionThrown = false;
public function __construct($your parameters) {
self::$exceptionThrown = true;
}
}
Now, every exception should be your own exception implementation instead of default Exception.
class Destroyer {
public function __destruct() {
if(MyException::exceptionThrown() {
Database::rollback();
} else {
Database::commit();
}
}
}
I'm working on a exception logging script, I use set_exception_handler() to handle uncaught exception.
Inside my custom exception handler, I use get_defined_vars() but it only return an array with a exception object, every variables created before exception thrown were disappear
$testing_var = 'testtesttest';
try {
throw new Exception("Error Processing Request");
} catch (Exception $e) {
var_dump(get_defined_vars()); // this could get $testing_var
}
set_exception_handler('exception_handler');
function exception_handler(exception)
{
var_dump(get_defined_vars()); // no, it can't get $testing_var, exception object only
}
throw new Exception("Error Processing Request");
In the scope where you are calling get_defined_vars() the variable you are after is not defined, so of course it will not be returned. from the docs:
This function returns a multidimensional array containing a list of all defined variables, be them environment, server or user-defined variables, within the scope that get_defined_vars() is called.
What are you trying to achieve? In general you should pass all information needed to handle the exception to the exception when constructing it. possibly using a custom exception class:
<?php
// custom exception class
// could be extended with constructor accepting an optional context
class ContextAwareException extends Exception
{
private $context;
public function setContext($context)
{
$this->context = $context;
}
public function getContext()
{
return $this->context;
}
}
function exception_handler($exception)
{
if ($exception instanceof ContextAwareException) {
$exception->getContext();
} else {
// we have no context
}
}
/*
* using this exception
*/
$testing_var = 'testtesttest';
$exception = new ContextAwareException("Error Processing Request");
$exception->setContext(get_defined_vars());
throw $exception;
I have found an alternate way to do this. I'm also looking for exception solution but this one works for me. If you use errors instead of exceptions - it seems to work.
set_error_handler('test','handler');
class test {
public static function handler($code, $error, $file = NULL, $line = NULL) {
throw new Exception($error, $code, 0, $file, $line);
return true;
}
}
$testVar='carolines';
try {
trigger_error('megamsg');
}
catch(Exception $e) {
var_dump($e);
$vars=$E BLABLABLA
}
Find yourself how to extract from $e. But if you debug You will see in trace handler function call with $testVar variable
Well, it is technically possible, but would this break the MVC architecture?
I'm not sure whether this type of communication is recommended between both controller and model. I will describe it using a simple example and two ways of doing it:
OPTION 1 (model throws exception and controller catches it):
class Controller {
private $model;
public function save($data) {
try {
$this->model->save($data);
} catch (Exception $e) {
// handle exception
}
}
}
class Model {
public function save($data) {
// Call to internal function to save data in BD
if (! $this->_save($data)) throw new Exception('Error saving data');
}
}
OPTION 2 (the controller handles the exception completely):
class Controller {
private $model;
public function save($data) {
try {
if (! $this->model->save($data)) throw new Exception('Error saving data');
} catch (Exception $e) {
// handle exception
}
}
}
class Model {
public function save($data) {
// Call to internal function to save data in BD
if (! $this->_save($data)) return false;
}
}
**
EDIT after some responses:
**
These are other ways to solve it based on your suggestions. I hope not to get things too complicated.
OPTION 3 (model handles the exception completely, as Ray said. KingCrunch also suggested to better do it in the model)
class Controller {
private $model;
public function save($data) {
if (! $this->model->save($data)) {
// possible action: redirect to the form with an error message
}
}
}
class Model {
public function save($data) {
try {
if (! $this->_save($data)) throw new Exception('Error saving data');
} catch (Exception $e) {
// handle exception
return false;
}
return true;
}
}
OPTION 4 (controller gets a custom child exception thrown by the model, as shiplu.mokadd.im said.)
class Controller {
private $model;
public function save($data) {
try {
$this->model->save($data);
} catch (Exception $e) {
if ($e instanceof ValidationException) {
// handle validation error
}
elseif ($e instanceof DBStorageException) {
// handle DB error
}
}
}
}
class Model {
public function save($data) {
if (! $this->_validate($data)) {
throw new ValidationException ('Validation error');
}
if (! $this->_save($data)) {
throw new DBStorageException ('Storage error');
}
}
}
Model can throw Exception and Controller or View should catch it. Otherwise you never know if everything is working properly down there. So use the first option. But make sure you are throwing properly abstracted Exception that is meaningful to the controller and View.
To illustrate the above bold line see these two throw statements which are used inside a model.
throw new Exception('SQL Error: '.$mysqli->error()); // dont use it
throw new DuplicateFieldException('Duplicate username'); // use this
The second example does not show internal error. Rather it hides it. Controller should never know whats happening inside.
In your code your tied a single model to a single controller. Controller does not represent a single model. It uses model. And it can use any number of model. So dont tie up a single model with a controller with variable like private $model.
Definitely first option. Some words:
It's the job of a Controller to ... well, control. This means, that it should take care, that at least an useful error message appears. Other parts of the application may do it before, when they are able to handle the exceptional case. That includes the model itself: If it is able to handle it, it should do it.
save() means "save". Don't misuse the return value for status information. When the method is not able to save() it is an exception and when a method doesn't have to give you something, then it shouldn't give you something.
I prefer option 3.
The Model should catch the exception, try to resolve it, if not percolate it up to the controller but only if it's something the controller could address and recover from. In this case, (some kind of DB save failure) catching it in the model returning false should be adequate resolution for the save error and provide enough for the Controller to know something went wrong when saving.
The controller should not need to worry about implementation details on how the model implements saving.
Is this how you would pass a value ("username" in example below) to a custom exception? The question being Would I use __construct()? Is using a custom exception for checking whether an important variable is set an overkill?
class customException extends Exception {
public function __construct($e) {
parent::__construct($e); // makes sure variable is set? [1]
$this->e = $e;
}
public function redirect_user() {
if($this->e === "username") {
header("Location: ");
}
}
}
class testing {
public function test() {
try {
if(!isset($_POST['username'])) {
throw new customException("username");
}
}
catch(customException $e) {
$e->redirect_user();
}
}
}
[1] http://php.net/manual/en/language.exceptions.extending.php#example-266
On a side note, what's the purpose of parent::__construct($e)? Wouldn't that be redundant?
There is no reason for your constructor at all. I would suggest using $this->getMessage() to access the value you are trying to set to $this->e.
So do something like this:
class customException extends Exception {
public function redirect_user() {
if($this->getMessage() === "username") {
print_r("Header");
}
}
}
It is much more straightforward and only extends the functionality of the base class rather than unnecessarily overriding the constructor.
That being said, I personally don't like the thought of using exceptions to execute application flow logic like you are doing. To me, custom Exceptions are useful to interface with custom logging systems, or to be able to log aspects of your application's state that are not available via the default Exception class.