Zend Framework - Smarty + Zend_View + Zend_Layout

Dla osób, które przyzwyczajone są do Smartów lub po prostu nie lubią znaczników phpowch w widokach, istnieje kilka sposobów na zastąpienie domyślnych phtmli na tple. W poprzednim wpisie pokazałem prostą implementację struktury modułowej - teraz do tej struktury załaduję Smarty. W założeniach chce by każdy moduł dysponował swoim, odrębnym layoutem. W przypadku małych aplikacji wystarczą dwa proste moduły: default po stronie klienta i admin dla administratora, gdzie każda moduł dysponuje innym, odrębnym szablonem strony. Dodatkowo jeśli potrzebowalibyśmy np: modułu bloga, to ten jako 3 dołączylibyśmy do struktury katalogów.

Przykładowa struktura aplikacji - 3 moduły:


index.php
application/
    assistant/
    modules/
        default/
            controllers/
                IndexController.php
            models/
            views/
                layout.tpl
                index/
                    action.tpl
        blog/
            controllers/
                IndexController.php
            models/
            views/
                layout.tpl
                index/
                    action.tpl
        admin/
            controllers/
                IndexController.php
            models/
            views/
                layout.tpl
                index/
                    action.tpl
library/
    Zend/
public/
    images/
    js/
    css/
cache/
    templates_c
    smarty_cache

By móc jednak korzystać z takiej struktury niezbędne są zmiany w kodzie. Należy zastąpić domyślny widok Zenda - Zend_View - naszym widokiem - np. Smarty_View, a także nanieść drobne poprawki do Zend_Layout (wystarczy przeciążyć metody).

Aby tego dokonać utworzymy 2 nowe katalogi: “core” (dla naszych klas) i “smarty” (dla biblioteki Smarty, którÄ… można pobrać ze strony projektu), które umieÅ›cimy w “library”:


library/
    Zend/
    Core/
        Controller/
        View/
    Smarty/

Chcąc zwiększyć możliwości i funkcjonalność naszej aplikacji stworzymy własny kontroler frontowy. Umieścimy w nim wspólne dla wszystkich kontrolerów elementy, podmienimy domyślny widok i utworzymy Layout.


<?php

abstract class Core_Controller_Action extends Zend_Controller_Action {

	protected $config;

        public function init() {

        }

}

?>

Do podmiany domyślnego widoku będziemy potrzebowali własnej klasy widoku implementującej Zend_View_Interface. Wzór klasy znajduje się w dokumentacji Zenda. Kod pokazany poniżej zawiera drobne, niezbędne poprawki. Plik należy umieścić w library/Core/View/ pod nazwą Smarty.php :


?php
require_once 'Zend/View/Interface.php';
require_once 'Smarty/Smarty.class.php';

class Core_View_Smarty implements Zend_View_Interface
{
   /**
    * Smarty object
    * @var Smarty
    */
   protected $_smarty;

   /**
    * Constructor
    *
    * @param string $tmplPath
    * @param array $extraParams
    * @return void
    */
   public function __construct($tmplPath = null, $extraParams = array())
   {
      $this->_smarty = new Smarty;

      if (null !== $tmplPath) {
         $this->setScriptPath($tmplPath);
      }

      foreach ($extraParams as $key => $value) {
         $this->_smarty->$key = $value;
      }
   }

   /**
    * Return the template engine object
    *
    * @return Smarty
    */
   public function getEngine()
   {
      return $this->_smarty;
   }

   /**
    * Set the path to the templates
    *
    * @param string $path The directory to set as the path.
    * @return void
    */
   public function setScriptPath($path)
   {
      if (is_readable($path)) {
         $this->_smarty->template_dir = $path;
         return;
      }
      throw new Exception('Invalid path provided :' . $path);
   }

   /**
    * Set the path to the copile directory
    *
    * @param string $path The directory to set as the path.
    * @return void

    */

