I'm wanting to create a new instance of my Class and assign it's attributes the values that are returned. The reason for this is I'm creating a series of methods inheriting from the calling class, as opposed to using static methods which I already had working.
Example of what I'm using currently:
public static function findById($id) {
$id = self::escapeParam($id);
$idVal = is_int($id) ? "i" : "s";
$sql = "SELECT * FROM ".static::$db_table." WHERE id = ? LIMIT 1";
return static::findByQuery($sql,$idVal,$id);
}
public static function findByQuery($sql,$bindChar = '',$bindVal = '') {
try {
$callingClass = get_called_class();
$object = new $callingClass;
$statement = Database::$connection->prepare($sql);
if(!empty($bindChar)) :
$statement->bind_param($bindChar, $bindVal);
endif;
if($statement->execute()) :
$result = $statement->get_result();
$object = $result->fetch_object();
endif;
$statement->close();
if(!empty($object)) :
return $object;
endif;
} catch(Exception $e) {
}
}
What I tried was writing an instantiation method that creates a new instance of my class, and then assign each attribute of the object the value it returns from an array from a tutorial I did. However, the tutorial was fairly outdated and didn't use any new syntax or binding, so I was trying to rework this.
Example from the tutorial below:
public static function find_by_id($id) {
global $database;
$the_result_array = static::find_by_query("SELECT * FROM " . static::$db_table . " WHERE id = $id LIMIT 1");
return !empty($the_result_array) ? array_shift($the_result_array) : false;
}
public static function find_by_query($sql) {
global $database;
$result_set = $database->query($sql);
$the_object_array = array();
while($row = mysqli_fetch_array($result_set)) {
$the_object_array[] = static::instantation($row);
}
return $the_object_array;
}
public static function instantation($the_record){
$calling_class = get_called_class();
$the_object = new $calling_class;
foreach ($the_record as $the_attribute => $value) {
if($the_object->has_the_attribute($the_attribute)) {
$the_object->$the_attribute = $value;
}
}
return $the_object;
}
private function has_the_attribute($the_attribute) {
return property_exists($this, $the_attribute);
}
What I was trying to do from the tutorial, was to return my result as an array using a while, and then assigning a variable by passing the built array into the static::instantation() method, but it doesn't seem to ever be working correctly, as any public functions I create in my calling class (Admin for example) aren't called after as they don't exist due to the Class not being instantiated.
mysqli_result::fetch_object() accepts the class name as the first argument. You can pass the class name as an argument to that method and get the instance of the model. I am not sure why you have that much code but consider my example which I wrote based on your own code:
<?php
class Model
{
public static function findByQuery(string $sql, ?string $bindChar = null, ?string $bindVal = null): ?static
{
$statement = Database::$connection->prepare($sql);
if ($bindChar) :
$statement->bind_param($bindChar, $bindVal);
endif;
$statement->execute();
$result = $statement->get_result();
return $result->fetch_object(static::class);
}
}
class User extends Model
{
private $id;
}
class Database
{
public static mysqli $connection;
}
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
Database::$connection = new mysqli('localhost', 'user', 'password', 'test');
$user = User::findByQuery('SELECT ? as id', 's', 'Dharman');
var_dump($user);
The output from that example is:
object(User)#4 (1) {
["id":"User":private]=>
string(7) "Dharman"
}
As you can see, the code created an instance of the class using late-static binding and it also assigned the value to a private property, which you can't do otherwise.
P.S. My example is a little bit tidier. I added parameter typing and removed a lot of unnecessary code. In particular, I remove empty try-catch which is a terrible practice.
I have now got this working, although I feel this is probably not the best way of doing it.
I'm primarily front end so please comment if there are improvements or best practices.
public static function findByQuery($sql,$bindChar = '',$bindVal = '') {
try {
$statement = Database::$connection->prepare($sql);
if(!empty($bindChar)) :
$statement->bind_param("$bindChar", $bindVal);
endif;
if($statement->execute()) :
$result = $statement->get_result();
$output = $result->fetch_object();
endif;
$statement->close();
if(!empty($output)) :
$class = get_called_class();
$object = new $class;
foreach(get_object_vars($output) as $key => $value) :
$object->$key = $value;
endforeach;
endif;
if(!empty($object)) :
return $object;
endif;
} catch(Exception $e) {
}
}
My initial thoughts were declaring an object and then I thought that the PHP fetch_object call would have just assigned my object it's properties after initiating the Class but that wasn't the case.
So what I've done is that if the statement is successful and a results object is created, I then get the object properties and values with the get_object_vars() command, and then loop through these as a key value pair, assigning each attribute it's returned value.
I can confirm this works as I can now run $admin->remove() from my removal script, as opposed to what I was having to do before which was Admin::remove($id);
Code snippet for class lnemail_fetch
<?php Part of heritage_classes.php
// Declare classes
class lnemail_fetch {
// return string in format "title | factoid"
public $result;
public function get_ln_info()
{
include ("./includes/LOheritage-config.php");
mysql_connect("$dbhost", "$dbuser", "$dbpass") or die(mysql_error());
mysql_select_db("$dbname") or die(mysql_error());
$query = "SELECT * FROM lnemail";
$result = mysql_query($query);
$this->result = $result;
}
}
?>
Code Snippet from larger program It lists a MySQL table
require_once('./includes/heritage_classes.php');
$newlnemail_fetch = new lnemail_fetch;
$newlnemail_fetch->get_ln_info();
$newresult = $newlnemail_fetch->result;
echo "lnemail File display <br />";
while($row = mysql_fetch_array($newresult))
{
echo $row['ln_email']. " | " . $row['ln_date'] . " | " . $row['ln_week'] ;
echo "<br />";
}
Is this use of PHP OOP considered good practice even though it works nicely for now?
I would say no, it's no good use of OOP.
Areas for improvement:
Separate the db connection and query stuff.
Separate the db result handling. Implementing a result object that is iterable will be a good idea.
Not using the mysql extension and switching to mysqli is a very good idea. It will also give you an OOP interface to MySQL for free.
Probably aspects of escaping input inside SQL strings should be considered, but this is undecidable because no such code has been shown.
Will some future release break it?
Yes, because you are using the old and (now) deprecated mysql_* functions.
Code snippet for class lnemail_fetch
The name lnemail is not really a good name for a class, because when I look at it I have no idea what ln means. Also class names are often UpperCamelCased and methods camelCased.
Now to actually look at your code:
When looking at your class it is just a class and currently has nothing to do with OOP. What I would have done is make the $result property private, because currently your is simply some container for data. Also I would introduce another class which will be reponsible for accessing the data from the database (or whatever storage you have). I would also introduce another class to represent a single email and an factory class to build these mail objects. This would look something like the following:
// not sure whether inbox is the correct name, because I don't really have a good idea of what the class represents
class Inbox
{
private $storage;
private $mailFactory;
public function __construct($storage, $mailFactory)
{
$this->storage = $storage;
$this->mailFactory = $mailFactory;
}
public function fetchAllMails()
{
$mailRecordset = $this->storage->fetchAll();
$mails = array();
foreach ($mailRecordset as $mailRecord) {
$mails[] = $this->mailFactory->create($mailRecord);
}
return $mails;
}
}
class InboxStorage
{
private $dbConnection;
public function __construct(\PDO $dbConnection)
{
$this->dbConnection = $dbConnection;
}
public function fetchAll()
{
$stmt = $this->dbConnection->query('SELECT * FROM lnemail');
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
}
class Email
{
private $email;
private $date;
private $week;
public function __construct($email, $date, $week)
{
$this->email = $email;
$this->date = $date;
$this->week = $week;
}
public function getEmail()
{
return $this->email;
}
public function getDate()
{
return $this->date;
}
public function getWeek()
{
return $this->week;
}
}
class EmailFactory
{
public function create($record)
{
return new Email($record['email'], $record['date'], $record['week']);
}
}
And you can run it like following:
// initialize all the objects we are going to need
$emailFactory = new EmailFactory();
$dbConnection = new \PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$inboxStorage = new InboxStorage($dbConnection);
$inbox = new Inbox($inboxStorage, $mailFactory);
// run the code
foreach($inbox->fetchAllMails() as $email) {
echo $mail->email . ' | ' . $mail->date . ' | ' . $mail->week . '<br>';
}
It's not really a true class because lnemail_fetch isn't an object. All you are doing is making a container and having to make the container merely to call a function that could be static and return the result rather than assign it.
A better class might include the newer mysqli rather than the dated mysql and work as follows. It makes the rows into objects with the columns being properties (variables;
<?php
class lnemail {
public $ln_emai;
public $ln_date;
public $ln_week;
public static function Fetch($dbhost,$dbuser,$dbpass,$dbname) {
$db = new mysqli($dbhost, $dbuser, $dbpass,$dbname) or die(mysql_error());
$query = "SELECT * FROM lnemail";
$result = $db->query($query);
$returnArr = array();
while($obj = $result->fetch_object('lnemail') {
$returnArr[] = $obj;
}
return $returnArr;
}
}
Then
<?php
require_once("./includes/LOheritage-config.php");
require_once('./includes/heritage_classes.php');
$lnemails = lnemail::Fetch($dbhost,$dbuser,$dbpass,$dbname);
echo "lnemail File display <br />";
foreach($obj as $lnemail) {
echo $obj->ln_email. " | " . $obj->ln_date . " | " . $obj->ln_week;
echo "<br />";
}
I have a class like this:
class someClass {
public static function getBy($method,$value) {
// returns collection of objects of this class based on search criteria
$return_array = array();
$sql = // get some data "WHERE `$method` = '$value'
$result = mysql_query($sql);
while($row = mysql_fetch_assoc($result)) {
$new_obj = new $this($a,$b);
$return_array[] = $new_obj;
}
return $return_array;
}
}
My question is: can I use $this in the way I have above?
Instead of:
$new_obj = new $this($a,$b);
I could write:
$new_obj = new someClass($a,$b);
But then when I extend the class, I will have to override the method. If the first option works, I won't have to.
UPDATE on solutions:
Both of these work in the base class:
1.)
$new_obj = new static($a,$b);
2.)
$this_class = get_class();
$new_obj = new $this_class($a,$b);
I have not tried them in a child class yet, but I think #2 will fail there.
Also, this does not work:
$new_obj = new get_class()($a,$b);
It results in a parse error: Unexpected '('
It must be done in two steps, as in 2.) above, or better yet as in 1.).
Easy, use the static keyword
public static function buildMeANewOne($a, $b) {
return new static($a, $b);
}
See http://php.net/manual/en/language.oop5.late-static-bindings.php.
You may use ReflectionClass::newInstance
http://ideone.com/THf45
class A
{
private $_a;
private $_b;
public function __construct($a = null, $b = null)
{
$this->_a = $a;
$this->_b = $b;
echo 'Constructed A instance with args: ' . $a . ', ' . $b . "\n";
}
public function construct_from_this()
{
$ref = new ReflectionClass($this);
return $ref->newInstance('a_value', 'b_value');
}
}
$foo = new A();
$result = $foo->construct_from_this();
Try using get_class(), this works even when the class is inherited
<?
class Test {
public function getName() {
return get_class() . "\n";
}
public function initiateClass() {
$class_name = get_class();
return new $class_name();
}
}
class Test2 extends Test {}
$test = new Test();
echo "Test 1 - " . $test->getName();
$test2 = new Test2();
echo "Test 2 - " . $test2->getName();
$test_initiated = $test2->initiateClass();
echo "Test Initiated - " . $test_initiated->getName();
When running, you'll get the following output.
Test 1 - Test
Test 2 - Test
Test Initiated - Test
How can i pass a class as a parameter in my function
So far i've tried
$sc = new SampleClass();
SampleFunction($sc);
function SampleFunction(&$refClass)
{
echo $refClass->getValue();
}
this is a simplified example of what im doing.. i actually have to do complex procedures inside this sample function. I'm not getting any response from the sample function. What am i doing wrong? thank you
UPDATE
char.php
class Charss {
var $name=0;
var $hp=500;
var $spd=10;
var $rtime=10;
var $dmg=10;
function __construct( $name, $hp, $spd, $rtime , $dmg) {
$this->name = $name;
$this->hp = $hp;
$this->spd = $spd;
$this->rtime = $rtime;
$this->dmg = $dmg;
}
function get_name() {
return $this->name;
}
function set_name($new_name) {
$this->name = $new_name;
}
function get_hp() {
return $this->hp;
}
function set_hp($new_hp) {
$this->hp = $new_hp;
}
function get_spd() {
return $this->spd;
}
function set_spd($new_spd) {
$this->spd = $new_spd;
}
function get_rtime() {
return $this->rtime;
}
function set_rtime($new_rtime) {
$this->rtime = $new_rtime;
}
function get_dmg() {
return $this->get_dmg;
}
function set_dmg($new_dmg) {
$this->dmg = $new_dmg;
}
}
myclass.php
require("char.php");
class Person {
function try_process()
{
$chr1 = new Charss("Player1",500,3,0,50);
$chr2 = new Charss("Player2",500,6,0,70);
while ($chr1->get_hp() > 0 && $chr2->get_hp() > 0)
{
$sth = min($chr1->get_rtime(), $chr2->get_rtime());
if ($chr1->get_rtime() == 0 && $chr2->get_rtime() > 0)
{
exit;
Fight($chr1,$chr2);
$chr1->set_rtime($chr1->get_spd());
}
elseif ($chr2->get_rtime() == 0 && $chr1->get_rtime() > 0)
{
Fight($chr2,$chr1);
$chr2->set_rtime($chr2->get_spd());
}
else
{
Fight($chr1,$chr2); #having trouble with this
$chr1->set_rtime($chr1->get_spd());
}
$chr1->set_rtime($chr1->get_rtime() - $sth);
$chr2->set_rtime($chr2->get_rtime() - $sth);
}
}
function Fight($atk,$def)
{
$def->set_hp($def->get_hp() - $atk->get_dmg());
echo $atk->get_name() . " attacked " . $def->get_name() . " for " . $atk->get_dmg() . " damage";
}
}
so im calling the function try_process on button click
What you're actually doing there is passing an object, not a class.
$sc = new SampleClass();
creates an instance of SampleClass, aka an object.
I assume there's some error being thrown elsewhere as what you have is correct.
I tested the following code and got the expected output:
class SampleClass
{
public function getValue()
{
return 4;
}
}
$sc = new SampleClass();
SampleFunction($sc);
function SampleFunction(&$refClass)
{
echo $refClass->getValue();
}
Output: 4
If you provide more details of your actual code we might be able to determine the problem.
I can't see anything wrong with your code
using &$refClass is however is not recommended and I guess willbe removed from future iteration of PHP version
but here is an example
class objects are passed as reference I suppose so no need of '&'
http://ideone.com/GbmUy
Why is the function argument a reference? Probably shouldn't be.
Other than that, there's nothing wrong with you posted, so the error is likely within SampleClass.
Others have answered pretty well, but this is a silly little example to show you how to modify the class (either by calling a property setter, or setting public properties directly)
class foo {
private $member1;
public $member2;
public function __construct($member1,$member2) {
$this->member1=$member1;
$this->member2=$member2;
}
public function SetMember1($value) {
$this->member1 = $value;
}
public function GetMember1() {
return $this->member1;
}
}
function SetMembers(foo $obj, $member1, $member2) {
// Call a setter
$obj->SetMember1($member1);
// Set a member variable directly
$obj->member2 = $member2;
}
$obj = new foo('default member 1', 'default member 2');
echo "member1 (before): {$obj->GetMember1()}\n";
echo "member2 (before): {$obj->member2}\n";
// Change values
SetMembers($obj, 'new member1', 'new member2');
echo "member1 (after): {$obj->GetMember1()}\n";
echo "member2 (after): {$obj->member2}\n";
This will output:
member1 (before): default member 1
member2 (before): default member 2
member1 (after): new member1
member2 (after): new member2
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?
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.
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);
?>
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.
}
}
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. :)
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.
You could do something like this:
public function __construct($param)
{
if(is_int($param)) {
$this->id = $param;
} elseif(is_object($param)) {
// do something else
}
}
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.
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
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));
}
}
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);
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;
}
}
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!
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
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.
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
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;
}
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).
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;
}
}
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.
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.
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>");
}
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
}
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).
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;