Namespaces e Resolução de nomes de Classes

Artigo que explica o funcionamento de namespaces e do novo recurso para resolução de nomes de classes do PHP, introduzido no PHP 5.5.

Introdução

Recentemente, assisti ao PHPCast episódio 8 e confesso que fiquei um pouco surpreso ao notar que um recurso bem simples introduzido no PHP 5.5 não foi compreendido plenamente por todos participantes. Trata-se de uma nova sintaxe da linguagem que permite a obtenção do nome qualificado de classes. Então, vou mostrar com mais detalhes este novo recurso que, certamente, também deve ser dúvida de muitos programadores.


A sintaxe ::class

Bom, a sintaxe ::class é usada após um nome de classe para obter o nome qualificado da classe, ou seja, o nome completo da classe incluindo seu namespace. Veja o exemplo:

<?php
class Teste {
    public function exibirNome() {
        echo Teste::class;
    }
}

$t = new Teste();
$t->exibirNome(); // imprime: "Teste"

Bom, a utilização é, de fato, muito simples. Mas a dúvida maior não é nem como usar, mas onde usar. Vamos ver primeiro o que são namespaces e, então, saber finalmente onde podemos usar esta nova sintaxe.


Noções sobre Namespace

A grosso modo, o namespace dá um "nome mais elaborado" para uma classe. Em determinados pontos do código, você pode utilizar estas classes pelo seu nome principal, pelo seu nome completo ou por um apelido. É como ocorre nas relações entre as pessoas: todo mundo tem um nome completo (nome + sobrenome) para evitar ambiguidade, mas as pessoas que se conhecem se comunicam apenas chamando-as pelo primeiro nome ou por um apelido. Inclusive pessoas diferentes podem dar apelidos diferentes para a mesma pessoa ou, ainda, uma pessoa chamar outra por vários apelidos diferentes. O mesmo ocorre no namespace. Fazendo a analogia com as relações pessois, fica fácil entender o conceito de namespace.

Vamos ver um exemplo. Primeiro, temos um arquivo contendo uma classe "Conexao" e que possui o namespace "RubsPHP\AcessoDados".

<?php
namespace RubsPHP\AcessoDados;
class Conexao {
    ...
}

Note que o namespace pode ter níveis hierárquicos delimitados por "\", e isso pode ser organizado de diferentes formas. Cabe ao arquiteto do software planejar estes nomes para facilitar a legibilidade do código. Além disso, o namespace precisa ser a primeira instrução efetiva do arquivo só podendo ter antes, no máximo, espaços, quebras de linha, comentários PHP e a tag de abertura de código PHP (<?php). Uma vez declarado o namespace para um arquivo, ele se aplica a todo aquele arquivo (e apenas o próprio arquivo). Todas as classes e funções que você declarar ali terão aquele namespace acoplado a elas e qualquer classe ou função que você chamar levarão em conta o namespace que você está e namespace do recurso que você chamou.

Supondo agora que estamos em outro arquivo e queremos usar esta classe. Se o arquivo também possui o namespace "RubsPHP\AcessoDados", igual ao exemplo anterior, então ele consegue instanciar aquela classe sem precisar informar o nome completo dela, pois eles são como "dois conhecidos da mesma familia" e se conhecem automaticamente:

<?php
namespace RubsPHP\AcessoDados;
class Cache {
    public function exemplo() {
        ...
        $conexao = new Conexao();                      // Funciona
        $conexao = new \RubsPHP\AcessoDados\Conexao(); // Funciona tambem
        ...
    }
}

Note que a classe "Cache" consegue instanciar objetos da classe "Conexao" pelo nome principal ou pelo nome completo, pois possuem o mesmo namespace. Para usar o nome completo, é preciso iniciar o nome com "\", para indicar que é um caminho absoluto. Isso é bastante intuitivo para quem usa um terminal de comandos e está navegando em diretórios (passando caminhos relativos ou absolutos).

Supondo agora que queremos instanciar a classe "Conexao" em um arquivo que possui um namespace diferente. Neste caso, precisaríamos instanciar a classe pelo seu nome completo:

