Rozšiřování funkčnosti rodičovské třídy pomocí hooks

K napsání tohoto příspěvku mě inspirovala práce na jednom projektu v Zend Frameworku, který je opravdu dobře napsaný z hlediska dědičnosti atp. Někdo bude mít možná pocit, že tu znovuobjevuji kolo, ale někomu to třeba pomůže. Pokud vás zajímá co jsou to hooks, tak čtěte dále.

Problém

Rodičovský controller má v sobě většinu funkcionality. Takže třeba pro update stačí načíst data, do proměnné controlleru dát form a zavolat parent::update(). Všechno šlape jako hodinky do chvíle, než je potřeba nějak rozšířit funkcionalitu nad možnosti parent controlleru. Např. přidat nějakou složitou validaci dat, přidat nějaký ruhý form, atp. V tu chvíli se na první pohled zdá, že jediná šance je zkopírovat kód parent controlleru a udělat v něm potřebné úpravy. A docela dlouho sem to takhle (prasecky) dělal.

Řešení

Do chvíle, než mě Martin Hujer upozornil na to, že se na tohle dají hezky použít hooks! :) Ne že bych je neznal už dřív. V ZendFrameworku je používám dnes a denně např. ve FrontController pluginech (dispatchLoopShutdown, preDispatch, …). Ale vůbec mi nedošlo, že bych je mohl použít.

Coto, toto?

Cože to ty hooks jsou? Pro neznalé: Hooks jsou procedury, které nalepíte někam doprostřed kódu a ve zděděné třídě do nich pak napíšete co potřebujete, aniž byste museli měnit rodičovskou třídu. Pochopitelné? Moc ne, že.

Příklad pomůže.

Původní zdrojový kód

<?php
// ParentController
function updateAction()
{
    //nějaký kód co nastavuje třeba form, title, oescapování, atp.
    $this->view->headTitle('test');
    //semhle bych chtěl vložit nějaký svůj kód
    $this->_model->update($this->_data);
    //nějaký další kód
}

Zdrojový kód s hooks

<?php
// ParentController
function updateAction()
{
    //nějaký kód co nastavuje třeba form, title, oescapování, atp.
    $this->view->headTitle('test');
    $this->_beforeUpdate();
    $this->_model->update($this->_data);
    $this->_afterUpdate();
    //nějaký další kód
}
<?php
protected function _beforeUpdate(){}
protected function _afterUpdate(){}
<?php
class IndexController extends ParentController {
    protected function _beforeUpdate()
    {
        //tady si můžu zavolat co potřebuji a provede se to
        //před updatem DB
        //např.:
        $this->_data['date'] = '2009-01-01';
    }
}