OOP (PHP) - Force overridden method to call according parent-method
Asked Answered
M

5

11

I have a general problem with this use case: I have a class A. This class has a non-abstract method doStuffCallback() which could be overridden but it's not necessary for every subclass. But: I want to ensure that if the method is overriden the subclass-method must call the parents method.

Example:

abstract class A {
    private function doStuff() {
        $this->doStuffCallback();
    }

    protected function doStuffCallback() {
        // IMPORTANT CODE HERE
    }
}

class B extends A {
    protected function doStuffCallback() {
        parent::doStuffCallback(); // I want to enforce this because the parents method code is important

        // ALSO IMPORTANT CODE
    }
}

Because the overridden method does the same thing it would be very ugly to define two methods for the same responsibility and a private helper-method which calls both. Like this:

abstract class A {
    private function doStuff() {
        $this->callDoStuffCallback();
    }

    private function callDoStuffCallback() {
        $this->internalDoStuffCallback();
        $this->doStuffCallback();

        // This is VERY ugly
    }

    private function internalDoStuffCallback() {
        // IMPORTANT CODE HERE
    }

    protected function doStuffCallback() {}
}

class B extends A {
    protected function doStuffCallback() {
        // IMPORTANT CODE
    }
}

This is really ugly and laborious. So my question: Is there a way in PHP to force overriden methods to call the parents method?

Metathesis answered 4/10, 2015 at 18:48 Comment(0)
I
7

No. There is no such language feature in PHP; this restriction is not possible in most subtype-'OO' languages.

Instead programs must rely on explicit documentation contracts; and hopefully, unit testing to ensure conformance.


Guards may also be employed such that, at some point by and by when a method on the parent class is used, it could throw an exception if the 'current state' is not valid (eg. such and such a method has not been called yet). This may also be made more explicit by making the subclass required to call (as defined in the documentation contract) some special method, instead of simply the overriden super method. However, such is outside of any type system.

While the self:: scope could be used (eg. call non-overriden method which calls overriden method), this would involve further magic (eg. some stack state) to avoid infinite recursion loops; and it would be as easy to accidentally omit usage.

My recommendation is to call a (private) method that calls this 'maybe overriden' method in relationship to whatever logic applies, as shown in the example (although hopefully with more task specific tames). Then the (protected) overriden method is not expected or required to handle any of the special logic itself; nor is it meant to be called directly outside of the context established by the parent class - it is just what it currently claims to be, a special callback.

Interdict answered 4/10, 2015 at 19:5 Comment(1)
Thanks for your answer! I think exactly the same. I believe that the OOP we use nowadays isn't complete yet. There are so many aspects left which are important but not handled.Metathesis
R
5

I know this is an old topic but I was asking myself the same question and what I did is :

abstract class A {
    private function doStuff() {
        $this->doStuffCallback();
    }

    final protected function doStuffCallback() {
        // IMPORTANT CODE HERE

        $this->callNewFunction();
    }

    abstract protected function callNewFunction();
} 

class B extends A {
    protected function callNewFunction() {
        // ALSO IMPORTANT CODE
    }
}

So basically I would mark as "final" the function you wish to force the code for every child and then call a new "Abstract" function to force the childs to implement it. If you do not wish to force the new "Abstract" function, simply don't make it abstract.

Edit : This is basically @Fabian Schmengler's answer but more concrete with your example.

Ruffner answered 19/3, 2018 at 16:11 Comment(1)
Yes, this approach is also known as the Template method pattern. But as mentioned I don't want to enforce all derived types to implement this method. Only if they want to override a specific method I want to make sure the parents sibling gets called too.Metathesis
M
4

I tend to disagree with "This is VERY ugly". It is the standard way of handling this use case and a variant of the Template Method Pattern.

Now I am just guessing because you did not provide a real example but if you say that the two methods "do the same thing", there might be something wrong with your design. If they do the same thing, why is calling the parent implementation necessary if the subclass does the same thing in a different way? To me it sounds like the method actually does more than one thing and you might be able to break it down into several parts that can be overridden individually (or not, then make them private or final).

Malay answered 5/10, 2015 at 7:0 Comment(1)
Yes, "the same thing" isn't the correct term here. These methods have the same responsibility but the subclass-method extends it for it's own additional requirements.Metathesis
A
0

If someone nowadays come across this ancient question, I think there's a possible solution using destructors.

abstract class Authorized_Access
{    
    private bool $is_authorized = false;

    /**
     * All child classes are forced to call parent::__construct() during it's lifetime
     * 
     */
    public function __construct($something_needed_for_authorization)
    {
        $this->authorize($something_needed_for_authorization);
    }


    /**
     * Our Authorization method
     */
    private function authorize($args)
    {
        // DO WHAT YOU NEED HERE...

        // IF AUTHORIZED
        $this->is_authorized = true;
    }




    /**
     * Throw fatal error if the object wasn't authorized until now
     */
    public final function __destruct()
    {
        if ( ! $this->is_authorized ) {
            trigger_error("Access not Authorized!", E_USER_ERROR);
        }
    }
}

And the child class could be :

class Admin_Access extends Authorized_Access
{
     public function __construct($args)
     {
        $something_needed_for_authorization = $args['whatever-you-need'];
        parent::__construct($something_needed_for_authorization);

        // SOME MORE CODE...
     }

     // AND MAYBE SOME OTHER METHODS...
}

So basically, in the end of php execution, all objects are being destructed (if not earlier). The parent class Authorized_Access will throw Fatal Error if the child object has not called the parent method until then.

So we can say that "child class is forced to call the parent's method".

You could also make the authorize() method public/protected and call it directly from child object instead of parent's constructor (but for me it makes more sense to rather call the constructor). Idea remains the same...

Arleanarlee answered 21/3 at 15:30 Comment(0)
T
-3

No, you can access, you can use method for parent, like this

<?php

class A {

   function s1($p1) {
      echo 's1: '.$p1;
   }
}


class B extends A {

    public function callParent($method, $p1) {
        parent::$method($p1);
    }
}


$b = new B();

$b->callParent('s1', 'param1');

or replace extending on magic methods __call and etc. https://github.com/StagnantIce/php_extend_magic/blob/master/AExtendClass.php

Trapes answered 4/10, 2015 at 19:3 Comment(1)
How does this solve my problem? I think this is a less intelligent solution than just to expect the subclass doing the right implementation with documentation (call the parent-method when overriding). I mean your examples requires that EVERY CLIENT of the class and not just the implemented class itself have to call the parent method reflexion-wise. This is the most counter-intuitive and unsafe approach I've ever seen. No offense :)Metathesis

© 2022 - 2024 — McMap. All rights reserved.