Assuming I have a parent class:
class Parent {
//...
}
and a child class with methods:
class Child extends Parent {
public function foo() {
// process and return data
}
public function bar() {
// process and return data
}
// way more methods...
}
then is there a generic way in PHP to handle any exceptions thrown in the child methods? For example, a handler in the Parent class? Or do I need to wrap all the methods bodies in a separate try-catch block?
What I want to achieve, is to return an empty array() if any of the child methods throw any kind of exception.
Yes, this is possible. Well, a parent cannot know all possible child methods, but it can know when an undefined method is called by implementing the __call magic method.
We can use this method to create a try-catch "wrapper" dynamically.
Add this method to your parent:
public function __call($method, $args)
{
// If method call ends with 'Safe'
$isSafeMethod = substr($method, -strlen('Safe')) === 'Safe';
if (!$isSafeMethod) {
trigger_error('Call to undefined method '.__CLASS__.'::'.$method.'()', E_USER_ERROR);
return null;
}
// Strip 'Safe' suffix from method name
$wrappedMethodName = substr($method, 0, strpos($method, 'Safe'));
try {
return $this->$wrappedMethodName($args);
} catch (Exception $e) {
return [];
}
}
Now, anytime you want to invoke this try-catch wrapper, just append "Safe" to the method name that you want to wrap. Full code + example:
class TestParent {
public function __call($method, $args)
{
// If method call ends with 'Safe'
$isSafeMethod = substr($method, -strlen('Safe')) === 'Safe';
if (!$isSafeMethod) {
trigger_error('Call to undefined method '.__CLASS__.'::'.$method.'()', E_USER_ERROR);
return null;
}
// Strip 'Safe' suffix from method name
$wrappedMethodName = substr($method, 0, strpos($method, 'Safe'));
try {
return $this->$wrappedMethodName($args);
} catch (Exception $e) {
return [];
}
}
}
class TestChild extends TestParent {
public function throwingMethod()
{
throw new RuntimeException();
}
public function succeedingMethod()
{
return 'Success';
}
}
$child = new TestChild();
// With 'Safe' try-catch invoked in parent
var_dump($child->throwingMethodSafe()); // Empty array
var_dump($child->succeedingMethodSafe()); // 'Success'
// Without 'Safe' try-catch
var_dump($child->throwingMethod()); // throws RuntimeException as expected
Output in 3v4l.org
Sidenote: Please don't catch the Exception class as it is too general and will make debugging a living hell later on ("Why is this method returning an array??")
From my personal experience create customized exception handler and return empty array if you get that exception.
These link will help you to understand the exception handling in PHP:
https://www.w3schools.com/php/php_exception.asp
http://php.net/manual/en/language.exceptions.php
Related
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
I have a class in php that works with the chainning method, but the problem is that I want to chain the methods in some order.
class Chain {
public function foo () {
return $this;
}
public function bar () {
return $this;
}
public function some () {
return $this;
}
}
So, if I use this class, then I can chain this methods in 9 different ways (all the possible combinations of 3 elements)
But what happen if I determine that the method some always must to be chained after foo or bar and not in other way?
$chain = new Chain();
$chain->foo->bar(); //works; i.e: the method some is optional
$chain->foo()->bar()->some(); //works
$chain->bar()->foo()->some(); //works
$chain->some()->bar()->foo(); //throws an exception
I think that I can do this setting boolean values, something like: when the method foo or bar are called, then I set the value to some var to true, and when the developer calls the some function, if that var is false, then throws an exception, otherwise is allowed to continue.
But I need something more elegant, such as pattern or a built-in solution.
There is another way to do it?
The very rough example I imagine will still have some lines of code in each method
<?php
class Chain {
private $_register = array();
public function foo () {
$this->register(__METHOD__);
return $this;
}
public function bar () {
$this->register(__METHOD__);
return $this;
}
public function some () {;
$this->verify('foo'); // foo() should be called before some();
$this->register(__METHOD__);
echo 'it\'s ok';
return $this;
}
public function verify($method) {
if(array_key_exists($method, $this->_register) && $this->_register[$method] == true) {
return true;
}
else {
throw new Exception('Some exception');
}
}
public function register($method) {
$method = str_replace(__CLASS__.'::', '', $method);
$this->_register[$method] = true;
}
}
What do we do here - we have a register() and verify() methods. (they can be helpers, but for the current purpose I added them in the class.
Each method should have before it's returning value a register to itself. Calling $this->register(__METHOD__) from foo() will add in the private array 'foo' => true.
The verify() method checks if foo exist as array key and if its value is true. If it is - the script will continue. Otherwise - throws exception.
In this case:
$chain = new Chain();
$chain->bar()->some()->foo(); //throws an exception
Fatal error: Uncaught exception 'Exception' with message 'Some
exception' in ...
$chain = new Chain();
$chain->foo()->some()->foo(); // ok
it's ok
The problem here is that we establish a "convention". You need to pass __METHOD__ to the register function so after it replace the classname it will add only the method name in the array. So later, in the function where you need to verify if one or more functions are called before this, you need to use the method name as string i.e. $this->verify('foo');
Ofcourse you can play different scenarios without stripping and testing with strpos() or adding () after the methodname for easier recognition if you are verifying a method or smth else.
But at least it will save you from making for each method, different variable to fill i.e.
function foo() {
$this->_foo = true;
return $this;
}
function bar() {
$this->_bar = true;
return $this;
}
Forcing the caller to stick to a certain order of calls just as an end to itself is hardly useful at all. Supposedly what you're really interested in is to make sure the state of the object is valid when you call some() and throw an exception if it's not. In that case, yes, you would check certain indicators of your object's state and throw an exception when this state does not fulfil the requirements that some() may be called. As a concrete example:
$api = new SomeAPI;
$api->setUserID($id);
$api->setSecretKey($secret);
$api->call('something');
Here call() would check that the user id and access key has been set, otherwise it can't do its job. Whether these calls are chained or not is irrelevant and just a syntactic detail.
Alternatively, you could return certain objects of other (sub) classes from your methods which physically make it impossible to call certain methods on them if certain conditions haven't been met:
public function bar() {
if ($this->foo) {
return new SubFoo($this->foo);
} else {
return new SubBar;
}
}
This may be overly complicated though.
[edit] updated the title to more accurately reflect the problem
The problem I am trying to solve is this: I need to know if a method was called via parent:: and while I can use debug_backtrace it seems like there must be a better way to do this.
I've been looking into late static binding but perhaps I don't understand it well enough to fathom a solution.
The method in question is __call so I can't simply pass in an extra parameter as its an error to have more or less then exactly two.
The reason for trying to solve this problem is that the parent class has __call but the child may or may not have _call. If the child doesn't have it, and the parent doesn't dispatch the call, then I'd like to throw an exception or error. If the child does have the method then I'll return false (no we didn't handle this) and let the child _call method carry on.
So far my only working solution is to have the child call parent::__call wrapped in a try/catch block and have the parent throw an exception by default if it does not route the request.
ie.
class Parent {
public function __call( $method, $params ) {
if( preg_match( $this->valid, $method ) {
$this->do_stuff();
// if child has a call method, it would skip on true
return true;
}
elseif( ** CHILD HAS CALL METHOD ** ) {
// this would let the child's _call method kick in
return false;
}
else {
throw new MethodDoesNotExistException($method);
}
}
}
class Child extends Parent {
public function __call( $method, $params ) {
if( ! parent::__call( $method, $params ) ) {
do_stuff_here();
}
}
}
While throwing an exception if the parent doesn't handle the method works, I'm just trying to see if there's a more elegant solution, as using exceptions for flow-controll doesn't seem quite right. But neither does using a stacktrace to figure out the caller, either.
This should do in your parent class:
if (__CLASS__ != get_class($this))
I'm not entirely sure if this fits your needs and I also consider this kind of hacks to be really bad from the OO design point of view. However, it was a fun thing to code :)
<?php
class ParentClass
{
public function __call( $method, $params )
{
if($method === 'one')
{
echo "Parent\n";
return true;
}
elseif($this->shouldForwardToSubclass($method))
{
return false;
}
else
{
throw new Exception("No method");
}
}
protected function shouldForwardToSubclass($methodName)
{
$myClass = get_class($this);
if (__CLASS__ != $myClass)
{
$classObject = new ReflectionClass($myClass);
$methodObject = $classObject->getMethod('__call');
$declaringClassName = $methodObject->getDeclaringClass()->getName();
return $myClass == $declaringClassName;
}
else
{
return false;
}
}
}
class ChildClass1 extends ParentClass {
public function __call( $method, $params ) {
if( ! parent::__call( $method, $params ) )
{
echo "Child handle!\n";
}
}
}
class ChildClass2 extends ParentClass {
}
later doing:
$c = new ChildClass1();
$c->one();
$c->foo();
$c = new ChildClass2();
$c->foo();
would yield:
Parent
Child handle!
PHP Fatal error: Uncaught exception 'Exception' with message 'No method' in /home/andres/workspace/Playground/test.php:18
Stack trace:
#0 /home/andres/workspace/Playground/test.php(58): ParentClass->__call('foo', Array)
#1 /home/andres/workspace/Playground/test.php(58): ChildClass2->foo()
#2 {main}
HTH
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.