Chegando aqui exatamente 2 anos depois que a pergunta original foi feita, há algumas coisas que quero destacar. (Não me peça para apontar muitas coisas , sempre).
Gancho adequado
Para instanciar uma classe de plug-in, o gancho adequado deve ser usado. Não existe uma regra geral para isso, pois depende do que a classe faz.
Usar um gancho muito cedo, como não "plugins_loaded"
costuma fazer sentido, porque um gancho desse tipo é acionado para solicitações de administrador, frontend e AJAX, mas muitas vezes um gancho posterior é muito melhor porque permite instanciar classes de plug-in apenas quando necessário.
Por exemplo, uma classe que faz coisas para modelos pode ser instanciada "template_redirect"
.
De um modo geral, é muito raro que uma classe precise ser instanciada antes de "wp_loaded"
ser demitida.
Classe sem Deus
A maioria de todas as classes usadas como exemplos em respostas mais antigas usa uma classe chamada like "Prefix_Example_Plugin"
ou "My_Plugin"
... Isso indica que provavelmente existe uma classe principal para o plugin.
Bem, a menos que um plug-in seja feito por uma única classe (nesse caso, nomeá-lo após o nome do plug-in é absolutamente razoável), crie uma classe que gerencie todo o plug-in (por exemplo, adicionando todos os ganchos de que um plug-in precisa ou instanciando todas as outras classes de plug-in ) pode ser considerada uma má prática, como um exemplo de um objeto divino .
Na programação orientada a objetos, o código deve tender a ser SOLID, onde "S" significa "princípio de responsabilidade única" .
Isso significa que toda classe deve fazer uma única coisa. No desenvolvimento de plug-ins do WordPress, significa que os desenvolvedores devem evitar usar um único gancho para instanciar uma classe principal de plug - ins, mas diferentes ganchos devem ser usados para instanciar classes diferentes, de acordo com a responsabilidade da classe.
Evite ganchos no construtor
Esse argumento foi introduzido em outras respostas aqui, no entanto, quero comentar esse conceito e vincular essa outra resposta, onde foi amplamente explicado no âmbito do teste de unidade.
Quase 2015: o PHP 5.2 é para zumbis
Desde 14 de agosto de 2014, o PHP 5.3 atingiu seu fim de vida . Definitivamente está morto. O PHP 5.4 será suportado por todo o ano de 2015, ou seja, por mais um ano no momento em que estou escrevendo.
No entanto, o WordPress ainda suporta PHP 5.2, mas ninguém deve escrever uma única linha de código que suporte essa versão, especialmente se o código for OOP.
Existem diferentes razões:
- PHP 5.2 morto há muito tempo, nenhuma correção de segurança foi lançada, o que significa que não é seguro
- PHP 5.3 adicionou um monte de recursos para PHP, funções anônimas e namespaces alles über
- versões mais recentes do PHP são muito mais rápidas . PHP é grátis. A atualização é gratuita. Por que usar uma versão mais lenta e insegura se você pode usar uma versão mais rápida e segura de graça?
Se você não quiser usar o código PHP 5.4+, use pelo menos 5.3+
Exemplo
Neste ponto, é hora de revisar as respostas mais antigas com base no que eu disse até aqui.
Uma vez que não precisamos mais nos preocupar com o 5.2, podemos e devemos usar namespaces.
Para melhor explicar o princípio da responsabilidade única, meu exemplo usará 3 classes, uma que faz algo no front-end, uma no back-end e uma terceira usada nos dois casos.
Classe de administrador:
namespace GM\WPSE\Example;
class AdminStuff {
private $tools;
function __construct( ToolsInterface $tools ) {
$this->tools = $tools;
}
function setup() {
// setup class, maybe add hooks
}
}
Classe de front-end:
namespace GM\WPSE\Example;
class FrontStuff {
private $tools;
function __construct( ToolsInterface $tools ) {
$this->tools = $tools;
}
function setup() {
// setup class, maybe add hooks
}
}
Interface de ferramentas:
namespace GM\WPSE\Example;
interface ToolsInterface {
function doSomething();
}
E uma classe Tools, usada pelos outros dois:
namespace GM\WPSE\Example;
class Tools implements ToolsInterface {
function doSomething() {
return 'done';
}
}
Tendo essas aulas, posso instancia-las usando ganchos adequados. Algo como:
require_once plugin_dir_path( __FILE__ ) . 'src/ToolsInterface.php';
require_once plugin_dir_path( __FILE__ ) . 'src/Tools.php';
add_action( 'admin_init', function() {
require_once plugin_dir_path( __FILE__ ) . 'src/AdminStuff.php';
$tools = new GM\WPSE\Example\Tools;
global $admin_stuff; // this is not ideal, reason is explained below
$admin_stuff = new GM\WPSE\Example\AdminStuff( $tools );
} );
add_action( 'template_redirect', function() {
require_once plugin_dir_path( __FILE__ ) . 'src/FrontStuff.php';
$tools = new GM\WPSE\Example\Tools;
global $front_stuff; // this is not ideal, reason is explained below
$front_stuff = new GM\WPSE\Example\FrontStuff( $tools );
} );
Inversão de Dependência e Injeção de Dependência
No exemplo acima, usei namespaces e funções anônimas para instanciar classes diferentes em ganchos diferentes, colocando em prática o que eu disse acima.
Observe como os namespaces permitem criar classes nomeadas sem nenhum prefixo.
Eu apliquei outro conceito que foi indiretamente mencionado acima: Injeção de Dependência , é um método para aplicar o Princípio de Inversão de Dependência , o "D" na sigla SOLID.
A Tools
classe é "injetada" nas outras duas classes quando são instanciadas, portanto é possível separar a responsabilidade.
Além disso, AdminStuff
e FrontStuff
classes usam dicas de tipo para declarar que precisam de uma classe que implementa ToolsInterface
.
Dessa maneira, nós mesmos ou usuários que usamos nosso código podemos usar implementações diferentes da mesma interface, tornando nosso código não acoplado a uma classe concreta, mas a uma abstração: é exatamente disso que se trata o Princípio da Inversão de Dependência.
No entanto, o exemplo acima pode ser melhorado. Vamos ver como.
Carregador automático
Uma boa maneira de escrever um código OOP mais legível é não misturar a definição de tipos (interfaces, classes) com outro código e colocar todos os tipos em seu próprio arquivo.
Esta regra também é um dos padrões de codificação PSR-1 1 .
No entanto, para fazer isso, antes de poder usar uma classe, é necessário exigir o arquivo que a contém.
Isso pode ser esmagador, mas o PHP fornece funções utilitárias para carregar automaticamente uma classe quando necessário, usando um retorno de chamada que carrega um arquivo com base em seu nome.
Usando espaços para nome, fica muito fácil, porque agora é possível combinar a estrutura da pasta com a estrutura do espaço para nome.
Isso não é apenas possível, mas também é outro padrão PSR (ou melhor, 2: PSR-0 agora obsoleto e PSR-4 ).
Seguindo esses padrões, é possível fazer uso de diferentes ferramentas que lidam com o carregamento automático, sem a necessidade de codificar um carregador automático personalizado.
Devo dizer que os padrões de codificação do WordPress têm regras diferentes para nomear arquivos.
Portanto, ao escrever o código para o núcleo do WordPress, os desenvolvedores precisam seguir as regras do WP, mas ao escrever o código personalizado, é uma opção do desenvolvedor, mas o uso do padrão PSR é mais fácil de usar as ferramentas já escritas 2 .
Padrões de acesso global, registro e localizador de serviço.
Um dos maiores problemas ao instanciar classes de plug-in no WordPress é como acessá-las em várias partes do código.
O próprio WordPress usa a abordagem global : as variáveis são salvas no escopo global, tornando-as acessíveis em qualquer lugar. Todo desenvolvedor de WP digita a palavra global
milhares de vezes em sua carreira.
Essa também é a abordagem que usei para o exemplo acima, mas é ruim .
Essa resposta já é longa demais para permitir que eu explique melhor o porquê, mas ler os primeiros resultados no SERP para "variáveis globais más" é um bom ponto de partida.
Mas como é possível evitar variáveis globais?
Existem maneiras diferentes.
Algumas das respostas mais antigas aqui usam a abordagem de instância estática .
public static function instance() {
if ( is_null( self::$instance ) ) {
self::$instance = new self;
}
return self::$instance;
}
É fácil e muito bom, mas obriga a implementar o padrão para todas as classes que queremos acessar.
Além disso, muitas vezes essa abordagem coloca o caminho para cair no problema da classe god, porque os desenvolvedores tornam acessível uma classe principal usando esse método e depois o usam para acessar todas as outras classes.
Eu já expliquei o quão ruim é uma classe god, portanto, a abordagem de instância estática é um bom caminho a percorrer quando um plug-in precisa apenas tornar acessível uma ou duas classes.
Isso não significa que ele possa ser usado apenas para plugins com apenas algumas classes; de fato, quando o princípio de injeção de dependência é usado corretamente, é possível criar aplicativos bastante complexos sem a necessidade de tornar globalmente acessível um grande número de objetos.
No entanto, algumas vezes os plugins precisam tornar acessíveis algumas classes e, nesse caso, a abordagem de instância estática é esmagadora.
Outra abordagem possível é usar o padrão de registro .
Esta é uma implementação muito simples:
namespace GM\WPSE\Example;
class Registry {
private $storage = array();
function add( $id, $class ) {
$this->storage[$id] = $class;
}
function get( $id ) {
return array_key_exists( $id, $this->storage ) ? $this->storage[$id] : NULL;
}
}
Usando essa classe, é possível armazenar objetos no objeto de registro por um ID, portanto, tendo acesso a um registro, é possível ter acesso a todos os objetos. Obviamente, quando um objeto é criado pela primeira vez, ele precisa ser adicionado ao registro.
Exemplo:
global $registry;
if ( is_null( $registry->get( 'tools' ) ) ) {
$tools = new GM\WPSE\Example\Tools;
$registry->add( 'tools', $tools );
}
if ( is_null( $registry->get( 'front' ) ) ) {
$front_stuff = new GM\WPSE\Example\FrontStuff( $registry->get( 'tools' ) );
$registry->add( 'front', front_stuff );
}
add_action( 'wp', array( $registry->get( 'front' ), 'wp' ) );
O exemplo acima deixa claro que, para ser útil, o registro precisa estar acessível globalmente. Uma variável global para o único registro não é muito ruim; no entanto, para puristas não globais, é possível implementar a abordagem de instância estática para um registro ou talvez uma função com uma variável estática:
function gm_wpse_example_registry() {
static $registry = NULL;
if ( is_null( $registry ) ) {
$registry = new GM\WPSE\Example\Registry;
}
return $registry;
}
A primeira vez que a função é chamada, ela instancia o registro e, nas chamadas subsequentes, apenas o devolve.
Outro método específico do WordPress para tornar uma classe acessível globalmente está retornando uma instância de objeto de um filtro. Algo assim:
$registry = new GM\WPSE\Example\Registry;
add_filter( 'gm_wpse_example_registry', function() use( $registry ) {
return $registry;
} );
Depois disso, em qualquer lugar, o registro é necessário:
$registry = apply_filters( 'gm_wpse_example_registry', NULL );
Outro padrão que pode ser usado é o padrão do localizador de serviço . É semelhante ao padrão do registro, mas os localizadores de serviço são passados para várias classes usando injeção de dependência.
O principal problema desse padrão é que ele oculta as dependências de classes, dificultando a manutenção e a leitura do código.
DI Containers
Independentemente do método usado para tornar o localizador de registro ou serviço globalmente acessível, os objetos precisam ser armazenados lá e, antes de serem armazenados, precisam ser instanciados.
Em aplicações complexas, onde existem muitas classes e muitas delas possuem várias dependências, instanciar classes requer muito código; portanto, a possibilidade de erros aumenta: o código que não existe não pode ter erros.
Nos últimos anos, surgiram algumas bibliotecas PHP que ajudam os desenvolvedores a instanciar e armazenar facilmente instâncias de objetos, resolvendo automaticamente suas dependências.
Essas bibliotecas são conhecidas como contêineres de injeção de dependência porque são capazes de instanciar classes resolvendo dependências e também para armazenar objetos e devolvê-los quando necessário, agindo de maneira semelhante a um objeto de registro.
Geralmente, ao usar contêineres DI, os desenvolvedores precisam configurar as dependências para cada classe do aplicativo e, na primeira vez em que uma classe é necessária no código, ela é instanciada com dependências apropriadas e a mesma instância é retornada repetidamente em solicitações subsequentes. .
Alguns contêineres DI também são capazes de descobrir automaticamente dependências sem configuração, mas usando a reflexão do PHP .
Alguns recipientes DI bem conhecidos são:
e muitos outros.
Quero ressaltar que, para plugins simples, que envolvem apenas poucas classes e classes não possuem muitas dependências, provavelmente não vale a pena usar contêineres DI: o método de instância estática ou um registro acessível global são boas soluções, mas para plugins complexos o benefício de um contêiner DI se torna evidente.
Obviamente, mesmo objetos de contêineres DI precisam estar acessíveis para serem usados no aplicativo e, para esse propósito, é possível usar um dos métodos vistos acima, variável global, variável estática de instância, objeto retornado via filtro e assim por diante.
Compositor
Usar o contêiner DI geralmente significa usar código de terceiros. Atualmente, no PHP, quando precisamos usar uma biblioteca externa (não apenas os contêineres DI, mas qualquer código que não faça parte do aplicativo), basta fazer o download e colocá-lo na pasta do aplicativo não é considerado uma boa prática. Mesmo se formos os autores desse outro pedaço de código.
A dissociação de um código de aplicativo de dependências externas é sinal de melhor organização, melhor confiabilidade e melhor sanidade do código.
Compositor , é o padrão de fato na comunidade PHP para gerenciar dependências do PHP. Longe de ser popular também na comunidade WP, é uma ferramenta que todo desenvolvedor de PHP e WordPress deve conhecer, se não usar.
Essa resposta já é do tamanho de um livro para permitir uma discussão mais aprofundada, e também discutir o Composer aqui provavelmente está fora de tópico, foi mencionado apenas por uma questão de completude.
Para mais informações, visite o site do Composer e também vale a pena ler este minisite com curadoria de @Rarst .
1 PSR são regras de padrões PHP lançadas pelo PHP Framework Interop Group
2 O Composer (uma biblioteca que será mencionada nesta resposta), entre outras coisas, também contém um utilitário de carregamento automático.