Абстрактные свойства PHP - PullRequest
115 голосов
/ 03 октября 2011

Есть ли способ определить свойства абстрактного класса в PHP?

abstract class Foo_Abstract {
    abstract public $tablename;
}

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

Ответы [ 9 ]

141 голосов
/ 03 октября 2011

Нет такой вещи, как определение свойства.

Вы можете объявить свойства только потому, что они являются контейнерами данных, зарезервированных в памяти при инициализации.

С другой стороны, функция может быть объявлена ​​(типы, имя, параметры) без определения (тело функции отсутствует) и, таким образом, может быть сделана абстрактной.

«Аннотация» указывает только на то, чточто-то было объявлено, но не определено, и поэтому перед его использованием необходимо определить это или оно станет бесполезным.

45 голосов
/ 03 октября 2011

Нет, нет способа обеспечить это с помощью компилятора, вы должны использовать проверки во время выполнения (скажем, в конструкторе) для переменной $tablename, например:

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

Чтобы применить это для всех производных классов Foo_Abstract, вы должны сделать конструктор Foo_Abstract final, предотвращающий переопределение.

Вместо этого вы можете объявить абстрактный метод получения:

abstract class Foo_Abstract {
  abstract public function get_tablename();
}

class Foo extends Foo_Abstract {
  protected $tablename = 'tablename';
  public function get_tablename() {
    return $this->tablename;
  }
}
21 голосов
/ 02 октября 2012

Как указано выше, такого точного определения не существует. Однако я использую этот простой обходной путь, чтобы заставить дочерний класс определить свойство "abstract":

abstract class Father 
{
  public $name;
  abstract protected function setName(); // now every child class must declare this 
                                      // function and thus declare the property

  public function __construct() 
  {
    $this->setName();
  }
}

class Son extends Father
{
  protected function setName()
  {
    $this->name = "son";
  }

  function __construct(){
    parent::__construct();
  }
}
18 голосов
/ 24 июня 2014

В зависимости от контекста свойства, если я хочу принудительно объявить свойство абстрактного объекта в дочернем объекте, мне нравится использовать константу с ключевым словом static для свойства в конструкторе абстрактного объекта или в setter / getterметоды.

Кроме этого, дочерний объект переопределяет свойство родительского объекта и методы, если они переопределены.Например, если свойство объявлено как protected в родительском элементе и переопределено как public в дочернем элементе, результирующее свойство является открытым.Однако, если свойство объявлено private в родительском объекте, оно останется private и не будет доступно ребенку.

http://www.php.net//manual/en/language.oop5.static.php

abstract class AbstractFoo
{
    public $bar;

    public function __construct()
    {
       $this->bar = static::BAR;
    }
}

class Foo extends AbstractFoo
{
    //const BAR = 'foobar';
}

$foo = new Foo; //Fatal Error: Undefined class constant 'BAR' (uncomment const BAR = 'foobar';)
echo $foo->bar;
6 голосов
/ 15 июля 2016

Я задал себе тот же вопрос сегодня, и я хотел бы добавить свои два цента.

Причина, по которой мы хотели бы abstract свойства, состоит в том, чтобы убедиться, что подклассы определяют их и генерируют исключениякогда они этого не делают.В моем конкретном случае мне нужно было что-то, что могло бы работать с static союзником.

В идеале мне хотелось бы что-то вроде этого:

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.
}

В итоге я получил эту реализацию

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
{
}

Как видите, в A я не определяю $prop, но использую его в static геттере.Поэтому следующий код работает

B::getProp();
// => 'B prop'

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

В C, с другой стороны, я не определяю $prop, поэтому я получаю исключения:

C::getProp();
// => Exception!

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

Я долженвызовите метод getProp(), чтобы получить исключение, и я не могу получить его при загрузке класса, но это довольно близко к желаемому поведению, по крайней мере, в моем случае.

Я определяю getProp() как final чтобы избежать того, что какой-нибудь умный парень (он же через 6 месяцев) испытывает соблазн сделать

class D extends A {
    public static function getProp() {
        // really smart
    }
}

D::getProp();
// => no exception...
5 голосов
/ 13 апреля 2018

Потребность в абстрактных свойствах может указывать на проблемы проектирования.Хотя многие ответы реализуют вид шаблона метода шаблона , и он работает, он всегда выглядит немного странно.

Давайте посмотрим на оригинальный пример:

abstract class Foo_Abstract {
    abstract public $tablename;
}

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

Чтобы отметить что-то abstract, нужно указать, что это необходимо.Ну, обязательное значение (в данном случае) является обязательной зависимостью, поэтому ее следует передать конструктору во время создания экземпляра :

class Table
{
    private $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function name(): string
    {
        return $this->name;
    }
}

Тогдаесли вам действительно нужен более конкретный именованный класс, вы можете наследовать так:

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

Это может быть полезно, если вы используете DI-контейнер и вам нужно передавать разные таблицы для разных объектов.

5 голосов
/ 03 октября 2011

Как вы могли бы узнать, просто протестировав свой код:

Неустранимая ошибка: свойства не могут быть объявлены абстрактными в ... в строке 3

Нет,нет.Свойства не могут быть объявлены абстрактными в PHP.

Однако вы можете реализовать абстрактную функцию получения / установки, это может быть тем, что вы ищете.

Свойства не реализованы (особенно публичные свойства), они просто существуют (или нет):

$foo = new Foo;
$foo->publicProperty = 'Bar';
1 голос
/ 04 января 2019

PHP 7 упрощает создание абстрактных «свойств». Как и выше, вы создадите их, создав абстрактные функции, но с помощью PHP 7 вы можете определить тип возвращаемого значения для этой функции, что значительно упрощает создание базового класса, который может расширять каждый.

<?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;
1 голос
/ 08 мая 2017

, если значение имени таблицы никогда не изменится в течение времени жизни объекта, следующее будет простой, но безопасной реализацией.

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';
    }
}

ключом здесь является то, что строковое значение 'users' указано и возвращается непосредственно в getTablename () в реализации дочернего класса. Функция имитирует свойство «только для чтения».

Это довольно похоже на опубликованное ранее решение, в котором используется дополнительная переменная. Мне также нравится решение Марко, хотя оно может быть немного сложнее.

...