views:

1092

answers:

8

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?

+2  A: 

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
+1 for a very workable solution. However, for the class in mind I'll use @Kris' method.
JannieT
+3  A: 
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
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
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
i wonder what happens when a new developer is added to a project with lots of code like this
Kris
+10  A: 

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
All that leads to is awesome spaghetti code. But it is indeed probably the easiest way to do it.
Kris
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
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
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
small typo $this->id = $id; should be $this->id = $idOrRow;
JannieT
A: 

As far as I know overloading is not supported in PHP. You can only overload properties' get and set methods with overload(); (http://www.php.net/manual/en/overload.examples.basic.php)

ApoY2k
A: 

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
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
Sounds like classic polymorphism to me, otherwise known as object oriented programming.
rojoca
A: 

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
Looks neat. I'm not familiar with factories. In your example, would $myObj be of the type MyClass? What would the two static functions look like that return a constructed instance of $myObj?
JannieT
I would use seperate methods like Kris did to prevent one big factory method.
Ikke
indeed, @Kris solution is the best.
gpilotino
+7  A: 

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
Looks like you've just answered the question I asked gpilotino. Thanks! Very clear.
JannieT
@JannieT, not really, factories are a bit more complicated than this, And imho probably overkill for this problem
Kris
can you elaborate more, i don't think it's overkill.
gpilotino
@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
A: 

or even better 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: break; //No arguments

case 1: $this->do_something($arguments[0]); break; //One argument           

case 2: $this->do_something_else($arguments[0], $arguments[1]); break; //Two arguments            

}

}

paishin