Propriétés abstraites PHP

126

Existe-t-il un moyen de définir des propriétés de classe abstraites en PHP?

abstract class Foo_Abstract {
    abstract public $tablename;
}

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

Réponses:

154

Il n’existe pas de définition d’une propriété.

Vous ne pouvez déclarer des propriétés que car ce sont des conteneurs de données réservés en mémoire lors de l'initialisation.

Une fonction par contre peut être déclarée (types, nom, paramètres) sans être définie (corps de fonction manquant) et donc, peut être rendue abstraite.

"Abstract" indique seulement que quelque chose a été déclaré mais pas défini et donc avant de l'utiliser, vous devez le définir ou il devient inutile.

Mathieu Dumoulin
la source
59
Il n'y a aucune raison évidente que le mot «abstrait» ne puisse pas être utilisé sur des propriétés statiques - mais avec une signification légèrement différente. Par exemple, cela pourrait indiquer qu'une sous-classe doit fournir une valeur pour la propriété.
frodeborli
2
Dans TypeScript, il existe des propriétés abstraites et des accesseurs . Il est triste qu'en php, ce soit impossible.
Илья Зеленько
52

Non, il n'y a aucun moyen d'imposer cela avec le compilateur, vous devriez utiliser des vérifications au moment de l'exécution (par exemple, dans le constructeur) pour la $tablenamevariable, par exemple:

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

Pour appliquer cela à toutes les classes dérivées de Foo_Abstract, vous devez créer le constructeur de Foo_Abstract, évitant ainsi le remplacement final.

Vous pouvez plutôt déclarer un getter abstrait:

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
la source
Belle fonctionnalité, j'aime la façon dont vous implémentez les propriétés abstraites.
Mathieu Dumoulin
4
Cela vous obligerait à rendre le constructeur final dans la classe de base abstraite.
hakre
3
Quelques explications: si vous effectuez la vérification à l'intérieur du constructeur et si elle doit être obligatoire, vous devez vous assurer qu'elle est exécutée à chaque instanciation d'instance. Par conséquent, vous devez éviter qu'elle ne soit supprimée, par exemple en étendant la classe et en remplaçant le constructeur. Le mot - clé final autoriseriez-vous à le faire.
hakre
1
J'aime la solution "abstract getter". Lorsque vous déclarez une fonction dans un résumé de classe, vous devez déclarer la classe elle-même abstraite. Cela signifie que la classe est inutilisable à moins d'être étendue et entièrement implémentée. Lors de l'extension de cette classe, vous devez fournir une implémentation pour la fonction "getter". Cela signifie que vous devez également créer une propriété associée à l'intérieur de la classe d'extension, car la fonction doit avoir quelque chose à renvoyer. En suivant ce modèle, vous obtenez le même résultat que si vous le feriez en déclarant une propriété abstraite, c'est aussi une approche claire et nette. C'est comme ça que ça se fait.
Salivan
1
L'utilisation d'un getter abstrait vous permet également de l'implémenter en générant une valeur, au lieu de renvoyer une valeur constante, lorsque cela est logique. Une propriété abstraite ne vous permettrait pas de le faire, en particulier une propriété statique.
Tobia du
27

En fonction du contexte de la propriété, si je veux forcer la déclaration d'une propriété d'objet abstrait dans un objet enfant, j'aime utiliser une constante avec le staticmot - clé de la propriété dans le constructeur d'objet abstrait ou les méthodes setter / getter. Vous pouvez éventuellement utiliser finalpour empêcher la méthode d'être remplacée dans les classes étendues.

À part cela, l'objet enfant remplace la propriété et les méthodes de l'objet parent s'il est redéfini. Par exemple, si une propriété est déclarée comme protecteddans le parent et redéfinie comme publicdans l'enfant, la propriété résultante est publique. Cependant, si la propriété est déclarée privatedans le parent, elle restera privateet ne sera pas disponible pour l'enfant.

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
la source
4
La solution la plus élégante ici
Jannie Theunissen
24

