web-dev-qa-db-fra.com

Meilleure façon de créer plusieurs constructeurs dans PHP

Vous ne pouvez pas placer deux fonctions __construct avec des signatures d'argument uniques dans une classe PHP. J'aimerais faire ceci:

class Student 
{
   protected $id;
   protected $name;
   // etc.

   public function __construct($id){
       $this->id = $id;
      // other members are still uninitialized
   }

   public function __construct($row_from_database){
       $this->id = $row_from_database->id;
       $this->name = $row_from_database->name;
       // etc.
   }
}

Quelle est la meilleure façon de faire cela en PHP?

324

Je ferais probablement quelque chose comme ça:

<?php

class Student
{
    public function __construct() {
        // allocate your stuff
    }

    public static function withID( $id ) {
        $instance = new self();
        $instance->loadByID( $id );
        return $instance;
    }

    public static function withRow( array $row ) {
        $instance = new self();
        $instance->fill( $row );
        return $instance;
    }

    protected function loadByID( $id ) {
        // do query
        $row = my_awesome_db_access_stuff( $id );
        $this->fill( $row );
    }

    protected function fill( array $row ) {
        // fill all properties from array
    }
}

?>

Alors si je veux un étudiant dont je connais l'identifiant:

$student = Student::withID( $id );

Ou si j'ai un tableau de la ligne db:

$student = Student::withRow( $row );

Techniquement, vous ne construisez pas plusieurs constructeurs, mais des méthodes d'assistance statiques, mais vous évitez ainsi beaucoup de code spaghetti dans le constructeur.

442
Kris

La solution de Kris est vraiment agréable, mais je trouve le mélange entre le style de l’usine et le niveau de fluence amélioré:

<?php

class Student
{

    protected $firstName;
    protected $lastName;
    // etc.

    /**
     * Constructor
     */
    public function __construct() {
        // allocate your stuff
    }

    /**
     * Static constructor / factory
     */
    public static function create() {
        $instance = new self();
        return $instance;
    }

    /**
     * FirstName setter - fluent style
     */
    public function setFirstName( $firstName) {
        $this->firstName = $firstName;
        return $this;
    }

    /**
     * LastName setter - fluent style
     */
    public function setLastName( $lastName) {
        $this->lastName = $lastName;
        return $this;
    }

}

// create instance
$student= Student::create()->setFirstName("John")->setLastName("Doe");

// see result
var_dump($student);
?>
80
timaschew

PHP est un langage dynamique, vous ne pouvez donc pas surcharger les méthodes. Vous devez vérifier les types de votre argument comme ceci:

class Student 
{
   protected $id;
   protected $name;
   // etc.

   public function __construct($idOrRow){
    if(is_int($idOrRow))
    {
        $this->id = $idOrRow;
        // other members are still uninitialized
    }
    else if(is_array($idOrRow))
    {
       $this->id = $idOrRow->id;
       $this->name = $idOrRow->name;
       // etc.  
    }
}
40
Daff
public function __construct() {
    $parameters = func_get_args();
    ...
}

$o = new MyClass('One', 'Two', 3);

Maintenant $ paramètres sera un tableau avec les valeurs 'Un', 'Deux', 3.

Modifier,

Je peux ajouter que

func_num_args()

vous donnera le nombre de paramètres de la fonction.

22
Björn

Depuis la version 5.4, PHP prend en charge traits . Ce n'est pas exactement ce que vous recherchez, mais une approche simpliste basée sur les traits serait:

trait StudentTrait {
    protected $id;
    protected $name;

    final public function setId($id) {
        $this->id = $id;
        return $this;
    }

    final public function getId() { return $this->id; }

    final public function setName($name) {
        $this->name = $name; 
        return $this;
    }

    final public function getName() { return $this->name; }

}

class Student1 {
    use StudentTrait;

    final public function __construct($id) { $this->setId($id); }
}

class Student2 {
    use StudentTrait;

