Абстрактные свойства PHP


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

abstract class Foo_Abstract {
    abstract public $tablename;
}

class Foo extends Foo_Abstract {
    //Foo must 'implement' $property
    public $tablename = 'users';   
}
Author: Tamás Pap, 2011-10-03

8 answers

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

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

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

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

 127
Author: Mathieu Dumoulin, 2013-07-03 12:48:01

Нет, нет способа обеспечить это с помощью компилятора, вам придется использовать проверки во время выполнения (скажем, в конструкторе) для переменной $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;
  }
}
 42
Author: connec, 2011-10-03 18:13:55

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

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();
  }
}
 20
Author: ulkas, 2016-03-02 08:28:42

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

Кроме того, дочерний объект переопределяет свойство и методы родительского объекта, если они переопределены. Например, если свойство объявлено как 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;
 13
Author: fyrye, 2016-07-15 13:54:20

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

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

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

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

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

$foo = new Foo;
$foo->publicProperty = 'Bar';
 5
Author: hakre, 2011-10-03 12:31:23

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

Причина, по которой мы хотели бы получить свойства 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
Author: Marco Pallante, 2016-07-15 13:48:16

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

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

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 и должны передавать разные таблицы для разных объектов.

 2
Author: sevavietl, 2018-04-13 19:23:26

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

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

Ключевым моментом здесь является то, что строковое значение "пользователи" указывается и возвращается непосредственно в getTableName() в реализации дочернего класса. Функция имитирует свойство "только для чтения".

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

 1
Author: ck.tan, 2017-05-08 14:43:31