<?php
namespace RubsPHP\Model;
class Artigo {
    public function exemplo() {
        ...
        $conexao = new Conexao();                      // Nao funciona
        $conexao = new \RubsPHP\AcessoDados\Conexao(); // Funciona
        ...
    }
}

Como eu disse, os arquivos podem "apelidar" algumas classes. Assim não precisam usar o nome completo delas. Veja:

<?php
namespace RubsPHP\Model;
use \RubsPHP\AcessoDados\Conexao as Con;

class Artigo {
    public function exemplo() {
        ...
        $conexao = new Conexao();                      // Nao funciona
        $conexao = new \RubsPHP\AcessoDados\Conexao(); // Funciona
        $conexao = new Con();                          // Funciona
        ...
    }
}

OK, "Con" não é um nome muito bom, embora seja curto. Supondo que não quiséssemos apelidar, apenas usar seu nome principal mesmo. Neste caso, basta mudar a linha com a declaração "use" para não apelidar com nada:

<?php
namespace RubsPHP\Model;
use \RubsPHP\AcessoDados\Conexao;

class Artigo {
    public function exemplo() {
        ...
        $conexao = new Conexao();                      // Funciona
        $conexao = new \RubsPHP\AcessoDados\Conexao(); // Funciona
        ...
    }
}

Uma observação importante é que, a partir do PHP 5.3, quando foi introduzido o recurso de namespaces, TODAS as classes e funções nativas do PHP possuem um namespace. O namespace padrão é o "\", ou seja, namespace vazio. Então se estamos em um arquivo com namespace e queremos usar um recurso nativo, como o PDO, precisamos lembrar que ele tem o namespace. Veja o exemplo:

<?php
namespace RubsPHP\Model;

class Artigo {
    public function exemplo() {
        ...
        $pdo = new PDO(...);   // Nao Funciona
        $pdo = new \PDO(...);  // Funciona
        ...
    }
}

Claro que também podemos dar apelidos para os recursos nativos:

<?php
namespace RubsPHP\Model;

// Dois apelidos diferentes para a mesma classe
use \PDO as ConexaoBD;
use \PDO as Conexao;

class Artigo {
    public function exemplo() {
        ...
        $pdo = new ConexaoBD(...); // Funciona
        $pdo = new Conexao(...);   // Funciona
        $pdo = new \PDO(...);      // Funciona
        ...
    }
}

Onde usar a sintaxe de resolução de nomes de classes

Ufa! Finalmente podemos voltar ao assunto da "sintaxe de resolução de nomes de classes".

Como vimos na seção sobre namespace, em determinados pontos do código podemos chamar outra classe/função pelo seu nome completo, nome principal ou apelido, desde que tudo seja declarado corretamente. Então a sintaxe de resolução de nomes nos permite, a partir de um nome principal de classe ou a partir de um apelido, saber qual é o nome completo da classe em questão. Vamos ver algo parecido com o último exemplo:

<?php
namespace RubsPHP\Model;

// Dois apelidos diferentes para a mesma classe
use \PDO as ConexaoBD;
use \PDO as Conexao;

class Artigo {
    public function exemplo() {
        ...
        echo Artigo::class;                    // Imprime "RubsPHP\Model\Artigo"
        echo ConexaoBD::class;                 // Imprime "PDO"
        echo Conexao::class;                   // Imprime "PDO"
        echo \PDO::class;                      // Imprime "PDO"
        ...
    }
}

E onde precisamos do nome completo das classes?

  • Para gerar logs de erros mais descritivos.
  • Para realizar a reflexão sobre uma classe.
  • Para instanciar objetos a partir de nomes de classes armazenados em variáveis.
  • Para chamar métodos estáticos de uma classe através de um callback.

Exemplo de Reflexão:

<?php
namespace RubsPHP\Model;

class Artigo {
    ...
}

// Funciona
$rc = new \ReflectionClass(Artigo::class);

// Funciona
$rc = new \ReflectionClass('RubsPHP\Model\Artigo');

// Nao funciona
$rc = new \ReflectionClass('Artigo');

Note que a primeira forma de se obter a reflexão foi mais simples.

Você deve saber que o PHP permite que se instancie objetos a partir de nomes de classes guardados em variáveis:

