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

2
У TypeScript є абстрактні властивості та аксесуари . Сумно, що в php це неможливо.
Илья Зеленько

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_Ab абстракт, вам доведеться створити конструктор Foo_Ab абстракт 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;
  }
}

Приємна функція, мені подобається, як ви реалізуєте абстрактні властивості.
Матьє Думулін

4
Це вимагатиме від вас завершення конструктора в абстрактному базовому класі.
хакре

3
Деякі пояснення: Якщо ви робите перевірку всередині конструктора, і якщо вона повинна бути обов'язковою, вам потрібно переконатися, що вона виконується при кожному екземплярі екземпляра. Тому вам потрібно не допустити його видалення, наприклад, розширенням класу та заміною конструктора. Остаточне ключове слово ви б дозволити зробити це.
хакре

1
Мені подобається рішення "абстрактного геттера". Коли ви оголошуєте функцію в абстрактному класі, ви повинні оголосити сам клас абстрактним. Це означає, що клас є непридатним, якщо не буде розширено та повністю реалізовано. Розширюючи цей клас, ви повинні забезпечити реалізацію функції "getter". Це означає, що ви також повинні створити пов'язане властивість всередині класу, що розширюється, оскільки функція повинна щось повертати. Слідуючи цій схемі, ви отримуєте такий самий результат, як якщо б ви оголосили абстрактне властивість, це також чистий і чіткий підхід. Ось як це робиться насправді.
Саліван

1
Використання абстрактного геттера також дозволяє реалізувати його, генеруючи значення, на відміну від повернення постійного значення, коли це має сенс зробити. Абстрактна властивість не дозволила б зробити це, особливо статична властивість.
Тобія

27

Залежно від контексту властивості, якщо я хочу змусити декларувати властивість абстрактного об'єкта в дочірньому об'єкті, мені подобається використовувати константу з staticключовим словом для властивості в конструкторі абстрактних об'єктів або методах setter / getter. Ви можете додатково використовувати, 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;

4
Найелегантніше рішення тут
Jannie Theunissen

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();
  }
}

Елегантний, але не вирішує питання щодо staticвластивостей.
Роберт

1
Я не думаю, що ти можеш мати приватне для абстрактних методів.
Zorji

@ Phate01, як я це розумію, у коментарі, який він сам стверджує the only "safe" methods to have in a constructor are private and/or final ones, хіба я не вирішував такий випадок? я використовую приватних осіб у ньому
ulkas

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

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 і вам доведеться передавати різні таблиці для різних об'єктів.


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

якщо значення імені таблиці ніколи не зміниться протягом життя об’єкта, наступне буде простою, але безпечною реалізацією.

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

Це досить схоже на рішення, розміщене раніше, в якому використовується додаткова змінна. Мені також подобається рішення Марко, хоча воно може бути трохи складніше.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.