    final public function __construct($id, $name) { $this->setId($id)->setName($name); }
}

On se retrouve avec deux classes, une pour chaque constructeur, ce qui est un peu contre-productif. Pour garder un peu de raison, je vais jeter dans une usine:

class StudentFactory {
    static public function getStudent($id, $name = null) {
        return 
            is_null($name)
                ? new Student1($id)
                : new Student2($id, $name)
    }
}

Donc, tout se résume à ceci:

$student1 = StudentFactory::getStudent(1);
$student2 = StudentFactory::getStudent(1, "yannis");

C'est une approche horriblement verbeuse, mais cela peut être extrêmement pratique.

15
yannis

Comme cela a déjà été montré ici, il existe de nombreuses façons de déclarer des constructeurs multiple en PHP, mais aucune d’elles n’est la manière correct de le faire (puisque PHP ne le permet techniquement pas). Mais cela ne nous empêche pas de pirater cette fonctionnalité ... Voici un autre exemple:

<?php

class myClass {
    public function __construct() {
        $get_arguments       = func_get_args();
        $number_of_arguments = func_num_args();

        if (method_exists($this, $method_name = '__construct'.$number_of_arguments)) {
            call_user_func_array(array($this, $method_name), $get_arguments);
        }
    }

    public function __construct1($argument1) {
        echo 'constructor with 1 parameter ' . $argument1 . "\n";
    }

    public function __construct2($argument1, $argument2) {
        echo 'constructor with 2 parameter ' . $argument1 . ' ' . $argument2 . "\n";
    }

    public function __construct3($argument1, $argument2, $argument3) {
        echo 'constructor with 3 parameter ' . $argument1 . ' ' . $argument2 . ' ' . $argument3 . "\n";
    }
}

$object1 = new myClass('BUET');
$object2 = new myClass('BUET', 'is');
$object3 = new myClass('BUET', 'is', 'Best.');

Source: Le moyen le plus simple d'utiliser et de comprendre plusieurs constructeurs:

J'espère que cela t'aides. :)

15
Nasif Md. Tanjim

Vous pouvez faire quelque chose comme ça:

public function __construct($param)
{
    if(is_int($param)) {
         $this->id = $param;
    } elseif(is_object($param)) {
     // do something else
    }
 }
13

Une autre option consiste à utiliser des arguments par défaut dans le constructeur, comme ceci:

class Student {

    private $id;
    private $name;
    //...

    public function __construct($id, $row=array()) {
        $this->id = $id;
        foreach($row as $key => $value) $this->$key = $value;
    }
}

Cela signifie que vous devrez instancier avec une ligne comme celle-ci: $student = new Student($row['id'], $row), mais garde votre constructeur agréable et propre.

D'autre part, si vous voulez utiliser le polymorphisme, vous pouvez créer deux classes comme ceci:

class Student {

    public function __construct($row) {
         foreach($row as $key => $value) $this->$key = $value;
    }
}

class EmptyStudent extends Student {

    public function __construct($id) {
        parent::__construct(array('id' => $id));
    }
}
4
rojoca

comme indiqué dans les autres commentaires, comme php ne supporte pas la surcharge, les "astuces de vérification de type" du constructeur sont généralement évitées et le modèle d'usine est utilisé plus rapidement

c'est à dire.

$myObj = MyClass::factory('fromInteger', $params);
$myObj = MyClass::factory('fromRow', $params);
4
gpilotino

Vous pouvez faire quelque chose comme ce qui suit, qui est vraiment facile et très propre:

public function __construct()    
{
   $arguments = func_get_args(); 

   switch(sizeof(func_get_args()))      
   {
    case 0: //No arguments
        break; 
    case 1: //One argument
        $this->do_something($arguments[0]); 
        break;              
    case 2:  //Two arguments
        $this->do_something_else($arguments[0], $arguments[1]); 
        break;            
   }
}
4
paishin

On a déjà répondu à cette question avec des moyens très intelligents de répondre à l'exigence, mais je me demande pourquoi ne pas prendre un peu de recul et poser la question de base: pourquoi avons-nous besoin d'une classe à deux constructeurs? Si ma classe a besoin de deux constructeurs, la façon dont je conçois mes classes nécessite probablement un peu plus de réflexion pour proposer une conception plus propre et plus testable.

Nous essayons de mélanger comment instancier une classe avec la logique de classe réelle.

Si un objet Student est dans un état valide, est-il important qu'il ait été construit à partir de la ligne d'une base de données ou de données provenant d'un formulaire Web ou d'une requête CLI?

Maintenant, pour répondre à la question qui peut se poser ici, à savoir que si nous n’ajoutons pas la logique de création d’un objet à partir d’une ligne de base de données, comment créer un objet à partir des données de base de données, nous pouvons simplement ajouter une autre classe, appelez-le StudentMapper si vous êtes à l'aise avec le modèle de mappeur de données, dans certains cas, vous pouvez utiliser StudentRepository, et si rien ne correspond à vos besoins, vous pouvez créer une StudentFactory pour gérer toutes sortes de tâches de construction d'objet.

