ChatGPT解决这个技术问题 Extra ChatGPT

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

我也梦想命名构造函数和方法重载+1
就我而言,我希望有一个受保护的构造函数,它比公共的参数少一个 - 为了标准化它的工厂方法。我需要一个能够创建自身副本的类,并且工厂位于抽象类中,但具体类可能具有需要第二个参数的构造函数 - 抽象类不知道。
不是真正有价值的东西,而是我前段时间偶然发现的东西:date_c.php 中的 DatePeriod 类有多个构造函数。但我不知道 PHP 在内部用它做了什么。

K
Kris

我可能会做这样的事情:

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

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


看起来你刚刚回答了我问 gpilotino 的问题。谢谢!非常清楚。
@gpilotino,矫枉过正,因为您还需要另一个类(或方法),它基本上只包含一个开关/案例决策树,最后只是做我已经在两种方法中做过的事情。工厂在您无法轻松定义问题的确切约束的情况下更有用,例如创建表单元素。但是,这只是我的意见并记录在案;我不声称这是事实。
我们不能也将 __construct() 设为私有,以防止有人偶尔分配“未初始化”实例吗?
@mlvljr:可以,但我建议将其设为受保护而不是私有。否则,如果你打算扩展你的课程,你很可能会遇到麻烦。
PHP 5.3 中关于您可能应该使用 new static() 而不是 new self() 的注意事项,因为 new static() 在子类中会更有效地工作。
t
timaschew

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

+1;这种类型的解决方案可以产生非常好的代码。尽管我会在此解决方案中选择 setLastName(或更确切地说是所有 setter)来返回 $this,而不是在同一属性上有效地使用两个 setter。
作为曾经编译过的人,像 C# 这样的静态类型语言,这种做事方式很适合我。
提供静态创建方法与仅以相同方式使用构造函数有何不同? $student = new Student()->setFirstName("John")->setLastName("Doe");
该代码存在一个重要问题:您无法确保实例有效(这就是存在构造函数的原因),并且通常不可变的类更可取。
这是针对此类问题看到的最干净的代码,并且可以通过在 create() 方法中使用 return new self() 来清洁
D
Daff

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

所有这一切都是令人敬畏的意大利面条代码。但这确实可能是最简单的方法。
如果您像使用静态类型语言一样创建构造函数,它将变成意大利面条代码。但你没有。无论如何,为该参数创建具有一个参数且没有类型(无类型 == 任何类型)的两个构造函数在任何语言中都不起作用(例如,在一个类中具有两个具有一个 Object 参数的 Java 构造函数,或者)。
我的意思是,您根据外部影响在同一范围内做不同的事情,这不是一个糟糕的解决方案(因为它会起作用),而不是我会选择的那个。
“动态”的语言并不排除函数/构造函数重载的可能性。它甚至不排除静态类型。即便如此,仍然有可能允许纯粹基于参数计数的重载。请不要以“动态”为借口。
我喜欢这种为类用户简化代码的方式,它实现了 OP 想要的。如果您创建两个函数(如 Kris 的回答)并在构造函数中适当地调用这些函数,则它不会是意大利面条代码。检查参数的代码可能没有那么复杂。这当然假设有某种方法可以将参数彼此区分开来,就像在这种情况下一样。
B
Björn
public function __construct() {
    $parameters = func_get_args();
    ...
}

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

现在 $paramters 将是一个值 'One'、'Two'、3 的数组。

编辑,

我可以补充

func_num_args()

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


这如何解决知道通过了什么的问题?我认为这会使问题复杂化,因为您不必检查参数的类型,而必须检查是否设置了 x 参数,然后检查它的类型。
知道传递了什么类型并不能解决问题,但它是 PHP 中“多个构造函数”的方法。类型检查由 OP 来做。
我想知道当一个新的开发人员被添加到一个有很多这样的代码的项目中会发生什么
N
Nasif Md. Tanjim

正如这里已经展示的那样,在 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:

希望这可以帮助。 :)


这是最好的解决方案。如果将 PHP 5.6+ 与新的 ... 运算符一起使用,会更加优雅。
当然,这不适用于 JannieT 的原始问题,因为她想要的构造函数是 __construct($id)__construct($row_from_database)。两者都有一个参数,可能是第一个为 int,第二个为 arrayobject。当然,数字的附加可以扩展为某种 C++ 风格的参数签名(即 __construct_i($intArg)__construct_a($arrayArg))。
+1:我有点喜欢这样,但是在嵌套的ctors中使用类型信息进行了扩展并且没有双下划线前缀。谢谢你的灵感!
您甚至可以在示例代码中添加反射,以对以 __construct 开头的类的每个函数的参数应用类型检查,并以这种方式匹配适当的构造函数
A
Andrei Serdeliuc ॐ

