Back-End

26 dez, 2013

Como realizar a integração do CodeIgniter com Doctrine 2 – Buscas – #Melhores2013

Publicidade

Tenho recebido diversos e-mails com perguntas relacionadas a como fazer consultas nos dados armazenados. Então, resolvi escrever este artigo para dar continuidade ao primeiro. A base dos arquivos que vou utilizar são os apresentados no artigo anterior.

Preparações

1. Bootstrap

É interessante fazermos um bootstrap, assim, quando instanciarmos uma entidade, o arquivo será carregado automaticamente e não precisaremos nos preocupar com includes. Para isso, utilizarei a função spl_autoload_register.

Vamos então criar um arquivo chamado “autoload_register”, que deve ficar dentro de “/application/libraries” com o seguinte código:

//Registra o autoload das classes
spl_autoload_register('Autoload_Register::register');

/**
 * Executa o autoload de classes que não são do CI nem de PEAR
 */
class Autoload_Register
{

    const CI_PREFIX = "CI_";
    const PEAR = 'PEAR';

    /**
     * Faz o registro
     *
     * @param string $classe
     * @return void
     */
    public static function register($classe)
    {
        $paths = array();        
        $paths[] = APPPATH . 'models/Entities';

        //Classes do CI e do PEAR são ignoradas
        if (strstr($classe, self::CI_PREFIX) || stristr($classe, self::PEAR))
        {
            //Nada a fazer
            return;
        }

        //Remove o namesapce
        $vetClasse = explode('\\', $classe);
        $classe = $vetClasse[count($vetClasse)-1];

        //Garante que "$paths" seja um array
        if (is_array($paths) && count($paths) > 0)
        {
            //Localiza arquivos nos paths determinados no inicio da classe
            foreach ($paths as $dir)
            {
                $files = self::find($dir, $classe . EXT);

                //Se tiver arquivos a serem incluidos, executa o require_once para cada um deles
                if (is_array($files) && count($files) > 0)
                {
                    foreach ($files as $file)
                    {
                        if (is_file($file))
                        require_once $file;
                    }
                }
            }
        }
    }

    // ------------------------------------------------------------------------

    /**
     * Procura por arquivos
     *
     * @param string $dir
     * @param string $pattern
     * @return array
     */
    private function find($dir, $pattern)
    {
        // Elimina caracteres inválidos para comandos shell
        $dir = escapeshellcmd($dir);

        // pega a lista de todas as coincidencias no diretorio corrente
        $files = glob("$dir/$pattern");

        // procura a lista de todos os sub diretorios no diretorio corrente
        // diretorios iniciados por um ponto também são incluidos
        foreach (glob("$dir/{.[^.]*,*}", GLOB_BRACE | GLOB_ONLYDIR) as $sub_dir)
        {
            $arr = self::find($sub_dir, $pattern);  // chamada revursiva
            $files = array_merge($files, $arr); // faz um merge no array com os arquivos do sub diretorio
        }

        // retorna todos os arquivos encontrados
        return $files;
    }

}

Criado o arquivo, precisamos colocá-lo no autoload do CI. A chave ‘libraries’ da matriz $autoload deve ficar da seguinte forma:

$autoload['libraries'] = array('database', 'Doctrine', 'autoload_register');

Pronto, quando o CI carregar essa library, a primeira coisa que ela vai fazer é registrar no SPL_AUTOLAD o método “register” da classe “Autoload_Register”. E sempre que for criada uma nova instância de uma classe, o PHP vai passar primeiro por esse método “register”, que por sua vez vai procurar dentro na pasta “models/Entities” o arquivo que ele deve fazer o require.

2. Preparando os dados

Antes de fazer o teste, vou adicionar um campo no banco de dados chamado “idade”. Para isso vou adicionar a propriedade $idade na classe “usuário”. Isso vai facilitar explicações futuras.

/**
 * @Column(type="integer", nullable=true)
 * @var type
 */
public $idade;

E vou rodar um comando do doctrine para atualizar a base de dados:

php doctrine orm:schema-tool:update --force

Feito isso, podemos fazer um teste alterando a forma popular do objeto “usuário” para o seguinte:

public function index()
{
    $usuario = new usuario();
    $usuario->set_nome('Ricardo');
    $usuario->set_email('meuemail@mail.com.br');
    $usuario->set_telefone('11-1122-3345');
    $usuario->set_celular('11-9988-7765');
    $usuario->set_idade(25);

    $this->doctrine->em->persist($usuario);
    $this->doctrine->em->flush();
}