Bottomline est de garder la couche de persistance hors de notre tête lorsque nous travaillons sur les objets de domaine.

3
Waku-2

Je sais que je suis très tard pour la soirée, mais j’ai proposé un schéma assez souple qui devrait permettre des implémentations vraiment intéressantes et polyvalentes.

Configurez votre classe comme vous le feriez normalement, avec les variables de votre choix.

class MyClass{
    protected $myVar1;
    protected $myVar2;

    public function __construct($obj = null){
        if($obj){
            foreach (((object)$obj) as $key => $value) {
                if(isset($value) && in_array($key, array_keys(get_object_vars($this)))){
                    $this->$key = $value;
                }
            }
        }
    }
}

Lorsque vous faites votre objet simplement passer un tableau associatif avec les clés du tableau les mêmes que les noms de vos vars, comme alors ...

$sample_variable = new MyClass([
    'myVar2'=>123, 
    'i_dont_want_this_one'=> 'This won\'t make it into the class'
    ]);

print_r($sample_variable);

La print_r($sample_variable); après cette instanciation génère les éléments suivants:

MyClass Object ( [myVar1:protected] => [myVar2:protected] => 123 )

Comme nous avons initialisé $group à la valeur null dans notre __construct(...), il est également correct de ne rien transférer dans le constructeur, comme dans le cas de ...

$sample_variable = new MyClass();

print_r($sample_variable);

Maintenant, le résultat est exactement comme prévu:

MyClass Object ( [myVar1:protected] => [myVar2:protected] => )

La raison pour laquelle j'ai écrit ceci est pour que je puisse passer directement la sortie de json_decode(...) à mon constructeur, sans trop m'en inquiéter.

Cela a été exécuté dans PHP 7.1. Prendre plaisir!

2
David Culbreth

Permettez-moi d'ajouter mon grain de sable ici

Personnellement, j'aime bien ajouter des constructeurs en tant que fonctions statiques renvoyant une instance de la classe (l'objet). Le code suivant est un exemple:

 class Person
 {
     private $name;
     private $email;

     public static function withName($name)
     {
         $person = new Person();
         $person->name = $name;

         return $person;
     }

     public static function withEmail($email)
     {
         $person = new Person();
         $person->email = $email;

         return $person;
     }
 }

Notez que vous pouvez maintenant créer une instance de la classe Person comme ceci:

$person1 = Person::withName('Example');
$person2 = Person::withEmail('yo@mi_email.com');

J'ai pris ce code de:

http://alfonsojimenez.com/post/30377422731/multiple-constructors-in-php

2
Salvi Pascual

Voici une façon élégante de le faire. Créez un trait qui activera plusieurs constructeurs en fonction du nombre de paramètres. Vous ajouteriez simplement le nombre de paramètres au nom de la fonction "__construct". Donc, un paramètre sera "__construct1", deux "__construct2" ... etc.

trait constructable
{
    public function __construct() 
    { 
        $a = func_get_args(); 
        $i = func_num_args(); 
        if (method_exists($this,$f='__construct'.$i)) { 
            call_user_func_array([($this,$f)],$a); 
        } 
    } 
}

class a{
    use constructable;

    public $result;

    public function __construct1($a){
        $this->result = $a;
    }

    public function __construct2($a, $b){
        $this->result =  $a + $b;
    }
}

echo (new a(1))->result;    // 1
echo (new a(1,2))->result;  // 3
1
Jed Lynch

Pour php7, je compare aussi le type des paramètres, vous pouvez avoir deux constructeurs avec le même nombre de paramètres mais un type différent.

