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

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

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

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

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

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

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. } } 
 public function __construct() { $parameters = func_get_args(); ... } $o = new MyClass('One', 'Two', 3); 

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

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

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

 func_num_args() 

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

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

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

Как уже было показано здесь, существует много способов объявления 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($param) { if(is_int($param)) { $this->id = $param; } elseif(is_object($param)) { // do something else } } 

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

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

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

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

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

то есть.

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

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

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

  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

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

 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 } 

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

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

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

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

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

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

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

затем $ myObject -> doSomething ($ arg1, $ arg2, …, $ argM)

он будет вызывать doSomethingM где вы определили M args;