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

126

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

abstract class Foo_Abstract {
    abstract public $tablename;
}

class Foo extends Foo_Abstract {
    //Foo must 'implement' $property
    public $tablename = 'users';   
}
Тамаш Пап
источник

Ответы:

154

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

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

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

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

Матье Дюмулен
источник
59
Нет очевидной причины, по которой слово «абстрактный» нельзя использовать для статических свойств - но с несколько другим значением. Например, это может указывать на то, что подкласс должен предоставить значение для свойства.
frodeborli 05
2
В TypeScript есть абстрактные свойства и средства доступа . Печально, что в php это невозможно.
Илья Зеленько 04
52

Нет, нет способа принудительно применить это с помощью компилятора, вам придется использовать проверки времени выполнения (скажем, в конструкторе) для $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;
  }
}
Конек
источник
Хорошая функция, мне нравится, как вы реализуете абстрактные свойства.
Mathieu Dumoulin
4
Это потребует от вас сделать конструктор окончательным в абстрактном базовом классе.
hakre 03
3
Некоторое объяснение: если вы выполняете проверку внутри конструктора и если она должна быть обязательной, вам необходимо убедиться, что она выполняется при каждом создании экземпляра. Поэтому вам необходимо предотвратить его удаление, например, путем расширения класса и замены конструктора. Окончательное ключевое слово вы бы позволить сделать это.
hakre 03
1
Мне нравится решение "абстрактного геттера". Когда вы объявляете функцию в абстрактном классе, вы должны объявить сам класс абстрактным. Это означает, что класс непригоден для использования, если он не расширен и не реализован полностью. При расширении этого класса вы должны предоставить реализацию для функции «получения». Это означает, что вы также должны создать связанное свойство внутри расширяющегося класса, потому что функция должна что-то возвращать. Следуя этому шаблону, вы получите такой же результат, как если бы вы объявили абстрактное свойство, это также чистый и ясный подход. Вот как это делается на самом деле.
Саливан
1
Использование абстрактного получателя также позволяет вам реализовать его путем генерации значения, в отличие от возврата постоянного значения, когда это имеет смысл. Абстрактное свойство не позволит вам этого сделать, особенно статическое свойство.
Tobia
27

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

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

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

abstract class AbstractFoo
{
    public $bar;

    final 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;
fyrye
источник
4
Самое элегантное решение здесь
Дженни
24

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

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();
  }
}
ulkas
источник
Элегантно, но не решает проблемы с staticнедвижимостью.
Робберт
1
Я не думаю, что у вас могут быть частные для абстрактных методов.
Zorji
@ Phate01, насколько я понимаю, в самом комментарии говорится the only "safe" methods to have in a constructor are private and/or final ones, разве мой обходной путь не такой?
Я
4
Это выглядит красиво, но не заставляет дочерний класс устанавливаться $name. Вы можете реализовать эту setName()функцию без ее фактической настройки $name.
JohnWE
3
Я думаю, что использование getNameвместо $nameработает лучше. abstract class Father { abstract protected function getName(); public function foo(){ echo $this->getName();} }
Хамид
7

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

Причина, по которой нам нужны 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...
Марко Палланте
источник
Это очень гениальный прием. Надеюсь, этого не потребуется делать в будущем.
CMCDragonkai
6

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

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

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

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

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

$foo = new Foo;
$foo->publicProperty = 'Bar';
hakre
источник
6

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

Давайте посмотрим на исходный пример:

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

sevavietl
источник
3

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

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

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 () в реализации дочернего класса. Функция имитирует свойство «только для чтения».

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

ck.tan
источник