在PHP中执行多个构造函数的最佳方法

你不能把两个具有唯一参数签名的__construct函数放在一个PHP类中。 我想这样做:

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

什么是在PHP中这样做的最好方法?

我可能会做这样的事情:

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

那么如果我想要一个学生,我知道这个ID:

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

或者,如果我有一个数据库的行:

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

从技术上讲,你不是build立多个构造函数,只是静态的帮助器方法,但是你可以这样避免构造器中的许多意大利面代码。

克里斯的解决scheme真的很好,但我发现工厂和stream利风格的组合更好:

 <?php class Student { protected $firstName; protected $lastName; // etc. /** * Constructor */ public function __construct() { // allocate your stuff } /** * Static constructor / factory */ public static function create() { $instance = new self(); return $instance; } /** * FirstName setter - fluent style */ public function setFirstName( $firstName) { $this->firstName = $firstName; return $this; } /** * LastName setter - fluent style */ public function setLastName( $lastName) { $this->lastName = $lastName; return $this; } } // create instance $student= Student::create()->setFirstName("John")->setLastName("Doe"); // see result var_dump($student); ?> 

PHP是一种dynamic语言,所以你不能重载方法。 你必须像这样检查你的参数的types:

 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. } } 
 public function __construct() { $parameters = func_get_args(); ... } $o = new MyClass('One', 'Two', 3); 

现在$参数将是一个值为'1','2',3的数组。

编辑,

我可以补充一点

 func_num_args() 

会给你一些参数给函数。

从版本5.4开始,PHP支持特性 。 这不是你正在寻找的东西,而是一种简单的基于特征的方法:

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

我们最终得到两个类,每个构造函数一个类,这有点适得其反。 为了保持一定的理智,我会投入一个工厂:

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

所以,这一切归结为:

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

这是一个非常详细的方法,但它可以非常方便。

你可以做这样的事情:

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

正如在这里已经显示的那样,在PHP中声明multiple构造函数有很多方法,但是没有一个方法是correct的(因为PHP在技术上是不允许的)。 但是这并不能阻止我们攻击这个function…这是另外一个例子:

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

来源: 最简单的方法来使用和理解多个构造函数:

希望这可以帮助。 🙂

另一种select是在构造函数中使用默认参数

 class Student { private $id; private $name; //... public function __construct($id, $row=array()) { $this->id = $id; foreach($row as $key => $value) $this->$key = $value; } } 

这意味着你将需要像这样的一行来实例化: $student = new Student($row['id'], $row)但是保持你的构造器很好干净。

另一方面,如果你想使用多态,那么你可以创build两个类,如下所示:

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

你可以做下面这样的事情,这很容易,很干净:

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

正如其他评论所述,因为PHP不支持重载,通常在构造函数中的“types检查技巧”是避免的,工厂模式是使用intead

即。

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

让我在这里添加我的沙粒

我个人喜欢添加一个构造函数作为返回类(对象)的实例的静态函数。 下面的代码是一个例子:

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

请注意,现在您可以像这样创buildPerson类的实例:

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

我从这个代码:

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

按数据types调用构造函数:

 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 } 

这个问题已经用非常聪明的方式来回答,但是我想知道为什么不退后一步问我们为什么需要一个带有两个构造函数的类? 如果我的类需要两个构造函数,那么可能我devise我的类的方式需要更多的考虑来提出一个更清洁,更可testing的devise。

我们试图混淆如何用实际的类逻辑实例化一个类。

如果一个Student对象处于有效状态,那么它是从一个DB的行构build还是从一个Web表单或一个cli请求中构造出来?

现在回答这个问题,如果我们不添加从db行创build对象的逻辑,那么我们如何从db数据创build一个对象,我们可以简单地添加另一个类,将其称为StudentMapper if你对数据映射器模式很熟悉,在某些情况下,你可以使用StudentRepository,如果没有任何东西符合你的需求,你可以让一个StudentFactory来处理各种对象构造任务。

底线是当我们在处理域对象的时候保持持久层。

据我所知在PHP中不支持重载。 你只能用overload()方法重载属性的get和set方法。 ( http://www.php.net/manual/en/overload.examples.basic.php

为了回应克里斯的最佳答案(这惊人地帮助devise我自己的类btw),这是一个修改后的版本,可能会发现它的用处。 包括从任何列中进行select和从数组中转储对象数据的方法。 干杯!

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

你总是可以添加一个额外的参数给称为模式的构造函数,然后在其上执行一个switch语句…

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

如果你想添加更多的function,那么在任何时候也可以使用这种方法,只需要在switch语句中添加另一个案例,并且还可以检查以确保有人发送了正确的东西 – 在上面的例子中,所有的数据都可以除了C被设置为“something”,所以类中的错误标志被设置,并且控制返回到主程序,以便决定下一步做什么(在这个例子中,我只是告诉它退出错误消息“无效模式” – 但也可以将其循环,直到find有效数据为止)。

对于php7,我也比较了参数types,你可以有两个具有相同参数但不同types的构造函数。

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

要使用它:

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

我创build了这个方法,让它不仅用于构造函数,而且用于方法:

我的构造函数:

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

我的doSomething方法:

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

两者都适用于这个简单的方法:

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

所以你可以申报

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

要么

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

等等 :)

使用时:

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

它会调用__constructN ,在那里你定义了N参数

那么$ myObject – > doSomething($ arg1,$ arg2,…,$ argM)

它会调用doSomethingM ,在那里你定义了M args;