Best way to do multiple constructors in PHP

Viewed 226565

You can't put two __construct functions with unique argument signatures in a PHP class. I'd like to do this:

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

What is the best way to do this in PHP?

25 Answers

Here is an elegant way to do it. Create trait that will enable multiple constructors given the number of parameters. You would simply add the number of parameters to the function name "__construct". So one parameter will be "__construct1", two "__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

This question has already been answered with very smart ways to fulfil the requirement but I am wondering why not take a step back and ask the basic question of why do we need a class with two constructors? If my class needs two constructors then probably the way I am designing my classes needs little more consideration to come up with a design that is cleaner and more testable.

We are trying to mix up how to instantiate a class with the actual class logic.

If a Student object is in a valid state, then does it matter if it was constructed from the row of a DB or data from a web form or a cli request?

Now to answer the question that that may arise here that if we don't add the logic of creating an object from db row, then how do we create an object from the db data, we can simply add another class, call it StudentMapper if you are comfortable with data mapper pattern, in some cases you can use StudentRepository, and if nothing fits your needs you can make a StudentFactory to handle all kinds of object construction tasks.

Bottomline is to keep persistence layer out of our head when we are working on the domain objects.

I know I'm super late to the party here, but I came up with a fairly flexible pattern that should allow some really interesting and versatile implementations.

Set up your class as you normally would, with whatever variables you like.

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

When you make your object just pass an associative array with the keys of the array the same as the names of your vars, like so...

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

print_r($sample_variable);

The print_r($sample_variable); after this instantiation yields the following:

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

Because we've initialize $group to null in our __construct(...), it is also valid to pass nothing whatsoever into the constructor as well, like so...

$sample_variable = new MyClass();

print_r($sample_variable);

Now the output is exactly as expected:

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

The reason I wrote this was so that I could directly pass the output of json_decode(...) to my constructor, and not worry about it too much.

This was executed in PHP 7.1. Enjoy!

This is my take on it (build for php 5.6).

It will look at constructor parameter types (array, class name, no description) and compare the given arguments. Constructors must be given with least specificity last. With examples:

// demo class
class X {
    public $X;

    public function __construct($x) {
        $this->X = $x;
    }

    public function __toString() {
        return 'X'.$this->X;
    }
}

// demo class
class Y {
    public $Y;

    public function __construct($y) {
        $this->Y = $y;
    }
    public function __toString() {
        return 'Y'.$this->Y;
    }
}

// here be magic
abstract class MultipleConstructors {
    function __construct() {
        $__get_arguments       = func_get_args();
        $__number_of_arguments = func_num_args();

        $__reflect = new ReflectionClass($this);
        foreach($__reflect->getMethods() as $__reflectmethod) {
            $__method_name = $__reflectmethod->getName();
            if (substr($__method_name, 0, strlen('__construct')) === '__construct') {
                $__parms = $__reflectmethod->getParameters();
                if (count($__parms) == $__number_of_arguments) {
                    $__argsFit = true;
                    foreach ($__parms as $__argPos => $__param) {
                        $__paramClass= $__param->getClass();
                        $__argVar = func_get_arg($__argPos);
                        $__argVarType = gettype($__argVar);
                        $__paramIsArray = $__param->isArray() == true;
                        $__argVarIsArray = $__argVarType == 'array';
                        // parameter is array and argument isn't, or the other way around.
                        if (($__paramIsArray && !$__argVarIsArray) ||
                            (!$__paramIsArray && $__argVarIsArray)) {
                            $__argsFit = false;
                            continue;
                        }
                        // class check
                        if ((!is_null($__paramClass) && $__argVarType != 'object') ||
                            (is_null($__paramClass) && $__argVarType == 'object')){
                            $__argsFit = false;
                            continue;
                        }
                        if (!is_null($__paramClass) && $__argVarType == 'object') {
                            // class type check
                            $__paramClassName = "N/A";
                            if ($__paramClass)
                                $__paramClassName = $__paramClass->getName();
                            if ($__paramClassName != get_class($__argVar)) {
                                $__argsFit = false;
                            }
                        }
                    }
                    if ($__argsFit) {
                        call_user_func_array(array($this, $__method_name), $__get_arguments);
                        return;
                    }
                }
            }
        }
        throw new Exception("No matching constructors");
    }
}

