constructor сайтов - Лучший способ сделать несколько конструкторов в PHP



multiple-constructors (17)

Вы не можете поместить две функции __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?


Answers

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

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

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

то есть.

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

В ответ на лучший ответ Криса (который удивительно помог разработать мой собственный класс 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>");  
}

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


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

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

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

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

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

} 

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

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


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

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

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

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

Суть заключается в том, чтобы сохранить слой устойчивости на голове, когда мы работаем над объектами домена.


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

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

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


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

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

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

Редактировать,

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

func_num_args()

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


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


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

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

 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


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

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

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

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

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;


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

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

Один важный аспект этого вопроса, который еще не затронул другие ответы, заключается в том, что для базового класса безопасно вызывать виртуальных членов внутри своего конструктора, если это то, что ожидают производные классы . В таких случаях разработчик производного класса несет ответственность за то, что все методы, которые выполняются до завершения строительства, будут вести себя так же разумно, насколько это возможно в сложившихся обстоятельствах. Например, в C ++ / CLI конструкторы завернуты в код, который вызовет Dispose на частично построенном объекте, если конструкция завершится с ошибкой. Вызов Dispose в таких случаях часто необходим для предотвращения утечек ресурсов, но методы Dispose должны быть подготовлены для возможности того, что объект, на котором они выполняются, возможно, не был полностью сконструирован.





php constructor multiple-constructors