wake-up-neo.com

Beste Möglichkeit, mehrere Konstruktoren in PHP

Sie können nicht zwei __construct-Funktionen mit eindeutigen Argumentsignaturen in eine PHP Klasse einfügen. Ich würde dies gerne tun:

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

Was ist der beste Weg, um dies in PHP zu tun?

324

Ich würde wahrscheinlich so etwas machen:

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

?>

Wenn ich dann einen Studenten möchte, bei dem ich den Ausweis kenne:

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

Oder wenn ich ein Array der DB-Zeile habe:

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

Technisch gesehen erstellen Sie nicht mehrere Konstruktoren, sondern nur statische Hilfsmethoden. Auf diese Weise vermeiden Sie jedoch viel Spaghetti-Code im Konstruktor.

442
Kris

Die Lösung von Kris ist wirklich nett, aber ich finde die Mischung aus fabrikmäßigem und flüssigem Stil besser:

<?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 ist eine dynamische Sprache, sodass Sie Methoden nicht überladen können. Sie müssen die Arten Ihres Arguments wie folgt überprüfen:

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

Jetzt sind $ parameters ein Array mit den Werten 'One', 'Two', 3.

Bearbeiten,

Das kann ich hinzufügen

func_num_args()

gibt die Anzahl der Parameter für die Funktion an.

22
Björn

Ab Version 5.4 unterstützt PHP Traits . Dies ist nicht genau das, was Sie sind Auf der Suche nach, aber ein simpler Ansatz basierend auf Merkmalen wäre:

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

Wir haben zwei Klassen, eine für jeden Konstruktor, was etwas kontraproduktiv ist. Um die Vernunft zu wahren, werfe ich eine Fabrik ein:

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

Es kommt also alles auf Folgendes an:

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

Es ist eine schrecklich wortreiche Herangehensweise, aber es kann äußerst praktisch sein.

15
yannis

Wie hier bereits gezeigt wurde, gibt es viele Möglichkeiten, multiple Konstruktoren in PHP zu deklarieren, aber keine davon ist die correct Möglichkeit, dies zu tun (seit PHP = technisch nicht erlaubt), aber es hindert uns nicht daran, diese Funktionalität zu hacken ... Hier ist ein weiteres Beispiel:

<?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.');

Quelle: Der einfachste Weg, mehrere Konstruktoren zu verwenden und zu verstehen:

Hoffe das hilft. :)

15

Sie könnten so etwas tun:

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

Eine andere Möglichkeit ist die Verwendung von Standardargumenten im Konstruktor wie folgt

class Student {

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

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

Das bedeutet, dass Sie mit einer Zeile wie der folgenden instanziieren müssen: $student = new Student($row['id'], $row), aber Ihren Konstruktor schön und sauber halten.

Wenn Sie hingegen den Polymorphismus nutzen möchten, können Sie zwei Klassen erstellen:

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

wie in den anderen Kommentaren angegeben, werden normalerweise die "Typprüfungstricks" im Konstruktor vermieden und das Factory-Pattern wird intead verwendet, da PHP das Überladen nicht unterstützt

dh.

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

Sie könnten so etwas wie das Folgende tun, was wirklich einfach und sehr sauber ist:

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

Diese Frage wurde bereits auf sehr clevere Weise beantwortet, aber ich frage mich, warum ich nicht einen Schritt zurücktreten und die grundlegende Frage stellen sollte, warum wir eine Klasse mit zwei Konstruktoren benötigen. Wenn meine Klasse zwei Konstruktoren benötigt, muss die Art und Weise, wie ich meine Klassen entwerfe, wahrscheinlich nicht mehr berücksichtigt werden, um ein Design zu entwickeln, das sauberer und überprüfbarer ist.

Wir versuchen zu verwechseln, wie eine Klasse mit der tatsächlichen Klassenlogik instanziiert wird.

Wenn sich ein Student-Objekt in einem gültigen Zustand befindet, spielt es dann eine Rolle, ob es aus der Zeile einer Datenbank oder Daten aus einem Webformular oder einer CLI-Anforderung erstellt wurde?

Um nun die Frage zu beantworten, ob wir, wenn wir die Logik zum Erstellen eines Objekts aus einer Datenbankzeile nicht hinzufügen, wie wir ein Objekt aus den Datenbankdaten erstellen, einfach eine weitere Klasse hinzufügen und sie StudentMapper nennen, wenn Sie sind mit Data Mapper-Mustern vertraut. In einigen Fällen können Sie StudentRepository verwenden. Wenn nichts Ihren Anforderungen entspricht, können Sie eine StudentFactory für alle Arten von Objektkonstruktionsaufgaben erstellen.

Das Entscheidende ist, die Persistenzschicht aus dem Kopf zu halten, wenn wir an den Domänenobjekten arbeiten.

3
Waku-2

Ich weiß, dass ich zu spät zur Party komme, aber ich habe mir ein ziemlich flexibles Muster ausgedacht, das einige wirklich interessante und vielseitige Implementierungen ermöglichen sollte.

Richten Sie Ihre Klasse wie gewohnt mit beliebigen Variablen ein.

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

Wenn Sie ein Objekt erstellen, übergeben Sie einfach ein assoziatives Array, wobei die Schlüssel des Arrays mit den Namen Ihrer Variablen identisch sind.

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

print_r($sample_variable);

Die print_r($sample_variable); nach dieser Instanziierung ergibt folgendes:

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

Da wir $group In unserer Funktion __construct(...) auf null initialisieren, ist es auch gültig, dem Konstruktor so etwas wie nichts zu übergeben ...

$sample_variable = new MyClass();

print_r($sample_variable);

Jetzt ist die Ausgabe genau wie erwartet:

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

Der Grund, warum ich das geschrieben habe, war, dass ich die Ausgabe von json_decode(...) direkt an meinen Konstruktor übergeben konnte, ohne mir zu viele Sorgen darüber zu machen.

Dies wurde ausgeführt in PHP 7.1. Viel Spaß!

2
David Culbreth

Lassen Sie mich hier mein Sandkorn hinzufügen

Ich persönlich mag es, Konstruktoren als statische Funktionen hinzuzufügen, die eine Instanz der Klasse (das Objekt) zurückgeben. Der folgende Code ist ein Beispiel:

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

Beachten Sie, dass Sie jetzt eine Instanz der Person-Klasse wie folgt erstellen können:

$person1 = Person::withName('Example');
$person2 = Person::withEmail('[email protected]_email.com');

Ich habe diesen Code genommen von:

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

2
Salvi Pascual

Hier ist eine elegante Möglichkeit, dies zu tun. Erstellen Sie ein Merkmal, mit dem mehrere Konstruktoren bei der angegebenen Anzahl von Parametern aktiviert werden können. Sie würden einfach die Anzahl der Parameter zum Funktionsnamen "__construct" hinzufügen. Ein Parameter ist also "__construct1", zwei "__construct2" ... usw.

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

Für PHP7 vergleiche ich auch den Parametertyp. Sie können zwei Konstruktoren mit der gleichen Anzahl von Parametern, aber unterschiedlichem Typ haben.

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

}

