que - sobrecarga de constructores php
La mejor manera de hacer mĂșltiples constructores en PHP (19)
No puede poner dos funciones __construct con firmas de argumentos únicas en una clase de PHP. Me gustaría hacer esto:
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.
}
}
¿Cuál es la mejor manera de hacer esto en PHP?
A partir de la versión 5.4, PHP soporta traits . Esto no es exactamente lo que está buscando, pero un enfoque simplista basado en rasgos sería:
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); }
}
Terminamos con dos clases, una para cada constructor, que es un poco contraproducente. Para mantener un poco de cordura, voy a tirar en una fábrica:
class StudentFactory {
static public function getStudent($id, $name = null) {
return
is_null($name)
? new Student1($id)
: new Student2($id, $name)
}
}
Entonces, todo se reduce a esto:
$student1 = StudentFactory::getStudent(1);
$student2 = StudentFactory::getStudent(1, "yannis");
Es un enfoque horriblemente detallado, pero puede ser extremadamente conveniente.
Como ya se ha mostrado aquí, hay muchas formas de declarar multiple
constructores en PHP, pero ninguna de ellas es la forma correct
de hacerlo (ya que PHP técnicamente no lo permite). Pero no nos impide hackear esta funcionalidad ... Aquí hay otro ejemplo:
<?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.'');
Fuente: La forma más fácil de usar y comprender múltiples constructores:
Espero que esto ayude. :)
Creé este método para permitir su uso no solo en constructores sino también en métodos:
Mi constructor
function __construct() {
$paramsNumber=func_num_args();
if($paramsNumber==0){
//do something
}else{
$this->overload(''__construct'',func_get_args());
}
}
Mi método de hacer algo:
public function doSomething() {
$paramsNumber=func_num_args();
if($paramsNumber==0){
//do something
}else{
$this->overload(''doSomething'',func_get_args());
}
}
Ambos trabajos con este sencillo método:
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);
}
}
Así que puedes declarar
__construct1($arg1), __construct2($arg1,$arg2)...
o
methodName1($arg1), methodName2($arg1,$arg2)...
y así :)
Y cuando se usa:
$myObject = new MyClass($arg1, $arg2,..., $argN);
llamará __constructN
, donde definió N
args
entonces $ myObject -> doSomething ($ arg1, $ arg2, ..., $ argM)
llamará doSomethingM
, donde definiste M
args;
Déjame agregar mi grano de arena aquí
Personalmente, me gusta agregar constructores como funciones estáticas que devuelven una instancia de la clase (el objeto). El siguiente código es un ejemplo:
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;
}
}
Tenga en cuenta que ahora puede crear una instancia de la clase Person como esta:
$person1 = Person::withName(''Example'');
$person2 = Person::withEmail(''yo@mi_email.com'');
Tomé ese código de:
http://alfonsojimenez.com/post/30377422731/multiple-constructors-in-php
En respuesta a la mejor respuesta de Kris (que sorprendentemente ayudó a diseñar mi propia clase por cierto), aquí hay una versión modificada para aquellos que puedan encontrarla útil. Incluye métodos para seleccionar desde cualquier columna y volcar datos de objetos desde una matriz. ¡Aclamaciones!
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>");
}
Esta pregunta ya ha sido respondida con formas muy inteligentes de cumplir el requisito, pero me pregunto por qué no retroceder un paso y hacer la pregunta básica de por qué necesitamos una clase con dos constructores. Si mi clase necesita dos constructores, entonces probablemente la forma en que estoy diseñando mis clases necesita un poco más de consideración para encontrar un diseño que sea más limpio y más comprobable.
Estamos intentando mezclar cómo crear una instancia de una clase con la lógica de clase real.
Si un objeto de Estudiante está en un estado válido, ¿importa si se construyó a partir de la fila de un DB o datos de un formulario web o una solicitud de cli?
Ahora para responder a la pregunta que puede surgir aquí de que si no agregamos la lógica de crear un objeto a partir de la fila db, entonces, ¿cómo creamos un objeto a partir de los datos db, simplemente podemos agregar otra clase, llamémosla StudentMapper si? te sientes cómodo con el patrón del mapeador de datos, en algunos casos puedes usar StudentRepository, y si nada se ajusta a tus necesidades, puedes crear una StudentFactory para manejar todo tipo de tareas de construcción de objetos.
La línea de fondo es mantener la capa de persistencia fuera de nuestra cabeza cuando estamos trabajando en los objetos del dominio.
La solución de Kris es realmente agradable, pero me parece mejor la combinación de fábrica y estilo fluido:
<?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);
?>
Llamar a los constructores por tipo de datos:
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
}
Otra opción es usar argumentos predeterminados en el constructor como este
class Student {
private $id;
private $name;
//...
public function __construct($id, $row=array()) {
$this->id = $id;
foreach($row as $key => $value) $this->$key = $value;
}
}
Esto significa que tendrá que crear una instancia con una fila como esta: $student = new Student($row[''id''], $row)
pero mantiene su constructor agradable y limpio.
Por otro lado, si desea utilizar el polimorfismo, puede crear dos clases como las siguientes:
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));
}
}
PHP es un lenguaje dinámico, por lo que no puede sobrecargar los métodos. Tienes que verificar los tipos de tus argumentos de esta manera:
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.
}
}
Para php7, comparo el tipo de parámetros también, puede tener dos constructores con el mismo número de parámetros pero un tipo diferente.
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");
}
}
}
Para usarlo:
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;
}
}
Podrías hacer algo como esto:
public function __construct($param)
{
if(is_int($param)) {
$this->id = $param;
} elseif(is_object($param)) {
// do something else
}
}
Podrías hacer algo como lo siguiente que es realmente fácil y muy limpio:
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;
}
}
Por lo que sé, la sobrecarga no es compatible con PHP. Solo puede sobrecargar los métodos de obtención y configuración de propiedades con sobrecarga (); ( http://www.php.net/manual/en/overload.examples.basic.php )
Probablemente haría algo como esto:
<?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
}
}
?>
Entonces si quiero un estudiante donde conozco la identificación:
$student = Student::withID( $id );
O si tengo una matriz de la fila db:
$student = Student::withRow( $row );
Técnicamente no estás construyendo múltiples constructores, solo métodos de ayuda estática, pero puedes evitar muchos códigos de espagueti en el constructor de esta manera.
Sé que llego muy tarde a la fiesta aquí, pero se me ocurrió un patrón bastante flexible que debería permitir algunas implementaciones realmente interesantes y versátiles.
Configure su clase como lo haría normalmente, con las variables que desee.
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;
}
}
}
}
}
Cuando creas tu objeto, simplemente pasa una matriz asociativa con las claves de la matriz, al igual que los nombres de tus vars, así ...
$sample_variable = new MyClass([
''myVar2''=>123,
''i_dont_want_this_one''=> ''This won/'t make it into the class''
]);
print_r($sample_variable);
El print_r($sample_variable);
Después de esta instanciación se obtiene lo siguiente:
MyClass Object ( [myVar1:protected] => [myVar2:protected] => 123 )
Como hemos inicializado $group
a null en nuestro __construct(...)
, también es válido no pasar nada en absoluto al constructor también, como así ...
$sample_variable = new MyClass();
print_r($sample_variable);
Ahora la salida es exactamente como se esperaba:
MyClass Object ( [myVar1:protected] => [myVar2:protected] => )
La razón por la que escribí esto fue para poder pasar directamente la salida de json_decode(...)
a mi constructor, y no preocuparme demasiado.
Esto fue ejecutado en PHP 7.1. ¡Disfrutar!
Siempre se podría agregar un parámetro adicional al constructor llamado algo así como el modo y luego realizar una instrucción de cambio en él ...
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'' ) ;
También con ese método en cualquier momento, si desea agregar más funciones, simplemente puede agregar otro caso a la declaración de cambio, y también puede verificar que alguien haya enviado lo correcto. En el ejemplo anterior, todos los datos están bien. a excepción de C, ya que se establece en "algo" y, por lo tanto, se establece el indicador de error en la clase y el control se devuelve al programa principal para que decida qué hacer a continuación (en el ejemplo, le dije que saliera con una mensaje de error "modo inválido" - pero alternativamente, podría volverlo en bucle hasta que se encuentren datos válidos).
como se indica en los otros comentarios, ya que php no admite la sobrecarga, por lo general se evitan los "trucos de verificación de tipos" en el constructor y se usa el patrón de fábrica en la cabeza.
es decir.
$myObj = MyClass::factory(''fromInteger'', $params);
$myObj = MyClass::factory(''fromRow'', $params);
public function __construct() {
$parameters = func_get_args();
...
}
$o = new MyClass(''One'', ''Two'', 3);
Ahora $ paramters será una matriz con los valores ''One'', ''Two'', 3.
Editar,
Puedo añadir que
func_num_args()
le dará el número de parámetros a la función.