multiple-constructors sobrecarga - La mejor manera de hacer múltiples constructores en PHP




que un (17)

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

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?


Podrías hacer algo como esto:

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


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. :)


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.


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

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.


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

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


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

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.


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

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

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.


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;


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

} 

Aunque la pregunta ha sido respondida, solo quiero reiterar que las sales utilizadas para el hash deben ser aleatorias y no como una dirección de correo electrónico como se sugiere en la primera respuesta.

Más explicaciones están disponibles en: http://www.pivotalsecurity.com/blog/password-hashing-salt-should-it-be-random/

Recientemente tuve una discusión sobre si los hash de contraseña con sal con bits aleatorios son más seguros que el de sal con sales conocidas o supuestas. Veamos: si el sistema que almacena la contraseña está comprometido, así como el sistema que almacena la sal aleatoria, el atacante tendrá acceso tanto al hash como a la sal, por lo que no importa si la sal es aleatoria o no. El atacante puede generar tablas de arco iris pre-calculadas para romper el hash. Aquí viene la parte interesante: no es tan trivial generar tablas precalculadas. Tomemos el ejemplo del modelo de seguridad WPA. Su contraseña WPA nunca se envía a un punto de acceso inalámbrico. En su lugar, está saturado con su SSID (el nombre de la red, como Linksys, Dlink, etc.). Una muy buena explicación de cómo funciona esto está aquí. Para recuperar la contraseña del hash, deberá conocer la contraseña y el atributo salt (nombre de la red). Church of Wifi ya tiene tablas hash pre-computadas que tienen 1000 SSID principales y aproximadamente 1 millón de contraseñas. El tamaño de todas las tablas es de unos 40 GB. Como puede leer en su sitio, alguien usó 15 arreglos FGPA durante 3 días para generar estas tablas. Suponiendo que la víctima está utilizando el SSID como "a387csf3" y la contraseña como "123456", ¿será descifrado por esas tablas? ¡No! .. no puede. Incluso si la contraseña es débil, las tablas no tienen hashes para SSID a387csf3. Esta es la belleza de tener sal al azar. Disuadirá a los crackers que prosperan en las tablas precalculadas. ¿Puede detener a un hacker determinado? Probablemente no. Pero el uso de sales aleatorias proporciona una capa adicional de defensa. Mientras estamos en este tema, analicemos la ventaja adicional de almacenar sales aleatorias en un sistema separado. Escenario # 1: los hashes de contraseña se almacenan en el sistema X y los valores de sal utilizados para el hashing se almacenan en el sistema Y. Estos valores de sal son adivinables o conocidos (por ejemplo, nombre de usuario) Escenario # 2: los hashes de contraseña se almacenan en el sistema X y los valores de sal utilizados para Los hashing se almacenan en el sistema Y. Estos valores de sal son aleatorios. En caso de que el sistema X haya sido comprometido, como puede adivinar, hay una gran ventaja de usar sal aleatoria en un sistema separado (Escenario # 2). El atacante deberá adivinar los valores de adición para poder descifrar hashes. Si se usa una sal de 32 bits, se requerirán iteraciones de 2 ^ 32 = 4,294,967,296 (aproximadamente 4,2 mil millones) para cada contraseña que se adivine.





php constructor multiple-constructors