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 %}

Encuentro Viral 2

ev2

El próximo 23 de octubre, sábado, por la tarde, quien le apetezca puede pasarse por la sala de usos múltiples del Ayuntamiento de El Madroño (Sevilla) para charlar sobre las posibilidades abiertas por la Red para el cuidado de la y el ejercicio de los derechos ciudadanos.

Yo daré una charla sobre Internet y mundo rural.

Noticia en Synaptica: Encuentro Viral sobre Internet, salud y comunidad rural
Información del Evento: Encuentro Viral 2

Web Candidatura de José María Gómez Ruiz

pepegomez

He terminado una pequeña web de José María Gómez Ruiz para la candidatura a Hermano Mayor de la Soledad de San Lorenzo de Sevilla.

Web candidatura Hermano Mayor Soledad de San Lorenzo Sevilla

Es sencilla y está sobre WordPress (aunque no funciona como un blog) y con la plantilla Modernist de Rodrigo Galindez

Singleton para PHP 5.3, y parche para 5.2

singleton

Mediante esta clase abstracta de podremos crear nuestras clases simplemente extendiendo esta clase

  1. abstract class Singleton {
  2.  
  3.      protected function __construct() {
  4.      }
  5.      final public static function getInstance( $calledClassName = null ) {
  6.          static $aoInstance = array();
  7.   if ( $calledClassName == null ) {
  8.          $calledClassName = get_called_class();
  9.          
  10.          }
  11.          if (! isset ($aoInstance[$calledClassName])) {
  12.              $aoInstance[$calledClassName] = new $calledClassName();
  13.         }
  14.  
  15.          return $aoInstance[$calledClassName];
  16.      }
  17.      final private function __clone() {
  18.      }
  19.  }

Ejemplo:

  1.  
  2. class DataBase extends Singleton {
  3. []
  4. }
  5.  
  6. $db = DataBase::getInstance();

El problema que nos encontraremos en PHP 5.2.x es que no existe la función get_called_class por lo que deberemos tener esa función.

  1. if(!function_exists('get_called_class')) {
  2.     class class_tools {
  3.         static $i = 0;
  4.         static $fl = null;
  5.         static function get_called_class() {
  6.             $bt = debug_backtrace();
  7.             if(self::$fl == $bt[2]['file'].$bt[2]['line']) {
  8.                 self::$i++;
  9.             } else {
  10.                 self::$i = 0;
  11.                 self::$fl = $bt[2]['file'].$bt[2]['line'];
  12.             }
  13.  
  14.             $lines = file($bt[2]['file']);
  15.  
  16.             preg_match_all('
  17.                /([a-zA-Z0-9\_]+)::'.$bt[2]['function'].'/',
  18.                 $lines[$bt[2]['line']-1],
  19.                 $matches
  20.             );
  21.             $returnValue = $matches[1][self::$i];
  22.    // comprobamos si lo llamamos desde un call_user_func y similar
  23.             if ( empty( $returnValue ) && isset( $bt[3]['function'] ) && in_array($bt[3]['function'],array('call_user_func', 'call_user_func_array'))) {            
  24.               $returnValue = $bt[3]['args'][0][0];
  25.            
  26.             }
  27.             return $returnValue ;
  28.         }
  29.     }
  30.     function get_called_class() {
  31.         return class_tools::get_called_class();
  32.     }
  33. }

Como se puede ver utiliza la funcion debug_backtrace() para determinar a que clase estamos llamando. Con esta función ya no tendremos problemas a la hora de instanciar nuestros “Singleton” en php.

Ejemplos de uso que funcionan perfectamente en PHP 5.3, y ahora en PHP 5.2

  1. $objeto = DataBase::getInstance() ;
  2. []
  3. $miClase = 'DataBase';
  4. $objeto = call_user_func( array( $miClase, 'getInstance'));

PHP-FPM sobre Ubuntu 8.10

fastcgi-fpm

Tomada la decisión de sólo desarrollar, en el caso de , para la familia 5.3 o superior. En gran medidad por todas las mejoras y cambios que aporta. Para instalarlo y ser procesado desde un servidor web , este debe ser compilado como FastCGI.

La elección de frente a la usual de php5 como fastcgi y un wrapper, es simple: Además de eliminarl el wrapper, php-fpm nos aporta muchas ventajas

