Właściwości abstrakcyjne PHP

126

Czy istnieje sposób na zdefiniowanie abstrakcyjnych właściwości klas w PHP?

abstract class Foo_Abstract {
    abstract public $tablename;
}

class Foo extends Foo_Abstract {
    //Foo must 'implement' $property
    public $tablename = 'users';   
}
Tamás Pap
źródło

Odpowiedzi:

154

Nie ma czegoś takiego jak definiowanie właściwości.

Właściwości można deklarować tylko, ponieważ są to kontenery danych zarezerwowanych w pamięci podczas inicjalizacji.

Z drugiej strony funkcja może być zadeklarowana (typy, nazwa, parametry) bez zdefiniowania (brak treści funkcji), a zatem może być abstrakcyjna.

„Abstrakt” oznacza jedynie, że coś zostało zadeklarowane, ale nie zostało zdefiniowane, dlatego przed użyciem należy to zdefiniować lub stanie się bezużyteczne.

Mathieu Dumoulin
źródło
58
Nie ma oczywistego powodu, dla którego słowo „abstrakcja” nie mogło być użyte na statycznych właściwościach - ale w nieco innym znaczeniu. Na przykład może wskazywać, że podklasa musi podać wartość właściwości.
frodeborli
2
W TypeScript istnieją abstrakcyjne właściwości i metody dostępu . Szkoda, że ​​w php jest to niemożliwe.
Илья Зеленько
52

Nie, nie ma sposobu, aby wymusić to za pomocą kompilatora, musiałbyś użyć sprawdzeń w czasie wykonywania (powiedzmy w konstruktorze) dla $tablenamezmiennej, np .:

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

Aby wymusić to dla wszystkich klas pochodnych Foo_Abstract, musiałbyś utworzyć konstruktor Foo_Abstract final, zapobiegając nadpisywaniu.

Zamiast tego możesz zadeklarować abstrakcyjny getter:

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

class Foo extends Foo_Abstract {
  protected $tablename = 'tablename';
  public function get_tablename() {
    return $this->tablename;
  }
}
Connec
źródło
Niezła funkcja, podoba mi się sposób implementacji właściwości abstrakcyjnych.
Mathieu Dumoulin
4
Wymagałoby to uczynienia konstruktora ostatecznym w abstrakcyjnej klasie bazowej.
hakre
3
Kilka wyjaśnień: Jeśli wykonujesz sprawdzenie wewnątrz konstruktora i jeśli powinno być to obowiązkowe, musisz upewnić się, że zostanie wykonane przy każdej instancji instancji. Dlatego musisz zapobiec jego usunięciu, np. Rozszerzając klasę i zastępując konstruktora. Końcowy kluczowe byłoby pozwolić, aby to zrobić.
hakre
1
Podoba mi się rozwiązanie „abstrakcyjnego gettera”. Kiedy deklarujesz funkcję w abstrakcji klasy, musisz zadeklarować abstrakcyjną klasę. Oznacza to, że klasa jest bezużyteczna, chyba że zostanie rozszerzona iw pełni zaimplementowana. Rozszerzając tę ​​klasę, musisz dostarczyć implementację funkcji „getter”. Oznacza to, że musisz również utworzyć powiązaną właściwość wewnątrz klasy rozszerzającej, ponieważ funkcja musi mieć coś do zwrócenia. Postępując zgodnie z tym wzorcem, uzyskasz taki sam wynik, jak w przypadku deklaracji właściwości abstrakcyjnej, jest to również czyste i jasne podejście. Tak to się właściwie robi.
Salivan
1
Użycie abstrakcyjnego gettera pozwala również na zaimplementowanie go poprzez wygenerowanie wartości, w przeciwieństwie do zwracania stałej wartości, gdy ma to sens. Właściwość abstrakcyjna nie pozwala na to, zwłaszcza właściwość statyczna.
Tobia,
27

W zależności od kontekstu właściwości, jeśli chcę wymusić deklarację abstrakcyjnej właściwości obiektu w obiekcie podrzędnym, lubię używać stałej ze staticsłowem kluczowym dla właściwości w konstruktorze obiektów abstrakcyjnych lub metodach ustawiających / pobierających. Opcjonalnie można użyć, finalaby zapobiec przesłonięciu metody w klasach rozszerzonych.

Poza tym obiekt potomny przesłania właściwość i metody obiektu nadrzędnego, jeśli zostanie ponownie zdefiniowany. Na przykład, jeśli właściwość jest zadeklarowana jako protectednadrzędna i przedefiniowana tak, jak publicw przypadku elementu podrzędnego, wynikowa właściwość jest publiczna. Jeśli jednak nieruchomość zostanie zadeklarowana privatew rodzicu, pozostanie privatei nie będzie dostępna dla dziecka.

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
źródło
4
Najbardziej eleganckie rozwiązanie tutaj
Jannie Theunissen
24

