PHP abstrakte Eigenschaften

126

Gibt es eine Möglichkeit, abstrakte Klasseneigenschaften in PHP zu definieren?

abstract class Foo_Abstract {
    abstract public $tablename;
}

class Foo extends Foo_Abstract {
    //Foo must 'implement' $property
    public $tablename = 'users';   
}
Tamás Pap
quelle

Antworten:

154

Es gibt keine Definition einer Eigenschaft.

Sie können Eigenschaften nur deklarieren, da es sich um Container mit Daten handelt, die bei der Initialisierung im Speicher reserviert wurden.

Eine Funktion hingegen kann deklariert werden (Typen, Namen, Parameter), ohne definiert zu sein (Funktionskörper fehlt) und somit abstrakt gemacht werden.

"Zusammenfassung" zeigt nur an, dass etwas deklariert, aber nicht definiert wurde. Bevor Sie es verwenden, müssen Sie es definieren, sonst wird es unbrauchbar.

Mathieu Dumoulin
quelle
58
Es gibt keinen offensichtlichen Grund, warum das Wort "abstrakt" nicht für statische Eigenschaften verwendet werden könnte - aber mit einer etwas anderen Bedeutung. Beispielsweise könnte dies darauf hinweisen, dass eine Unterklasse einen Wert für die Eigenschaft bereitstellen muss.
Frodeborli
2
In TypeScript gibt es abstrakte Eigenschaften und Accessoren . Es ist traurig, dass es in PHP unmöglich ist.
Злья Зеленько
52

Nein, es gibt keine Möglichkeit, dies zu erzwingen. Mit dem Compiler müssten Sie Laufzeitprüfungen ( $tablenamez. B. im Konstruktor) für die Variable verwenden, z.

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

Um dies für alle abgeleiteten Klassen von Foo_Abstract zu erzwingen, müssten Sie den Konstruktor von Foo_Abstract erstellen, um ein Überschreiben zu finalverhindern.

Sie könnten stattdessen einen abstrakten Getter deklarieren:

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

class Foo extends Foo_Abstract {
  protected $tablename = 'tablename';
  public function get_tablename() {
    return $this->tablename;
  }
}
Verbindung
quelle
Nettes Feature, ich mag, wie Sie abstrakte Eigenschaften implementieren.
Mathieu Dumoulin
4
Dies würde erfordern, dass Sie den Konstruktor in der abstrakten Basisklasse endgültig machen.
hakre
3
Einige Erklärungen: Wenn Sie die Überprüfung im Konstruktor durchführen und diese obligatorisch sein sollte, müssen Sie sicherstellen, dass sie bei jeder Instanzinstanziierung ausgeführt wird. Daher müssen Sie verhindern, dass es entfernt wird, z. B. indem Sie die Klasse erweitern und den Konstruktor ersetzen. Das letzte Schlüsselwort würden Sie zulassen.
hakre
1
Ich mag die "Abstract Getter" -Lösung. Wenn Sie eine Funktion in einer Klassenzusammenfassung deklarieren, müssen Sie die Klasse selbst als abstrakt deklarieren. Dies bedeutet, dass die Klasse unbrauchbar ist, wenn sie nicht erweitert und vollständig implementiert ist. Wenn Sie diese Klasse erweitern, müssen Sie eine Implementierung für die Funktion "getter" bereitstellen. Dies bedeutet, dass Sie auch eine verwandte Eigenschaft innerhalb der erweiterten Klasse erstellen müssen, da die Funktion etwas zurückgeben muss. Wenn Sie diesem Muster folgen, erhalten Sie das gleiche Ergebnis wie beim Deklarieren einer abstrakten Eigenschaft. Dies ist auch ein sauberer und klarer Ansatz. So wird es tatsächlich gemacht.
Salivan
1
Wenn Sie einen abstrakten Getter verwenden, können Sie ihn auch implementieren, indem Sie einen Wert generieren , anstatt einen konstanten Wert zurückzugeben, wenn dies sinnvoll ist. Eine abstrakte Eigenschaft würde dies nicht zulassen, insbesondere eine statische Eigenschaft.
Tobia
27

Abhängig vom Kontext der Eigenschaft, wenn ich die Deklaration einer abstrakten Objekteigenschaft in einem untergeordneten Objekt erzwingen möchte, verwende ich gerne eine Konstante mit dem staticSchlüsselwort für die Eigenschaft im abstrakten Objektkonstruktor oder in den Setter / Getter-Methoden. Sie können optional verwenden final, um zu verhindern, dass die Methode in erweiterten Klassen überschrieben wird.

Ansonsten überschreibt das untergeordnete Objekt die übergeordneten Objekteigenschaften und -methoden, wenn es neu definiert wird. Wenn beispielsweise eine Eigenschaft wie protectedim übergeordneten Element deklariert und wie im untergeordneten Element neu definiert wird public, ist die resultierende Eigenschaft öffentlich. Wenn die Eigenschaft jedoch privateim übergeordneten Element deklariert ist, bleibt sie erhalten privateund steht dem untergeordneten Element nicht zur Verfügung.

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
quelle
4
Die eleganteste Lösung hier
Jannie Theunissen
24

