Skip to content

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 com ajax 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 em

add_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');
        }
    }

Merge request reports