Remoção do Acoplamento dos Menus
Descrição
Como apresentado em #38 (closed) há um problema de acoplamento ao cadastrar hooks ajax
:
As ações
admin_post
iniciadas por um hook comajax
devem ser cadastradas no arquivo principal do plugin para funcionamento correto. Isso causa acoplamento das ações com as funções que elas executam como visto emadd_action('admin_post_create_new_essay', 'StyledNewEssayMenu::handle_new_essay_submit');
De forma geral, o hook admin_post_<nome_do_hook>
deve ser criado a partir da página principal do plugin e, por isso, não pode ser cadastrado diretamente pelo menu que vai utilizá-lo. Felizmente, uma solução para isso é conhecida. Basta utilizar as estruturas descritas no boilerplate de plugins wordpress.
Aplicando a Solução
Uma vez que só encontramos o boilerplate após o início do desenvolvimento do plugin é necessária uma refatoração dos arquivos para adequá-los a solução proposta. Vamos dividir o processo de refatoração por partes
salvaguarda-redlog.php
Definição do Plugin: Inicialmente o estado do arquivo principal do plugin era
<?php
/**
* Plugin Name: salvaguarda-redlog
* Description: Operacoes de Gerenciamento do Programa Salvaguarda.
*/
/*Inclusão de Pacotes*/
/* Definição das Páginas */
function description_page()
{
echo '<h1>Plugin de Gerenciamento de Redações - Programa Salvaguarda </h1>';
}
function essay_menu()
{
// Defines Menus Frontend
// Adds Menus To Wordpress
}
/* Definição dos Shortcodes */
function essay_submit(){
// Defines shortcodes
}
function init_shorts(){
// Register Shortcodes
}
/* Executa Ações */
add_action('admin_menu', 'essay_menu');
add_action('init','init_shorts');
/* Cadastra Hooks */
add_action('admin_post_create_new_essay', 'StyledNewEssayMenu::handle_new_essay_submit');
// ...
Agora, após refatorar os menus de administração e seu cadastro o arquivo principal do plugin se encontra no seguinte estado
<?php
/**
* Plugin Name: salvaguarda-redlog
* Description: Operacoes de Gerenciamento do Programa Salvaguarda.
*/
/*Inclusão de Pacotes*/
/* Definição dos Shortcodes */
function essay_submit(){
// ...
}
function init_shorts(){
essay_submit();
}
add_action('init','init_shorts');
// ===========================================================================================
/*Definição de Constantes */
// ...
/* Hooks de Ativação e Desativação*/
// ...
require plugin_dir_path( __FILE__ ) . 'includes/core/class-salvaguarda-redlog.php';
require REDLOGPATH . 'includes/db-access/db.php';
// Inicia Plugin cadastrando funções públicas
// de adm e hooks
function run_plugin_salvaguarda_redlog() {
$db = DBAccessPoint::get_db_access_point();
$plugin = new SalvaguardaRedlog($db);
$plugin->run();
}
run_plugin_salvaguarda_redlog();
?>
É interessante mencionar que $db = DBAccessPoint::get_db_access_point()
utiliza uma estrutura similar a do singleton para garantir a criação de um único ponto de acesso ao BD e assim economizar memória.
SalvaguardaRedlog
Classe É a classe responsável por cadastrar todas as ações do plugin.
class SalvaguardaRedlog {
// Atributes
public function __construct($db) {
// ...
$this->load_dependencies();
// $this->set_locale();
$this->define_admin_hooks();
$this->define_public_hooks();
}
private function load_dependencies() {
// ...
$this->loader = new SalvaguardaRedlogLoader();
}
private function define_admin_hooks() {
$plugin_admin = new SalvaguardaRedlogAdmin( $this->get_plugin_name(), $this->get_version(), $this->db );
// ...
}
private function define_public_hooks() {
// ...
}
public function run() {
$this->loader->run();
}
// Getters
// ...
}
Os métodos define_admin_hooks
e define_public_hooks
utilizam o objeto SalvaguardaRedlogLoader
para cadastrar seus hooks. Até o momento só foi refatora da a seção de adm do plugin então não há nada de interessante na definição dos hooks publicos. Em relação a definição dos hooks de administração é aplicada uma estratégia de composites
para obter os hooks de SalvaguardaRedlogAdmin
e seus filhos.
SalvaguardaRedlogAdmin
e padrão de composites
Classe Contém todas as funcionalidades da administração do plugin. Para isso, aceita filhos (e.g. Styled<Função>Menu
) de forma que cada filho é cadastrado como uma página como visto no método
public function add_admin_menu_page()
{
$pos = 200;
add_menu_page('Gerenciamento de Redações', 'Salvaguarda', 'manage_options', 'salvaguarda_admin_menu', array($this, 'description_page'), '', 200);
foreach ($this->composites as $i => $comp)
{
add_submenu_page(
'salvaguarda_admin_menu',
$comp->get_page_title(),
$comp->get_menu_title(),
$comp->get_capability(),
$comp->get_menu_slug(),
array($comp, 'render'),
$pos + $i
);
}
}
Vale ainda notar que cada menu implementa a interface ICompositeHTML
interface ICompositeHTML
{
// Getters and Setter
// ...
// Composite Functions
public function add(ICompositeHTML $e): void;
public function enqueue_styles(): void;
public function enqueue_scripts(): void;
public function handle_hooks(): void;
public function render(): void;
}
Dessa forma um menu mais complexo, por exemplo um menu que possibilite a seleção de um usuário e a edição de seus dados, não precisa ser definido como uma classe. Basta compor o menu como um objeto do tipo CompositeHTML
que tem como filhos os menus atômicos StyledStudentSelectMenu
e StyledStudentEditMenu
.
Menus Refatorados
Um problema com os menus era o código duplicado. Muitas vezes um SimpleMenu
e StyledMenu
implementavam a mesma funcionalidade com uma view
diferente o que tornava necessário que o código para lidar com uma determinada operação estivesse contido nas duas classes. Por isso, cria-se o menu SampleMenu
que implementa as funções necessárias para lidar com a regra de negócio, mas omite a view
. Sendo assim, não deve ser utilizado diretamente e os menus SimpleMenu
e StyledMenu
devem estendê-lo adicionando uma view e, se necessário, alguma regra adicional. Um exemplo dessa aplicação é
class SampleNewEssayMenu extends CompositeHTML
{
public function enqueue_scripts(): void
{
// ...
}
public function get_hooks(): array
{
// ...
}
public function handle_hooks(): void
{
// ...
}
public function handle_new_essay_submit(): void
{
// ...
}
}
De forma que é estendido pelo StyledMenu
com uma view
e css
criando o menu completo
class StyledNewEssayMenu extends SampleNewEssayMenu
{
public function enqueue_styles(): void
{
wp_enqueue_style('new-essay-style', plugin_dir_url(__FILE__) . 'css/styled-new-essay.css', array());
parent::enqueue_styles();
}
public function render(): void
{
include_once('styled-new-essay-view.php');
}
}