PHP abstract properties

前端 未结 9 1405
终归单人心
终归单人心 2020-12-07 16:16

Is there any way to define abstract class properties in PHP?

abstract class Foo_Abstract {
    abstract public $tablename;
}

class Foo extends Foo_Abstract          


        
相关标签:
9条回答
  • 2020-12-07 16:37

    if tablename value will never change during the object's lifetime, following will be a simple yet safe implementation.

    abstract class Foo_Abstract {
        abstract protected function getTablename();
    
        public function showTableName()
        {
            echo 'my table name is '.$this->getTablename();
        }
    }
    
    class Foo extends Foo_Abstract {
        //Foo must 'implement' getTablename()
        protected function getTablename()
        {
            return 'users';
        }
    }
    

    the key here is that the string value 'users' is specified and returned directly in getTablename() in child class implementation. The function mimics a "readonly" property.

    This is fairly similar to a solution posted earlier on which uses an additional variable. I also like Marco's solution though it can be a bit more complicated.

    0 讨论(0)
  • 2020-12-07 16:40

    The need for abstract properties can indicate design problems. While many of answers implement kind of Template method pattern and it works, it always looks kind of strange.

    Let's take a look at the original example:

    abstract class Foo_Abstract {
        abstract public $tablename;
    }
    
    class Foo extends Foo_Abstract {
        //Foo must 'implement' $property
        public $tablename = 'users';   
    }
    

    To mark something abstract is to indicate it a must-have thing. Well, a must-have value (in this case) is a required dependency, so it should be passed to the constructor during instantiation:

    class Table
    {
        private $name;
    
        public function __construct(string $name)
        {
            $this->name = $name;
        }
    
        public function name(): string
        {
            return $this->name;
        }
    }
    

    Then if you actually want a more concrete named class you can inherit like so:

    final class UsersTable extends Table
    {
        public function __construct()
        {
            parent::__construct('users');
        }
    }
    

    This can be useful if you use DI container and have to pass different tables for different objects.

    0 讨论(0)
  • 2020-12-07 16:46

    No, there is no way to enforce that with the compiler, you'd have to use run-time checks (say, in the constructor) for the $tablename variable, e.g.:

    class Foo_Abstract {
      public final function __construct(/*whatever*/) {
        if(!isset($this->tablename))
          throw new LogicException(get_class($this) . ' must have a $tablename');
      }
    }
    

    To enforce this for all derived classes of Foo_Abstract you would have to make Foo_Abstract's constructor final, preventing overriding.

    You could declare an abstract getter instead:

    abstract class Foo_Abstract {
      abstract public function get_tablename();
    }
    
    class Foo extends Foo_Abstract {
      protected $tablename = 'tablename';
      public function get_tablename() {
        return $this->tablename;
      }
    }
    
    0 讨论(0)
  • 2020-12-07 16:52

    PHP 7 makes it quite a bit easier for making abstract "properties". Just as above, you will make them by creating abstract functions, but with PHP 7 you can define the return type for that function, which makes things a lot easier when you're building a base class that anyone can extend.

    <?php
    
    abstract class FooBase {
    
      abstract public function FooProp(): string;
      abstract public function BarProp(): BarClass;
    
      public function foo() {
        return $this->FooProp();
      }
    
      public function bar() {
        return $this->BarProp()->name();
      }
    
    }
    
    class BarClass {
    
      public function name() {
        return 'Bar!';
      }
    
    }
    
    class FooClass extends FooBase {
    
      public function FooProp(): string {
        return 'Foo!';
      }
    
      public function BarProp(): BarClass {
        // This would not work:
        // return 'not working';
        // But this will!
        return new BarClass();
      }
    
    }
    
    $test = new FooClass();
    echo $test->foo() . PHP_EOL;
    echo $test->bar() . PHP_EOL;
    
    0 讨论(0)
  • 2020-12-07 16:55

    As you could have found out by just testing your code:

    Fatal error: Properties cannot be declared abstract in ... on line 3

    No, there is not. Properties cannot be declared abstract in PHP.

    However you can implement a getter/setter function abstract, this might be what you're looking for.

    Properties aren't implemented (especially public properties), they just exist (or not):

    $foo = new Foo;
    $foo->publicProperty = 'Bar';
    
    0 讨论(0)
  • 2020-12-07 16:57

    I've asked myself the same question today, and I'd like to add my two cents.

    The reason we would like abstract properties is to make sure that subclasses define them and throw exceptions when they don't. In my specific case, I needed something that could work with statically.

    Ideally I would like something like this:

    abstract class A {
        abstract protected static $prop;
    }
    
    class B extends A {
        protected static $prop = 'B prop'; // $prop defined, B loads successfully
    }
    
    class C extends A {
        // throws an exception when loading C for the first time because $prop
        // is not defined.
    }
    

    I ended up with this implementation

    abstract class A
    {
        // no $prop definition in A!
    
        public static final function getProp()
        {
            return static::$prop;
        }
    }
    
    class B extends A
    {
        protected static $prop = 'B prop';
    }
    
    class C extends A
    {
    }
    

    As you can see, in A I don't define $prop, but I use it in a static getter. Therefore, the following code works

    B::getProp();
    // => 'B prop'
    
    $b = new B();
    $b->getProp();
    // => 'B prop'
    

    In C, on the other hand, I don't define $prop, so I get exceptions:

    C::getProp();
    // => Exception!
    
    $c = new C();
    $c->getProp();
    // => Exception!
    

    I must call the getProp() method to get the exception and I can't get it on class loading, but it is quite close to the desired behavior, at least in my case.

    I define getProp() as final to avoid that some smart guy (aka myself in 6 months) is tempted to do

    class D extends A {
        public static function getProp() {
            // really smart
        }
    }
    
    D::getProp();
    // => no exception...
    
    0 讨论(0)
提交回复
热议问题