Wie oben erwähnt, gibt es keine so genaue Definition. Ich verwende diese einfache Problemumgehung jedoch, um die untergeordnete Klasse zu zwingen, die "abstrakte" Eigenschaft zu definieren:

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
quelle
Elegant, löst aber das Problem für staticImmobilien nicht.
Robbert
1
Ich glaube nicht, dass Sie für abstrakte Methoden privat sein können.
Zorji
@ Phate01 wie ich es verstehe, in dem Kommentar selbst heißt es the only "safe" methods to have in a constructor are private and/or final ones, ist meine Problemumgehung nicht so ein Fall?
Ich
4
Das sieht gut aus, zwingt aber eine untergeordnete Klasse nicht dazu, tatsächlich zu setzen $name. Sie können die setName()Funktion implementieren , ohne sie tatsächlich einzustellen $name.
JohnWE
3
Ich denke, dass die Verwendung getNameanstelle von $namebesser funktioniert. abstract class Father { abstract protected function getName(); public function foo(){ echo $this->getName();} }
Hamid
7

Ich habe mir heute die gleiche Frage gestellt und möchte meine zwei Cent hinzufügen.

Der Grund, warum wir abstractEigenschaften wünschen, besteht darin, sicherzustellen, dass Unterklassen sie definieren und Ausnahmen auslösen, wenn dies nicht der Fall ist. In meinem speziellen Fall brauchte ich etwas, das mit staticVerbündeten zusammenarbeiten konnte.

Idealerweise möchte ich so etwas:

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

Am Ende war ich bei dieser Implementierung

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

Wie Sie sehen können, Adefiniere ich in nicht $prop, aber ich benutze es in einem staticGetter. Daher funktioniert der folgende Code

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

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

In C, auf der anderen Seite, ich nicht definieren $prop, so dass ich Ausnahmen erhalten:

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

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

Ich muss die getProp() Methode aufrufen , um die Ausnahme zu erhalten, und ich kann sie beim Laden der Klasse nicht erhalten, aber sie kommt zumindest in meinem Fall dem gewünschten Verhalten ziemlich nahe.

Ich sehe , getProp()wie finalzu vermeiden , dass einige smart guy (aka mich in 6 Monaten) ist versucht zu tun

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

D::getProp();
// => no exception...
Marco Pallante
quelle
Das ist ein sehr genialer Hack. Hoffentlich muss dies in Zukunft nicht mehr getan werden.
CMCDragonkai
6

Wie Sie durch einfaches Testen Ihres Codes hätten herausfinden können:

Schwerwiegender Fehler: Eigenschaften können in ... in Zeile 3 nicht als abstrakt deklariert werden

Nein, da ist kein. Eigenschaften können in PHP nicht als abstrakt deklariert werden.

Sie können jedoch eine Zusammenfassung der Getter- / Setter-Funktion implementieren. Dies ist möglicherweise das, wonach Sie suchen.

Eigenschaften sind nicht implementiert (insbesondere öffentliche Eigenschaften), sie existieren nur (oder nicht):

$foo = new Foo;
$foo->publicProperty = 'Bar';
hakre
quelle
6

Die Notwendigkeit abstrakter Eigenschaften kann auf Entwurfsprobleme hinweisen. Während viele Antworten eine Art Template-Methodenmuster implementieren und es funktioniert, sieht es immer irgendwie seltsam aus.

Schauen wir uns das ursprüngliche Beispiel an:

abstract class Foo_Abstract {
    abstract public $tablename;
}

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

Etwas zu markieren abstractbedeutet, es als ein Muss anzuzeigen. Nun, ein Must-Have-Wert (in diesem Fall) ist eine erforderliche Abhängigkeit, daher sollte er während der Instanziierung an den Konstruktor übergeben werden :

class Table
{
    private $name;

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

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

Wenn Sie dann tatsächlich eine konkretere benannte Klasse möchten, können Sie wie folgt erben:

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

Dies kann nützlich sein, wenn Sie DI-Container verwenden und unterschiedliche Tabellen für unterschiedliche Objekte übergeben müssen.

sevavietl
quelle
3

PHP 7 erleichtert das Erstellen abstrakter "Eigenschaften" erheblich. Wie oben werden Sie sie erstellen, indem Sie abstrakte Funktionen erstellen. Mit PHP 7 können Sie jedoch den Rückgabetyp für diese Funktion definieren. Dies erleichtert die Erstellung einer Basisklasse, die jeder erweitern kann, erheblich.

<?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
quelle
1

Wenn sich der Wert des Tabellennamens während der Lebensdauer des Objekts niemals ändert, folgt eine einfache, aber sichere Implementierung.

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

Der Schlüssel hier ist, dass der Zeichenfolgenwert 'users' angegeben und direkt in getTablename () in der Implementierung der untergeordneten Klasse zurückgegeben wird. Die Funktion ahmt eine "schreibgeschützte" Eigenschaft nach.

Dies ist einer zuvor veröffentlichten Lösung ziemlich ähnlich, bei der eine zusätzliche Variable verwendet wird. Ich mag auch Marcos Lösung, obwohl sie etwas komplizierter sein kann.

ck.tan
quelle