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_postiniciadas por um hook comajaxdevem 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
Definição do Plugin: salvaguarda-redlog.php
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.
Classe SalvaguardaRedlog
É 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.
Classe SalvaguardaRedlogAdmin e padrão de composites
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');
}
}