Лучший способ сделать несколько конструкторов в 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?

Ответ 1

Я бы, наверное, сделал что-то вроде этого:

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

?>

Тогда, если мне нужен Student, где я знаю ID:

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

Или, если у меня есть массив строки db:

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

Технически вы не создаете несколько конструкторов, а только статические вспомогательные методы, но вы можете избежать большого количества кода спагетти в конструкторе таким образом.

Ответ 2

Решение Криса действительно приятно, но я считаю сочетание factory и беглого стиля лучше:

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

Ответ 3

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

Ответ 4

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

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

Теперь $paramters будет массивом со значениями "Один", "Два", "3".

Изменить

Могу добавить, что

func_num_args()

предоставит вам количество параметров для функции.

Ответ 5

Как уже было показано здесь, существует много способов объявления конструкторов multiple в PHP, но ни один из них не является способом 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.');

Источник: Самый простой способ использования и понимания нескольких конструкторов:

Надеюсь, это поможет.:)

Ответ 6

Начиная с версии 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); }
}

В итоге мы получаем два класса: по одному для каждого конструктора, что немного контрпродуктивно. Чтобы поддерживать некоторую здравомыслие, я запишу factory:

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

Это ужасно подробный подход, но это может быть очень удобно.

Ответ 7

Вы можете сделать что-то вроде этого:

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

Ответ 8

Другой вариант - использовать аргументы по умолчанию в конструкторе, подобном этому

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

как указано в других комментариях, поскольку php не поддерживает перегрузку, обычно избегают "трюков проверки типа" в конструкторе и используется шаблон factory intead

т.

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

Ответ 10

Вы можете сделать что-то вроде следующего, что очень просто и очень чисто:

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

Ответ 11

На этот вопрос уже были ответы очень разумные способы выполнения требования, но мне интересно, почему бы не сделать шаг назад и задать основной вопрос, почему нам нужен класс с двумя конструкторами? Если моему классу нужны два конструктора, то, вероятно, именно так, как я разрабатываю свои классы, требуется немного больше внимания, чтобы придумать более чистую и более проверяемую конструкцию.

Мы пытаемся смешивать процесс создания класса с фактической логикой класса.

Если объект Student находится в допустимом состоянии, имеет значение, если он был создан из строки базы данных или данных из веб-формы или запроса cli?

Теперь, чтобы ответить на вопрос, что это может возникнуть здесь, если мы не добавим логику создания объекта из строки db, то как мы создадим объект из данных db, мы можем просто добавить другой класс, вызвать это StudentMapper, если вам нравится шаблон карты данных, в некоторых случаях вы можете использовать StudentRepository, и если ничто не соответствует вашим потребностям, вы можете сделать StudentFactory для решения всех задач по строительству объектов.

Нижняя строка состоит в том, чтобы удерживать слой persistence из нашей головы, когда мы работаем над объектами домена.

Ответ 12

Позвольте мне добавить здесь мое зерно песка

Мне лично нравится добавлять конструкторы в качестве статических функций, возвращающих экземпляр класса (объекта). Ниже приведен пример кода:

 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('[email protected]_email.com');

Я взял этот код из:

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

Ответ 13

Я знаю, что я очень опоздал на вечеринку здесь, но я придумал довольно гибкий шаблон, который должен позволить некоторые действительно интересные и универсальные реализации.

Настройте свой класс, как обычно, с любыми переменными, которые вам нравятся.

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. Наслаждайтесь!

Ответ 14

Для 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;
    }
}

Ответ 15

Вот элегантный способ сделать это. Создать черту, которая будет включать несколько конструкторов с учетом количества параметров. Вы бы просто добавили количество параметров к имени функции "__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

Хм, удивлен, пока не вижу ответа, предположим, я брошу свою шляпу на ринг.

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

Ответ 17

Насколько я знаю, перегрузка не поддерживается в PHP. Вы можете перегружать свойства get и set методами с помощью функции overload(); (http://www.php.net/manual/en/overload.examples.basic.php)

Ответ 18

Конструкторы вызовов по типу данных:

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

} 

Ответ 19

В ответ на лучший ответ Криса (который поразительно помог разработать мой собственный класс btw), здесь приведена модифицированная версия для тех, кто может найти ее полезной. Включает методы для выбора из любого столбца и данных объекта демпинга из массива. Ура!

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

Ответ 20

Вы всегда можете добавить дополнительный параметр в конструктор, называемый как-то вроде режима, а затем выполнить оператор 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 установлено значение "что-то", и поэтому флаг ошибки в классе задан, и управление возвращается обратно в основную программу, чтобы решить, что делать дальше (в примере, который я только что сказал ему, выйдите с сообщением об ошибке "недействительный режим", но в качестве альтернативы вы можете запрограммировать его назад до тех пор, пока не будут найдены действительные данные.)

Ответ 21

Я создал этот метод, чтобы использовать его не только для конструкторов, но и для методов:

Мой конструктор:

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;