Proprietà astratte PHP


126

Esiste un modo per definire le proprietà di classe astratte in PHP?

abstract class Foo_Abstract {
    abstract public $tablename;
}

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

Risposte:


154

Non esiste una definizione di proprietà.

È possibile dichiarare le proprietà solo perché sono contenitori di dati riservati in memoria durante l'inizializzazione.

D'altra parte una funzione può essere dichiarata (tipi, nome, parametri) senza essere definita (corpo della funzione mancante) e quindi può essere resa astratta.

"Astratto" indica solo che qualcosa è stato dichiarato ma non definito e quindi prima di usarlo, è necessario definirlo o diventa inutile.


59
Non vi è alcuna ragione ovvia per cui la parola "astratto" non possa essere utilizzata su proprietà statiche , ma con un significato leggermente diverso. Ad esempio, potrebbe indicare che una sottoclasse deve fornire un valore per la proprietà.
frodeborli,

2
In TypeScript ci sono proprietà e accessori astratti . È triste che in php sia impossibile.
Илья Зеленько,

52

No, non c'è modo di imporlo con il compilatore, dovresti usare i controlli di runtime (diciamo, nel costruttore) per la $tablenamevariabile, ad esempio:

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

Per imporre questo per tutte le classi derivate di Foo_Abstract dovresti creare il costruttore di Foo_Abstract final, prevenendo l'override.

Invece potresti dichiarare un getter astratto:

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

class Foo extends Foo_Abstract {
  protected $tablename = 'tablename';
  public function get_tablename() {
    return $this->tablename;
  }
}

Bella caratteristica, mi piace come si implementano le proprietà astratte.
Mathieu Dumoulin,

4
Ciò richiederebbe di rendere definitivo il costruttore nella classe base astratta.
hakre,

3
Qualche spiegazione: se si esegue il controllo all'interno del costruttore e se dovrebbe essere obbligatorio, è necessario assicurarsi che venga eseguito ad ogni istanza dell'istanza. Pertanto è necessario impedire che venga rimosso, ad esempio estendendo la classe e sostituendo il costruttore. L' ultima parola chiave consentiresti di farlo.
hakre,

1
Mi piace la soluzione "abstract getter". Quando si dichiara una funzione in un abstract di classe, è necessario dichiarare astratta la classe stessa. Ciò significa che la classe è inutilizzabile a meno che non sia estesa e completamente implementata. Quando si estende quella classe, è necessario fornire un'implementazione per la funzione "getter". Ciò significa che è necessario anche creare una proprietà correlata all'interno della classe di estensione, poiché la funzione deve avere qualcosa da restituire. Seguendo questo schema si ottiene lo stesso risultato come se si dichiarasse una proprietà astratta, è anche un approccio pulito e chiaro. Ecco come è effettivamente fatto.
Salivan,

1
L'uso di un getter astratto consente anche di implementarlo generando un valore, anziché restituire un valore costante, quando ha senso farlo. Una proprietà astratta non ti permetterebbe di farlo, specialmente una proprietà statica.
Tobia,

27

A seconda del contesto della proprietà se voglio forzare la dichiarazione di una proprietà di oggetto astratto in un oggetto figlio, mi piace usare una costante con la staticparola chiave per la proprietà nel costruttore di oggetti astratti o nei metodi setter / getter. È possibile utilizzare facoltativamente finalper impedire che il metodo venga sovrascritto in classi estese.

Oltre a ciò, l'oggetto figlio sovrascrive la proprietà e i metodi dell'oggetto padre se ridefinito. Ad esempio, se una proprietà viene dichiarata come protectednel padre e ridefinita come publicnel figlio, la proprietà risultante è pubblica. Tuttavia, se la proprietà è dichiarata privatenel genitore rimarrà privatee non sarà disponibile per il figlio.

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
La soluzione più elegante qui
Jannie Theunissen il

24

Come detto sopra, non esiste una definizione così precisa. Tuttavia, utilizzo questa semplice soluzione per forzare la classe figlio a definire la proprietà "astratta":

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