Jak stwierdzono powyżej, nie ma takiej dokładnej definicji. Ja jednak używam tego prostego obejścia, aby zmusić klasę podrzędną do zdefiniowania właściwości „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();
  }
}
ulkas
źródło
Elegancki, ale nie rozwiązuje problemu dotyczącego staticnieruchomości.
Robbert
1
Nie sądzę, że można mieć prywatne dla metod abstrakcyjnych.
Zorji,
@ Phate01, jak rozumiem, w samym komentarzu, który stwierdza the only "safe" methods to have in a constructor are private and/or final ones, czy moje obejście nie jest w takim przypadku? używam w tym szeregowych
ulkas
4
Wygląda to ładnie, ale nie zmusza klasy podrzędnej do faktycznego ustawienia $name. Możesz zaimplementować tę setName()funkcję bez jej faktycznego ustawienia $name.
JohnWE
3
Myślę, że używanie getNamezamiast $namedziała lepiej. abstract class Father { abstract protected function getName(); public function foo(){ echo $this->getName();} }
Hamid
7

Zadałem sobie dzisiaj to samo pytanie i chciałbym dodać moje dwa centy.

Powodem, dla którego chcielibyśmy abstractwłaściwości jest upewnienie się, że podklasy je definiują i zgłaszanie wyjątków, gdy tego nie robią. W moim przypadku potrzebowałem czegoś, co mogłoby współpracować z staticsojusznikiem.

Idealnie chciałbym coś takiego:

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

Skończyło się na tej implementacji

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

Jak widać, Anie definiuję $prop, ale używam go w staticgetterze. Dlatego działa następujący kod

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

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

W Cnatomiast nie definiuję $prop, więc dostaję wyjątki:

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

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

Muszę wywołać getProp() metodę, aby uzyskać wyjątek i nie mogę jej uzyskać podczas ładowania klasy, ale jest dość blisko pożądanego zachowania, przynajmniej w moim przypadku.

I zdefiniować getProp()jako finalcelu uniknięcia że niektóre mądry facet (aka siebie w ciągu 6 miesięcy) chciałoby się zrobić

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

D::getProp();
// => no exception...
Marco Pallante
źródło
To bardzo pomysłowy hack. Miejmy nadzieję, że nie trzeba tego robić w przyszłości.
CMCDragonkai
6

Jak mogłeś się dowiedzieć, po prostu testując swój kod:

Błąd krytyczny: właściwości nie można zadeklarować jako abstrakcyjne w ... w wierszu 3

Nie, nie ma. Właściwości nie mogą być deklarowane jako abstrakcyjne w PHP.

Możesz jednak zaimplementować abstrakcyjną funkcję pobierającą / ustawiającą, może to być to, czego szukasz.

Właściwości nie są zaimplementowane (zwłaszcza właściwości publiczne), po prostu istnieją (lub nie):

$foo = new Foo;
$foo->publicProperty = 'Bar';
hakre
źródło
6

Potrzeba abstrakcyjnych właściwości może wskazywać na problemy projektowe. Chociaż wiele odpowiedzi implementuje wzorzec metody Template i działa, zawsze wygląda to trochę dziwnie.

Spójrzmy na oryginalny przykład:

abstract class Foo_Abstract {
    abstract public $tablename;
}

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

Zaznaczenie czegoś abstractoznacza wskazanie, że jest to rzecz obowiązkowa. Cóż, wartość obowiązkowa (w tym przypadku) jest wymaganą zależnością, więc powinna zostać przekazana do konstruktora podczas tworzenia instancji :

class Table
{
    private $name;

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

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

Następnie, jeśli faktycznie chcesz bardziej konkretnej nazwanej klasy, możesz dziedziczyć w następujący sposób:

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

Może to być przydatne, jeśli używasz kontenera DI i musisz przekazywać różne tabele dla różnych obiektów.

sevavietl
źródło
3

PHP 7 znacznie ułatwia tworzenie abstrakcyjnych "właściwości". Tak jak powyżej, stworzysz je, tworząc abstrakcyjne funkcje, ale w PHP 7 możesz zdefiniować typ zwracania dla tej funkcji, co znacznie ułatwia budowanie klasy bazowej, którą każdy może rozszerzyć.

<?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;
Dropa
źródło
1

jeśli wartość tablename nigdy się nie zmieni w czasie życia obiektu, następująca będzie prosta, ale bezpieczna implementacja.

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

kluczem jest tutaj to, że wartość ciągu „users” jest określona i zwrócona bezpośrednio w funkcji getTablename () w implementacji klasy potomnej. Funkcja naśladuje właściwość „tylko do odczytu”.

Jest to dość podobne do rozwiązania opublikowanego wcześniej, w którym używana jest dodatkowa zmienna. Podoba mi się również rozwiązanie Marco, chociaż może być nieco bardziej skomplikowane.

ck.tan
źródło