Mirin webspace

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

5. 12. 2007 - Komentáře (8) Zend Framework

Zend_Application stále žije

Tento příspěvěk by se dal nazvat také "za bootstrap štíhlejší." Jde o to, že tak, jak je v současné době Zend_Framework navržen, je nutné v bootstrap souboru index.php provést spoustu činností. Bootstrap tak velmi bobtná a hlavně se v něm hromadí globální kód, což se mi jako příznivnici OOP a zapouzdření moc nelíbí. Proto jsem se nechal inspirovat přístupem, který byl v návrhu rozšíření Zend_Application a Zend_Application_Resource. Oba návrhy byly nakonec zamítnuty, na konci článku se pokusím najít důvody proč.

V bootstapu je nutné provést celou inicializaci aplikace, jde zejména o tyto kroky:

  • nastavit a inicializovat router
  • nastavit a inicializovat front controller
  • nastavit locale a jazyk
  • inicializovat autoloader (pokud se používá)
  • nastavit Zend_Config
  • nastavit set_include_path() pokud to používáme
  • spustit vlastní odbavování požadavků (dispatch)

To je spousta činností a bootstrap se tak stává značně rozsáhlý, nepřehledný a hlavně jeho veškerý kód je v globálním prostoru, což není úplně ideální. Proto jsem se rozhodl jít cestou obalové třídy, která nám zastřeší veškerou činnost spojenou s inicializací aplikace. Bootstrap pak obsahuje pouze toto:


<?php
//set a global error reporting and logging, depends on our 
//environment
error_reporting(E_ALL);
ini_set('display_errors','Off');
ini_set('log_errors','1');

/**
 * the application class
 */
require_once ("/path-to-MyAppClass/MyApp.php");

MyApp::createApp()->run(); 
?>  

Což už je poměrně elegatní a jasně nám to říká, o co jde. Vytvoří se objekt aplikace a ta se spustí. Jak je možné si všimnout, třída implementuje návrhový vzor singleton. Vycházel jsem z toho, že spouštíme vždy jen jednu instanci celé aplikace. Tak a co třída MyApp vlastně obsahuje? Kostra vypadá nějak následovně


 /** 
  * Enable/disable debug settings
  */
 const DEBUG=true;

 /** The application XML configuration file name
  */ 
 const CONFIG_FILENAME="config.xml";

 /** directory path to the Zend Framework library
  * @var string
  */
 const ZF_DIR="/usrt/local/lib/zendframework/library";

 /** Singleton instance
  * @var MyApp
  */
 private static $_instance = null;
  
 /** application config
  * @var Zend_Config
  */
  private $_config;

 /** application router
  * @var Zend_Controller_Router_Interface
  */
 private $_router;

 /** application front controller
  * @var Zend_Controller_Front
  */
 private $_ctrl;
 
 /** Full main application directoty path.
  * @var string
  */
 private $_mainAppDir;

//==========================================
/**
 * Singleton pattern implementation makes "new" 
 * unavailable
 * @return void
 */
private function __construct()
{
}
   
//==========================================
/**
 * Singleton pattern implementation makes "clone" 
 * unavailable
 * @return void
 */
private function __clone()
{}

//===========================================
/**
 * Singleton instance accessor
 * @return MyApp
 */
public static function getInstance()
{
 if (null === self::$_instance) {
   throw new Exception("The application isn't created, call createApp() first.");
 }
 return self::$_instance;
}
 
//===========================================
/**
 * Singleton pattern implementation.
 * Only one instance for a whole system is allowed.
 * @param string $appDir application directory, if is empty
 * current directory with this script is set as the application directory
 * @return MyApp
 */
public static function createApp($appDir="")
{
 if (null === self::$_instance) {
  self::$_instance = new self();
 } else {
  throw new Exception("Application is already created");
 }  
 
 ...
 self::$_instance->_loadConfig();
 self::$_instance->_initRouter();
 self::$_instance->_initFrontController();
 ...
}   

//===========================================
/**
 * Sets a locale and language by language string.
 */
public function setLocale($localeStr=null)
{
}  

//===========================================
/**
 * Loads the main configuration from the XML config and set configuration -
 * $_config. Sets include_path for some necessary directories.
 */
private function _loadConfig()
{
}

//===========================================
/**
 * Inits application router and sets necessary routers for application's action
 * controllers.
 */
private function _initRouter()
{
}

//===========================================
/**
 * Initializes application's front controller.
 */
private function _initFrontController()
{
}

//===========================================
/**
 * Runs our application.
 * Just dispatches requests into an appropriate controller actions.
 */
public function run()
{
  ...
  ...
  //call dispatcher, dispatch requests into the controller action
 $this->_ctrl->dispatch();
}

Z výše uvedeného je asi jasné co se stane při volání v bootstrapu. Přes metodu createApp se vytvoří nová instance objektu, tato metoda zároveň načte konfiguraci, nainicializuje router a front controller, nastaví locale/jazyk.

