400

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?

Jannie Theunissen
  • 28,256
  • 21
  • 100
  • 127
  • 121
    I dream of named constructors and method overload too +1 – SparK Nov 04 '11 at 17:05
  • In my case, I want to have a protected constructor that has one less required argument than the public one - for the sake of standardizing its factory method. I need a class to be able to create copies of itself, and the factory is in an abstract class, but the concrete classes may have constructors that require a second argument - which the abstract class has no idea of. – XedinUnknown Sep 16 '16 at 12:00
  • Not really something of value but something I stumbled upon some time ago: the class DatePeriod in date_c.php has multiple constructors. But I do not know what PHP does internally with it. – aProgger Aug 07 '20 at 08:29

25 Answers25

548

I'd probably do something like this:

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

?>

Then if i want a Student where i know the ID:

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

Or if i have an array of the db row:

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

Technically you're not building multiple constructors, just static helper methods, but you get to avoid a lot of spaghetti code in the constructor this way.

Kris
  • 40,604
  • 9
  • 72
  • 101
  • 3
    Looks like you've just answered the question I asked gpilotino. Thanks! Very clear. – Jannie Theunissen Nov 09 '09 at 14:48
  • 1
    @JannieT, not really, factories are a bit more complicated than this, And imho probably overkill for this problem – Kris Nov 09 '09 at 14:51
  • 1
    can you elaborate more, i don't think it's overkill. – gpilotino Nov 09 '09 at 15:50
  • 8
    @gpilotino, overkill because you'd need yet another class, (or method) that would basically just consist of a switch/case decision tree, in the end just doing what I already did in two methods. factories are more useful in situations where you can't easily define the exact constraints of a problem, like creating form elements. but then, that's just my opinion and for the record; I don't claim it to be fact. – Kris Nov 09 '09 at 16:24
  • 5
    And could not we also make `__construct()` private, to prevent someone from ocassionally allocating a "non-ininitialized" instance? – mlvljr Apr 13 '11 at 15:08
  • 5
    @mlvljr: you could, but i'd suggest making it protected instead of private. otherwise you'll most likely run into trouble if you're ever going to extend your class. – Kris Apr 23 '11 at 23:20
  • @Kris So, you chose not to do it in educational purposes (i.e. to make the example's main idea easier to underastand)? – mlvljr Apr 26 '11 at 08:37
  • 1
    Not really, I generally don't see a problem with allocating empty instances to be able to manually set them up. (actually, I like always being able to do `new ClassName();`) – Kris Apr 26 '11 at 10:44
  • its not the question. Method overloading in not possible in PHP. – Murtaza Khursheed Hussain Nov 26 '13 at 10:58
  • 2
    It solves the problem the OP was having. And overloading (http://php.net/manual/it/language.oop5.overloading.php) _is_ possible in php, just not in the same way you'd do it in languages like c# or java – Kris Nov 26 '13 at 11:43
  • 15
    Note from PHP 5.3 on you should probably use `new static()` rather than `new self()`, since `new static()` will work more sanely in child classes. – Buttle Butkus May 26 '14 at 01:59
  • You could use something like [this](https://gist.github.com/AndroxxTraxxon/6bb8bd370c6b7ad9f48ccd612c7b5d83), where they make an associative array, and only use what's in the array. – David Culbreth Aug 21 '18 at 22:16
  • WOW. That's no fun. – Wael Assaf Jul 23 '19 at 19:53
  • Just adding a little sidenote: If you use this approach, you should set your usual constructor's visibility to `private`, as it should not be accessed normally (also see [this](https://www.php.net/manual/en/language.oop5.decon.php#object.construct); section "Static Creation Methods"). You may also specify the [static return type](https://php.watch/versions/8.0/static-return-type) to narrow the return type down to your class. – DevelJoe Jul 08 '22 at 13:52
97

The solution of Kris is really nice, but I prefer a mix of factory and fluent style:

<?php

class Student
{

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

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

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

    /**
     * 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);
?>
timaschew
  • 16,254
  • 6
  • 61
  • 78
  • 2
    +1; This type of solution can yield really nice code. Although I would opt for `setLastName` (or rather all setters) in this solution to return `$this` instead of having effectively two setters on the same property. – Kris Aug 26 '13 at 22:23
  • 4
    As someone used to compiled, statically typed languages like C# this way of doing things just sits nicely with me. – Adam Jul 11 '14 at 11:35
  • How does providing a static create method differ from just using the constructor in the same way? `$student = new Student()->setFirstName("John")->setLastName("Doe");` – Jeger May 30 '16 at 15:11
  • 4
    There's an important problem with that code: you can't ensure that the instance is valid (that's why there are constructors) and usually immutable classes are preferable. – Pedro Amaral Couto Apr 16 '17 at 11:53
  • 1
    This is most clean code which is seen for such question, and it can cleaner by use `return new self()` in the `create()` method – Nazari Jan 25 '21 at 14:45
  • Hi I'm learning more about PHP objects and, like Jeger, I'm wondering why the create() function is needed and what it does. Can anyone explain that? Thanks! – HugoScott Oct 22 '21 at 15:31
  • In this case it's a static function - it means you can call it without the `new` keyword. It also returns a new instance. You could also do it instead of ` Student::create()` also this: `new Student()` – timaschew Oct 23 '21 at 19:00
46

PHP is a dynamic language, so you can't overload methods. You have to check the types of your argument like this:

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.  
    }
}
Daff
  • 43,734
  • 9
  • 106
  • 120
  • 24
    All that leads to is awesome spaghetti code. But it is indeed probably the easiest way to do it. – Kris Nov 09 '09 at 14:35
  • If you create your constructors as you would in a statically typed language it will become spaghetti code. But you don't. Creating two constructors with one parameter and no type (no type == any type) for that parameter will not work in any language, anyway (e.g. it won't work to have two Java constructors with one Object parameter each in one class, either). – Daff Nov 09 '09 at 15:08
  • 1
    What i meant is that you're doing different things in the same scope based on outside influence, It's not a bad solution (since it will work), just not the one I would choose. – Kris Nov 09 '09 at 16:27
  • Well I like your factory approach, too. I just think that you would have to check the variable type at one point after all. – Daff Nov 09 '09 at 20:51
  • small typo $this->id = $id; should be $this->id = $idOrRow; – Jannie Theunissen Nov 19 '09 at 01:39
  • 15
    A language being "dynamic" does not exclude the possibility for function/constructor overloads. It does not even exclude static typing. And even if, there would still be the possibility of allowing overloads based purely on argument-count. Please don't use "dynamic" as an excuse for things. – Sebastian Mach Jan 13 '15 at 10:08
  • 1
    I like this as a way of simplifying the code for the user of the class, and it accomplishes what the OP wanted. It won't be spaghetti code if you make two functions (like Kris' answer) and just call the functions appropriately in the constructor. The code for checking arguments is likely not that complicated. This assumes of course that there is some way to distinguish the arguments from each other as in this case. – Dovev Hefetz Nov 03 '15 at 13:56
27
public function __construct() {
    $parameters = func_get_args();
    ...
}

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

Now $paramters will be an array with the values 'One', 'Two', 3.

Edit,

I can add that

func_num_args()

will give you the number of parameters to the function.

Björn
  • 29,019
  • 9
  • 65
  • 81
  • 3
    How does this solve the problem of knowing what was passed? I think it complicates the issue as instead of having to check the type of the parameter, you have to check if x parameter is set and then the type of it. – Andrei Serdeliuc ॐ Nov 09 '09 at 08:52
  • It doesn't solve the problem to know what type was passed, but it's the way to go for "multiple constructors" in PHP. Type checking is up to OP to do. – Björn Nov 09 '09 at 08:54
  • 23
    i wonder what happens when a new developer is added to a project with lots of code like this – Kris Nov 09 '09 at 15:01
  • In my case this is a fairly good solution as I only need two simple variants and the arguments are not ambiguous to not know which "ctor version" to call. Also there is less change to be made in code if I would want to just change the ctor from v1 to v2 for an object. As a C++ developer it is very strange to throw exception from constructor for invalid arguments, but PHP is a strange language anyway :)) – Dani Feldman May 17 '23 at 12:43
27

As has already been shown here, there are many ways of declaring multiple constructors in PHP, but none of them are the correct way of doing so (since PHP technically doesn't allow it). But it doesn't stop us from hacking this functionality... Here's another example:

<?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: The easiest way to use and understand multiple constructors:

Hope this helps. :)

Nasif Md. Tanjim
  • 3,862
  • 4
  • 28
  • 38
  • 1
    This is the best solution. Can be even more elegant if using PHP 5.6+ with the new `...` operator. – Chris Bornhoft Apr 01 '15 at 18:28
  • Of course this won't work with JannieT's original question since her desired constructors were `__construct($id)` and `__construct($row_from_database)`. Both have one argument, presumably either an **int** for the first and an **array** or **object** for the second. The appending of a number could, of course, be extended to be some sort of argument signature in C++ style (i.e., `__construct_i($intArg)` and `__construct_a($arrayArg)`). – LavaSlider Sep 02 '15 at 13:21
  • +1: I kinda like this, but extended with type information and without the double underscore prefixes in the nested ctors. Thanks for the inspiration! – Kris Nov 23 '15 at 23:04
  • You even could add reflection to your example code to apply type checks on parameters of each function of the class that starts with __construct and match the appropriate constructor that way – Tom Kraak Feb 26 '20 at 12:28
16

As of version 5.4, PHP supports traits. This is not exactly what you are looking for, but a simplistic trait based approach would be:

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

We end up with two classes, one for each constructor, which is a bit counter-productive. To maintain some sanity, I'll throw in a factory:

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

So, it all comes down to this:

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

It's a horribly verbose approach, but it can be extremely convenient.

yannis
  • 6,215
  • 5
  • 39
  • 49
16

You could do something like this:

public function __construct($param)
{
    if(is_int($param)) {
         $this->id = $param;
    } elseif(is_object($param)) {
     // do something else
    }
 }
Andrei Serdeliuc ॐ
  • 5,828
  • 5
  • 39
  • 66
9

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
Jed Lynch
  • 1,998
  • 18
  • 14
7

Another option is to use default arguments in the constructor like this

class Student {

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

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

This means you'll need to instantiate with a row like this: $student = new Student($row['id'], $row) but keeps your constructor nice and clean.

On the other hand, if you want to make use of polymorphism then you can create two classes like so:

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));
    }
}
rojoca
  • 11,040
  • 4
  • 45
  • 46
  • 3
    now you have two classes with different names but the same functionality just a different signature on the constructor, sounds like a pretty bad idea to me. – Kris Nov 09 '09 at 15:36
  • 4
    Sounds like classic polymorphism to me, otherwise known as object oriented programming. – rojoca Nov 09 '09 at 18:59
  • 9
    Creating multiple classes to provide different constructors is indeed a bad idea. Classes that `extends` other classes should extend, meaning they should have added functionality, thats the point of OOP, not this. – Jonathan Aug 12 '12 at 15:09
4

as stated in the other comments, as php does not support overloading, usually the "type checking tricks" in constructor are avoided and the factory pattern is used intead

ie.

$myObj = MyClass::factory('fromInteger', $params);
$myObj = MyClass::factory('fromRow', $params);
gpilotino
  • 13,055
  • 9
  • 48
  • 61
4

You could do something like the following which is really easy and very clean:

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;            
   }
}
Carrie Kendall
  • 11,124
  • 5
  • 61
  • 81
paishin
  • 51
  • 2
  • 2
    why assign `func_get_args` to a variable and call it again in the next line? would also be better if you only called `func_get_args` after deciding you need to based on `fund_num_args`. – Kris Aug 15 '12 at 20:32
  • 7
    Imho this is the opposite of a clean solution – Jonathan Feb 16 '13 at 12:46
4

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.

Waku-2
  • 1,136
  • 2
  • 13
  • 26
4

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!

David Culbreth
  • 2,610
  • 16
  • 26
  • You can do some cool stuff like throwing an exception when an unexpected value is entered in the array. There is an example of this on a [gist that I wrote up](https://gist.github.com/AndroxxTraxxon/6bb8bd370c6b7ad9f48ccd612c7b5d83) – David Culbreth Aug 21 '18 at 22:18
4

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
stanley mbote
  • 956
  • 1
  • 7
  • 17
3

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.

galmok
  • 869
  • 10
  • 21
2

Let me add my grain of sand here

I personally like adding a constructors as static functions that return an instance of the class (the object). The following code is an example:

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

Note that now you can create instance of the Person class like this:

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

I took that code from:

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

Salvi Pascual
  • 1,788
  • 17
  • 22
2

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;
}
  • 1
    I also thought of this when I had to solve the following problem. My class should get a constructor that can be called either without a parameter or with a defined number of parameters (in this case 3). With the array it is very easy to check using empty and count and take appropriate action. If empty then terminate the function, because there is nothing to assign or if the number of parameters or their value does not fit throw appropriate exceptions. Translated with www.DeepL.com/Translator (free version) – Alexander Behling Dec 10 '21 at 16:01
2

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

lukas.j
  • 6,453
  • 2
  • 5
  • 24
  • This is the cleaniest solution which is possible with PHP since overloading as we know from other programming languages like Java or C# isn't possible with PHP due to its interpreting. With traditional overloading the right constructor is choosen at runtime based on its signature which means the runtime does the same checks you do explicitly in your php constructor. So I think it's ok. However there could be also a problem if your have parameters with the same datatype but a different meaning. Then traditional overloading wouldn't work also since the functions signature wouldn't differ. – Alexander Behling Jan 17 '23 at 15:07
  • By the way the so called overloading methods in PHP e.g. __set can also be use for proper checking. If you want to ensure, that no dynamic class variable are created which would be possible with a method like __set. In __set you could also add logic so only values which makes sense in the context are stored in the corresponding class variable. – Alexander Behling Jan 17 '23 at 15:07
1

For php7, I compare parameters type as well, you can have two constructors with same number of parameters but different type.

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

}

To use it:

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;
    }
}
Serginho
  • 7,291
  • 2
  • 27
  • 52
1

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.

YuraV
  • 103
  • 1
  • 7
1

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.

Sophie Su
  • 73
  • 1
  • 3
0

Call constructors by data type:

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

} 
viral
  • 3,724
  • 1
  • 18
  • 32
0

In response to the best answer by Kris (which amazingly helped design my own class btw), here is a modified version for those that might find it useful. Includes methods for selecting from any column and dumping object data from array. Cheers!

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

You could always add an extra parameter to the constructor called something like mode and then perform a switch statement on it...

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

Also with that method at any time if you wanted to add more functionality you can just add another case to the switch statement, and you can also check to make sure someone has sent the right thing through - in the above example all the data is ok except for C as that is set to "something" and so the error flag in the class is set and control is returned back to the main program for it to decide what to do next (in the example I just told it to exit with an error message "invalid mode" - but alternatively you could loop it back round until valid data is found).

TheKLF99
  • 255
  • 2
  • 4
0

I created this method to let use it not only on constructors but in methods:

My constructor:

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

My doSomething method:

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

Both works with this simple method:

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 you can declare

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

or

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

and so on :)

And when using:

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

it will call __constructN, where you defined N args

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

it will call doSomethingM, , where you defined M args;

jechaviz
  • 551
  • 1
  • 9
  • 23