trait GenericConstructorOverloadTrait
{
    /**
     * @var array Constructors metadata
     */
    private static $constructorsCache;
    /**
     * Generic constructor
     * GenericConstructorOverloadTrait constructor.
     */
    public function __construct()
    {
        $params = func_get_args();
        $numParams = func_num_args();

        $finish = false;

        if(!self::$constructorsCache){
            $class = new \ReflectionClass($this);
            $constructors =  array_filter($class->getMethods(),
                function (\ReflectionMethod $method) {
                return preg_match("/\_\_construct[0-9]+/",$method->getName());
            });
            self::$constructorsCache = $constructors;
        }
        else{
            $constructors = self::$constructorsCache;
        }
        foreach($constructors as $constructor){
            $reflectionParams = $constructor->getParameters();
            if(count($reflectionParams) != $numParams){
                continue;
            }
            $matched = true;
            for($i=0; $i< $numParams; $i++){
                if($reflectionParams[$i]->hasType()){
                    $type = $reflectionParams[$i]->getType()->__toString();
                }
                if(
                    !(
                        !$reflectionParams[$i]->hasType() ||
                        ($reflectionParams[$i]->hasType() &&
                            is_object($params[$i]) &&
                            $params[$i] instanceof $type) ||
                        ($reflectionParams[$i]->hasType() &&
                            $reflectionParams[$i]->getType()->__toString() ==
                            gettype($params[$i]))
                    )
                ) {
                    $matched = false;
                    break;
                }

            }

            if($matched){
                call_user_func_array(array($this,$constructor->getName()),
                    $params);
                $finish = true;
                break;
            }
        }

        unset($constructor);

        if(!$finish){
            throw new \InvalidArgumentException("Cannot match construct by params");
        }
    }

}

Pour l'utiliser:

class MultiConstructorClass{

    use GenericConstructorOverloadTrait;

    private $param1;

    private $param2;

    private $param3;

    public function __construct1($param1, array $param2)
    {
        $this->param1 = $param1;
        $this->param2 = $param2;
    }

    public function __construct2($param1, array $param2, \DateTime $param3)
    {
        $this->__construct1($param1, $param2);
        $this->param3 = $param3;
    }

    /**
     * @return \DateTime
     */
    public function getParam3()
    {
        return $this->param3;
    }

    /**
     * @return array
     */
    public function getParam2()
    {
        return $this->param2;
    }

    /**
     * @return mixed
     */
    public function getParam1()
    {
        return $this->param1;
    }
}
1
Serginho

Hmm, surpris que je ne voie pas encore cette réponse, supposons que je jette mon chapeau dans le ring.

class Action {
    const cancelable    =   0;
    const target        =   1
    const type          =   2;

    public $cancelable;
    public $target;
    public $type;


    __construct( $opt = [] ){

        $this->cancelable   = isset($opt[cancelable]) ? $opt[cancelable] : true;
        $this->target       = isset($opt[target]) ?     $opt[target] : NULL;
        $this->type         = isset($opt[type]) ?       $opt[type] : 'action';

    }
}


$myAction = new Action( [
    Action::cancelable => false,
    Action::type => 'spin',
    .
    .
    .
]);

Vous pouvez éventuellement séparer les options dans leur propre classe, telle que l'extension de SplEnum.

abstract class ActionOpt extends SplEnum{
    const cancelable    =   0;
    const target        =   1
    const type          =   2;
}
1
Garet Claborn

En réponse à la meilleure réponse de Kris (qui a étonnamment aidé à concevoir ma propre classe), voici une version modifiée pour ceux qui pourraient la trouver utile. Inclut des méthodes de sélection dans une colonne et de dumping des données d'objet à partir d'un tableau. À votre santé!

public function __construct() {
    $this -> id = 0;
    //...
}

public static function Exists($id) {
    if (!$id) return false;
    $id = (int)$id;
    if ($id <= 0) return false;
    $mysqli = Mysql::Connect();
    if (mysqli_num_rows(mysqli_query($mysqli, "SELECT id FROM users WHERE id = " . $id)) == 1) return true;
    return false;
}

public static function FromId($id) {
    $u = new self();
    if (!$u -> FillFromColumn("id", $id)) return false;
    return $u;
}

public static function FromColumn($column, $value) {
    $u = new self();
    if (!$u -> FillFromColumn($column, $value)) return false;
    return $u;
}

public static function FromArray($row = array()) {
    if (!is_array($row) || $row == array()) return false;
    $u = new self();
    $u -> FillFromArray($row);
    return $u;
}

protected function FillFromColumn($column, $value) {
    $mysqli = Mysql::Connect();
    //Assuming we're only allowed to specified EXISTENT columns
    $result = mysqli_query($mysqli, "SELECT * FROM users WHERE " . $column . " = '" . $value . "'");
    $count = mysqli_num_rows($result);
    if ($count == 0) return false;
    $row = mysqli_fetch_assoc($result);
    $this -> FillFromArray($row);
}

