web-dev-qa-db-fra.com

Comment surcharger le constructeur de classe dans les traits dans PHP> = 5.4

Dans PHP 5, je peux surcharger les constructeurs (et toutes les autres méthodes). Mais si j'obtiens du code comme celui-ci:

class Base {

    public function __construct($a, $b) {
        echo $a+$b;
    }


    public function sayHello() {
        echo 'Hello ';
    }
}


trait SayWorld {

    public function __construct($a, $b, $c = 0) {
        echo (int)$c * ($a+$b);
    }

    public function sayHello($a = null) {
        parent::sayHello();
        echo 'World!'.$a;
    }
}

class MyHelloWorld extends Base {
    use SayWorld;
}

$o = new MyHelloWorld(2, 3);
$o->sayHello(1);

J'ai une erreur:

Erreur fatale: MyHelloWorld a des définitions de constructeurs en collision provenant de traits

Comment puis-je le réparer? Vous pouvez tester mon code ici .

56
Guy Fawkes

Je pense que pour le moment, la seule façon de faire ce que vous voulez est:

class MyHelloWorld extends Base {

    use SayWorld {
        SayWorld::__construct as private __swConstruct;
    }

    public function __construct($a, $b, $c = 0)
    {
        $this->__swConstruct($a, $b, $c);
    }
}

Édition 2:

Mon conseil, basé sur plus d'un an de traitement des traits en PHP, est le suivant: évitez d'écrire des constructeurs en traits du tout , ou si vous devez - au moins les rendre sans paramètre. Les avoir en traits va à l'encontre de l'idée des constructeurs en général, qui est: les constructeurs doivent être spécifiques à une classe à laquelle ils appartiennent. D'autres langages évolués de haut niveau ne prennent même pas en charge l'héritage implicite des constructeurs. En effet, les constructeurs ont une relation beaucoup plus forte avec la classe que les autres méthodes. En fait, ils ont une relation si forte que même LSP ne s'applique pas à eux. Les traits du langage Scala (un langage très mature et SOLIDE - sympathique successeur de Java), peut ' t avoir un constructeur avec des paramètres .

Modifier 1:

Il y avait un bug dans PHP 5.4.11, qui permettait en fait d'aliaser une méthode de superclasse. Mais cela a été considéré comme un non-non par le PHP, donc nous sommes toujours coincés avec cette solution encombrante que j'ai présentée ci-dessus. Mais ce bogue a soulevé une discussion sur ce qui peut être fait avec cela, et j'espère qu'il sera ciblé dans les prochaines versions.

Pendant ce temps, je suis tombé sur le même problème encore et encore. Mon irritation a augmenté de façon exponentielle avec le nombre de paramètres et de lignes de docblock qui ont dû être répétés beaucoup de fois pour utiliser le trait. J'ai donc trouvé le modèle suivant afin de m'en tenir à la règle DRY autant que possible:

Au lieu de répéter un ensemble complet de paramètres comme celui-ci:

trait SayWorld {

    /**
     * This is a valid docblock.
     *
     * @param int $a Doc comment.
     * @param int $b Doc comment.
     */
    public function __construct($a, $b) {
        echo (int)$c * ($a+$b);
    }
}

class MyHelloWorld extends Base {

    use SayWorld {
        SayWorld::__construct as private __swConstruct;
    }

    /**
     * Repeated and unnecessary docblock.
     *
     * @param int $a Doc comment.
     * @param int $b Doc comment.
     * @param int $c Doc comment.
     */
    public function __construct($a, $b, $c = 0)
    {
        $this->__swConstruct($a, $b);
    }
}

J'écris une classe un peu comme un Tuple (concept familier aux C # et Python utilisateurs), et je l'utilise à la place d'une liste interminable de paramètres:

class SayWorldConstructTuple
{
    public $a;

    public $b;

    public function __construct($a, $b)
    {
        $this->a = $a;
        $this->b = $b;
    }
}

class MyHelloWorld extends Base {

    use SayWorld {
        SayWorld::__construct as private __swConstruct;
    }

    /**
     * New and valid docblock.
     *
     * @param SayWorldConstructTuple $Tuple
     * @param int $c Additional parameter.
     */
    public function __construct(SayWorldConstructTuple $Tuple, $c = 0)
    {
        $this->__swConstruct($Tuple->a, $Tuple->b);
        $this->c = $c;
    }
}

Remarque: ce modèle est bien sûr plus utile avec une plus grande quantité de paramètres constructeurs de Tuple et plus de classes utilisant le Tuple.

Il peut être automatisé davantage en utilisant la nature dynamique de PHP.

100
Maciej Sz

Essayer:

use SayWorld {
  Base::__construct insteadof SayWorld;
}

Réf: PHP Docs

6
Martin

Ancien poste, mais au cas où cela aiderait quelqu'un:

J'avais une situation similaire mais j'ai décidé d'utiliser une approche légèrement différente. J'écrivais un plugin WordPress et je voulais transmettre les informations du plugin (version, nom, domaine de texte, etc.) mais je ne voulais pas changer chaque fichier lors de la refactorisation ou étendre un autre classe, j'ai donc créé un trait avec un constructeur qui appelle simplement une fonction init pour des opérations spécifiques à la classe.

trait HasPluginInfoTrait{
    public function __construct() { 

        $this->plugin_name        = PLUGIN_NAME;
        $this->version            = PLUGIN_VERSION;

        if ( method_exists( $this, 'init' ){
            $this->init();
        }
    }
}

class SampleClass {
    use HasPluginInfoTrait;

    private function init(){
        // Code specific to SampleClass
    }
}
1
Chris Schloegel