Elegante, ma non risolve il problema delle staticproprietà.
Robbert,

1
Non penso che tu possa avere privati ​​per metodi astratti.
Zorji,

@ Phate01 a quanto ho capito, nel commento stesso afferma the only "safe" methods to have in a constructor are private and/or final ones, non è il mio modo di aggirare il caso?
sto

4
Sembra carino, ma non obbliga una classe per bambini a essere effettivamente impostata $name. È possibile implementare la setName()funzione senza che sia effettivamente impostata $name.
JohnWE,

3
Penso che l'utilizzo getNameinvece di $namefunzioni meglio. abstract class Father { abstract protected function getName(); public function foo(){ echo $this->getName();} }
Hamid,

7

Oggi mi sono posto la stessa domanda e vorrei aggiungere i miei due centesimi.

Il motivo per cui vorremmo le abstractproprietà è assicurarsi che le sottoclassi le definiscano e generino eccezioni quando non lo fanno. Nel mio caso specifico, avevo bisogno di qualcosa che potesse funzionare con un staticalleato.

Idealmente, vorrei qualcosa del genere:

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

Ho finito con questa implementazione

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

Come puoi vedere, Anon lo definisco $prop, ma lo uso in modo staticgetter. Pertanto, il seguente codice funziona

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

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

D' Caltra parte, non definisco $prop, quindi ottengo delle eccezioni:

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

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

Devo chiamare il getProp() metodo per ottenere l'eccezione e non riesco a ottenerlo durante il caricamento della classe, ma è abbastanza vicino al comportamento desiderato, almeno nel mio caso.

Definisco getProp()come finalevitare che un ragazzo in gamba (alias me stesso in 6 mesi) sia tentato di farlo

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

D::getProp();
// => no exception...

Questo è un trucco molto geniale. Spero che questo non debba essere fatto in futuro.
CMCDragonkai,

6

Come avresti potuto scoprire testando il tuo codice:

Errore irreversibile: le proprietà non possono essere dichiarate astratte in ... alla riga 3

No non c'è. Le proprietà non possono essere dichiarate astratte in PHP.

Tuttavia, puoi implementare un estratto della funzione getter / setter, questo potrebbe essere quello che stai cercando.

Le proprietà non sono implementate (in particolare le proprietà pubbliche), esistono solo (o meno):

$foo = new Foo;
$foo->publicProperty = 'Bar';

6

La necessità di proprietà astratte può indicare problemi di progettazione. Mentre molte risposte implementano una specie di modello di metodo Template e funziona, sembra sempre strano.

Diamo un'occhiata all'esempio originale:

abstract class Foo_Abstract {
    abstract public $tablename;
}

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

Contrassegnare qualcosa abstractsignifica indicarlo come un must. Bene, un valore indispensabile (in questo caso) è una dipendenza richiesta, quindi dovrebbe essere passato al costruttore durante l'istanza :

class Table
{
    private $name;

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

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

Quindi, se in realtà vuoi una classe di nome più concreta, puoi ereditare in questo modo:

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

Ciò può essere utile se si utilizza un contenitore DI e si devono passare tabelle diverse per oggetti diversi.


3

PHP 7 rende un po 'più semplice la creazione di "proprietà" astratte. Proprio come sopra, li creerai creando funzioni astratte, ma con PHP 7 puoi definire il tipo di ritorno per quella funzione, il che rende le cose molto più facili quando stai costruendo una classe base che chiunque può estendere.

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

se il valore di tablename non cambierà mai durante il ciclo di vita dell'oggetto, seguirà un'implementazione semplice ma sicura.

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

la chiave qui è che il valore della stringa 'utenti' è specificato e restituito direttamente in getTablename () nell'implementazione della classe figlio. La funzione imita una proprietà "sola lettura".

Questo è abbastanza simile a una soluzione pubblicata in precedenza in cui utilizza una variabile aggiuntiva. Mi piace anche la soluzione di Marco, sebbene possa essere un po 'più complicata.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.