   public function setCompilePath($path)
   {
      if (is_readable($path)) {
         $this->_smarty->compile_dir   = $path;
         return;
      }
      throw new Exception('Invalid path provided :' . $path);
   }

   /**
    * Set the path to the smarty cache directory
    *
    * @param string $path The directory to set as the path.
    * @return void
    */
   public function setCachePath($path)
   {
      if (is_readable($path)) {
         $this->_smarty->cache_dir   = $path;
         return;
      }
      throw new Exception('Invalid path provided :' . $path);
   }

   /**
    * Set the path to the smarty configs directory
    *
    * @param string $path The directory to set as the path.
    * @return void
    */
   public function setConfigPath($path)
   {
      if (is_readable($path)) {
         $this->_smarty->config_dir   = $path;
         return;
      }
      throw new Exception('Invalid path provided :' . $path);
   }

   /**
    * Retrieve the current template directory
    *
    * @return string
    */
   public function getScriptPaths()
   {
      return array($this->_smarty->template_dir);
   }

   /**
    * Alias for setScriptPath
    *
    * @param string $path
    * @param string $prefix Unused
    * @return void
    */
   public function setBasePath($path, $prefix = 'Zend_View')
   {
      return $this->setScriptPath($path);
   }

   /**
    * Alias for setScriptPath
    *
    * @param string $path
    * @param string $prefix Unused
    * @return void
    */
   public function addBasePath($path, $prefix = 'Zend_View')
   {
      return $this->setScriptPath($path);
   }

   /**
    * Assign a variable to the template
    *
    * @param string $key The variable name.
    * @param mixed $val The variable value.
    * @return void
    */
   public function __set($key, $val)
   {
      $this->_smarty->assign($key, $val);
   }

   /**
    * Retrieve an assigned variable
    *
    * @param string $key The variable name.
    * @return mixed The variable value.
    */
   public function __get($key)
   {
      return $this->_smarty->get_template_vars($key);
   }

   /**
    * Allows testing with empty() and isset() to work
    *
    * @param string $key
    * @return boolean
    */
   public function __isset($key)
   {
      return (null !== $this->_smarty->get_template_vars($key));
   }

   /**
    * Allows unset() on object properties to work
    *
    * @param string $key
    * @return void
    */
   public function __unset($key)
   {
      $this->_smarty->clear_assign($key);
   }

   /**
    * Assign variables to the template
    *
    * Allows setting a specific key to the specified value, OR passing an array
    * of key => value pairs to set en masse.
    *
    * @see __set()
    * @param string|array $spec The assignment strategy to use (key or array of key
    * => value pairs)
    * @param mixed $value (Optional) If assigning a named variable, use this
    * as the value.
    * @return void
    */
   public function assign($spec, $value = null)
   {
      if (is_array($spec)) {
         $this->_smarty->assign($spec);
         return;
      }

      $this->_smarty->assign($spec, $value);
   }

   /**
    * Clear all assigned variables
    *
    * Clears all variables assigned to Zend_View either via {@link assign()} or
    * property overloading ({@link __get()}/{@link __set()}).
    *
    * @return void
    */
   public function clearVars()
   {
      $this->_smarty->clear_all_assign();
   }

   /**
    * Processes a template and returns the output.
    *
    * @param string $name The template to process.
    * @return string The output.
    */
   public function render($name)
   {
      return $this->_smarty->fetch($name);
   }
}

?>

Abyśmy mogli dodatkowo dysponować osobnym szablonem widoku, w każdym module o takiej samej nazwie (layout.tpl), należy rozszerzyć klasę Zend_Layout i przeciążyc odpowiednie metody (Operacja ta jest niezbędna w momencie, gdy chcemy bo framework sam wywoływał odpowiedni layout w zależności od modułu, w tym przypadku będzie to layout.tpl, znajdujący się w każdym module w katalogu view. Zend_Layout domyślnie renderuje layout na podstawie jego nazwy, poprawiona wersja będzie renderowała szablon na podstawie jego kompletnej ścieżki). Następujący kod należy umieścić w pliku Layout.php w library/Core/ :