Repare que foi retirado o “$this->load->model(‘Entities/usuario’, ‘usuario’);” do arquivo que fizemos no artigo anterior. Tembém foram implementados os getters e setters da classe “usuario”.

Para melhorar os testes e não precisar gastar a criatividade inserindo diversos usuários, podemos utilizar o site http://www.generatedata.com para gerar dados aleatórios e popular o banco.

Mão na massa

Finalmente vamos ao que interessa: fazer as consultas. Existem diversas formas de consultar um dado com o Doctrine. Vou citar aqui as que mais utilizo e a partir daí cada um poderá utilizar o que achar mais adequado para cada situação.

A documentação está disponível no site do Doctrine.

1. find()

A sintaxe é: $em->find($entidade, $id). A função find() vai procurar na entidade informada o registro com o ID solicitado. Podemos utilizar da segunte forma:

    /**
     * Localiza o usuario para ser editado
     * 
     * @param int $id
     */
    public function editar($id)
    {
        $usuario = $this->doctrine->em->find('usuario', $id);

        if ($usuario instanceof usuario)
        {
            echo 'Nome: ' . $usuario->get_nome() . '<br>';
            //Aqui pode carregar a view para editar o usuário
        }
        else
        {
            echo 'Usuário não localizado';
            //Volta para a listagem informando que o usuário não existe
        }
    }

 

2. findOneBy()

A sintaxe é: $em->getRepository($entidade)->findOneBy(array($campo => $valor)). Esta função faz uma busca na entidade informada buscando pelo campo e valores solicitados. Veja um exemplo:

    /**
     * Consulta pelo campo e valor solicitado
     *
     * @param string $campo Nome do campo onde se deseja fazer a busca
     * @param mixed $valor Valor que deve ser localizado
     * @link http://doctrine-orm.readthedocs.org/en/latest/reference/working-with-objects.html#by-simple-conditions
     */
    public function localizar($campo, $valor)
    {
        $usuario = $this->doctrine
                        ->em
                        ->getRepository('usuario')
                        ->findOneBy(array($campo => $valor));

        if ($usuario instanceof usuario)
        {
            echo 'Nome: ' . $usuario->get_nome() . '<br>';
        }
        else
        {
            echo 'Usuário não localizado';
        }
    }

Então, para buscar o usuário “ricardo” basta fazer o seguinte:

$this->localizar('nome', 'ricardo');

Isto irá gerar uma SQL assim: SELECT * FROM usuario WHERE nome = ‘ricardo’.

3. DQL – Doctrine Query Language

Para fazermos buscas mais avançadas, podemos utilizar as DQLs (Doctrine Query Language), onde iremos escrever um código muito parecido com a SQL com pequenos detalhe de diferença, veja:

    /**
     * Localiza um usuário que tenha a idade entre dois valores
     *
     * @param type $idade_menor
     * @param type $idade_maior
     * @link http://doctrine-orm.readthedocs.org/en/latest/reference/working-with-objects.html#by-dql DQL
     */
    public function localizar_por_idade($idade_menor, $idade_maior)
    {
        $usrs = $this->doctrine
                ->em
                ->createQuery("SELECT u FROM usuario u WHERE u.idade >= {$idade_menor} and u.idade <= {$idade_maior}")
                ->getResult();

        if (is_array($usrs) && count($usrs) > 0)
        {
            foreach ($usrs as $i => $usuario)
            {
                if ($usuario instanceof usuario)
                {
                    echo "{$i} - Nome: {$usuario->get_nome()}<br>";
                }
            }
        }

    }

Veja que interessante, na SQL, no lugar do nome da tabela, utilizei o nome da entidade que criamos e em vez de utilizar um * para trazer todos os campos, utilizei o apelido dado à entidade.

Dessa forma, poderemos inclusive fazer joins quando necessário. No próximo artigo vou escrever sobre o relacionamento de entidades. Vamos gerar a entidade “endereco” e por fim faremos uma busca com join entre as tabelas para mostrar o poder da DQL.

Conclusão

O Doctrine nos dá muita rapidez no desenvolvimento – isso será percebido mais ainda no próximo artigo, quando eu mostrar os relacionamentos entre entidades e a facilidade de acesso aos dados.

Existem outras forma de fazer busca no Doctrine, mas espero que com essas que apresentei aqui, seja possível você começar a entender melhor como ele funciona e partir para novas implementações. Os arquivos do artigo estão disponíveis aqui.

No próximo artigo irei abordar o relacionamento entre entidades.

Espero que esse artigo possa ajudar vocês. Um abraço e até o próximo!