with - php oop constructor




Melhor maneira de fazer vários construtores em PHP (13)

A partir da versão 5.4, o PHP suporta traits . Isso não é exatamente o que você está procurando, mas uma abordagem baseada em traços simplista seria:

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

Acabamos com duas classes, uma para cada construtor, o que é um pouco contraproducente. Para manter alguma sanidade, vou colocar uma fábrica:

class StudentFactory {
    static public function getStudent($id, $name = null) {
        return 
            is_null($name)
                ? new Student1($id)
                : new Student2($id, $name)
    }
}

Então, tudo se resume a isto:

$student1 = StudentFactory::getStudent(1);
$student2 = StudentFactory::getStudent(1, "yannis");

É uma abordagem terrivelmente detalhada, mas pode ser extremamente conveniente.

Você não pode colocar duas funções __construct com assinaturas de argumentos exclusivos em uma classe PHP. Eu gostaria de fazer isso:

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

Qual é a melhor maneira de fazer isso em PHP?


A solução do Kris é muito legal, mas acho melhor a mistura de estilo de fábrica e fluente:

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

Construtores de chamada por tipo de dados:

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

} 

Deixe-me adicionar meu grão de areia aqui

Eu pessoalmente gosto de adicionar construtores como funções estáticas que retornam uma instância da classe (o objeto). O código a seguir é um exemplo:

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

Observe que agora você pode criar uma instância da classe Person assim:

$person1 = Person::withName('Example');
$person2 = Person::withEmail('[email protected]_email.com');

Eu peguei esse código de:

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


Esta questão já foi respondida com maneiras muito inteligentes para cumprir o requisito, mas eu estou querendo saber por que não dar um passo atrás e fazer a pergunta básica de por que precisamos de uma classe com dois construtores? Se minha classe precisar de dois construtores, provavelmente a maneira que eu estou projetando minhas classes precisa de um pouco mais de consideração para criar um design que seja mais limpo e mais testável.

Estamos tentando misturar como instanciar uma classe com a lógica de classe real.

Se um objeto Student estiver em um estado válido, será que importa se ele foi construído a partir da linha de um banco de dados ou dados de um formulário da Web ou de uma solicitação de cli?

Agora, para responder à pergunta que pode surgir aqui que, se não adicionarmos a lógica de criar um objeto a partir da linha db, como criamos um objeto a partir dos dados db, podemos simplesmente adicionar outra classe, chamá-la de StudentMapper se você está confortável com o padrão de mapeamento de dados, em alguns casos, você pode usar o StudentRepository e, se nada atender às suas necessidades, você pode criar uma StudentFactory para lidar com todos os tipos de tarefas de construção de objetos.

Bottomline é manter a camada de persistência fora de nossa cabeça quando estamos trabalhando nos objetos de domínio.


Eu criei este método para deixar usá-lo não apenas em construtores, mas em métodos:

Meu construtor:

function __construct() {
    $paramsNumber=func_num_args();
    if($paramsNumber==0){
        //do something
    }else{
        $this->overload('__construct',func_get_args());
    }
}

Meu método do DoSomething:

public function doSomething() {
    $paramsNumber=func_num_args();
    if($paramsNumber==0){
        //do something
    }else{
        $this->overload('doSomething',func_get_args());
    }
}

Ambos funcionam com este método simples:

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

Então você pode declarar

__construct1($arg1), __construct2($arg1,$arg2)...

ou

methodName1($arg1), methodName2($arg1,$arg2)...

e assim por diante :)

E quando usando:

$myObject =  new MyClass($arg1, $arg2,..., $argN);

ele irá chamar __constructN , onde você definiu N args

então $ myObject -> doSomething ($ arg1, $ arg2, ..., $ argm)

ele chamará doSomethingM , onde você definiu M args;


Eu sei que estou super atrasado para a festa aqui, mas eu criei um padrão bastante flexível que deveria permitir algumas implementações realmente interessantes e versáteis.

Configure sua turma como você faria normalmente, com qualquer variável que desejar.

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

Quando você faz seu objeto apenas passar um array associativo com as chaves do array da mesma forma que os nomes dos seus vars, assim ...

$sample_variable = new MyClass([
    'myVar2'=>123, 
    'i_dont_want_this_one'=> 'This won\'t make it into the class'
    ]);

print_r($sample_variable);

O print_r($sample_variable); após essa instanciação produz o seguinte:

MyClass Object ( [myVar1:protected] => [myVar2:protected] => 123 )

Porque nós inicializamos $group a null em nossa __construct(...) , também é válido não passar nada no construtor, assim como ...

$sample_variable = new MyClass();

print_r($sample_variable);

Agora a saída é exatamente como esperada:

MyClass Object ( [myVar1:protected] => [myVar2:protected] => )

A razão pela qual eu escrevi isso foi para que eu pudesse passar diretamente a saída de json_decode(...) para meu construtor, e não me preocupar muito com isso.

Isso foi executado no PHP 7.1. Apreciar!


Outra opção é usar argumentos padrão no construtor 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;
    }
}

Isso significa que você precisará instanciar com uma linha como esta: $student = new Student($row['id'], $row) mas mantém seu construtor legal e limpo.

Por outro lado, se você quiser fazer uso de polimorfismo, então você pode criar duas classes assim:

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

Para o php7, eu também comparo o tipo de parâmetros, você pode ter dois construtores com o mesmo número de parâmetros, mas 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 usá-lo:

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


Você poderia fazer algo como o seguinte, que é realmente fácil e muito limpo:

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

Você sempre pode adicionar um parâmetro extra ao construtor chamado algo como mode e então executar uma instrução switch nele ...

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

Também com esse método a qualquer momento, se você quiser adicionar mais funcionalidades, basta adicionar outro caso à instrução switch, e também verificar se alguém enviou a coisa certa - no exemplo acima, todos os dados estão corretos. exceto para C como isso é definido como "algo" e assim o sinalizador de erro na classe é definido e o controle é retornado para o programa principal para que ele decida o que fazer a seguir (no exemplo que acabei de dizer para sair com um mensagem de erro "modo inválido" - mas, alternativamente, você pode repeti-lo até que dados válidos sejam encontrados).


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

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

Agora $ paramters será um array com os valores 'One', 'Two', 3.

Editar,

Eu posso adicionar isso

func_num_args()

lhe dará o número de parâmetros para a função.





multiple-constructors