D. Petrov
D. Petrov

Reputation: 1167

Make abstract class method only publicly accessible from child classes

First off, we're talking about PHP 7.4.10, but any general intel is appreaciated!

Summarized question: I'd like to define a static method in an abstract class in such a way, that the method can only be called publicly from child classes extending the abstract class but not from the abstract class itself. Sorry if I'm being too basic here, but I literally have been searching for hours for an answer and can't even find any discussions on the topic.

Let's consider the following example (exaplantion in the comments). I want to be able to call Apple::printName() and Pear::printName() but not Fruit::printName().

abstract class Fruit
{
    /*
     * Oblige every child class to define a string name, nothing unusual
     */
    protected abstract static function name() : string;

    /*
     * The problem is with the access modifier of this method here
     *** 
     * If it is public, everything is fine with Apple::printName() and Pear::printName(), 
     * but one can call Fruit::printName() from outside,
     * resulting in PHP Error: Cannot call abstract method Fruit::name()
     * this is still sort of okay, since an error will be thrown anyway, 
     * but I don't want the runtime to even enter the method's body
     * I'd like to get an access restriction error.
     *** 
     * If it is protected, then we automatically can't call Apple::printName nor Pear::printName()
     ***
     * So, is there a way to define the parent static method only publicly accessible from child classes without copying code?
     */
    public static function printName()
    {
        return "My name is: " . static::name();
    }
}

class Apple extends Fruit
{
    protected static function name() : string
    {
        return "apple";
    }
}

class Pear extends Fruit
{
    protected static function name() : string
    {
        return "pear";
    }
}

echo Apple::printName(); //prints "My name is: apple"
echo Pear::printName(); //prints "My name is: pear"
echo Fruit::printName(); //PHP Error: Cannot call abstract method Fruit::name() at line...

I'm also open for any alternative approaches as to how one might achieve the desired behaviour.

Upvotes: 1

Views: 1180

Answers (3)

D. Petrov
D. Petrov

Reputation: 1167

So after reading all the answers I came up with a workaround-ish solution, finally deciding in favor of @chilliNUT's suggestion. I ended up proceeding as follows:

abstract class Fruit
{
    protected abstract static function name() : string;

    /*
     * Basically, if you call the default method from any child (not necessarily a direct child) class, no problems,
     * but if you call it directly from the baseclass, you get the somewhat acceptable exception :)
     */
    public static function printName()
    {
        if(!is_subclass_of(static::class, self::class))
        {
            throw new \BadMethodCallException("Can't call base Fruit class methods directly!");
        }

        return "My name is: " . static::name();
    }
}

//those are abstract now, just to match the intention of them being utility singletons
abstract class Apple extends Fruit
{
    protected static function name() : string
    {
        return "apple";
    }
}

abstract class Pear extends Fruit
{
    protected static function name() : string
    {
        return "pear";
    }
}

Anyways, from @Rain's answer I could see why this tends to be a bad practice and actually not the best direction to keep developing in, so I'll consider different strategies for what I'm trying to achieve. My curiosity has been satisfied at the end of the day! Thank you all for the participation!

Upvotes: 1

Rain
Rain

Reputation: 3926

public static function printName() {}

This is a definition of a concrete method. One might think that it's okay to call this method since it's non abstract public method. So why Fruit has it if it does nothing to it ?

So one solution is to define a trait and use it only inside those that actually need it ?

trait Printer { 
    
    public static function printName()
    {
        return "My name is: " . static::name();
    }   
}

abstract class Fruit
{
    protected abstract static function name() : string;
}

class Apple extends Fruit
{
    use Printer;
    protected static function name() : string
    {
        return "apple";
    }
}

class Pear extends Fruit
{
    use Printer;
    protected static function name() : string
    {
        return "pear";
    }
}

If however, your Fruit is just a definition for child classes like you mentioned in the comment and you happen to have PHP 8 :) then maybe just don't make printName static

public function printName()
{
    return "My name is: " . static::name();
}

And

$a = new Apple();
$p = new Pear();

echo $a->printName();
echo $p->printName();

Now echo Fruit::printName(); will give you the nice Fatal error you've been looking for.

Fatal error: Uncaught Error: Non-static method Fruit::printName() cannot be called statically

Upvotes: 1

chiliNUT
chiliNUT

Reputation: 19573

You can check if your instance is a subclass or not and then bail if it isnt

abstract class A1 {
    public static function childrenOnly() {
        return is_subclass_of(new static, 'A1');
    }
}

class A2 extends A1 {
}
Al::childrenOnly(); // errors out
A2::childrenOnly(); // true

Upvotes: 1

Related Questions