<?php

class Core_Layout extends Zend_Layout {

	public function __construct($options = null, $initMvc = false){

		return parent::__construct($options, $initMvc);
	}

	public static function startMvc($options = null)
    {
        if (null === self::$_mvcInstance) {
            self::$_mvcInstance = new self($options, true);
        } else {
            self::$_mvcInstance->setOptions($options);
        }

        return self::$_mvcInstance;
    }

    public function render($name = null)
    {
        if (null === $name) {
            $name = $this->getLayout();
        }

        if ($this->inflectorEnabled() && (null !== ($inflector = $this->getInflector())))
        {
            $name = $this->_inflector->filter(array('script' => $name));
        }

        $view = $this->getView();

        if (null !== ($path = $this->getViewScriptPath())) {
            if (method_exists($view, 'addScriptPath')) {
                $view->addScriptPath($path);
            } else {
                $view->setScriptPath($path);
            }
        } elseif (null !== ($path = $this->getViewBasePath())) {
            $view->addBasePath($path, $this->_viewBasePrefix);
        }
		/* renderuje layout na podstawie jego scieżki */
        return $view->render($this->getLayoutPath() . $name);
    }

}

?>

(Być może są prostsze sposoby o których nie wiem)

Teraz należy zebrać to wszystko do kupy i odpowiednią konfigurację zawrzeć w metodzie init() naszego nowego Front kontrolera (metoda init jest wywoływana na samym początku procesu, nim zostanie wykonana jakakolwiek akcja kontrolera):


<?php

abstract class Core_Controller_Action extends Zend_Controller_Action {

	protected $config;

        public function init() {

		$this->config = new Zend_Config_Ini('application/config.ini', null);

	        /* Zapisanie konfiguracji w rejestrze */
	        Zend_Registry::set('config',$config);

		/* nasz nowy widok Smarty View */
		$view = new Core_View_Smarty();

                /* ustawiamy niezbędne ścieżki */
		$view->setCompilePath('./cache/templates_c/');
		$view->setCachePath('./cache/cache_smarty/');

		$this->view = $view;

		/* viewRenderer */
		$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');

                /*ustawiamy ścieżki dla widoków dla każdego wywoływanego modułu */
		$viewRenderer
			->setView($view)
			->setViewScriptPathSpec(getcwd() . '/application/modules/:module/views/:controller/:action.:suffix')
			->setViewSuffix('tpl');

		/* konfiguracja Layoutu */
		$options = array(
				'viewSuffix' => 'tpl',
				'layoutPath' => getcwd() . '/application/modules/' . $this->_request->getModuleName() . '/views/',
                );

                /* utworzenie obiektu Layoutu */
		$this->layout = Core_Layout::startMvc($options);

                /*przekazanie Layoutu do szablonu */
		$view->assign( 'layout', $this->layout );
		$view->assign( 'location', $this->config->globals->location);
		}

}

?>

Aby wszystko działało poprawnie każdy z naszych kontrolerów musi teraz dziedziczyć po Core_Controller_Action zamiast po Zend_Controller_Action. Uzyskaliśmy dodatkowo prosty dostęp do konfiguracji ($this->config).

Przykład (plik: IndexController.php)


<?php

class IndexController extends Core_Controller_Action
{
	public function indexAction(){

                /* konfiguracja aplikacji */
		print_r($this->config->toArray());

		$this->view->hello = "Hello World";
	}

}

?>

Teraz, by do szablonu konkretnego modułu, zwracany był odpowiedni widok z akcji, należy w nim umieścić następujący kod (plik: layout.tpl):


{$layout->content}

Wywołanie na przykład adresu: www.naszadomena.pl/admin/index/test spowoduje wywołanie IndexControllera z modułu admin, wyrenderowanie layoutu admina wraz z widokiem zwróconym przez akcje test.

