Automatizando Getters e Setters no Doctrine ORM Artigo

Conheça os cursos gratuitos do WebDevBr! - Inscreva-se!


Este artigo foi publicado a 2 anos, 9 meses, 2 semanas, 6 dias atrás.

Quem trabalha com Doctrine ORM sabe como uma simples entidade pode virar um verdadeiro inferno quando temos que mapear uma quantidade maior de campos do banco de dados. Mas não precisam ser muitos, com poucos campos e muitoas tabelas já começa o sofrimento. Vamos entender.

Quando criamos uma entidade cada campo deve virar um atributo (variável) no objeto (classe) com os devidos métodos (funções) de setar (set) e requisitar (get) aquele dados. Uma tabela com 5 campos acaba com 10 métodos, uma tabela com 10 campos vai terminar com 20 métodos, agora imagine uma tabela com 20 campos, que tal 30, tá não precisa de tanto assim.

Pra você entender, este é um exemplo de uma entidade do Doctrine ORM:

Gostou deste artigo?

Receba atualizações semanais com novos artigos do WebDevBr e outras dicas!

<?php

namespace WebDevBrEntities;

use DoctrineORMMapping as ORM;

/**
 * @Entity @Table(name="users")
 */
class User
{
    /**
     * @Id @GeneratedValue @Column(type="integer")
     * @var int
     */
    protected $id;
    /**
     * @Column(type="string")
     * @var string
     */
    protected $name;

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }
}

Eu ainda costumo colocar um return $this; em cada método set, mas isso é papo pra outro dia, este é o exemplo exato da documentação.

Essa entidade é pequena, mas ela mapeia uma tabela com 2 campos apenas e ja dá pra perceber o problema.

Com o tempo você vai entender as reais vantagens de trabalhar desta forma, mas isso poderia ser mais automático não? Poderiamos intermediar isso e não precisar criar um método get e um set pra cada campo, assim só mapeariamos se for necessário tratar alguma coisa.

Vamos então a mágica

Métodos mágicos

Entre outros métodos mágico que o PHP disponibiliza temos dois que caem como luvas aqui, o __call() e o __set().

O _call() é executado sempre que você chama um método inacessível (private e protected ou que não exista), ele precisa de dois atributos, name e arguments:

public function __call($name, $arguments)
{
    // Nota: valor de $name é case sensitive.
    echo "Chamando o método '$name' "
         . implode(', ', $arguments). "n";
}

O $name guarda o nome do método, e o $arguments os parâmetros (em um array).

Note que o protected é visível para a própria classe e para as que a herdam, nestes casos o __call() não executa em classes herdadas porque elas tem acesso ao método.

O __set() é quase como o __call(), a não se que ele não executa um método e sim envia um valor a um atributo inacessível.

public function __set($name, $value)
{
    echo "Setando o valor '$value' na variável '$name'n";
    $this->data[$name] = $value;
}

Estes dois exemplos de __set() e __call() foram retirados da documentação.

Requisitando e setando dados automáticamente

Bem, eu não quero me preocupar em ficar criando getters e setters (já disse isso), mas também não quero perder as vantages de usá-los quando necessário, então vou usar a seguintes lógicas:

Para getters (getName(), getDescription()...): Se o método que eu chamei existir eu pego o valor dele, se não pegue direto da variável, agora se a variável não existir também vou retornar um erro.

Meu método deve se parecer com:

protected function get($method)
{
    $property = lcfirst(substr($method, 3));
    if (method_exists($this, $method)) {
        return call_user_func_array(array($this, $method), array());
    } else if (property_exists($this, $property)) {
        return $this->{$property};
    } else {
        throw new InvalidArgumentException("Property {$property} not exists!");
    }
}

A linha $property = lcfirst(substr($method, 3)); remove os 3 primeiros caracteres e deixa o primeiro caracter minúsculo, ou seja, se entrar getName ele transforma em name, se entrar getShortDescription ele sabe que o atributo se chama shortDescription.

Para setters (setName($value), setDescription($value)...): Se o atributo que eu chamei tiver um método set eu trato o valor com ele, se não envio direto para a variável, agora se a variável não existir também vou retornar um erro.

