Clase que permite transacciones anidadas con PDO

PDO, acrónimo de PHP Data Objects es un interfaz para acceder a bases de datos de PHP. A diferencia de las extensiones mysql y mysqli, que son exclusivas para MySQL, PDO puede trabajar con diferentes sistemas gestores de bases de datos, siempre y cuando haya driver para ella.

Las transacciones sirven para garantizar la integridad referencial de los datos. Una transacción está formada por varias órdenes SQL y bien se ejecutan todas las consultas en bloque o si alguna falla se vuelve al estado inicial antes de empezar la transacción, sin ejecutarse ninguna de ellas. Las transacciones deben cumplir con las propiedades ACID: Atomicidad, Consistencia, Isolation (aislamiento) y Durabilidad. (Nada que ver con el subgénero de música electrónica de finales de los 80 🙂 )

Las transacciones se anidan cuando existiendo una transacción en curso, se inicia otra. Esto por ejemplo sucede cuando desde un método donde se ha iniciado una transacción, para reciclar código (una de las virtudes de la programación orientada a objetos) se llama a otro método que inicia otra transacción.

Transacciones anidadas

Todo fue bien hasta el final y se ejecutan en bloque todas las transacciones (COMMIT)

Transacciones anidadas

¡Ups, algo fallo cuando ya casi finalizaba! ¡Déjalo todo como estaba! (ROLLBACK)

Desgraciadamente, desarrollando en LAMP, nada más iniciarse la segunda transacción se producirá un error fatal. MySQL no soporta las transacciones anidadas. En su documentación afirma que después de ejecutarse un BEGIN TRANSACTION ciertas órdenes producirán un COMMIT, entre ellas BEGIN TRANSACTION. Tampoco las soporta PostgreSQL. Una solución parcial que ambos sistemas incorporan son los SAVE POINTS.

A continuación viene el esquema de una clase que mediante los SAVE POINTS y controlando el número de transacciones anidadas mediante la propiedad transactionDepth, consigue algo parecido a anidar transacciones y por lo tanto evita el error fatal antes mencionado.

Si tu clase extiende la clase PDO, el método execute puede ser reemplazado llamando simplemente a $this->exec() Como digo, esta clase es un esquema para entender la idea, no está pensada para funcionar directamente sino que el programador interesado en ella deberá adaptarla.

class Db { 
  static private $instance = null; 
  private $connection = null;
  protected $transactionDepth = 0; 
 
  private function __construct() { 
  }
  
  private function _connect() { 
     if ($this->connection === null) {
         try {
            /* Código para conectarse a la BD */
         } catch (PDOException $e) {
            echo "error pdo: ";
            echo $e->getMessage();
         }
      }

      return $this;
   }
   
   /* Nada que clonar en el patrón de diseño Singleton */
   private function __clone()
   {
     
   }

   static public function getInstance()
   {
      if (is_null(self::$instance)) {
         self::$instance = new self();
      }

      return self::$instance;
   }

   static public function closeConnection()
   {
      if (!self::$instance === null) {
         self::$instance = null;
      }
      if (isset(self::$connection)) {
         unset(self::$connection);
      }
   }

   public function getConnection()
   {
      $this->_connect();

      return $this->connection;
   }

   protected function prepare($query, $params = array())
   {
      $stmt = $this->getConnection()->prepare($query);
      if (is_array($params)) {
         foreach ($params as $param => $value) {
            if (is_bool($value)) {
               $type = PDO::PARAM_BOOL;
            } elseif ($value === null) {
               $type = PDO::PARAM_NULL;
            } elseif (is_integer($value)) {
               $type = PDO::PARAM_INT;
            } else {
               $type = PDO::PARAM_STR;
            }
            $stmt->bindValue(":$param", $value, $type);
         }
      }

      return $stmt;
   }

   public function execute($sql, $params = array())
   {
      $stmt = $this->prepare($sql, $params);
      $stmt->execute();
      $stmt->closeCursor();

      return $stmt->rowCount();
   }

   public function begin()
   {
      if ($this->transactionDepth == 0) {
         $this->getConnection()->beginTransaction();
      }else{
         $this->execute("SAVEPOINT LEVEL{$this->transactionDepth}");
      }
      $this->transactionDepth++;
   }

   public function commit()
   {
      $this->transactionDepth--;
      if ($this->transactionDepth == 0) {
         return $this->getConnection()->commit();
      }else{
         return $this->execute("RELEASE SAVEPOINT LEVEL{$this->transactionDepth}");
      }
   }

   public function rollback()
   {
      if ($this->transactionDepth == 0) {
         throw new PDOException("Ninguna transacción en curso para retroceder");
      }
      $this->transactionDepth--;
      if ($this->transactionDepth == 0) {
         return $this->getConnection()->rollback();
      }else{
         return $this->execute("ROLLBACK TO SAVEPOINT LEVEL{$this->transactionDepth}");
      }
   }
}

El código también está disponible en Github.

En PHP un sistema de plantillas es prescindible

Si bien es cierto que PHP hace muchos años que dejó de ser un sistema de plantillas tampoco me parece que necesite implementar un sistema de plantillas de forma imperiosa. En su origen, PHP era un sencillo lenguaje, cuyo intérprete programó en C Rasmus Lerdorf, destinado a implementar su página personal, de ahí el acrónimo Personal Home Page. Mucho ha llovido desde entonces y hace tiempo que PHP es un lenguaje orientado a objetos bastante potente con muchas librerías a su disposición. A pesar de ello, un motor de plantillas me parece una sobrecarga innecesaria y mucho menos me convence el razonamiento de querer dotar al lenguaje de una especie de aura de seriedad. En casos concretos hace más bien que mal, pero hay 2 razones por las que en muchos casos me parece prescindible.

Primera: Un sistema de templates añade una sobrecarga no despreciable. El entorno más frecuente para PHP son los servidores web compartidos que alojan diversas webs. Los costes de alojamiento son reducidos por la poca carga que supone PHP, tanto en tiempo de CPU como RAM consumida. Poner en esos servidores mastodontes como Symfony tiene su peaje. El patrón MVC es conveniente, la sobrecarga no, y la V de vista no implica usar plantillas. Más sobre esto en mi segunda razón.

Hace ya años que PHP es usado también en grandes aplicaciones web, con miles de usuarios conectados simultáneamente y cuyo alojamiento no alcanza en un sólo servidor sino que se requiere de una server farm. Trabajé en una empresa así y recuerdo que la competencia de dicha empresa había apostado por Java, un lenguaje más «serio» (más empleado a nivel corporativo). El gasto en servidores de esa empresa multiplicaba en varias veces el de la nuestra y cuando se llega a cierto volumen los costes dejan de ser despreciables y empiezan a marcar diferencias. Que PHP, a diferencia de Java, no necesite ingentes recursos para declarar un simple entero es una poderosa razón por la que barrió a Java de la web.

Segunda: Algunos argumentan que PHP es demasiado «parlanchín» para ser empleado en la vista pero no estoy de acuerdo. Puede (o no) que esto:

{foreach $foo as $bar}

Sea mejor que esto:

