Mirin webspace

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

3. 4. 2008 - Komentáře (7) Zend Framework

Rozdělení administračního controlleru

Velmi často je potřeba do aplikace nějakým způsobem napasovat administraci. Přístupů, jak to řešit je více. Někdo preferuje generátory administračních rozhraní a různé CRUD generátory. Některé frameworky (myslím že Django je jedním z nich), takové generátory přímo obsahují. Já jsem si jednoduché administrační rozhraní k blogu napsal sám, není to zas tak náročné. Pokud se používá Zend Framework MVC s doménovými objekty, docela to jde. Je několik možností jak administraci do Zend Framework based projektu začlenit.

  • Administrační modul s controllery.
  • Administrační controllery v každém modulu.

Představme si aplikaci s moduly pro blog, implicitním modulem a modulem pro galerii.

app/
 blog/
  controllers/
   IndexController.php
  models/
  views/
 default/
  controllers/
   IndexController.php
  models/
  views/
 galery/
  controllers/
   IndexController.php
  models/
  views/
lib/
www/
 js/
 images/
 ...
...

První způsob s použitím modulu by mohl vypadat nějak následovně.

app/
 blog/
  controllers/
   IndexController.php
  models/
  views/
 default/
  controllers/
   IndexController.php
  models/
  views/
 galery/
  controllers/
   IndexController.php
  models/
  views/
 admin/
  controllers/
   BlogArticle.php
   BlogCategory.php
   GaleryCategory.php
   GaleryItem.php
lib/
www/
 js/
 images/
 ...
...

Tento způsob se mi moc nezamlouval zejména proto, protože smysl rozdělení aplikace do modulů vidím právě v tom, že moduly mají být jako jednotlivé samostatné části, které lze z aplikace libovolně vyndávat a zandávat. Admin modul ale přináší závislost na všech ostatních modulech, bude muset využívat modely ostatních modulů a mixuje administraci všech modulů dohromady. Proto jsem raději zvolil druhou variantu. V každém modulu vlastní admin controller.

app/
 blog/
  controllers/
   IndexController.php
   AdminController.php
  models/
  views/
 default/
  controllers/
   IndexController.php
   AdminController.php
  models/
  views/
 galery/
  controllers/
   IndexController.php
   AdminController.php
  models/
  views/
lib/
www/
 js/
 images/
 ...
...

Toto řešení jsem použil, ale vadilo mi na tom to, že můj admin controller byl příliš rozsáhlý, staral se o administraci všech věcí blogu - článků, kategorií, komentářů. Jak z toho ven? Rozdělit jeden admin controller na více controllerů ale nějakým způsobem je seskupit jako administrační controllery a to i na úrovni URL a routování, aby se dala např. jednoduchým způsobem provést autentikace.

Zend Framewok nabízí jedno řešení a to umístit controllery do jednoho společného adresáře. Pak se dá použít jako oddělovač adresářů v URL reprezentaci podtržítko, o této vlastnosti controllerů se obecně moc neví. Jak to tedy udělat? Struktura controllerů pro blog bude následující.

app/
 blog/
  controllers/
   IndexController.php
   Admin/
    EntryController.php
    CategoryController.php
  models/
  views/

Pak je třeba v URL používat podtržítkovou notaci a vystačíme s implicitní routou

:module/:controller/:action

Pokud tedy budeme chtít přidat v administraci kategorii článků bude URL vypadat takto.

/blog/admin_category/add

Tím zavoláme náš Blog_Admin_CategoryController::addAction(), který se bude hledat v souboru blog/controllers/Admin/CategoryController.php.

To je přesně to, co jsem potřeboval, jen mě tam ještě trochu vadilo to podtržítko jako oddělovač adresářů. Raději bych, aby tam bylo klasické /, url by pak vypadala mnohem lépe

/blog/admin/category/add

Upravíme tedy naši routu takto

 //blog module admin controller route
 $this->_router->addRoute('blogAdmin',
     new Zend_Controller_Router_Route('blog/admin/:controller/:action/:itemId',
     array('module' =>'blog','controller'=>'admin_index','action'=>null,'itemId'=>null))
 );

Tím zároveň nastavíme implicitní admin controller na IndexController v adresáři Admin.