Partiendo de que para esta distribución, desde los páquetes, está con la familia 5.2, el pasar a la 5.3 significa que:

a) Debemos compilarla desde las fuentes

b) Debemos instalar un nuevo repositorio donde si exista la versión 5.3.x para .

Existe un repositorio con esta familia de php, repositorio que deberemos añadir a nuestro archivo /etc/apt/sources.list

deb http://php53.dotdeb.org stable all

Una vez esto sólo deberemos actualizar y (si no teníamos instalado anteriormente ) el php5 instalar los nuevos paquetes

  1. sudo apt-get update

En nuestro caso instalaremos el php5-fqm: Un binario de php que se ejecuta como un servicio de FastCGI, facilmente configurable y con procesos separados (si así lo queremos) por usuario, grupo, …

  1. sudo apt-get install php5-fqm

Problemas:

Si queremos compilar módulos de php con pecl, necesitaremos instalar el php5-dev. La instalación de este paquete nos dará error por el paquete libtool, por lo que tendremos que hacer una instalación “peculiar”

  1. #instalamos el libtool
  2. sudo apt-get install libtool
  3. #descargamos el paquete de php5-dev
  4. cd /tmp
  5. wget http://php53.dotdeb.org/dists/stable/php5/binary-i386/php5-dev_5.3.2-0.dotdeb.2_i386.deb
  6. sudo dpkg –install –ignore-depends=libtool   php5-dev_5.3.2-0.dotdeb.2_i386.deb

Con esto tenemos el PHP5-Dev (de la 5.3.x). Al tratar de instalar cualquier módulo nos dará problemas del tipo:

  1. root@miservidor:/tmp# pecl install mongo
  2. downloading mongo-1.0.7.tgz …
  3. Starting to download mongo-1.0.7.tgz (53,750 bytes)
  4. ………….done: 53,750 bytes
  5. 16 source files, building
  6. running: phpize
  7. Configuring for:
  8. PHP Api Version:         20090626
  9. Zend Module Api No:      20090626
  10. Zend Extension Api No:   220090626
  11. configure.in:150: warning: LTOPTIONS_VERSION is m4_require'd but not m4_defun'd
  12. aclocal.m4:2943: LT_INIT is expanded from…
  13. aclocal.m4:2978: AC_PROG_LIBTOOL is expanded from…
  14. configure.in:150: the top level
  15. configure.in:150: warning: LTSUGAR_VERSION is m4_require'd but not m4_defun'd
  16. configure.in:150: warning: LTVERSION_VERSION is m4_require'd but not m4_defun'd
  17. configure.in:150: warning: LTOBSOLETE_VERSION is m4_require'd but not m4_defun'd
  18. configure:4584: error: possibly undefined macro: m4_ifval
  19.       If this token and others are legitimate, please use m4_pattern_allow.
  20.       See the Autoconf documentation.
  21. configure:7363: error: possibly undefined macro: _LT_SET_OPTIONS
  22. configure:7363: error: possibly undefined macro: LT_INIT
  23. ERROR: `phpize' failed

Esto se debe a la conf del libtool instalado (rutas y contenido), por lo que deberemos corregirlo:

  1. sudo ln -s /usr/share/libtool/config/ltmain.sh  /usr/share/libtool/ltmain.sh
  2. sudo ln -s /usr/share/aclocal/libtool.m4 /usr/share/libtool/libtool.m4
  3. cd /usr/share/aclocal
  4. cp libtool.m4  libtool.m4.original
  5. cat > bsolete.m4 ltoptions.m4 ltsugar.m4 ltversion.m4 >> libtool.m4

A partir de este momento ya podemos instalar cualquier módulo con pecl:

  1. pecl install mongo
  2. downloading mongo-1.0.7.tgz …
  3. Starting to download mongo-1.0.7.tgz (53,750 bytes)
  4. ………….done: 53,750 bytes
  5. 16 source files, building
  6. running: phpize
  7. Configuring for:
  8. PHP Api Version:         20090626
  9. Zend Module Api No:      20090626
  10. Zend Extension Api No:   220090626
  11. []
  12. uild process completed successfully
  13. Installing '/usr/lib/php5/20090626+lfs/mongo.so'
  14. install ok: channel://pecl.php.net/mongo-1.0.7
  15. configuration option "php_ini" is not set to php.ini location
  16. You should add "extension=mongo.so" to php.ini