Hooks, Signals e Shortcodes¶
O que você vai construir¶
Neste tutorial você vai adicionar comportamento reativo a extension demo (construida nos tutoriais 01 e 02):
- Action hook que dispara quando uma note e criada.
- Subscriber que reage ao hook e registra log.
- Filter que transforma o título da note (uppercase).
- Shortcode que renderiza notes dentro de qualquer area de texto Moodle.
Ao final, a extension tera o ciclo reativo completo: publicar, reagir, transformar e exibir.
Tempo estimado: ~20 minutos.
Pre-requisitos¶
- Tutoriais 01 e 02 concluidos (extension demo com CRUD de notes funcionando).
Passo 1: Publicar um action hook na criação¶
Edite classes/extensions/demo/note/note_service.php para disparar um hook quando uma note for criada.
Adicione o use no topo:
Altere o método create():
public function create(string $title, string $content): \local_middag\base\domain\item
{
$dto = new item_dto(
type: note::TYPE,
fullname: $title,
description: $content,
status: 'published',
metadata: [
'title' => $title,
'content' => $content,
],
);
$note = item_service::create($dto);
// Dispara o action hook apos a criação.
hook_manager::do_action('middag/demo/note_created', $note);
return $note;
}
O que esta acontecendo¶
hook_manager::do_action()pública um hook nomeado. Qualquer subscriber registrado paramiddag/demo/note_createdsera executado.- O hook e lateral: falhas em subscribers não afetam o fluxo principal (a note já foi criada com sucesso).
- A facade
hook_managerdelega para\local_middag\framework\kernel\manager\hook_manager, que executa callbacks registrados por prioridade.
Convencao de nomes: Hooks do MIDDAG seguem o padrão
middag/{extension}/{evento}. Isso evita colisao com hooks de outros plugins.
Passo 2: Reagir ao hook com um subscriber¶
Crie o arquivo:
<?php
declare(strict_types=1);
namespace local_middag\extensions\demo\hook;
defined('MOODLE_INTERNAL') || exit;
use local_middag\facade\hook_manager;
class note_created_subscriber
{
/**
* Registra o subscriber para o hook de criação de note.
*/
public static function register(): void
{
hook_manager::add_action(
'middag/demo/note_created',
[self::class, 'handle'],
10, // prioridade
1 // número de argumentos
);
}
/**
* Callback executado quando uma note e criada.
*
* @param \local_middag\base\domain\item $note A note recem-criada
*/
public static function handle(\local_middag\base\domain\item $note): void
{
// Log via debugging do Moodle (visível com developer debug ativado).
debugging(
sprintf(
'[demo] Note criada: ID=%d, título="%s"',
$note->get_id(),
$note->get_fullname()
),
DEBUG_DEVELOPER
);
}
}
O que esta acontecendo¶
- O arquivo reside em
hook/dentro da extension. O framework escaneia este diretorio automaticamente duranteboot()e chamaregister()em cada classe encontrada. hook_manager::add_action()registra o callback com prioridade 10 (default) e 1 argumento.- O método
handle()recebe a note criada e registra uma mensagem de debug.
Nota: Em produção, você usaria o sistema de logging do framework (
file_loggerfacade) em vez dedebugging(). Para o tutorial,debugging()e mais simples de verificar.
Passo 3: Criar um filter para o título¶
Agora você vai criar um filter que transforma titulos de notes. Primeiro, publique o filter no service. Depois, registre um callback.
3.1 Publicar o filter no service¶
Edite note_service.php. Altere o método create() para aplicar o filter no título antes de criar a note:
public function create(string $title, string $content): \local_middag\base\domain\item
{
// Aplica filters registrados no título.
$title = hook_manager::apply_filters('middag/demo/note_title', $title, [
'content' => $content,
]);
$dto = new item_dto(
type: note::TYPE,
fullname: $title,
description: $content,
status: 'published',
metadata: [
'title' => $title,
'content' => $content,
],
);
$note = item_service::create($dto);
// Dispara o action hook apos a criação.
hook_manager::do_action('middag/demo/note_created', $note);
return $note;
}
3.2 Registrar o filter na extension¶
Edite classes/extensions/demo/demo_extension.php para registrar o filter durante boot():
<?php
declare(strict_types=1);
namespace local_middag\extensions\demo;
defined('MOODLE_INTERNAL') || exit;
use local_middag\base\extension;
use local_middag\extensions\demo\note\note_service;
use local_middag\facade\hook_manager;
use Psr\Container\ContainerInterface;
class demo_extension extends extension
{
public const EXTENSION_IDNUMBER = 'demo';
public function get_icon(): string
{
return 'app';
}
public function register(ContainerInterface $container): void
{
// Container registration (auto-wired para note_service).
}
public function boot(): void
{
parent::boot();
// Registra um filter que converte titulos para uppercase.
hook_manager::add_filter(
'middag/demo/note_title',
function (string $title): string {
return mb_strtoupper($title);
},
10, // prioridade
1 // número de argumentos
);
}
}
O que esta acontecendo¶
hook_manager::apply_filters()passa o valor por todos os callbacks registrados e retorna o resultado transformado. Se nenhum filter estiver registrado, retorna o valor original.- Diferente de actions (laterais), filters propagam o valor de retorno em pipeline: cada callback recebe a saida do anterior.
- O
boot()chamaparent::boot()primeiro para garantir que controllers e hooks do diretoriohook/sejam registrados antes de adicionar lógica customizada.
Diferenca fundamental: Actions (
do_action) executam side-effects sem valor de retorno. Filters (apply_filters) transformam um valor em cadeia sincrona.
Passo 4: Criar um shortcode¶
Adicione o registro do shortcode no método boot() da extension. Edite demo_extension.php:
Dentro do boot(), apos o registro do filter, adicione:
// Registra shortcode [middag type="demo_notes"].
shortcode_manager::register('demo_notes', [self::class, 'render_notes_shortcode']);
Adicione o método estatico na classe demo_extension:
/**
* Renderiza o shortcode demo_notes.
*
* Uso: [middag type="demo_notes" limit="5"]
*
* @param array $attrs Atributos parseados do shortcode
*
* @return string HTML renderizado
*/
public static function render_notes_shortcode(array $attrs): string
{
$limit = (int) ($attrs['limit'] ?? 5);
$service = new \local_middag\extensions\demo\note\note_service();
$result = $service->list_all();
if ($result->is_empty()) {
return '<p>Nenhuma note encontrada.</p>';
}
$html = '<div class="middag-demo-notes">';
$html .= '<h4>Notas recentes</h4>';
$html .= '<ul>';
$count = 0;
foreach ($result->items() as $note) {
if ($count >= $limit) {
break;
}
$html .= '<li><strong>' . s($note->get_fullname()) . '</strong></li>';
$count++;
}
$html .= '</ul>';
$html .= '</div>';
return $html;
}
O que esta acontecendo¶
shortcode_manager::register()associa o tagdemo_notesa um callback. A facade delega para\local_middag\framework\service\shortcode_manager.- O shortcode e invocado pela sintaxe
[middag type="demo_notes"]em qualquer area de texto processada pelo MIDDAG. - Atributos adicionais (como
limit) são parseados automaticamente e passados como array ao callback. - Por default, a saida do shortcode e sanitizada via
clean_text(). Para output HTML sem sanitizacao, anote o método com#[trusted_output].
Passo 5: Usar o shortcode¶
Em qualquer area de texto do Moodle processada pelo MIDDAG (descricao de curso, bloco HTML, etc.), insira:
O framework substituira este tag pelo HTML renderizado: uma lista das 3 notes mais recentes.
Onde funciona: O shortcode e processado pelo filter
filter_middagquando ativo no contexto do Moodle. Certifique-se de que o filtro de texto MIDDAG esta habilitado em Administracao do site > Plugins > Filtros de texto.
Passo 6: Testar¶
6.1 Limpar cache¶
6.2 Testar o action hook (log)¶
- Ative o modo de debug do Moodle: Administracao do site > Desenvolvimento > Debugging > "DEVELOPER".
- Acesse
/local/middag/index.php/demo/notes/create. - Crie uma note com título "teste hooks" e conteúdo qualquer.
- Apos salvar, verifique as mensagens de debug. Você vera:
Note que o título aparece em uppercase -- o filter foi aplicado antes da criação.
6.3 Testar o filter¶
Na listagem de notes (/local/middag/index.php/demo), todas as notes criadas apos a ativacao do filter terao o título em uppercase.
6.4 Testar o shortcode¶
- Crie uma página ou bloco HTML no Moodle.
- No conteúdo, insira:
[middag type="demo_notes" limit="3"] - Salve e visualize a página.
- O shortcode sera substituido pela lista de notes.
Resultado¶
Sua extension demo agora possui o ciclo reativo completo:
| Mecanismo | O que faz | Onde esta |
|---|---|---|
do_action() |
Publica evento lateral | note_service::create() |
add_action() |
Reage ao evento | hook/note_created_subscriber.php |
apply_filters() |
Transforma valor em pipeline | note_service::create() |
add_filter() |
Registra transformacao | demo_extension::boot() |
shortcode_manager::register() |
Registra shortcode | demo_extension::boot() |
[middag type="..."] |
Invoca shortcode em texto | Qualquer area de texto Moodle |
Estrutura final de arquivos¶
classes/extensions/demo/
demo_extension.php
note/
note.php
note_service.php
controller/
demo_controller.php
hook/
note_created_subscriber.php
Resumo do que você aprendeu¶
| Conceito | O que faz |
|---|---|
hook_manager::do_action() |
Publica action hook (side-effect lateral) |
hook_manager::add_action() |
Registra subscriber para action hook |
hook_manager::apply_filters() |
Passa valor por pipeline de transformacao |
hook_manager::add_filter() |
Registra callback de transformacao |
shortcode_manager::register() |
Registra tag para renderizacao inline |
[middag type="..."] |
Sintaxe de invocacao de shortcode |
Diretorio hook/ |
Auto-discovered durante boot da extension |
O que aprender a seguir¶
Com os fundamentos de extension, persistência e reatividade cobertos, explore os guides do framework:
- Routing -- Rotas avancadas, middlewares, form requests e API controllers.
- Commands e Jobs -- Execução sincrona e assincrona de trabalho.
- Autorização -- Capabilities, policies e guards.
- API Publica -- Contracts, facades e estabilidade.