protected function FillFromArray(array $row) {
    foreach($row as $i => $v) {
        if (isset($this -> $i)) {
            $this -> $i = $v;
        }
    }
}

public function ToArray() {
    $m = array();
    foreach ($this as $i => $v) {
        $m[$i] = $v;    
    }
    return $m;
}

public function Dump() {
    print_r("<PRE>");
    print_r($this -> ToArray());
    print_r("</PRE>");  
}
0
James M

J'ai créé cette méthode pour l'utiliser non seulement sur les constructeurs mais aussi dans les méthodes:

Mon constructeur:

function __construct() {
    $paramsNumber=func_num_args();
    if($paramsNumber==0){
        //do something
    }else{
        $this->overload('__construct',func_get_args());
    }
}

Ma méthode de quelque chose:

public function doSomething() {
    $paramsNumber=func_num_args();
    if($paramsNumber==0){
        //do something
    }else{
        $this->overload('doSomething',func_get_args());
    }
}

Les deux fonctionnent avec cette méthode simple:

public function overloadMethod($methodName,$params){
    $paramsNumber=sizeof($params);
    //methodName1(), methodName2()...
    $methodNameNumber =$methodName.$paramsNumber;
    if (method_exists($this,$methodNameNumber)) {
        call_user_func_array(array($this,$methodNameNumber),$params);
    }
}

Afin que vous puissiez déclarer

__construct1($arg1), __construct2($arg1,$arg2)...

ou

methodName1($arg1), methodName2($arg1,$arg2)...

etc :)

Et lors de l'utilisation:

$myObject =  new MyClass($arg1, $arg2,..., $argN);

il appellera __constructN, où vous aurez défini N args

then $ myObject -> doQuelque chose ($ arg1, $ arg2, ..., $ argM)

il appellera doSomethingM, où vous avez défini M args;

0
jechaviz

Vous pouvez toujours ajouter un paramètre supplémentaire au constructeur appelé quelque chose comme Mode, puis exécuter une instruction switch dessus ...

class myClass 
{
    var $error ;
    function __construct ( $data, $mode )
    {
        $this->error = false
        switch ( $mode )
        {
            'id' : processId ( $data ) ; break ;
            'row' : processRow ( $data ); break ;
            default : $this->error = true ; break ;
         }
     }

     function processId ( $data ) { /* code */ }
     function processRow ( $data ) { /* code */ }
}

$a = new myClass ( $data, 'id' ) ;
$b = new myClass ( $data, 'row' ) ;
$c = new myClass ( $data, 'something' ) ;

if ( $a->error )
   exit ( 'invalid mode' ) ;
if ( $b->error )
   exit ('invalid mode' ) ;
if ( $c->error )
   exit ('invalid mode' ) ;

En outre, avec cette méthode à tout moment, si vous souhaitez ajouter plus de fonctionnalités, vous pouvez simplement ajouter un autre cas à l'instruction switch, et vous pouvez également vérifier que quelqu'un a bien envoyé la bonne chose - dans l'exemple ci-dessus, toutes les données sont correctes. à l'exception de C, qui est défini sur "quelque chose", l'indicateur d'erreur de la classe est défini et le contrôle est renvoyé au programme principal pour qu'il décide quoi faire ensuite (dans l'exemple que je viens de lui dire de quitter avec un message d'erreur "mode invalide" - mais vous pouvez également le boucler jusqu'à ce que des données valides soient trouvées).

0
TheKLF99

Appelez les constructeurs par type de données:

class A 
{ 
    function __construct($argument)
    { 
       $type = gettype($argument);

       if($type == 'unknown type')
       {
            // type unknown
       }

       $this->{'__construct_'.$type}($argument);
    } 

    function __construct_boolean($argument) 
    { 
        // do something
    }
    function __construct_integer($argument) 
    { 
        // do something
    }
    function __construct_double($argument) 
    { 
        // do something
    }
    function __construct_string($argument) 
    { 
        // do something
    }
    function __construct_array($argument) 
    { 
        // do something
    }
    function __construct_object($argument) 
    { 
        // do something
    }
    function __construct_resource($argument) 
    { 
        // do something
    }

    // other functions

} 
0
Viral

Autant que je sache, la surcharge n'est pas prise en charge en PHP. Vous ne pouvez surcharger que les méthodes get et set des propriétés avec overload (); ( http://www.php.net/manual/en/overload.examples.basic.php )

0
Florian Peschka