// how to use multiple constructors
class A extends MultipleConstructors {
    public $value;

    function __constructB(array $hey) {
        $this->value = 'Array#'.count($hey).'<br/>';
    }
    function __construct1(X $first) {
        $this->value = $first .'<br/>';
    }

    function __construct2(Y $second) {
        $this->value = $second .'<br/>';
    }
    function __constructA($hey) {
        $this->value = $hey.'<br/>';
    }

    function __toString() {
        return $this->value;
    }
}

$x = new X("foo");
$y = new Y("bar");

$aa = new A(array("one", "two", "three"));
echo $aa;

$ar = new A("baz");
echo $ar;

$ax = new A($x);
echo $ax;

$ay = new A($y);
echo $ay;

Result:

Array#3
baz
Xfoo
Ybar

Instead of the terminating exception if no constructor is found, it could be remove and allow for "empty" constructor. Or whatever you like.

I was facing the same issue on creating multiple constructors with different signatures but unfortunately, PHP doesn't offer a direct method to do so. Howerever, I found a trick to overcome that. Hope works for all of you too.

    <?PHP

    class Animal
    {

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

        if (method_exists($this, $function = '__construct'.$numberOfArguments)) {
            call_user_func_array(array($this, $function), $arguments);
        }
    }
   
    public function __construct1($a1)
    {
        echo('__construct with 1 param called: '.$a1.PHP_EOL);
    }
   
    public function __construct2($a1, $a2)
    {
        echo('__construct with 2 params called: '.$a1.','.$a2.PHP_EOL);
    }
   
    public function __construct3($a1, $a2, $a3)
    {
        echo('__construct with 3 params called: '.$a1.','.$a2.','.$a3.PHP_EOL);
    }
}

$o = new Animal('sheep');
$o = new Animal('sheep','cat');
$o = new Animal('sheep','cat','dog');

// __construct with 1 param called: sheep
// __construct with 2 params called: sheep,cat
// __construct with 3 params called: sheep,cat,dog

Hmm, surprised I don't see this answer yet, suppose I'll throw my hat in the 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',
    .
    .
    .
]);

You can optionally separate the options into their own class, such as extending SplEnum.

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

More modern aproach: You are mixing seperate classes into one, entity & data hydration. So for your case you should have 2 classes:

class Student 
{
   protected $id;
   protected $name;
   // etc.
}
class StudentHydrator
{
   public function hydrate(Student $student, array $data){
      $student->setId($data['id']);
      if(isset($data['name')){
        $student->setName($data['name']);
      }
      // etc. Can be replaced with foreach
      return $student;
   }
}

//usage
$hydrator = new StudentHydrator();
$student = $hydrator->hydrate(new Student(), ['id'=>4]);
$student2 = $hydrator->hydrate(new Student(), $rowFromDB);

Also please note that you should use doctrine or other ORM that already provides automatic entity hydration. And you should use dependency injection in order to skip mannualy creating objects like StudentHydrator.

Starting with PHP 8 we can use named arguments:

class Student {

  protected int $id;
  protected string $name;

  public function __construct(int $id = null, string $name = null, array $row_from_database = null) {
    if ($id !== null && $name !== null && $row_from_database === null) {
      $this->id = $id;
      $this->name = $name;
    } elseif ($id === null && $name === null
        && $row_from_database !== null
        && array_keys($row_from_database) === [ 'id', 'name' ]
        && is_int($row_from_database['id'])
        && is_string($row_from_database['name'])) {
      $this->id = $row_from_database['id'];
      $this->name = $row_from_database['name'];
    } else {
      throw new InvalidArgumentException('Invalid arguments');
    }
  }

}

$student1 = new Student(id: 3, name: 'abc');
$student2 = new Student(row_from_database: [ 'id' => 4, 'name' => 'def' ]);

With proper checking it is possible to rule out invalid combinations of arguments, so that the created instance is a valid one at the end of the constructor (but errors will only be detected at runtime).

Kris's answer is great, but as Buttle Butku commented, new static() would be preferred in PHP 5.3+.

So I'd do it like this (modified from Kris's answer):

<?php

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

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

    public static function withRow( array $row ) {
        $instance = new static();
        $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
    }
}

?>

Usage:

<?php

$student1 = Student::withID($id);
$student2 = Student::withRow($row);

?>

I also found an useful example in php.net OOP document.

Related