Comme indiqué ci-dessus, il n'existe pas de définition exacte. Cependant, j'utilise cette solution de contournement simple pour forcer la classe enfant à définir la propriété "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
la source
Élégant, mais ne résout pas le problème des staticpropriétés.
Robbert
1
Je ne pense pas que vous puissiez avoir privé pour les méthodes abstraites.
Zorji
@ Phate01 si je comprends bien, dans le commentaire lui-même, il dit the only "safe" methods to have in a constructor are private and/or final ones, ma solution de contournement n'est -elle pas un tel cas?
J'utilise des
4
Cela a l'air bien, mais cela ne force pas une classe enfant à se définir $name. Vous pouvez implémenter la setName()fonction sans qu'elle soit réellement définie $name.
JohnWE
3
Je pense que l'utilisation getNameau lieu de $namefonctionne mieux. abstract class Father { abstract protected function getName(); public function foo(){ echo $this->getName();} }
Hamid le
7

Je me suis posé la même question aujourd'hui et j'aimerais ajouter mes deux cents.

La raison pour laquelle nous aimerions des abstractpropriétés est de nous assurer que les sous-classes les définissent et lèvent des exceptions quand elles ne le font pas. Dans mon cas précis, j'avais besoin de quelque chose qui pourrait fonctionner avec un staticallié.

Idéalement, je voudrais quelque chose comme ceci:

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

J'ai fini avec cette implémentation

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

Comme vous pouvez le voir, dans Aje ne définit pas $prop, mais je l'utilise dans un staticgetter. Par conséquent, le code suivant fonctionne

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

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

En C, par contre, je ne définit pas $prop, donc j'obtiens des exceptions:

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

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

Je dois appeler la getProp() méthode pour obtenir l'exception et je ne peux pas l'obtenir lors du chargement de la classe, mais c'est assez proche du comportement souhaité, du moins dans mon cas.

Je définis getProp()comme finaléviter qu'un mec intelligent (alias moi-même dans 6 mois) soit tenté de faire

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

D::getProp();
// => no exception...
Marco Pallante
la source
C'est un hack très ingénieux. J'espère que cela n'aura pas besoin d'être fait à l'avenir.
CMCDragonkai
6

Comme vous auriez pu le découvrir en testant simplement votre code:

Erreur fatale: les propriétés ne peuvent pas être déclarées abstraites dans ... à la ligne 3

Non, il n'y en a pas. Les propriétés ne peuvent pas être déclarées abstraites en PHP.

Cependant, vous pouvez implémenter un résumé de la fonction getter / setter, c'est peut-être ce que vous recherchez.

Les propriétés ne sont pas implémentées (en particulier les propriétés publiques), elles existent juste (ou non):

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

Le besoin de propriétés abstraites peut indiquer des problèmes de conception. Bien que de nombreuses réponses implémentent une sorte de modèle de méthode Template et que cela fonctionne, cela semble toujours étrange.

Jetons un coup d'œil à l'exemple original:

abstract class Foo_Abstract {
    abstract public $tablename;
}

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

Marquer quelque chose, abstractc'est l'indiquer comme un incontournable. Eh bien, une valeur indispensable (dans ce cas) est une dépendance obligatoire, elle doit donc être transmise au constructeur lors de l'instanciation :

class Table
{
    private $name;

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

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

Ensuite, si vous voulez réellement une classe nommée plus concrète, vous pouvez hériter comme suit:

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

Cela peut être utile si vous utilisez un conteneur DI et devez passer différentes tables pour différents objets.

sévavietl
la source
3

PHP 7 facilite un peu la création de "propriétés" abstraites. Tout comme ci-dessus, vous les ferez en créant des fonctions abstraites, mais avec PHP 7, vous pouvez définir le type de retour pour cette fonction, ce qui facilite beaucoup les choses lorsque vous créez une classe de base que tout le monde peut étendre.

<?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
la source
1

si la valeur de nom_table ne changera jamais pendant la durée de vie de l'objet, la suite sera une implémentation simple mais sûre.

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 clé ici est que la valeur de chaîne 'users' est spécifiée et renvoyée directement dans getTablename () dans l'implémentation de la classe enfant. La fonction imite une propriété "lecture seule".

Ceci est assez similaire à une solution publiée précédemment sur qui utilise une variable supplémentaire. J'aime aussi la solution de Marco même si elle peut être un peu plus compliquée.

ck.tan
la source