在PHP中使用多个构造函数的最佳方法

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

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中最好的方法是什么?

229992 次浏览

你可以这样做:

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


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

现在$ parameters将是一个值为'One', 'Two', 3的数组。

编辑,

我可以加上

func_num_args()

会给出函数的参数个数。

PHP是一种动态语言,因此不能重载方法。你必须像这样检查参数的类型:

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

另一种选择是像这样在构造函数中使用默认参数

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),但保持你的构造函数漂亮和干净。

另一方面,如果你想利用多态性,那么你可以创建两个类,像这样:

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

正如在其他评论中所述,由于PHP不支持重载,通常会避免构造函数中的“类型检查技巧”,而是使用工厂模式

ie。

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

我可能会这样做:

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

或者如果我有一个db行数组:

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

从技术上讲,您不需要构建多个构造函数,而只是静态帮助方法,但是这样可以避免构造函数中的大量意大利面条代码。

你可以像下面这样做,非常简单明了:

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

Kris的解决方案真的很好,但我更喜欢工厂和流畅风格的混合:

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

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

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

让我在这里加一粒沙子

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

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

注意,现在你可以像这样创建Person类的实例:

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

我的代码来自:

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

正如这里已经展示的,在PHP中有许多声明multiple构造函数的方法,但没有一种是correct的方法(因为PHP技术上不允许这样做)。 但这并不能阻止我们破解这个功能…… 下面是另一个例子:

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

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

希望这能有所帮助。:)

按数据类型调用构造函数:

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


}

为了回应Kris的最佳答案(顺便说一句,他帮助我设计了自己的课程),这里有一个修改过的版本,供那些可能会觉得有用的人使用。包括用于从任何列中进行选择和从数组中转储对象数据的方法。干杯!

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

你总是可以在构造函数中添加一个额外的参数,比如mode,然后对它执行一个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' ) ;

同样,在任何时候,如果你想添加更多的功能,你可以在switch语句中添加另一个case,你也可以检查以确保有人发送正确的事,在上面的例子中是好的除了C的所有数据,设置为“某物”,所以班上错误标志设置和控制返回给它的主程序来决定下一步要做什么(在这个例子中我只是告诉它退出一个错误消息“无效模式”——但你也可以循环回轮,直到找到有效数据)。

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

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

我创建这个方法是为了让它不仅在构造函数中使用,而且在方法中使用:

我的构造函数:

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参数

< p >然后 $myObject -> doSomething($arg1, $arg2,…argM美元)< / p >

它将调用doSomethingM,,其中您定义了M参数;

这个问题已经用非常聪明的方法来满足需求,但我想知道为什么不退一步,问一个基本的问题,为什么我们需要一个有两个构造函数的类? 如果我的类需要两个构造函数,那么我设计类的方式可能需要更多的考虑,以提出一个更干净、更可测试的设计

我们正在尝试混合如何实例化一个类与实际的类逻辑。

如果一个学生对象是在一个有效的状态,那么它是否从一个DB或从一个web表单或cli请求的数据行构造?

现在回答这个问题,可能出现的,如果我们不添加从db行创建一个对象的逻辑,那么我们如何创建一个对象从数据库数据,我们可以简单地添加另一个类,称之为StudentMapper如果你熟悉数据映射模式,在某些情况下,您可以使用StudentRepository,如果没有适合你的需要你可以StudentFactory处理所有类型的对象建设任务。

当我们在处理领域对象时,底线是不要考虑持久化层。

我知道我在这方面非常晚,但我提出了一个相当灵活的模式,应该允许一些真正有趣和通用的实现。

像往常一样设置类,使用您喜欢的任何变量。

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

当你让你的对象传递一个关联数组时,数组的键与你的vars的名称相同,就像这样。

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


print_r($sample_variable);

实例化后的print_r($sample_variable);产生如下结果:

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

因为我们已经在__construct(...)中将$group初始化为null,所以不向构造函数传递任何东西也是有效的,就像这样…

$sample_variable = new MyClass();


print_r($sample_variable);

现在输出和预期的完全一样:

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

我这样写的原因是,我可以直接将json_decode(...)的输出传递给构造函数,而不用太担心它。

这是在PHP 7.1中执行的。享受吧!

这里有一种优雅的方法。创建trait,在给定参数数量的情况下启用多个构造函数。你只需将参数的数量添加到函数名“__construct”中。所以一个参数是"__construct1",两个"__construct2"…等。

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

嗯,很惊讶我还没有看到这个答案,假设我要参加竞选。

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

您可以选择将这些选项分离到它们自己的类中,例如扩展脾。

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

这是我对它的看法(为php 5.6构建)。

它将查看构造函数参数类型(数组,类名,无描述)并比较给定的参数。构造函数必须在最后以最少的特异性给出。例子:

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

结果:

Array#3
baz
Xfoo
Ybar

如果没有找到构造函数,可以将其删除并允许构造函数为“空”,而不是终止异常。或者你喜欢什么都行。

更现代的方法: 你将不同的类混合成一个实体;水合作用的数据。 所以对于你的案例,你应该有两个类:

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);
还请注意,您应该使用doctrine或其他已经提供自动实体水合作用的ORM。 你应该使用依赖注入来跳过手动创建像StudentHydrator这样的对象

在创建具有不同签名的多个构造函数时,我也面临着同样的问题,但不幸的是,PHP没有提供直接的方法来这样做。然而,我发现了一个技巧来克服它。希望也适用于你们所有人。

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

克丽丝的回答很好,但作为Buttle Butku 评论new static()在PHP 5.3+中更受欢迎。

所以我会这样做(根据克里斯的回答修改):

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


?>

用法:

<?php


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


?>

我还在php.net OOP文档中找到了一个有用的例子

从PHP 8开始,我们可以使用命名参数:

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

通过适当的检查,可以排除无效的参数组合,这样创建的实例在构造函数的末尾是一个有效的实例(但错误只会在运行时检测到)。