你可以这样做:

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

+1 是一个非常可行的解决方案。但是,对于要记住的课程,我将使用@Kris' 方法。
y
yannis

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

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


J
Jed Lynch

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

非常聪明、优雅且可重复使用。 :拍:
解析错误:语法错误,第 8 行出现意外的 ','
这是一个很棒的方法:)
r
rojoca

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

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

现在您有两个名称不同的类,但功能相同,只是构造函数上的签名不同,这对我来说听起来是个坏主意。
对我来说听起来像是经典的多态性,也称为面向对象编程。
创建多个类来提供不同的构造函数确实是个坏主意。 extends 其他类应该扩展的类,这意味着它们应该添加功能,这就是 OOP 的重点,而不是这个。
g
gpilotino

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

IE。

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

看起来很整洁。我对工厂不熟悉。在您的示例中,$myObj 是否属于 MyClass 类型?两个静态函数返回 $myObj 的构造实例会是什么样子?
我会像 Kris 那样使用单独的方法来防止一种大型工厂方法。
确实,@Kris 解决方案是最好的。
这看起来很像 C++ 标签
C
Carrie Kendall

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

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

为什么将 func_get_args 分配给一个变量并在下一行再次调用它?如果您在决定需要基于 fund_num_args 后仅调用 func_get_args,也会更好。
恕我直言,这与干净的解决方案相反
W
Waku-2

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

我们试图将如何实例化一个类与实际的类逻辑混为一谈。

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

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

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


D
David Culbreth

我知道我在这里参加聚会已经很晚了,但我想出了一个相当灵活的模式,它应该允许一些非常有趣和通用的实现。

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

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 )

因为我们在 __construct(...) 中将 $group 初始化为 null,所以也可以不向构造函数传递任何内容,就像这样......

$sample_variable = new MyClass();

print_r($sample_variable);

现在输出完全符合预期:

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

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

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


您可以做一些很酷的事情,例如在数组中输入意外值时抛出异常。 gist that I wrote up 上有一个这样的例子
s
stanley mbote

我在创建具有不同签名的多个构造函数时遇到了同样的问题,但不幸的是,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

S
Salvi Pascual

让我在这里添加我的沙粒

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

 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


T
That Realty Programmer Guy

嗯,很惊讶我还没有看到这个答案,假设我会把我的帽子扔进戒指。

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

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

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

当我不得不解决以下问题时,我也想到了这一点。我的类应该得到一个构造函数,可以在没有参数或定义数量的参数(在本例中为 3)的情况下调用该构造函数。使用数组很容易使用空和计数进行检查并采取适当的措施。如果为空,则终止函数,因为没有要分配的内容,或者如果参数的数量或其值不合适,则会引发适当的异常。使用 www.DeepL.com/Translator 翻译(免费版)
g
galmok

这是我的看法(为 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

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


S
Serginho

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

您能否展示如何使用两种不同的构造方法新建 MultiConstructorClass 的两个实例?谢谢。
我认为我的答案很巧妙,但这显然更好。
Y
YuraV

更现代的方法:您将单独的类混合为一个,实体和数据水合。因此,对于您的情况,您应该有 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 这样的对象。


l
lukas.j

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

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


佚名

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

v
viral

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

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

} 

你应该提到你从这里得到了这个代码片段——> php.net/manual/en/language.oop5.decon.php#99903
那是大约 6 个月前,请查看我的更新 @LavaSlider
@iRuth 我现在已经完全改变了它
T
TheKLF99

您总是可以向构造函数添加一个额外的参数,称为 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 除外,因为它设置为“某事”,因此设置了类中的错误标志,并将控制权返回给主程序以决定下一步该做什么(在示例中,我只是告诉它退出错误消息“无效模式” - 但或者您可以将其循环回来,直到找到有效数据)。


j
jechaviz

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

我的构造函数:

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 -> doSomething($arg1, $arg2,..., $argM)

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


S
Sophie Su

Kris's answer 很棒,但作为 Buttle Butku commentednew 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 文档中找到了 an useful example