W przyszłości postarm się przedstawić bardziej zaawansowane wykorzystanie obiektu Layout dla definiowania takich elementów jak nagłówek, stopka czy menu.

Aktualną strukturęnaszej aplikacji przedstawia poniższy schemat:


index.php
application/
    assistant/
    modules/
        default/
            controllers/
                IndexController.php
            models/
            views/
                layout.tpl
                index/
                    action.tpl
        blog/
            controllers/
                IndexController.php
            models/
            views/
                layout.tpl
                index/
                    action.tpl
        admin/
            controllers/
                IndexController.php
            models/
            views/
                layout.tpl
                index/
                    action.tpl
library/
    Zend/
    Core/
        Layout.php
        Controller/
            Action.php
        View/
            Smarty.php
    Smarty/
public/
    images/
    js/
    css/
cache/
    templates_c
    smarty_cache

Dodatkowo powinniśmy wyposażyć naszą aplikację w ErrorController, o czym szerzej napiszę w następnym wpisie.

5 Responses to “Zend Framework - Smarty + Zend_View + Zend_Layout”


  1. 1 Paweł

    Bardo fajne teksty, apopro powyższego to korzystanie ze Smarty wydaje mi się trochę przyrostem formy nad treścią. Zwykłe wykorzystanie PHP w szablonach wydaje mi się przyjemniejsze. Ale to oczywiście kwestia gustu.

  2. 2 admin

    Smarty znacznie poprawiają czytelność kodu. Chodzi mi tu głównie o webmasterów, w przypadku niektórych projektów wykorzystanie Smartów w widokach było koniecznością. Oczywiście wszystko ma swoje granice. W niewielkich projektach, o małym stopniu skomplikowania szablonów - sprawdzają się świetnie. Jednak gdy przychodzi do bardziej ambitnych zadań - Smarty po prostu utrudniają prace (oczywiście można kombinować, rejestrować własne funkcje czy modyfikatory, tylko pytanie czy naprawdę warto).

  3. 3 Jamnik

    Szablony to już taki standard w aplikacjach opartych na php. Na pewno nie zrezygnował bym z nich.Ale pytanie brzmi czy rzeczywiście Smarty są najlepszym rozwiązaniem w aplikacji opartej na Zend Frameworku? Wg mnie dostosowywać Smarty do nowych funkcjonalności wprowadzanych przez Framework mija się z celem. Dlatego coraz bardziej przemawia do mnie Zend_View.

  4. 4 Jacek

    dzięki za podsunięcie rady z tym Layoutem, ale mam pytanie o treść configa (application/config.ini)

  5. 5 Kamil Adryjanek

    Nie wiem czy dokładnie chodzi Ci o tego configa. To jest config z aplikacji już z podpiętym Doctrine:

    ; Podstawowe dane konfiguracyjne
    [globals]
    location = “http://xxx.xx”

    [meta]
    title = “title”
    description = “description”
    keywords = “keywords”

    ;trasy routingu
    [routes]

    ;trasa routingu dla strony głównej
    default.route = “:controller/:action/*”
    default.defaults.errorLayout = “default”
    default.defaults.module = “default”
    default.defaults.controller = “index”
    default.defaults.action = “index”
    default.defaults.web = “”

    ;trasa routingu dla panelu administracyjnego
    admin.route = “admin/:controller/:action/*”
    admin.defaults.errorLayout = “admin”
    admin.defaults.module = “admin”
    admin.defaults.controller = “index”
    admin.defaults.action = “index”

    ;bazy danych
    [database]

    name = “db-name”
    prefix = “”
    type = “mysql”
    dbname = “db=name”
    host = “localhost”
    username = “user”
    password = “pass”
    charset = “utf8″

Leave a Reply

PodglÄ…d komentarza:




About me:

  • PHP programmer
  • Symfony developer
  • Zend framework developer

Categories: