I tried to override the getMessage() to give the return message a default value but failed because all method in PHP Exception class are final.
Now I am just curious about why it is. I know I can work it around by adding a proxy method.
They are final to make sure they work in the exact same way in all inherited classes. A programmer will expect getMessage to work in the exact same way for all classes inherited directly or transitively from Exception. You can set the message in the constructor though, as you will be able to write your own constructor for your class.
I have the impression that you want a pattern like this:
<?php
class UserNotFoundException extends \RuntimeException {
const MESSAGE_TEMPLATE = 'Could not find the requested user: %s';
public static function fromId($user_id) {
$message = sprintf(self::MESSAGE_TEMPLATE, "ID=$user_id");
return new self($message);
}
public static function fromName($name) {
$message = sprintf(self::MESSAGE_TEMPLATE, "name=$name");
return new self($message);
}
}
try {
throw UserNotFoundException::fromId(314);
} catch (RuntimeException $e) {
echo $e->getMessage() . PHP_EOL;
}
try {
throw UserNotFoundException::fromName('john');
} catch (RuntimeException $e) {
echo $e->getMessage() . PHP_EOL;
}
Could not find the requested user: ID=314
Could not find the requested user: name=john
Tweak it to your exact needs. This is quite practical for reuse and does not risk interfering with the overall exception mechanism.
Write your own Exception.
class MyException extends \Exception
{
}
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
}
}
I'm trying to catch an error from my JWT class but i can't do it inside the class, the only place i can get it is from my main caller.
I'm calling this class with the error from my "API" where i start with the routing:
$router = new Router();
$router->all('/users', function()
{
$controller = new Controllers\UserController();
$controller->start();
});
$router->run();
After that i have my controller that will call my "API" class:
class UserAPI extends BaseAPI
{
protected $user;
protected $apiBase = "user";
function __construct($request, $origin)
{
parent::__construct($request);
$this->user = new User();
}
protected function logout()
{
if( isset($this->request[$this->apiBase . 'Data']) )
{
return $this->usuario->login($this->request[$this->apiBase . 'Data']);
}
else
{
return Helpers::errorResponse("User data not informed", 200);
}
}
}
And finally i have the problem, the User class where i want to catch an error but it wont work:
class User extends SimpleModel
{
public function logout($userData)
{
try
{
//At this point i will get an error since the provided token is invalid on purpose
$jwt = JWT::decode($userData['token'], SECRET_KEY, ['HS512']);
}
//Wont hit here even for a miracle
catch (Exception $exception)
{
echo "Caught ExceptFoo\n";
echo "Message: {$exception->getMessage()}\n";
}
}
}
The only place i could catch this error was on the routing file, wich is my index.php file.
For the JWT class i'm using Firebase JWT.
Relative class names (like Exception in your example) are always rooted to the namespace you are within. If you don't define a namespace, \ is used. Consider:
<?php
namespace Foo;
use Vendor\Package\Bar;
try {
Bar::throwAnException();
} catch (Exception $ex) {
die((string)$ex);
}
Here we have two relative class paths: Bar and Exception. PHP resolves Bar via the use statement to the absolute class path \Vendor\Package\Bar. PHP doesn't have a use statement corresponding to Exception, so PHP assumes you mean \Foo\Exception.
Clearly this isn't your intent. Unfortunately, PHP is silent when this situation occurs. It's bitten me a few times.
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()));
}
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.
Is it possible to remove the trace elements from the __toString.
What I want is something like this.
class DBException extends PDOException
{
public function __toString()
{
return get_class($this) . " '{$this->getMessage()}' in {$this->getFile()}({$this->getLine()})\n";
}
}
I've tried the above method but it doesn't seem to work. Any ideas?
If I use a try catch block below as example, I still get the trace data.
try {
// Do something here
}catch(DBException $e) {
echo $e;
}
I would have thought that echoing $e would trigger the __toString method in my DBException class.
What I've done in the past when I want to process an Exception with PDO (in this case to ensure that connection details aren't displayed to the user) is to extend the PDO class and change the exception handler briefly:
class extendedPDO extends PDO
{
public static function exception_handler(Exception $exception)
{
// Output the exception details
die('<h1>Database connection error<p>' . $exception->getMessage() . '</p>');
}
public function __construct($dsn, $username=null, $password=null, $options=array())
{
// Temporarily change the PHP exception handler while we . . .
set_exception_handler(array(__CLASS__, 'exception_handler'));
// Create PDO
parent::__construct($dsn, $username, $password, $options);
// Change the exception handler back to whatever it was before
restore_exception_handler();
}
}
Something like this?
public function __toString() {
$return = "Class: ".get_class($this)
."\nMessage: ".$this->getMessage()
."\nFile: ".$this->getFile()
."\nLine: ".$this->getLine()."\n";
return $return;
}