Sistema de hooks
Extensibilidade orientada a eventos no Mapas Culturais: nomenclatura de hooks, registro, binding de contexto, prioridades e padrões comuns.
O sistema de hooks é a principal forma de estender o Mapas Culturais sem modificar arquivos do núcleo. Um hook é um evento nomeado ao qual qualquer quantidade de callbacks PHP pode ser associada. Quando a aplicação dispara esse hook, todos os callbacks registrados para aquele nome são executados na ordem de prioridade.
Registrando um hook
$app = MapasCulturais\App::i();
$app->hook('hook.name', function() {
// callback body
});
// Com prioridade (número menor = prioridade maior; padrão é 10)
$app->hook('hook.name', function() {
// runs earlier
}, 5);Registre hooks dentro do método _init() de um módulo ou de um tema para que eles sejam configurados antes do processamento da primeira requisição.
Hook naming syntax
Os nomes dos hooks seguem um padrão estruturado que permite inscrições amplas e também específicas.
Hooks de entidade
entity.{action}:{timing}
entity({EntityClass}).{action}:{timing}| Pattern | Fires when |
|---|---|
entity.save:before | Any entity is about to be saved (insert or update) |
entity.save:after | Any entity was saved |
entity(Agent).save:before | An Agent entity is about to be saved |
entity(Agent).save:after | An Agent entity was saved |
entity.insert:before | Any entity is about to be inserted (first save) |
entity.insert:after | Any entity was inserted |
entity(Space).insert:after | A Space entity was inserted |
entity.update:before | Any entity is about to be updated |
entity.update:after | Any entity was updated |
entity.remove:before | Any entity is about to be deleted |
entity.remove:after | Any entity was deleted |
Inside entity hook callbacks, $this is bound to the entity instance:
$app->hook('entity(Agent).save:before', function() {
// $this is the Agent being saved
if (empty($this->shortDescription)) {
$this->shortDescription = 'No description provided.';
}
});Hooks de controller
{Method}({controllerId}.{actionName}):before
{Method}({controllerId}.{actionName}):after
ALL({controllerId}.{actionName}):before| Pattern | Fires when |
|---|---|
GET(agent.index):before | Before GET /agent/index |
POST(opportunity.create):after | After POST /opportunity/create |
ALL(registration.*):before | Before any action on the registration controller |
Hooks de view
view.render:before
view.render({templateName}):before
view.partial:before
view.partial({templateName}):after| Pattern | Fires when |
|---|---|
view.render:before | Before any template is rendered with a layout |
view.render(agent/single):before | Before agent/single is rendered |
view.partial:after | After any partial template is rendered |
API hooks
API({EntityClass}).paramsUse hooks de API para modificar parâmetros de consulta antes que a API execute uma busca:
$app->hook('API(Agent).params', function(&$params) {
// Restrict API results to enabled agents only
$params['status'] = 1;
});Template hooks
template({controllerId}/{actionName})Hooks de template permitem injetar HTML em pontos específicos de uma página renderizada:
$app->hook('template(agent/single)', function() {
echo '<div class="my-widget">Custom content</div>';
});Module lifecycle hooks
module({ClassName}).init:before
module({ClassName}).init:after
mapasculturais.initO contexto de $this
Quando um hook dispara de dentro de uma operação de entidade, o callback pode ser vinculado à entidade usando applyHookBoundTo. Isso é tratado automaticamente nos hooks de entidade, então $this dentro do callback se refere à entidade que disparou o hook:
$app->hook('entity(Opportunity).insert:after', function() {
// $this = the newly inserted Opportunity
$this->log->info("New opportunity created: {$this->name}");
});For hooks fired without binding, $this is not available unless you use use in the closure:
$myService = new MyService();
$app->hook('mapasculturais.init', function() use ($myService) {
$myService->initialize();
});Passagem de argumentos
Hooks podem passar argumentos adicionais aos callbacks. Os métodos applyHook e applyHookBoundTo aceitam um array de argumentos:
// Hook fired with extra arguments
$app->applyHook('my.hook', [$entityId, $extraData]);
// Callback receives them as parameters
$app->hook('my.hook', function($entityId, $extraData) {
// use $entityId and $extraData
});Prioridades
A prioridade padrão é 10. Números menores executam primeiro. Quando dois callbacks têm a mesma prioridade numérica, eles rodam na ordem em que foram registrados.
// Runs first
$app->hook('entity.save:before', function() {
// high priority
}, 1);
// Runs after
$app->hook('entity.save:before', function() {
// lower priority
}, 20);Removing hooks
To remove all callbacks from a hook:
$app->hooks->clear('entity(Agent).save:before');
// Remove all hooks
$app->hooks->clear();Comma-separated hook registration
Register a single callback on multiple hooks at once by separating names with commas:
$app->hook('entity.insert:after, entity.update:after', function() {
// fires after both insert and update
});Negated hooks (exclusion)
Prefix a hook name with - to remove a previously registered callback from that hook:
$app->hook('-entity(Agent).save:before', $callbackToExclude);Using hooks in themes vs. modules
In a module
Register hooks inside _init(). The module config array is available as $this->_config. Prefer modules for hooks that add backend behavior independent of the visual layer.
class Module extends \MapasCulturais\Module
{
function _init(): void
{
$app = App::i();
$app->hook('entity(Registration).insert:after', function() {
// send a notification
});
}
}In a theme
Register hooks inside the theme's _init(). Use theme hooks for view injection, asset enqueueing, and UI-level customization.
class Theme extends \MapasCulturais\Themes\BaseV2\Theme
{
protected function _init(): void
{
parent::_init();
$app = \MapasCulturais\App::i();
$app->hook('view.render(site/index):before', function() {
$this->enqueueScript('app', 'home', 'js/home.js');
});
}
}Complete list of entity hook events
| Hook | Timing |
|---|---|
entity.new | After __construct of any entity |
entity({Class}).new | After __construct of a specific entity |
entity.load | After any entity is loaded from DB |
entity({Class}).load | After a specific entity class is loaded |
entity.save:before | Before insert or update |
entity.save:after | After insert or update |
entity.insert:before | Before first-time save |
entity.insert:after | After first-time save |
entity.update:before | Before update of existing entity |
entity.update:after | After update of existing entity |
entity.remove:before | Before soft or hard delete |
entity.remove:after | After soft or hard delete |
To discover which hooks fire during a specific operation, enable hook logging in your development configuration:
'app.log.hook' => true,Hook names will be written to var/logs/app.log as requests are processed.
Esse material é fruto do Programa de Difusão Nacional - Funarte Redes das Artes, realizado pelo Laboratório do Futuro (entidade vinculada à Universidade Federal do Ceará) no ano de 2025.