Poté se přes metodu run dispatchne aktuální požadavek. Důležité je v pravý okamžik nastavit autoload, je to nutné udělat ještě před použitím jakékoli třídy Zend Frameworku. Nejvhodnější místo k tomu je privátní konstruktor.


private function __construct()
{
 //add Zend Framework dir to the include path
 set_include_path(get_include_path().PATH_SEPARATOR.self::ZF_DIR);
 //set timezone for date and time functions
 date_default_timezone_set('Europe/Prague');

 //register __autoload for the Zend_Loader
 require_once "Zend".DIRECTORY_SEPARATOR."Loader.php";

 Zend_Loader::registerAutoload();
}  

Třídu používám i pro nastavení locales a jazyka aplikace. Protože třída je implementována jako singleton, je dostupná globálně v celé aplikaci přes volání MyApp::getInstance().

Nabízí se otázka proč třídy Zend_Application a Zend_Application_Resource, které měly zajišťovat podobnou funcionalitu v Zend Frameworku nakonec nejsou a nebudou. Podle mne je to proto, že vytvořit takovou třídu, která by byla dostatečně obecná a přitom neobsahovala nadbytečný kód je dost složité, protože i já pro každou aplikaci implementuji jinou aplikační třídu, časem uvidím, zda se z toho vyvrbí nějaká vhodná abstrakce, ze které by se dalo vytvořit něci jako Zend_Application.

Dalším důvodem je částečná duplicita s třídou Zend_Registry, která slouží jako globální registr pro uchovánání např. jazyka, konfigurace apod. Registr používám také a to pro uložení konfigurace v Zend_Config. Jeden z důvodů je asi ten, že aplikační třída měla v každém mém projektíku jiné jméno. Nicméně žádný jiný reálný důvod tam není.

Samozřejmě že je jedno, jestli budete používat výše popsané řešení nebo ne. Někomu klidně může vyhovovat sekvenční procedurální bootstrap, pravděpodobně to bude i o něco málo rychlejší. Mě se prostě líbí ta jednoduchost vytvářených bootstrapů App->run() a tradá. Zároveň jsem se nadobro zbavil veškerého procedurálního kódu a všude mám jen třídy a objekty, tak mi to vyhovuje.

P.S.: Ještě napíšu jeden článek, bude se týkat použitého řešení pro uchovávání URL a pak hodím první verzi zdrojových kódů celého webu (jak jsem slíbil) všem k dispozici.


Komentáře (8)

  1. Techi - 12. 12. 2007 18:07

    Bootstrap je bootstrap a já jsem rád, když vidím všechny tyto činnosti najednou. Takový běžný bootstrap zabírá kolem 1-2kB takže bych to s tím zapouzdřováním nepřeháněl.

    Taky můžeš činnosti jako je router, config, locale nastavit až v pluginech a trochu tak bootstrap odlehčit

  2. Jakub Podhorský - 12. 12. 2007 22:34

    musím souhlasit s techim sice jsem taky příznivce OOP ale nic se nemá přehánět :)

  3. koubel - 13. 12. 2007 11:03

    [1] - router do pluginu asi nepůjde odsunout, protože je nutné ho nastavit před voláním front controlleru, s locale a config by to asi šlo

  4. Jakub Podhorský - 13. 12. 2007 17:36

    router de krásně přesunout do pluginu :)

  5. koubel - 14. 12. 2007 09:06

    [4] - pravda, třeba přes routeStartup(), ale jako odlehčení bootstrapu se mi to stejně moc nezdá, možná pokud je třeba nějak speciálně manipulovat s routerem na základě requestu tak možná, jinak bych asi zůstal u klasické inicializace routeru v bootstrapu

  6. Jakub Podhorský - 14. 12. 2007 16:32

    jojo přesně takhle a nebo vracet třeba router pomocí getRouter() a pak ho předat před dispatchingem do front controlleru

    já to mám takhle protože stejně musím s adresou trochu manipulovat tak jsem to rovnou přesunul do pluginu ale jinak bych to taky nechal v bootstrapu

  7. sNop - 1. 10. 2008 14:35

    Bootstrap soubor je urcen ktomu kcemu urcen je a tak se v ZF i vyuziva, tak isto zastavam nazoj, ze neni potreba vytvaret dalsi objekt.

    Jeste ktym glob. promennym v bootstrap-u, kdyz se ti nelibi, tak na konci bootstrap-u muzes pouzit unset napr.:

    unset($frontController, $view, $configuration, $dbAdapter, $registry);
    Pekne je to znazornene v nove Official Quickstart, tento pristup mi max. vyhovuje a sem snim nadmiru spokojen.

  8. koubel - 13. 7. 2009 22:24

    Tak jak to tak vypadá, Zend boys byli jiného názoru než většina komentujících a Zend_Application je od 1.8.x definitivně součástí frameworku i oficiálního quickstartu.

Komentáře jsou uzavřeny.