Mirin webspace

Nejbohatší život má ten, kdo žije s minimem nároků

13. 7. 2008 Zend Framework PHP

Dependency Injection a Zend Framework

Dnes bych se rád zmínil o Dependency Injection (často se o něm mluví jako o návrhovém vzoru) a jeho vztahu k Zend Frameworku a návrhu aplikací obecně. Vycházel jsem ze článku Pádraica Bradyho, který se týká zejména návrhu Zend_Di, který je ale v současné době archivován a v nejbližší budoucnosti se do Zend Framoworku jistě nedostane. I tak je minimálně zajímavé se o Dependency Injection alespoň zmínit, v dnešní době se často skoňují slova jako Test Driven Development (TDD) a Behaviour Driven Development (BDD) a právě tam se Dependency Injection také uplatní.

Co to vlastně Dependency Injection znamená a v čem by nám tento pohled na věc mohl pomoci? Základní náhled by mohl být asi tento. Pokud již nějakou dobu používáte objektově orientované programování, tak jste jistě přišli na to, že nejlépe lze spolupráci jednotlivých objektů zajistit kompozicí. Jinak řečeno objekty používají jiné objekty a ty zase jiné atd. Nemusíte se tak vázat na jednotnou hierarchii objektů podle dědičnosti, ale můžete pracovat s objekty jako s množinou, kde jednotlivé objekty vkládáte (inject) jeden do druhého. Můžeme tak činit několika způsoby.

  • pomocí konstruktoru
  • přes setter
  • využitím nějakého externího objektu, který bude vkládání řídit

První dva přístupy jsou v Zend Frameworku široce používány, zajišťují tak vysokou udržovatelnost zdrojových kódů a nezávislost jednotlivých komponent. Třetí přístup je často zaměňován se Singleton a Registry patternem. Naházet objekty do registru a pak je v případě potřeby vybírat. To ale není to pravé, samotný objekt registru se pak stává tou závislostí, navíc se takový přístup hodí jen pro takové objekty, které používá většina controllerů.

Jako příklad Dependency Injection si vezměme controller, v jehož akci chceme poslat email. Předpokládejme toto:

  • jen některé actions budou potřebovat posílat email
  • instance Zend_Email nebude v Zend_Registry
  • Zend_Email jsme již dříve nastavili
class EmailController extends Zend_Controller_Action
{
    public function developerAction()
    {
        $mail = new Zend_Mail;
        $mail->setBodyText('This is the text of the mail.');
        $mail->setFrom('somebody@example.com', 'Some Sender');
        $mail->addTo('somebody_else@example.com', 'Some Recipient');
        $mail->setSubject('TestSubject');
        $mail->send();
    }
}

Zde vidíme jasnou závislost controlleru na Zend_Email objektu. A teď budeme pro účely testování potřebovat udělat ze Zend_Email objektu mock objekt - věříme Simonu Mundymu, který vytvořil Zend_Mail, že skutečně email pošle, proto nechceme nikoho mailem obtěžovat a přesto potřebujeme nějak chování controlleru otestovat, právě pro izolaci objektu Zend_Mail od controlleru nám pomůže Dependency Injection.

class EmailController extends Zend_Controller_Action
{
 
    public function developerAction()
    {
        $mail = Zend_Di::create('Zend_Mail');
        $mail->setBodyText('This is the text of the mail.');
        $mail->setFrom('somebody@example.com', 'Some Sender');
        $mail->addTo('somebody_else@example.com', 'Some Recipient');
        $mail->setSubject('TestSubject');
        $mail->send();
    }
 
}

Závislost controlleru na Zend_Email přenecháme na externím zprostředkovateli - Dependency Injection containeru - Zend_Di. Nicméně to není úplně všechno, co nám Dependency Injenction container nabízí, hlavní jeho výhodou je možnost závislosti tříd přesunout do nějaké externí konfigurace a pak pouze její změnou docílit nainjektování správných objektů a jejich závislostí zpět do aplikace.

Představme si, že máme controller action, která provádí nějakou dekoraci textu, třeba kurzívu a tučný text.

class FormatController extends Zend_Controller_Action
{
 
    public function boldemphasisAction()
    {
        $text = $this->getRequest()->text;
 
        $formatter = new Text_Bold(new Text_Emphasis($text));
 
        $this->view->text = $formatter->toString();
    }
}

Pokud chceme dosáhnout jiné modifikace textu (třeba změna barvy a velikosti), budeme muset projít náš kód a zaměnit použité třídy Text_Bold na Text_Color apod. S Dependency Injection containerem to dělat nemusíme, necháve závistosti injektovaných objektů na něm a přesuneme je do konfigurace.

./config/di/format/boldemphasis.php
return array(
    'Formatter' => array(
        'class' => 'Text_Bold',
        'arguments' => array(
            '__construct' => 'Emphasis'
        )
    ),
    'Emphasis' => array(
        'class' => 'Text_Emphasis'
    }
);
 
class FormatController extends Zend_Controller_Action
{
 
    public function decoratingAction()
    {
        $config = new Zend_Config( require '/path/to/config/di/format/boldemphasis.php' );
        $di = new Zend_Di_Container($config);
        $formatter = $di->loadClass('Formatter')->newInstance();
 
        $this->view->text = $formatter->toString();
    }
}

Jen změnou konfigurace Dependecy Injection containeru pak můžeme řídit, jaké formátování naše action bude provádět. Jistě jste si všimli, že jsme si zanesli do kódu závislost na Zend_Config, zbavíme se jí opět pomocí Dependency Injection containeru třeba takto

Zend_Di::loadClass('Zend_Config',
    require '/path/to/config/di/format/boldemphasis.php');

Dependency Injection není žádnou novinkou, široce je využíván zejména v Javě díky frameworkům jako Spring a Gooogle Juice, v PHP jsem se s tím zatím moc nesetkal a ani návrch Zend_Di se zatím do frameworku nedostane, podle autora i ostatních se z původně lightweight řešení začala klubat docela těžkopádná věc. Na druhou stranu vždyť ono nakonec PHP obsahuje koncept, kdy lze podle jmen proměnných vytvářet instance objektů - $var=new $className(), který v mnoha případech na Dependency Injection stačí.


Komentáře (0)

Komentáře jsou uzavřeny.