Implementar Haanga como sistema de plantillas para OpenCart

haanga

OpenCart es una plataforma de comercio electrónica hecha en , con un impecable, 100% MVC. Como prueba de concepto he reescrito el sistema de plantillas de OpenCart a Haanga (plantillas “Django” para PHP, über eficiente) de César Rodas.

¿ Por que Haanga ?, simplemente porque me gusta.
¿ Por qué OpenCart ?, simplemente porque me gusta como está desarrollado.

Implementación, he creado una clase TemplateEngine con el siguiente código:

  1.  class TemplateEngine {
  2.    public $template = null ;
  3.    public $default = array();
  4.    public $config = array();
  5.  
  6.    function __construct( $templateDir = null  ) {
  7.     $this->loadDefaults( $templateDir);
  8.    }
  9.    
  10.  
  11.    protected function loadDefaults( $templateDir) {
  12.     $this->default = array();
  13.      $this->config = array(
  14.     'template_dir' => (( is_null( $templateDir )) ? Settings::getInstance()->getValue('Web.root') .'' : $templateDir),
  15.     'cache_dir' =>  Settings::getInstance()->getValue('Web.root'). 'cache.templates',
  16.     'compiler' => array(  
  17.      'if_empty' => FALSE,
  18.      'autoescape' => FALSE,
  19.      'strip_whitespace' => TRUE,
  20.       'allow_exec'  => TRUE,
  21.      'global' => array( ),
  22.      'use_hash_filename' => FALSE  
  23.     )
  24.    );
  25.    }
  26.  
  27.   function loadEngine() {
  28.    // incluimos  
  29.     require_once Settings::getInstance()->getValue('Web.root') . 'lib/vendors/Haanga.php';
  30.      Haanga::configure($this->config);
  31.  
  32.  
  33.  
  34.   }
  35.     function loadTemplate( $name ) {
  36.     $this->template =   $name  ;
  37.    }
  38.     function display( $vars = array()) {
  39.     $this->loadEngine();
  40.      $vars = array_merge( $this->default, $vars);
  41.      Haanga::Load( $this->template , $vars);
  42.    
  43.    }
  44.  
  45.  }

Ahora sólo queda indicarle al que haga uso de esta clase:
en /system/engine/controller.php

Modificando los métodos render y fetch, renombrando el antiguo fetch a __fetch

  1. []
  2. protected function render($return = FALSE) {
  3.   foreach ($this->children as $child) {
  4.    $action = new Action($child);
  5.    $file   = $action->getFile();
  6.    $class  = $action->getClass();
  7.    $method = $action->getMethod();
  8.    $args   = $action->getArgs();
  9.  
  10.    if (file_exists($file)) {
  11.     require_once($file);
  12.  
  13.     $controller = new $class($this->registry);
  14.    
  15.     $controller->index();
  16.     $this->data[$controller->id] =  $controller->output;
  17.    
  18.     $this->addToModule( $controller->id, $controller->output );
  19.     } else {
  20.     exit('Error: Could not load controller ' . $child . '!');
  21.    }
  22.   }
  23.  
  24.   if ($return) {
  25.    return $this->fetch($this->template);
  26.   } else {
  27.    $this->output = $this->fetch($this->template);
  28.   }
  29.  }
  30.  
  31.   protected function addToModule( $module , $output ) {
  32.    $i = 0 ;
  33.    if ( !isset( $this->data['modules'] )) { return ;}
  34.    foreach( $this->data['modules'] as $item ) {
  35.     if ( $item['code'] == $module ) {
  36.      $this->data['modules'][$i]['output'] = $output;
  37.      return ;
  38.    
  39.     }
  40.     $i++;
  41.    }
  42.  
  43.   }
  44.  
  45.   protected function fetch($filename) {
  46.    if ( substr( $_SERVER['REQUEST_URI'], 0, 7) == '/admin/') {
  47.     return $this->__fetch( $filename);
  48.    
  49.    } else {
  50.           ob_start();
  51.        $this->templateEngine->loadTemplate( $filename );
  52.        $this->templateEngine->display( $this->data);
  53.      $content = ob_get_contents();
  54.         ob_end_clean();
  55.         return $content;
  56.         }
  57.     }
  58. protected function __fetch($filename) {
  59.   $file = DIR_TEMPLATE . $filename;
  60.       if (file_exists($file)) {
  61.    extract($this->data);
  62.     ob_start();
  63.      
  64.      require($file);
  65.      
  66.      $content = ob_get_contents();
  67.  
  68.         ob_end_clean();
  69.  
  70.         return $content;
  71.      } else {
  72.         exit('Error: Could not load template ' . $file . '!');
  73.      }
  74.  }
  75.  
  76. []

