elb98rm
elb98rm

Reputation: 800

Forced override of parent class method PHP

I have a situation that, despite a fair amount of googling/SO'ing, seems to escape me. Here's the issue in a nutshell:

Situation

PHP 5.3+ (but not specific yet - I'm actually running 5.5/6 but the environment is aiming to be flexible).

I'm attempting to write a solid implementation that will force other developers into best practice.

I have a parent class:

class A {
    public function doSomething() {
        // does something
    }
}

The standard use case is to use A::doSomething.

In special cases, an extension is required.

class B extends A {
    public function doSomething() {
        // does something .. AND
        $this->doSomethingElse()
    }

    private function doSomethingElse() {
        // does something else
    }
}

If I want to do something, then I can A::doSomething.
If I want to do something and do something else, then I can B::doSomething.

In order to fit the remit of forc[ing] other developers into best practice, I have implemented an interface:

interface C {

    public function doSomething() {
        // does something else
    }

    public function doSomethingElse() {
        // does something else
    }
}

Class B then implements Interface C. Sounds simple enough?

class B implements C {
    public function doSomething() {
        // does something .. AND
        $this->doSomethingElse()
    }

    private function doSomethingElse() {
        // does something else
    }
}

I don't want to assume that the child B implements C.
There could be a number of completely valid reasons to extend A to E, where E does not want to implement C.

My actual question:

If I do the following, and don't implement the doSomething method, the following has no errors:

class B implements C {
    private function doSomethingElse() {
        // does something else
    }
}

.. which is as you'd expect; A provides doSomething for B to satisfy the requirement for C.

The issue is that this would allow someone to write an incomplete method that would fail to throw errors. In the above case B::doSomething does not call B::doSomethingElse.

Is it possible for a method, interface (or similar technique) to require a child to implement a method at the current inheritance level?

Of course I can write notes, documentation etc... but the point of the interface is to make people do it correctly!

Other people who've asked similar questions were either (sometimes understandibly) misunderstood or the stock answer was "the architecture is wrong"... Which I'd like to challenge as the example below illustrates:

Here's an example with real world items:

A might have a method called, say... A::attemptToBuy
C would enforce methods like ID required.

Beer and Vegetable are both a type of ShopItem.
This is a standard and logical inheritance.

Beer needs to use BoozeInterface (or whatever is appropriate). This would also be true of the not yet implement, but possibly future requirement of Wine, Spirit etc etc.

Some items fit perfectly in to a generic ShopItem class. They need no extra functionality. ShopItem::attemptToBuy is a normal use case.

However - I have to rely on someone remembering to override the Wine::attemptToBuy and Spirit::attemptToBuy.

As you can see - if I want to really lock it down, I'd ideally be able to force an override at the current level of inheritance.
(I'm sure there are better examples of this, but I've tried to make it as obvious as possible).

I'm happy if the answer is "no, you can't do that"... I just want to know if you can. I have the code working, but just via a direct override that is unforced. But I want it to be forced!

Thanks in advance. Rick

Upvotes: 4

Views: 4504

Answers (2)

CD001
CD001

Reputation: 8472

I think you're going about it backwards - I'm not sure you want to enforce an override (easily done with abstract methods) in the client-developer created sub-classes, you want to provide a method that cannot be overridden in your provided abstract classes to ensure it does what you want it to?

If you provide the abstract classes ShopItem and BoozeItem thus:

<?php

// base ShopItem with default `attemptToBuy` functionality
abstract class ShopItem {

    public function attemptToBuy() {
        echo "buying ShopItem";
    }
}

// Booze sub-class, with an override on `attemptToBuy` 
// (e.g. applying age restrictions check)
abstract class BoozeItem extends ShopItem {

    final public function attemptToBuy() {
        echo "buying BoozeItem";
    }
}

?>

The client developer can then happily create their Beer class extending the BoozeItem sub-class.

<?php

class Beer extends BoozeItem { }

$oBevvy = new Beer();
$oBevvy->attemptToBuy();

?>

Outputs: buying BoozeItem

Since BoozeItem::attemptToBuy() has been declared final it can't be overridden by Booze::attemptToBuy() (trying to create that method will cause an error) - so your object functionality is locked down.

Beer can be extended to Lager or Ale (for instance) and those will inherit attemptToBuy() from BoozeItem (since Beer extends BoozeItem) but they won't be allowed to override that method as it's declared final - it'll simply be inherited with the defined behavior. This isn't what you want but it's probably the closest you'll get I think.

Upvotes: 0

versalle88
versalle88

Reputation: 1137

Why not make class A abstract and require those methods that way:

abstract class A
{
    abstract protected function doSomething() {}
    abstract protected function doSomethingElse() {}
}

Now any class that extends class A must define those two methods.

Cheers!

Upvotes: 2

Related Questions