Can a controller catch an exception thrown from a model? - php

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.

Related

Symfony 4, how return properly a error 500 server from a service Class

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
}
}

Laravel is throwing exception event when I have try, catch

I am using laravel 5.4. I have a method in a class that gets a new instance of a model class. The class's full name is computed at run time, so there is a chance the computed class name does not exists. In the case the class does not exists, I want to ignore any exception and I want to return null instead.
But, when an exception takes place, Laravel still throw the exception below even thought I believe it should not
[Symfony\Component\Debug\Exception\FatalThrowableError]
Class 'App\Models\CreatedBy' not found
Note that the string App\Models\CreatedBy was computed at run-time.
Here is my code
private function getForeignModelInstance()
{
try {
if (!$this->foreignModel) {
$model = $this->getFullForeignModel();
if ($model) {
$this->foreignModel = new $model();
}
}
return $this->foreignModel;
} catch (\Exception $e) {
return null;
}
}
How can ignore any error thrown and settle by returning null?
I think the best way is to prevent exception happen instead of hiding it. So before make a new class instance, check if its exists:
private function getForeignModelInstance()
{
try {
if (!$this->foreignModel) {
$model = $this->getFullForeignModel();
if ($model && class_exists($model)) {
$this->foreignModel = new $model();
}
return null;
}
return $this->foreignModel;
} catch (\Exception $e) {
return null;
}
}
Note: class_exists will not work using the short, aliased class name.

Redirect with exception variable

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.

PHPUnit - Throwing, capturing and handling custom exceptions

I'm trying to test a capturing and handling a custom exception in PHP.
I've extended the base exception type with some extra properties and methods.
One of the classes I'm stubbing can throw an exception, I want to be able to test that I'm correctly capturing and handling that exception (which in this case means building a response object to return from the call).
e.g.
try {
$objectBeingStubbed->doSomething();
} catch (\Exception $ex) {
if ($ex instanceof CustomExceptionType) {
$this->_errorResponse->error->message = $exception->getMessage();
$this->_errorResponse->error->code = $exception->getCode();
$this->_errorResponse->error->data = $exception->getData();
} else {
throw $ex;
}
}
I'm attempted to simulate the exception being thrown with:
$objectStub->expects($this->any())
->method('doSomething')
->will($this->throwException(new CustomExceptionType()));
But when the exception arrives in the class I'm testing it's now an instance of "Mock_ErrorResponse_????" which doesn't extend my custom exception. My exception is instead contained in a "$exception" property on the Mock_ErrorResponse.
Is there any way of handling this without being forced to do something horrible like:
if ($ex instanceof PHPUnit_Framework_MockObject_Stub_Exception) {
$ex = $ex->exception;
}
if ($ex instanceof CustomExceptionType) {
...
Inside the class I'm testing?
First of all, instead:
} catch (\Exception $ex) {
if ($ex instanceof CustomExceptionType) {
you should use try/catch structure:
// (...)
} catch (CustomExceptionType $e) {
// (...)
} catch (\Exception $e) {
// (...)
}
So, answering your question, basically probably you're doing sth wrong. Because when the stubbed method throws an exception, it should throw exactly exception that you've set with throwException method.
I don't know how you build your stub (maybe there something is broken, maybe namespaces) but please consider an example below which works fine.
class Unit
{
public function foo()
{
throw new \InvalidArgumentException();
}
public function bar()
{
try {
$this->foo();
} catch (\InvalidArgumentException $e) {
return true;
} catch (\Exception $e) {
return false;
}
return false;
}
}
class UnitTest extends \PHPUnit_Framework_TestCase
{
public function testBar()
{
$sut = $this->getMock('Unit', array('foo'));
$sut->expects($this->any())
->method('foo')
->will($this->throwException(new \InvalidArgumentException()));
$this->assertTrue($sut->bar());
}
}
Of course you can replace InvalidArgumentException with your own implementation exception and this still should work. If you'll still have problems with figure out what is wrong with your code please post more complete example (eg. how you build your stub). Maybe then I can help more.
Nowadays you can use the #expectedException php-doc annotation built-in in PHPUnit: https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.exceptions
/**
* #expectedException InvalidArgumentException
*/
public function testBar()
{
$sut = $this->getMock('Unit', array('foo'));
$sut->expects($this->any())
->method('foo')
->will($this->throwException(new \InvalidArgumentException()));
}

Passing variables/values to custom exception?

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.

Categories