Um es zu benutzen:

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, überrascht, dass ich diese Antwort noch nicht sehe, nehmen wir an, ich werfe meinen Hut in den 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',
    .
    .
    .
]);

Sie können die Optionen optional in eine eigene Klasse unterteilen, z. B. SplEnum erweitern.

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

Als Antwort auf die beste Antwort von Kris (die erstaunlicherweise dazu beigetragen hat, meine eigene Klasse zu entwerfen), ist hier eine modifizierte Version für diejenigen, die sie vielleicht nützlich finden. Enthält Methoden zum Auswählen aus einer beliebigen Spalte und zum Ablegen von Objektdaten aus dem Array. Prost!

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

Ich habe diese Methode erstellt, um sie nicht nur für Konstruktoren, sondern auch für Methoden verwenden zu können:

Mein Konstrukteur:

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

Meine doSomething-Methode:

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

Beides funktioniert mit dieser einfachen Methode:

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

So können Sie deklarieren

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

oder

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

und so weiter :)

Und bei der Verwendung von:

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

es wird __constructN aufgerufen, in dem Sie N args definiert haben

dann $ myObject -> doSomething ($ arg1, $ arg2, ..., $ argM)

es wird doSomethingM aufrufen, wo Sie M args definiert haben;

0
jechaviz

Sie können dem Konstruktor jederzeit einen zusätzlichen Parameter hinzufügen, der so etwas wie mode heißt, und dann eine switch-Anweisung ausführen ...

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

Auch mit dieser Methode können Sie jederzeit, wenn Sie weitere Funktionen hinzufügen möchten, der switch-Anweisung einen weiteren case hinzufügen und überprüfen, ob jemand das Richtige durchgeschickt hat - im obigen Beispiel sind alle Daten in Ordnung mit Ausnahme von C, da dies auf "irgendetwas" gesetzt ist, wird das Fehlerflag in der Klasse gesetzt und die Steuerung an das Hauptprogramm zurückgegeben, damit entschieden werden kann, was als nächstes zu tun ist (im Beispiel habe ich es gerade angewiesen, mit einem zu beenden) Fehlermeldung "ungültiger Modus" - alternativ können Sie auch eine Schleife durchführen, bis gültige Daten gefunden wurden.

0
TheKLF99

Aufruf von Konstruktoren nach Datentyp:

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

Soweit ich weiß, wird das Überladen in PHP nicht unterstützt. Sie können die get- und set-Methoden von Eigenschaften nur mit overload () überladen. ( http://www.php.net/manual/en/overload.examples.basic.php )

0
Florian Peschka