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.
Related
I wrote a new database class using PDO. I' struggling with a problem though. I usually return an object, so I can work with it. So when a query failed, I simply return $this. I have a method called wasSuccessful() that I use to make sure the query didn't fail.
$result = $database->query(...);
if ($result->wasSuccessful()) {
// do code
}
However, what do I do, when the method returns false? For example:
...
if (!$this->tableExists($table)) return false;
When this happens, PDO tells me that I can't run functions on a boolean value. How do I tackle this in the best possible way?
Thanks very much in advance!
Rather than relying on the return values of functions, you should use exceptions. Something like this simple example should give you the idea:
In your class:
class MyClass {
public function query($sql, ...$params) {
if (!$this->tableExists($table)) {
throw new \Exception("the table doesn't exist");
return false;
}
}
}
Then in your code:
try {
$result = $database->query(...);
// do stuff with $result, knowing it's ok
} catch (\Exception $e) {
echo "here's our error handling routine";
echo $e->getMessage();
}
In more complex projects it might be desirable to create your own exception classes. As well, you can use try/catch within your class, and throw exceptions from PDO to the higher-level code. For example:
class MyClass {
public function query($sql, ...$params) {
if (!$this->tableExists($table)) {
throw new \Exception("the table doesn't exist");
return false;
}
try {
//do something with the PDO object that might throw an exception
} catch (\PDOException $e) {
// just take this existing exception and throw it to the next catch block
throw $e;
return false;
}
}
}
I have a class named User that calls one of its own methods, setUsername(), upon construction, within a try/catch block. If setUsername() fails, it will throw an exception:
class User {
private $username;
public function __construct($input_username) {
try {
$this->setUsername($input_username);
} catch(Exception $e) {
throw $e;
}
}
private function setUsername($username) {
if(1 != 0)
throw new Exception("1 does not equal 0!!!");
$this->username = $username;
}
}
I then create a new User in an external function, in a separate file, within its own try/catch block. It's supposed to catch the exception passed through from the User class constructor:
namespace UserController;
function createUser(){
try {
$user = new \User('sample-user');
} catch(Exception $e) {
echo $e->getMessage();
}
}
Why, then, am I still getting an "Uncaught Exception" error?
It seems I was missing a statement at the top of the file that instantiates the class, since it is namespaced. After the namespace declaration, it needs to say:
use \Exception;
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()));
}
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.
In the following example, if the class does not exist, I want to catch the error and create a Null class instead.
But in spite of my try/catch statements, PHP simply tells me Class 'SmartFormasdfasdf' not found.
How can I get PHP to catch the 'class not found' error?
<?php
class SmartFormLogin extends SmartForm {
public function render() {
echo '<p>this is the login form</p>';
}
}
class SmartFormCodeWrapper extends SmartForm {
public function render() {
echo '<p>this is the code wrapper form</p>';
}
}
class SmartFormNull extends SmartForm {
public function render() {
echo '<p>the form "' . htmlentities($this->idCode) . '" does not exist</p>';
}
}
class SmartForm {
protected $idCode;
public function __construct($idCode) {
$this->idCode = $idCode;
}
public static function create($smartFormIdCode) {
$className = 'SmartForm' . $smartFormIdCode;
try {
return new $className($smartFormIdCode);
} catch (Exception $ex) {
return new SmartFormNull($smartformIdCode);
}
}
}
$formLogin = SmartForm::create('Login');
$formLogin->render();
$formLogin = SmartForm::create('CodeWrapper');
$formLogin->render();
$formLogin = SmartForm::create('asdfasdf');
$formLogin->render();
?>
Solution:
Thanks #Mchl, this is how I solved it then:
public static function create($smartFormIdCode) {
$className = 'SmartForm' . $smartFormIdCode;
if(class_exists($className)) {
return new $className($smartFormIdCode);
} else {
return new SmartFormNull($smartFormIdCode);
}
}
Because it's a fatal error. Use class_exists() function to check if class exist.
Also: PHP is not Java - unless you redefined default error handler, it will raise errors and not throw exceptions.
Old question, but in PHP7 this is a catchable exception. Though I still think the class_exists($class) is a more explicit way to do it. However, you could do a try/catch block using the new \Throwable exception type:
$className = 'SmartForm' . $smartFormIdCode;
try {
return new $className($smartFormIdCode);
} catch (\Throwable $ex) {
return new SmartFormNull($smartformIdCode);
}
php >= 7.0
php can catch 'class not found' as Throwable
try {
return new $className($smartFormIdCode);
} catch (\Throwable $ex) {
return new SmartFormNull($smartformIdCode);
}
You need to use class_exists to see if the class exists before you try and instantiate it.
Incidentally, if you're using a class autoloader, be sure to set the second arg to true.
Because php emits fatal error when you ty to create new object of non existing class. To make it work you will need php >= 5.3 and autoload function, where you should try to look for file with class definition or throw your custom exception.