Dále použijeme FrontController Plugin a routeShutdown() metodu, která se bude vykonávat až po skončení routování, v ní zjistíme, zda byla volána naše blogAdmin routa a pokud ano, tak upravíme jméno controlleru tak, že před jeho jméno přilepíme admin_. Tím pak v dispatcheru zajistíme volaní našeho správného admin controlleru.

class BlogAdminPlugin extends Zend_Controller_Plugin_Abstract
{
//==============================================================================
public function routeShutdown (Zend_Controller_Request_Abstract $request)
{
 $currentRouteName=Zend_Controller_Front::getInstance()->getRouter()
   ->getCurrentRouteName();
 
 if ($currentRouteName=='blogAdmin') {
  //if there isn't admin_ prefix in the controller name, add it
  if (strpos($request->getControllerName(),'admin_') !== 0) {
   $request->setControllerName('admin_'.$request->getControllerName());
  }
 }
}

Takže pak naše routa

/blog/admin/category/add

Zavolá se Blog_Admin_CategoryController::addAction(), který se bude hledat v souboru blog/controllers/Admin/CategoryController.php. Pokud uvedeme routu

/blog/admin

Zavolá se Blog_Admin_IndexController::indexAction(), který se bude hledat v souboru blog/controllers/Admin/IndexController.php.

Samozřejmě, že pak v bootstrapu musíme zajistit načtení našeho pluginu. Já kromě pluginu používám i action helper a v něm mám schovanou společnou funkcionalitu všech admin controllerů, ale to už není podstatné. O víkendu to snad dostanu do svn, tam si to budete moci prohlédnout.


Komentáře (7)

  1. Tomáš Fejfar - 3. 4. 2008 15:36

    Výborné !

    Už nějakou dobu hnedám použitelný zdroj informací o ZF a tvůj blog je přesně to, co hledám.

    Jen sem chtěl upozornit (i když do ZF zatim 100% nevidim, tak pardon, jestli je to blbost), že uvádíš, že se zavolá EntryController::addAction() a přitom v URL je "category", takžew by se měl volat CategoryController::addAction(), ne?

  2. koubel - 3. 4. 2008 16:05

    [1] Samozřejmě že to mám špatně, díky, opraveno

  3. koubel - 3. 4. 2008 16:25

    A ještě další chyba (kolik jich ještě dneska bude?), v názvech controllerů se samozřejmě musí objevit jak modul, tak ten adresář aby byl controller v celé aplikaci jendoznačný, takže nikoli jen CategoryController ale Blog_Admin_CategoryController, v článku jsem to opravil

  4. DREPO - 5. 9. 2008 00:14

    Prosim ta Mirin, ako v bootstrape nastavim include_path aj na modely, ked mam modularnu aplikaciu? Kcel by som totiz vyuzivat Zend_Loader a jeho registerAutoload().

  5. koubel - 7. 9. 2008 16:46

    [4] pokud máš modely rozeseté po různých adresářích, tak nejjednodušší cesta je všechny nasázet do include_path, všechno co je v include_path Zend_loader bude natahovat.

    Další možnost je asi zvolit pro jména tříd modelů nějakou konvenci a pak podědit Zend_Loader, pokud to nějak půjde (nevím).

    Dost často jsou modely společné pro všechny moduly, pak stačí jen jeden adresář pro všechny modely a ten přidat do include_path, tak jsem to měl já.

  6. DREPO - 12. 9. 2008 23:33

    koubel dik za odpoved. Ja som kcel najst riesenie, ked mam modularnu app a kcem moduly dynamicky pridavat alebo odoberat a pritom to nesmie vyzadovat zmeny v bootstrape. Takisto ukladat modely do jedneho adresara je vylucene.

    Proste k existujucej aplikacii dokopirovat napr. modul "gallery" a o nic sa nestarat. galllery by obsahoval svoje modely v sebe.

    A riesenie = Controller plugin:

    public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
    {
      set_include_path(get_include_path() . PATH_SEPARATOR
        . APP_PATH . '/application/modules/'
        . $request->getModuleName() . '/models');
    }
    Snad to pomoze aj niekomu dalsiemu.

  7. koubel - 14. 9. 2008 16:00

    [6] - no vida, pěkný

Komentáře jsou uzavřeny.