<?php foreach($foo as $bar){ ?>

Pero es idéntico a los «short tags» que PHP sigue permitiendo. Además, PHP dispone de una sintaxis alternativa para las estructuras de control:

<? foreach($foo as $bar): ?>

Esto también es objeto de crítica:

<?php echo htmlspecialchars($var, ENT_QUOTES, 'UTF-8') ?>

¿Por qué no usar esto?

<?=htmlspecialchars($var, ENT_QUOTES, 'UTF-8') ?>

Para que nadie sufra del síndrome metacarpiano podemos encapsular en algún helper esa función, como hace CakePHP:

<?=h($var) ?>

La función sería algo así:

function h($text)
{
return htmlspecialchars($text, ENT_COMPAT | ENT_HTML5, 'UTF-8');
}

Así mismo se pueden crear fácilmente otras funciones para agilizar la programación de las vistas.

También se argumenta que diseñadores y maquetadores no tienen por qué saber acerca de temas de seguridad como los ataques XSS y CSRF y que para que ellos puedan programar las vistas con seguridad debe implementarse un sistema de plantillas. En estos casos sí sería necesario usarlo y sólo añadiría lo siguiente: he conocido muchos diseñadores y no he visto ninguno interesado en programar en el lado servidor. Iría más lejos: no he conocido ninguno interesado en programar. A lo sumo, sí en aprender Flash en sus años buenos. Frecuentemente, ni tan siquiera Javascript, a pesar de incluir ficheros en sus diseños. Obviamente les interesan las funcionalidades que aporta pero en general no están interesados en saber cómo funciona. En la industria que yo conozco, programar las vistas acaba siendo trabajo también del programador y éste sí conoce (o debería) las implicaciones de seguridad. Si en la empresa los diseñadores sí están por la causa sí sería conveniente usar un sistema de templates.

Otras casos en los que puede ser interesante usarlo es cuando se requiere una sandbox con un susbset de las funciones propias y/o de PHP. Por ejemplo si el usuario desde el back end ha de poder acceder a arrays con datos y a ciertas funciones para transformar y operar esos datos. Para estos casos hay sistemas de templates como Twig que proporcionan una sandbox y sería razonable usarlos en vez de reinventar la rueda programando uno propio.

A pesar de no ser necesario un sistema de plantillas en la mayoría de aplicaciones web, ya consiguen hacer negocio con ellos. ¿Necesidad real o creada?

Exportar datos a Excel con PHP

Cuando queremos que el usuario pueda exportar los datos desde una aplicación web para poder abrirlos como una hoja de cálculo lo más recomendable es hacerlo en formato CSV (Comma-Separted Values) Ahora bien, hay casos en que el cliente insiste en que desea que se le abra inmediatamente su Microsoft Excel al hacer click sobre el link o botón. En estos casos, cuando generamos el fichero «al vuelo», la cabecera debe ser:

header('Content-Type: application/vnd.ms-excel');

En vez de:

header('Content-Type: text/csv'; charset=utf-8);

Si los datos contienen caracteres más allá de los primeros 128 ASCII, por ejemplo acentos, al abrirse Excel veremos que estos caracteres no se visualizan correctamente. ¿Por qué? Aunque UTF-8 es de lejos la codificación más usada en la web, y lo más probable es que tu aplicación trabajé con ella, Microsoft Excel sorprendentemente no trabaja con este formato. Los de Redmond pueden haber perdido parte de su poder pero no su idiosincrasia. ¿Cómo arreglar este desaguisado? Pasando los datos a UTF-16LE (Lower Endian) e indicando la codificación en la cabecera, pues con esta codificación sí trabajan. Para lograrlo, PHP dispone de una función para convertir codificaciones: mb_convert_encoding()

El siguiente código de ejemplo deshace el entuerto. En $participations
tenemos los datos a exportar, es un array. En este caso concreto, el código pertenece a una vista y por lo tanto el array ha llegado desde un controlador. El array $header contiene el nombre de cada columna para la hoja de cálculo y $fields contiene los índices de $participations, que coinciden con los nombres de los campos en la base de datos.

<?php

$participations = $render['main']['participations'];
$header = ['NOMBRE1', 'NOMBRE2', 'NOMBRE3', 'NOMBRE4', 'NOMBRE5'];
$fields = ['campo1', 'campo2', 'campo3', 'campo4', 'campo5'];

header("Content-Type: application/vnd.ms-excel; charset=UTF-16LE");
header('Content-Disposition: attachment; filename=participantes_' . makeUrlPhrase($render['main']['bla']['bla']) . '.xls');
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private", false);

$result = implode("\t", $header);
$result .= "\r\n";
if (!empty($participations)):
 foreach ($participations as $part):
  foreach ($fields as $iField):
   $result .= $part[$iField] . "\t";
  endforeach;
  $result .= "\r\n";
 endforeach;
endif;

echo mb_convert_encoding($result, 'UTF-16LE', 'UTF-8');

Como está haciendo servir el tabulador \t para indicar a Excel separación entre campos y el salto de línea \r\n como separación de registros, si creemos que en los datos puede existir alguno de estos caracteres especiales, deberemos escaparlos con la función preg_replace() De lo contrario la hoja no se importará correctamente.

Excel, fuente inagotable de virtudes, tratará campos como fechas, números de teléfono o códigos postales y los procesará para dejarlos inútiles. Por ejemplo, los códigos postales de Barcelona empiezan por cero: 08080. Excel tratará ese dato como un número entero y lo dejará en 8080. Para evitarlo hay que entrecomillar estos datos y una vez más la función preg_replace() acude a nuestro rescate:

if(preg_match("/^0/", $result) || preg_match("/^\+?\d{8,}$/", $result) || preg_match("/^\d{4}.\d{1,2}.\d{1,2}/", $result)) {
  $result = "'$result";
}

Con todo esto conseguiremos que al hacer click el usuario sobre un link, se descargará un fichero que se abrirá automáticamente en Excel y en el que los datos aparecerán correctamente.

En el caso de necesitar más funcionalidades, como por ejemplo añadir gráficas o imágenes a la hoja de cálculo, acabaremos antes si usamos una librería como PhpSpreadsheet en vez de usar nuestro propio código. En caso contrario, si se trata de una mera exportación de datos, implementar la solución con nuestro propio código, teniendo en cuenta lo aquí explicado, será la forma más productiva de proceder.

Cómo instalar XCache en un servidor compartido

La semana pasada quise instalar en un servidor compartido (Hostmonster para más señas) alguno de los diferentes sistemas de caché que existen para PHP. El único que ofrecen es un paquete de PEAR llamado “Cache” pero necesitaba instalar Apc, Xcache, MemCache o Redis. Me puse en contacto con su departamento técnico para consultar si era posible y la respuesta fue un no. Como cuando instalamos Subversion tampoco fueron ni muy optimistas ni proactivos, no hice demasiado caso a su respuesta y me lancé a intentarlo. Como Apc parecía que iba a ser problemático pues por lo que leí necesitaba ser root, lo intenté con Xcache. En primer lugar, en el directorio “home” de mi usuario en Hostmonster creé los directorios y bajé la versión de xCache para la versión de PHP que corre el servidor:

  1. mkdir modules xcache
  2. wget http://xcache.lighttpd.net/pub/Releases/1.3.2/xcache-1.3.2.tar.gz

Después descomprimí el módulo y lo compilé para la versión de PHP del servidor:

  1. tar -zxf xcache-1.3.2.tar.gz
  2. cd xcache-1.3.2
  3. phpize
  4. ./configure –enable-xcache
  5. make

Compruebo que esté bien:

  1. make test

Y ya sólo queda mover el módulo recién compilado a su directorio de destino:

  1. cd modules
  2. mv xcache.so /home2/usuario/modules

Ahora debemos editar php.ini para que PHP empiece a trabajar con Xcache. Añadí las siguientes líneas:

  1. zend_extension = /home2/usuario/modules/xcache.so
  2. zend_extension_ts = /home2/usuario/modules/xcache.so
  3. xcache.shm_scheme = «mmap»
  4. xcache.size = 32M
  5. xcache.count = 8
  6. xcache.slots = 8K
  7. xcache.ttl = 0
  8. xcache.gc_interval = 0
  9. xcache.var_size = 16M
  10. xcache.var_count = 1
  11. xcache.var_slots = 8K
  12. xcache.var_ttl = 0
  13. xcache.var_maxttl = 0
  14. xcache.var_gc_interval = 300
  15. xcache.test = Off
  16. xcache.readonly_protection = Off
  17. xcache.mmap_path = «/dev/zero»
  18. xcache.coredump_directory = «»
  19. xcache.cacher = On
  20. xcache.stat = On
  21. xcache.optimizer = Off
  22. xcache.coverager = Off
  23. xcache.coveragedump_directory = «»

También le podemos añadir un usuario y contraseña, ésta última debe ir “hashed” con md5 según explica la documentación:

  1. xcache.admin.user = «nombre_del_usuario»
  2. xcache.admin.pass = «4c882dcb24bcb1bc225391a602feca7c»

Para que la configuración tenga efecto deberíamos reiniciar los procesos fcgiphp5 pero eso no es siempre posible en un servidor compartido, así que no me quedó otra que esperar a que ello sucediera. Se pueden ver los procesos de la siguiente forma:

  1. ps aux | grep fcgiphp5

A continuación obtenemos la id del proceso y mediante kill mandamos la señal para matarlo. En todo caso, sea de este modo o bien esperando, en el resultado de phpinfo() deberíamos ver esto si XCache está funcionando correctamente:
XCache correctamente instalado

Configuración XCache

Configuración XCache

Instalarlo es muy fácil, ¿verdad? A continuación me puse otra vez en contacto con ellos para preguntarles si tenían algún inconveniente con la instalación pues mejor prevenir a que deshabiliten la web y su respuesta fue que no veían ningún inconveniente. Como pueden observar, en este tipo de servicios de alojamiento web es mejor ser proactivo y lanzarse a ver si es técnicamente posible.

Inefficiencies due to CakePHP ORM implementation

First let me say this article is not a critic against CakePHP (except one point I will talk about later), Object Relational Mapping, far than a trivial thing, it’s a very complicated one.

Let’s begin explaining what this TLA (three-letter acronym) is 🙂 Object Relational Mapping is a programming technique for allowing objects interact with a relational database, trying to allow the programmer to create, insert, update and delete objects in the database without typing a single line of SQL. The technical difficulties are well know (by the name «Object-relational impedance mismatch») and there is a lot of theory about them. In fact, some frameworks like CodeIgniter or Zend Framework avoid not only ORM but the models. This is not because these frameworks don’t follow the MVC pattern but because they follow a different philosophy: as it’s impossible to know how the datasource will be, better to don’t provide models and let them for the developer. They just provide some classes to interact with databases. The fact CakePHP implements ORM helps us to rapid develop an application, CRUD interfaces creation can be automatized, etc. CakePHP is RAD!
Sigue leyendo

Different ways from preventing form resubmission

HTTP is stateless, every request is independent from the previous one, when the connection is established there is a request, a respond and the connection finishes. That’s all. One common problem in web programming is when the user, after drinking more coffee than he can remember, compulsively and possessed by an extreme urge, clicks repeatedly the send button until the mouse wants to commit suicide. Then, the server receives all these requests without knowing if the user (the client) is sending the same information again and again. Another similar situation is when users, after submitting data to the server via a web form, decide they want to come back and click the browser’s previous button. Then, the browser pops up a message saying that they will resubmit the data again. If they don’t care and accept it, here there is our server receiving the same information again. A variant of this one is when the user refreshes the page after sending the post. (Forms that include files or server lag may trigger the user’s impatience) How to deal with those requests is something that we, as programmers, must take care of.

Now the solutions! First I will show you the way to prevent resending the form if the user navigates backwards or refreshes the page. The nicest solution is to implement two pages, let’s call them A and B. In page A the user submits the form and a Post request is send to the server. Our code must process this request and, if valid, resend it to the page B in a Get request. Let’s see server’s side code for page A for one of the nicest PHP frameworks out there, CakePHP:

public function admin_proxy() {
    $params = array();
    foreach ($this->data['User'] as $key=>$value) {
        if (!empty($value)) {
            $params[$key] = rawurlencode($value);
        }
    }
    $destination = array_merge(array('controller' => 'users', 'action' => 'admin_index'), $params);
    $this->redirect($destination);
}

First we encode the parameters before passing them to the action «admin_index», the B page that will receive the parameters via Get. Just if you are curious and / or you aren’t used to build applications for languages with accents, this will not work for languages like Spanish, Catalan or French:

urlencode(htmlentities($value, ENT_NOQUOTES, 'UTF-8'))

The website is in Spanish, that’s why I use rawurlencode() After this, you can see a redirect in the code. This will send a HTTP 302 status code to the browser, the usual method (although 303 is more standard friendly) for performing a redirection forcing the type to Get, regardless the previous request type. Now the page B receives the parameters, decodes them, and informs the user about the operation’s results or any other logic we may want to perform:

public function admin_index() {    
    if (!empty($this->params['named'])) {
        foreach ($this->params['named'] as $key=>$value)
            $this->data['User'][$key] = rawurldecode($value);
    }
        
    /*
     * Some cool things to do here...
    */
}

If we want to do anything with the data the user sends, like store a record in the database, send an e-mail, deface NASA web page, whatever, we must do it before the redirect, in the logic for page A.

Now a fast and dirty solution for preventing the user clicking the submit button multiple times: disable the send button after the user sends it using client side code in Javascript. Please, read carefully: after the user sends it, not after clicks the button. What about if the user clicks but the form isn’t submitted because has an error the user must correct? We can’t leave it disabled or the user will have real trouble trying to send it. Let’s see a solution with jQuery and Validate library:

$(document).ready(function() {
    $('#MyForm').validate({
        rules: {
            // Some rules here.
        },
        messages: {
            // Messages for rules violation.
        },
        submitHandler: function(){
            $('input.submit').attr('disabled', 'disabled');
            $('#MyForm').submit();
        }
    });
 });

SubmitHandler will be executed only after the user clicks the send button and the form is correctly filled, only then, the function it contains will disable the submit button. As stated before, this solution is fast, but what about the user that disables Javascript? It will not work. A good solution is to use the session variable in the server side. When the form is created we generate a token and we store it both in the session and in a form’s hidden input field. When the user submits the form we check if the session has the form’s token stored, if it does and both match we delete the variable from the session and the form’s data process continues, if it doesn’t exist or they don’t match, we refuse to process the request. The token can be generated with any hash function like md5 or sha1, being md5 faster. Here there is the idea implemented in simple PHP, without any framework:

<?php
    session_start();
    $aResults = [];

    if (!empty($_POST)) {
        $token = filter_input(INPUT_POST, 'token', FILTER_SANITIZE_STRING);
        if ($token == $_SESSION['token']) {
            // Process form data
            $aResults[] = 'I love your new submit!';
            $aResults[] = 'You said: ' . filter_input(INPUT_POST, 'some-field', FILTER_SANITIZE_STRING);
        }else{
            $aResults[] = 'Seems you already sent that!';
        }
    }
    $_SESSION['token'] = uniqid(md5(microtime()), true);
?>
<html>
<body>
    <?php
        if (!empty($aResults)):
            foreach ($aResults as $k => $v):
                echo "$v <br />";
            endforeach;
        endif;
    ?>
    <form method="post">
        <input type="hidden" name="token" value="<?=$_SESSION['token'] ?>" />
        <input type="text" name="some-field" value="" />
        <input type="submit" value="Submit" />
    </form>
</body>
</html>

And what about a malicious user doing form tampering? First of all, let me explain what form tampering is: it’s the name for an attack that consists in store the form into a file and modify it for sending it again against the server, the intentions aren’t always good. Thanks to CakePHP you don’t need to worry about this, if you use the «Security» component and the «Form» helper, automagically your site is protected. Well, let me explain the magic: the Form helper will add hidden token fields and the Security component will check them. Among other things, form submissions will not be accepted after a period of time that depends on the setting of «Security.level». The same idea can be implemented in any script for the web.

This weekend I was curious about how WordPress (3.3.1) solves this because I’m developing some projects with this CMS and I was surprised for my findings. The function wp_new_comment(), called from wp-comments-post.php calls wp_allow_comment(), both functions are located in comment.php, look what this last function does:

// Simple duplicate check
// expected_slashed ($comment_post_ID, $comment_author, $comment_author_email, $comment_content)
$dupe = "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = '$comment_post_ID' AND comment_approved != 'trash' AND ( comment_author = '$comment_author' ";
if ( $comment_author_email )
    $dupe .= "OR comment_author_email = '$comment_author_email' ";
$dupe .= ") AND comment_content = '$comment_content' LIMIT 1";
if ( $wpdb->get_var($dupe) ) {
    do_action( 'comment_duplicate_trigger', $commentdata );
    if ( defined('DOING_AJAX') )
        die( __('Duplicate comment detected; it looks as though you’ve already said that!') );

    wp_die( __('Duplicate comment detected; it looks as though you’ve already said that!') );
}

Is doing a query against the database, looking into the table «comments» if the content the user is commenting (post) has any comment not in the trash that belongs to that user (using the author name or the email if set) and the content (the comment’s body) is exactly the same. It works and prevents both multiple clicking and repeating yourself two years later, but what about performance? Any comment a user does requires this overload? If we examine the table comments we can see that the field comment_content type is «text». I’m not saying this query is slow, but it’s necessary? Databases are usually the bottleneck in websites, therefore the philosophy of avoiding as much as possible queries is great. Furthermore, WordPress sites are usually in shared servers and don’t expect great performance there… But WordPress has come a long way and sure their contributors are experienced developers therefore I guess they have a good reason for doing it this way and I would like to know. Anybody can bring me some light about this?