<?php
$nome = 'PDO';
$obj = new $nome(...);

O problema é que se o arquivo possui um namespace, o nome guardado na variável precisa ser completo:

<?php
namespace RubsPHP\Model;

class Artigo {
    ...
}

// Funciona
$nome = Artigo::class;
$obj = new $nome(...);

// Funciona
$nome = 'RubsPHP\Model\Artigo';
$obj = new $nome(...);

// Nao funciona
$nome = 'Artigo';
$obj = new $nome(...);

Note que a primeira forma de instanciar o objeto é mais simples que a segunda, que precisou declarar todo o nome da classe. O exemplo é ilustrativo, mas poderíamos ter colocado o nome da classe numa variável e passado para uma função factory, que seria responsável por instanciar o objeto. Esta função poderia estar no próprio arquivo ou em outro, e precisaria do nome completo da classe para instanciá-la.

Exemplo de uso de um callback que chama um método estático de uma classe que possui namespace:

<?php
namespace RubsPHP\Model;

class Artigo {
    // Exemplo de metodo estatico
    public static function teste() {
        return 123;
    }
}

// Funciona
$callback = array(Artigo::class, 'teste');
echo call_user_func($callback);

// Funciona
$callback = array('RubsPHP\Model\Artigo', 'teste');
echo call_user_func($callback);

// Nao funciona
$callback = array('Artigo', 'teste');
echo call_user_func($callback);

Enfim, a sintaxe ::class pode ser muito útil para códigos genéricos, que trabalham com o nome completo das classes. Ele tem utilidade semelhante às constantes mágicas __CLASS__, __METHOD__, __FUNCTION__, __FILE__, __LINE__ e __DIR__.


Observações

A sintaxe ::class também pode ser usada sobre as palavras chave self e static (self::class e static::class). No primeiro caso, ela devolve exatamente o mesmo valor devolvido pela constante mágica __CLASS__, já o segundo é usado para saber o nome completo da classe do objeto que chamou o método a partir de uma classe pai.

Uma observação sobre namespaces é que eles podem ser planejados para ter a mesma estrutura do caminho do arquivo. Por exemplo, os arquivos com o namespace "RubsPHP\Model" ficam justamente no diretório "RubsPHP/Model". Isso facilita a criação de um sistema de autoload automático. Assim que uma classe é requisitada, seu namespace é usado para saber onde ela está no sistema de arquivos.

6 comentários

Anônimo disse...

ótimo artigo!
e só pra complementar:
http://php.net/manual/pt_BR/language.oop5.late-static-bindings.php
vlws

Anônimo disse...

Parabéns pelo post! Procurei no seu site mas não encontrei nada sobre routing, route, ou routes, poderia preparar explicando como utilizar? Creio que é algo complexo, pois estou aprendendo a utilizar, mas ainda sem muito sucesso pra integrar alguma lib de routing ao sistema.

Rubens Takiguti Ribeiro (autor do blog) disse...

Anônimo, existem vários tipos de "routing" na computação, dependendo do contexto. Não me lembro de nada de "routing" relacionado a namespaces, a não ser o processo de autoload automático baseado no namespace que transforma o namespace da classe na "rota" para o arquivo onde está a classe. É sobre isso que você quer saber mais?

Raul Souza disse...

Após dito tudo isso e descomplicado um pouco o namespace, é interessante seguir o padrão da PSR-0 para fazer seu sistema/site compativel com o autoloading de uma forma fácil:
http://www.php-fig.org/psr/psr-0/

Parabéns pelo blog =)

Anônimo disse...

Fala Rubens beleza?

Então cara, acabei descobrindo esse recurso há alguns dias e achei uma ótima solução para evitar a repetição de strings com nomes de classes.

Outro ponto que eu achei curioso foi a similaridade com uma constante, será que é uma constante definida internamente pelo PHP, ou é apenas uma sintaxe específica e não necessáriamente um membro de classe?

De qualquer forma é um ótimo recurso e o seu texto é de muita qualidade, obrigado por escrever posts com assuntos desconhecidos por muitos, mas que ajuda muito a melhorar a visão
de quem já teve contato com esses conceitos.

Valeu e até mais...