Para evitar utilizar Haanga en la administración se comprueba que la url no sea de la administración, de ser así se utiliza el motor de plantillas original.

  1. []
  2.    if ( substr( $_SERVER['REQUEST_URI'], 0, 7) == '/admin/') {
  3.     return $this->__fetch( $filename);
  4. []

Una de las particularidades de las plantillas es que hacen uso de $$variable a la hora de mostrar la salida de los módulos, y es por esto (imposible, por lo que parece, en Haanga) que he añadido un método y comprobación para que cada módulo devuelva a la plantilla su salida en un elemento “output” de la matriz y es el que mostraremos.

Así, al final, una plantilla como la de la visualización de las categorías queda en algo como los siguientes ejemplos:
/catalog/view/theme/default/common/column_right.tpl

  1. <div id="column_right">
  2.  
  3.  {% for module in modules %}
  4.  
  5.     {{ module.output }}
  6.  
  7.   {% endfor %}
  8.  
  9. </div>

/catalog/view/theme/default/product/category.tpl

  1. {% extends "layout/default.html" %}
  2. {% block content %}
  3. <div id="content">
  4.   <div class="top">
  5.     <div class="left"></div>  
  6.     <div class="right"></div>
  7.     <div class="center">
  8.       <h1>{{ heading_title }}</h1>
  9.     </div>
  10.   </div>
  11.   <div class="middle">
  12.     <table style="padding-bottom:10px;">
  13.    <tr>
  14.         {% if thumb %}
  15.         <td><img src="{{ thumb }}" alt="{{ heading_title }}" /></td>  
  16.  {% endif %}
  17.         {% if description %}
  18.       <td>{{ description }}</td>
  19.  {% endif %}
  20.    </tr>    
  21.  </table>
  22.  
  23.  {% if  !categories  &&   !products %}<div class="content">{{ text_error|default:"" }}</div>{% endif %}
  24.      
  25.     {% if categories %}
  26.  
  27.    <table>
  28.     {% for category in categories %}
  29.      
  30.    
  31.           <a href="{{ category['href'] }}"><img src="{{ category['thumb'] }}" title="{{ category['name'] }}" alt="{{ category['name'] }}" style="margin-bottom: 3px;" /></a><br />
  32.           <a href="{{ category['href'] }}">{{ category['name'] }}</a>
  33.  
  34.     </table>
  35.     {% endfor %}
  36.  
  37.  {% endif %}
  38.  
  39.  
  40.  
  41.     {% if products %}
  42.     <div class="sort">
  43.       <div class="div1">
  44.         <select name="sort" onchange="location = this.value">
  45.          {% buffer sort_order %}{{sort}}-{{order}}{% endbuffer %}
  46.  
  47.          {% for sort  in sorts %}
  48.            <option value="{{ sort['href'] }}" {% if sort_order == sort['value'] %} selected="selected"{% endif %}>{{ sort['text'] }}</option>
  49.           {% endfor %}
  50.         </select>
  51.       </div>
  52.       <div class="div2">{{ text_sort }}</div>
  53.     </div>
  54.    
  55.    
  56.    {% inline "elements/lista_productos.html" %}
  57.    
  58.      
  59.      
  60.     <div class="pagination">{{ pagination }}</div>
  61.  
  62.  {% endif %}
  63.  
  64.   </div>
  65.   <div class="bottom">
  66.     <div class="left"></div>
  67.     <div class="right"></div>
  68.     <div class="center"></div>
  69.   </div>
  70. </div>
  71. {% endblock %}

Twig, el sistema de plantillas definitivo

Twig

Siempre he sido muy receloso a la hora de utilizar un sistema de plantillas para , pero la llegada de Twig ha sido todo un acontecimiento.

Con un límpio y optimizado  Fabien Potencier, el creador de Symfony ha creado un sistema en el que las plantillas están 100% libres de código (PHP) y siguen la fabulosa sintaxis de las plantillas de  Django. El resultado es un sistema potente y optimizado, ya que genera unas versiones en php de las plantillas con un código para quitarse el sombrero.

Aun estoy arañando la superficie de esta maravilla, pero con la creación de filtros, y añadiendo nuevas funcionalidades en breve tendremos una versión remozada (internamente) de SevillaPress.