Meu método deve se parecer com:

protected function set($property, $value)
{
    $methodName = "set".ucfirst($property);
    if (method_exists($this, $methodName)) {
        call_user_func_array(array($this,$methodName), array($value));
    } else if (property_exists($this, $property)) {
        $this->{$property} = $value;
    } else {
        throw new InvalidArgumentException("Property {$property} not exists!");
    }
}

Bem, desta vez não vou explicar o código, ta simples né.

Pra integrar com os métodos mágicos vamos apenas chamar os nossos novos métodos dentro dos métodos mágicos:

public function __set($property, $value) {
    $this->set($property, $value);
}

public function __call($method, $value) {
    return $this->get($method);
}

Prontinho, já tem muita coisa acontecendo aqui, quando requisitamos o valor de uma tributo ele vai checar a existência de um método set ou get (depende do que você está fazendo), e se existir, ele usará o método, caso contrário (o método não existir), ele apenas informa o valor.

Trabalhando com arrays

Bem útil também é criar um método que permita eu setar todos os dados baseados em um array e outro para retornar os dados do método em um Array, pra isso vou criar o setAll($data) para entrada de dados e um toArray() para o retorno do array:

public function setAll(Array $data)
{
    foreach($data as $k=>$v)
        $this->set($k, $v);
    return $this;
}

public function toArray()
{
    $array = [];

    foreach ($this as $k => &$v)
        $array[$k] = $v;

    return $array;
}

Abstract Class

Pra finalizar vamos incluir isso tudo em uma classe abstrata, ela deve se parecer com:

abstract class AbstractEntity
{
    public function __set($property, $value) {
        $this->set($property, $value);
    }

    public function __call($method, $value) {
        return $this->get($method);
    }

    protected function set($property, $value)
    {
        $methodName = "set".ucfirst($property);
        if (method_exists($this, $methodName)) {
            call_user_func_array(array($this,$methodName), array($value));
        } else if (property_exists($this, $property)) {
            $this->{$property} = $value;
        } else {
            throw new InvalidArgumentException("Property {$property} not exists!");
        }
    }

    protected function get($method)
    {
        $property = lcfirst(substr($method, 3));
        if (method_exists($this, $method)) {
            return call_user_func_array(array($this, $method), array());
        } else if (property_exists($this, $property)) {
            return $this->{$property};
        } else {
            throw new InvalidArgumentException("Property {$property} not exists!");
        }
    }

    public function setAll(Array $data)
    {
        foreach($data as $k=>$v)
            $this->set($k, $v);
        return $this;
    }

    public function toArray()
    {
        $array = [];

        foreach ($this as $k => &$v)
            $array[$k] = $v;

        return $array;
    }
}

Usando

Para usar você deve extender esta classe na sua entidade:

<?php

namespace WebDevBr\Entities;

use Doctrine\ORM\Mapping as ORM;

/**
 * @Entity @Table(name="users")
 */
class User extends AbstractEntity
{
    /**
     * @Id @GeneratedValue @Column(type="integer")
     * @var int
     */
    protected $id;
    /**
     * @Column(type="string")
     * @var string
     */
    protected $name;
}

Note que eu não criei nenhum get e nenhum set.

Agora para informar um novo dado basta:

$user->name = 'Erik Figueiredo';

Para setar vários:

$data = [
    'name'=>'Erik Figueiredo'
];
$user->setAll($data);

Para requisitar um único dado:

$name= $user->getName();

Para requisitar todos:

$data = $user->toArray();

Lembrando que sempre que criar um método set (setName($value) no exemplo) ou get (getName() no exemplo) você intercepta qualquer ação, ou seja, você pode usar os métodos para tratar a entrada e saída dos dados na sua aplicação normalmente, você não perde flexibilidade.

Bem, é isso que eu uso aqui, espero que seja útil pra vocês tanto quanto pra mim.


* Parcelamento apenas cartão de crédito! Pode haver uma pequena variação no parcelamento em relação a simulações apresentadas!