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

时间:2009-11-09 08:43:44

标签: php constructor multiple-constructors

您不能在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中执行此操作的最佳方法是什么?

23 个答案:

答案 0 :(得分:433)

我可能会这样做:

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

从技术上讲,你不是构建多个构造函数,只是构造静态辅助方法,但是你可以通过这种方式避免构造函数中的大量意大利面条代码。

答案 1 :(得分:79)

Kris的解决方案非常好,但我发现工厂和流畅的风格更好:

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

答案 2 :(得分:40)

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

答案 3 :(得分:22)

public function __construct() {
    $parameters = func_get_args();
    ...
}

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

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

编辑,

我可以添加

func_num_args()

将为您提供函数的参数数量。

答案 4 :(得分:15)

正如此处已经显示的那样,在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.');

来源: The easiest way to use and understand multiple constructors:

希望这会有所帮助。 :)

答案 5 :(得分:15)

从5.4版开始,PHP支持traits。这正是您所寻找的,但基于特征的简单方法将是:

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

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

答案 6 :(得分:13)

你可以这样做:

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

答案 7 :(得分:4)

您可以执行以下操作,这非常简单且非常干净:

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

答案 8 :(得分:4)

另一种选择是在构造函数中使用默认参数,如

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

答案 9 :(得分:4)

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

即。

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

答案 10 :(得分:3)

这个问题已经用非常聪明的方式来回答这个问题但是我想知道为什么不退后一步并问一个基本的问题:为什么我们需要一个有两个构造函数的类呢? 如果我的类需要两个构造函数,那么我设计类的方式可能需要更多的考虑来设计一个更干净,更可测试的设计。

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

如果Student对象处于有效状态,那么它是从DB的行构建还是从Web表单或cli请求构建的数据是否重要?

现在回答这里可能出现的问题,如果我们不添加从db行创建对象的逻辑,那么我们如何从db数据创建一个对象,我们可以简单地添加另一个类,调用如果您对数据映射器模式感到满意,可以使用StudentMapper,在某些情况下,您可以使用StudentRepository,如果没有任何东西可以满足您的需求,您可以使StudentFactory处理各种对象构建任务。

底线是在我们处理域对象时保持持久层不在我们的脑海中。

答案 11 :(得分:2)

让我在这里添加我的沙粒

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

 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

答案 12 :(得分:2)

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

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

df_xx[0:12]
Out[163]: 
               aaa  aaa_f
1993-01-05  125.25    NaN
1993-01-06  124.84    NaN
1993-01-07  125.09    NaN
1993-01-08  125.42    NaN
1993-01-11  125.36    NaN
1993-01-12  125.05    NaN
1993-01-13  125.87    NaN
1993-01-14  125.65    NaN
1993-01-15  126.05    NaN
1993-01-18  125.82    NaN
1993-01-19  125.46    NaN
1993-01-20  125.39    NaN

结果:

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

如果没有找到构造函数,则可以终止并允许“空”构造函数,而不是终止异常。或任何您喜欢的东西。

答案 13 :(得分:1)

更现代的方法: 您正在将单独的类混合为一个,实体和数据水合。 因此,对于您的情况,您应该有 2 个类:

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

另外请注意,您应该使用已经提供自动实体水合的学说或其他 ORM。 你应该使用依赖注入来跳过手动创建像 StudentHydrator 这样的对象。

答案 14 :(得分:1)

嗯,很惊讶,我还没有看到这个答案,我想把帽子戴在戒指里。

self

您可以选择将选项分为自己的类,例如扩展SplEnum。

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

答案 15 :(得分:1)

这是一种优雅的方式。创建特质,它将在给定参数数量的情况下启用多个构造函数。您只需将参数数量添加到函数名称“ __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

答案 16 :(得分:1)

我知道我在这里参加聚会迟到了,但是我想出了一个相当灵活的模式,该模式应该允许一些非常有趣且用途广泛的实现。

使用您喜欢的任何变量,像平常一样设置班级。

"relations": {
    "categories": {
      "type": "hasMany",
      "model": "category",
      "foreignKey": "category"
    },
    "category": {
      "type": "belongsTo",
      "model": "category",
      "foreignKey": "category"
    }
  }

当您创建对象时,只需传递一个关联数组,该数组的键与var的名称相同,就像这样...

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

此实例化后的$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 )中的$group初始化为null,所以也同样不向构造函数传递任何内容,也是如此……

__construct(...)

现在输出完全符合预期:

$sample_variable = new MyClass(); print_r($sample_variable);

之所以这样写,是因为我可以将MyClass Object ( [myVar1:protected] => [myVar2:protected] => )的输出直接传递给构造函数,而不必太担心。

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

答案 17 :(得分:1)

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

答案 18 :(得分:0)

我创建了这个方法,不仅可以在构造函数中使用它,还可以在方法中使用它:

我的构造函数:

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 args

然后    $ myObject - &gt; doSomething($ arg1,$ arg2,...,$ argM)

它将调用doSomethingM,您在其中定义了M args;

答案 19 :(得分:0)

您总是可以在构造函数中添加一个名为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语句中添加另一个案例,还可以检查以确保有人发送了正确的内容 - 在上面的示例中所有数据是正常的,除了C,因为它设置为“某事”,所以类中的错误标志被设置,控制返回到主程序,以便它决定下一步做什么(在我刚刚告诉它的例子中)退出并显示错误消息“无效模式” - 但也可以将其循环回来直到找到有效数据。

答案 20 :(得分:0)

为了回应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>");  
}

答案 21 :(得分:0)

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

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

} 

答案 22 :(得分:0)

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