Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic method to catch and handle exceptions thrown in child classes

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.

like image 737
lesssugar Avatar asked Aug 29 '17 09:08

lesssugar


Video Answer


1 Answers

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??")

like image 64
Pieter van den Ham Avatar answered Oct 01 '22 01:10

Pieter van den Ham