Jun 07

WordPress está disponible en varios idiomas gracias al sistema de localización GNU gettext. Dentro del código de WordPress nos encontramos dos funciones para hacer el trabajo con gettext mucho más sencillo y así poder programar plugins y temas multi-idioma, estas funciones son las mismas que usa internamente el propio WordPress. A pesar de disponer de estas facilidades es muy frecuente encontrarse temas en un único idioma, es cierto que hacer temas multi-idioma añade un coste extra a la confección de un tema, pero en mi opinión es algo que merece la pena si queremos dar mucha más visibilidad a nuestro trabajo. A continuación describo como usar estos mecanismos que proporciona el WordPress para realizar un tema multi-idioma.

[1] Escoger un nombre para el "dominio". Se trata de escoger un nombre para todo el conjunto de traducciones, normalmente el mismo nombre del tema nos servirá, lo único a tener en cuenta es que ha de ser un nombre único entre todos los temas y plugins instalados.

[2] Modificar las plantillas. Se trata de usar unas determinadas funciones PHP para sacar por pantalla cualquier cadena de texto que tengamos en nuestras plantillas.

Las funciones son _e($texto) y __($texto). Estas funciones buscan una traducción de $texto en el catálogo usando como índice el texto pasado y si no la encuentran devuelven $texto sin modificar, el idioma a traducir lo define la constante WPLANG (en wp-config.php). La única diferencia entre las dos funciones es que __() devuelve con un return() el texto traducido y _e() lo imprime por pantalla con un echo().

Un ejemplo de __e():

<h2>About</h2>

por:

<h2><?php _e('About','ejemplo_domain'); ?></h2>

Un ejemplo de __():

the_content('Read the rest of this entry');

por:

the_content(__('Read the rest of this entry','ejemplo_domain'));

Para la correcta traducción de las frases en ocasiones es necesario usar printf() o sprinf() junto con __(). Por ejemplo:

printf(__('You are currently browsing the %1$s weblog archives for the %2$s category.','ejemplo_domain'), get_settings('siteurl'), single_cat_title('',false));

Esto permite que el traductor entienda más el significado de la frase que si la troceamos con varios _e(), también permite cambiar el orden de las variables (%1$s y %2$s) si nuestro idioma lo requiere.

A parte de los textos que se mostrarán por pantalla tenemos otros strings importantes a internacionalizar, son los que definen el formato de las fechas. Por ejemplo:

the_time('l, F jS, Y');

por:

the_time(__('l, F jS, Y'),'ejemplo_domain');

[3] Crear el catálogo de traducciones. Existen varios programas para trabajar con archivos POT, a continuación describo brevemente como usar poEdit para generar el archivo POT y MO a partir de las plantillas anteriormente modificadas.

  • Instalamos poEdit.
  • Configuraciones iniciales como nuestro nombre, etc. en "File -> Preferences".
  • Creamos un nuevo catálogo con "File -> New Catalog". En el cuadro de diálogo que aparece:
    • En "Project Info" introducimos el nombre del proyecto, idioma, etc. y la codificación utf-8 (por defecto en todo WP).
    • En "Paths" añadimos un único path, el ".".
    • En "Keywords" añadimos los nombres de las funciones usadas para trabajar con gettext, "__e" y "__" (sin las comillas).
  • Lo siguiente es generar el catálogo. Es necesario guardarlo en el mismo directorio que los archivos del tema y ponerle el nombre del locale deseado (p.e. es_ES).

Si todo ha ido bien poEdit escaneará todas nuestras plantillas y generará un archivo POT listo para empezar a traducir. Cada vez que se guarda el catálogo se generará automáticamente el MO, que es el que realmente usa el código PHP, a no ser que configuremos lo contrario en "Preferences".

[4] Cargar las traducciones desde el tema. Ahora sólo falta que nuestro tema cargue el catálogo apropiado dependiendo del idioma definido, esto lo conseguimos añadiendo lo siguiente antes de cualquier llamada a __() o _e(), un buen lugar es al principio del index.php o del header.php:

load_theme_textdomain('ejemplo_domain');

[5] Listos! En el siguiente acceso a nuestro blog veremos todas las frases traducidas.

Aunque el procedimiento descrito está centrado en WP y las funciones que nos brinda para la internacionalización es muy parecido al uso de otros sistemas basados en gettext, como plugins Smarty, o al uso directo de las funciones gettext del PHP.

Si quieres más información puedes consultar los capítulos Traduciendo WordPress y Escribiendo un Plugin de la documentación de WordPress.

Etiquetas:
May 16

El motor de PHP es uno de los más rápidos generando páginas web aún así nunca será tan rápido como no tener que generar nada y que el servidor web devuelva páginas estáticas. A pesar de disponer de una aplicación web escrita en PHP podemos conseguir la misma velocidad que en una web estática cacheando las páginas generadas. Lo que se porpone a continuación es un sistema de cache donde las páginas sólo se generarán la primera vez que se acceda a ellas, en siguientes peticiones el servidor web devolverá las páginas estáticas generadas.

Existen varias propuestas de sistemas de cache donde siempre se ejecuta un script PHP trabajando con el output-buffer para guardar la respuesta. Estos sistemas de cache tiene el inconveniente de que a pesar de tener la página cacheada siempre estamos ejecutando un script PHP con el coste en rendimiento que ello conlleva. Como solución se propone trabajar con el output-buffer pero usar la directiva de configuración ErrorDocument del Apache para que si ya tenemos la página generada no se ejecute ni un script PHP.

La directiva ErrorDocument se usa para configurar lo que el servidor devolverá al cliente en caso de error. Pues bien, se trata de configurar el error que indica que no existe la página solicitada (error 404) para que apunte a un script PHP donde generaremos la página. En la siguiente petición a la misma página el Apache devolverá directamente el archivo HTML generado.

Algunas consideraciones previas:

  • Necesitas tener permisos de escritura en el directorio donde pretendas guardar las páginas. (esto lo puedes conseguir fijando los permisos a 777).
  • Necesitas poder configurar la directiva ErrorDocument de tu servidor Apache. (algunos hostings no lo permiten).

[1] Configurar el Apache

Debemos añadir lo siguiente en nuestro .htaccess o en la configuración del servidor Apache:

ErrorDocument 404 genera_pagina.php

[2] Añadir el código necesario

Es necesario añadir un bloque de código al principio y al fin de cada script. A continuación una implementación de ejemplo:

//obtenemos el nombre de la página solicitada
$pagina = array_pop(explode('/',$_SERVER['REQUEST_URI']));
//iniciamos el output buffer
ob_start();
//.............................
//código que genera la página
echo 'hola';
//.............................
//obtenemos el output bufer
$contenido = ob_get_contents();
//guardamos la pagina estática
file_put_contents($pagina,$contenido);
//volcamos por pantalla y cerramos el output buffer
ob_end_flush();

Se puede guardar el anterior código en un script llamado "genera_pagina.php" sustituyendo la linea echo "hola" por el código de nuestra aplicación (p.e. con un include()) o podemos dividir el código en dos scripts y usar las directivas auto-append-file y auto-prepend-file para no tener que modificar nuestra aplicación (cortar por las lineas de puntos).

En el ejemplo se obtiene sólo el nombre de la página solicitada y se crea una archivo con el mismo nombre con el contenido de la respuesta. Pero no soporta directorios, está pensado para trabajar sobre un directorio en concreto o en una web sin directorios en las URL. Cambiando la primera linea del ejemplo podéis hacer que se comporte de otra forma, el ejemplo simplemente obtiene el último trozo de la URL usando como delimitador "/".

A diferencia de otras propuestas de cache aquí estamos modificando el Apache para que ejecute un script en caso de no encontrar una página. Es por esto que en vuestra aplicación debéis generar una página de error en el caso de que la página solicitada no exista. Lo correcto es que en esta página de error se envíe la header correspondiente tal y como lo haría el Apache por defecto:

header("HTTP/1.0 404 Not Found");

[3] Mantenimiento de la cache

Por ultimo nos falta programar un proceso en el cron para que elimine los archivos de la cache que han caducado. Por ejemplo para mantener una cache de una hora:

0 * * * * find /web -mtime +1h -print0 | xargs -0 rm -f

Se ha de sustituir "/web" por la ruta física donde tenemos nuestra aplicación web. Si no queremos programar un cron siempre podemos hacer lo anterior desde algún script PHP que se ejecute constantemente en nuestra web.

Lo que comento en este post no es nada nuevo, ya fue propuesto por Rasmus Lerdorf en el PHPCon del 2002 (aunque un poco caducada la presentación que enlazo merece la pena).

Etiquetas:
May 02

Como continuación del anterior post voy a explicar brevemente como montar una cache de objetos en memoria compartida usando APC. Esto nos permite disponer de una cache accesible para cualquier proceso Apache que se esté ejecutando en el servidor. Podemos guardar cualquier variable (resultados de queries, páginas html, cualquier tipo de objeto, etc.) y esta se conservará entre las distintas peticiones hasta que la borremos de cache o expire el TTL que queramos.

Una forma rápida de ver su uso es con un ejemplo:

$obj = new Objeto();
apc_store('key',serialize($obj),3600);
$result = unserialize(apc_fetch('key'));
var_dump($result);

Usando apc_store() guardamos $obj en cache durante una hora. Podemos recuperar o borrar $obj de la cache usando el identificador key. Es necesario realizar serialize/unserialize si guardamos datos como objetos o arrays, para variables simples no es necesario.

Para borrar el anterior objeto de cache:

apc_delete('key');

Para evitar el uso de define el cual es bastante lento disponemos de apc_define_constants() y apc_load_constants() para guardar y recuperar constantes de cache.

El APC ofrece una interfaz gráfica para ver el uso que hacemos de la cache, que opciones tenemos activas, archivos "compilados", variables almacenadas, etc. a la vez que nos permite borrar todo el contenido de la cache. Podemos obtener la misma información con la función apc_cache_info() y para borrar la cache tenemos apc_clear_cache() aunque la interfaz gráfica no deja de ser interesante.

Un snapshot de ejemplo:

APC snapshot

Para instalarla en FreeBSD se trata de copiar el archivo /usr/local/share/doc/APC/apc.php en algún lugar dentro de nuestro document root del Apache.

Etiquetas:
Abr 26

Como prometía en el post dedicado a la optimización de ADOdb voy a explicar como instalar el sistema de cache APC sobre un servidor FreeBSD 6.x. El APC (Alternative PHP Cache) es un sistema de cache de opcode para PHP, sirve para cachear el código intermedio del PHP y así no tener que interpretar todos los scripts en cada ejecución. Para almacenar este código "compilado" se usa la memoria compartida del sistema. A parte el APC nos ofrece funciones para poder almacenar y recuperar datos de cache.

El APC es una extensión PECL que no viene incluida por defecto con el PHP (esto cambiará con la futura versión 6). A continuación describo como instalar y configurar el APC sobre FreeBSD.

[1] Instalar el port

Suponiendo que tenemos instalado y funcionando un servidor web (Apache+PHP) sólo nos falta añadirle la extensión PECL con:

# portinstall pecl-APC

Si no trabajas con portupgrade:

# cd /usr/ports/www/pecl-APC/
# make install clean

Con esto compilamos e instalamos el APC. Si todo va bien acabará el proceso y podremos ver esta nueva linea en el archivo /usr/local/etc/php/extensions.ini:

extension=apc.so

Cuando lo compilas se dan a escoger tres opciones: MMAP, SEMAPHORES y PHP4_OPT. Es aconsejable seleccionar sólo MMAP y si trabajas con PHP4 la última también. La opción de SEMAPHORES dependiendo de tu sistema puede provocar cierta inestabilidad y no ofrece muchas mejoras en rendimiento.

La última versión del APC (pecl-APC-3.0.14) se compila siempre con soporte mmap aunque desactives la opción (o esto es lo que me pasa a mi en los servidores bajo FreeBSD).

Ahora sólo falta configurar correctamente las directivas apropiadas en el php.ini y un restart (o reload) del Apache.

[2] Configurando el APC

La configuración por defecto del APC es apropiada en muchas situaciones aunque bajo FreeBSD deberíamos configurar correctamente el tamaño de memoria compartida. Esto se consigue con las siguientes directivas:

apc.shm_segments=1
apc.shm_size=32

En FreeBSD el tamaño de memoria compartida por defecto es de 32MB. Puedes optar por aumentarlo o por dejarlo igual y usar varios segmentos configurando la directiva apc.shm_segments. Para aumentar el tamaño de la memoria compartida a 128MB en FreeBSD:

# sysctl kern.ipc.shmmax=134217728
# sysctl kern.ipc.shmall=32768

Si quieres conservar estos valores después de reiniciar el sistema debes añadirlos en /etc/sysctl.conf.

Se debe fijar SHMALL (kern.ipc.shmall) a SHMMAX/PAGE_SIZE. Este valor en el ejemplo descrito de 128MB de memoria compartida nos queda como: 134217728/4096 = 32768. Puedes ejecutar el comando pagesize para conocer el tamaño de una página de memoria (PAGE_SIZE) en tu sistema y ipcs -M para verificar la configuración de la memoria compartida.

También hacer notar en este punto que con la última versión del APC bajo FreeBSD no permite usar varios segmentos de memoria compartida y estás obligado a sólo usar un segmento, si necesitas más memoria debes aumentar el tamaño de memoria compartida del sistema.

El resto de directivas de configuración dependen mucho del tipo de aplicación PHP. Dependiendo del número de visitas, cantidad de archivos a cachear, frecuencia de cambio de los archivos, etc. Una configuración de ejemplo con pequeñas notas acerca del significado de las directivas usadas (en el archivo /usr/local/share/doc/APC/INSTALL tienes todas las directivas disponibles detalladas):

; Activa el APC
apc.enabled=1
; Número de segmentos de memoria compartida
apc.shm_segments=1
; Tamaño de la memoria compartida
apc.shm_size=128
; Un número aproximado de archivos fuente a cachear
apc.num_files_hint=6000
; Un número aproximado de variables a cachear
apc.user_entries_hint=100
; Segundos que dejamos en cache una entrada que ya no se usa
apc.ttl=600
; Idem al anterior pero para las variables de usuario
apc.user_ttl=600
; Segundos que dejamos una entrada cacheada en el recolector de basura
apc.gc_ttl=0
; Indica si se cachea por defecto.
apc.cache_by_default=On
; Expresiones regulares para saber que archivos cacheamos
; Resulta útil si se usa en combinación con la directiva anterior
apc.filters=""
; Indica si se activa el APC para el modo CLI del PHP
apc.enable_cli=0
; Indica el tamaño máximo de archivos a cachear
apc.max_file_size=1M
; Indica si el APC ha de verificar si los archivos han sido modificados
; para actualizar la cache
apc.stat=1

De todos los cacheadores de código que he usado APC es con diferencia el más estable aunque no es perfecto. En situaciones de mucho tráfico y si constantemente estás cambiando los archivos al final consigues un fantástico segfault del Apache. Una gran opción si dispones de una aplicación que no está en constante desarrollo es usar la directiva apc.stat a 0, con este parámetro consigues mucha más estabilidad.

Es un gran invento y es muy recomendable su uso, puedes llegar a ver loads de CPU reducidos al 50% y ganar un 20% de memoria. Pero como todo tiene bugs y al menos yo en el escenario donde lo uso (decenas de millones de páginas vistas por mes + clúster de decenas de servidores BSD) es un tanto inestable en ciertas situaciones... pero mucho más estable que algo como EAccelerator el cual en el anterior escenario no aguanta ni 5 minutos.

Si usas APC sobre FreeBSD en webs con mucho tráfico y no tienes ni un segfault nunca... no te cortes y comenta que directivas/opciones estás usando.

En un siguiente post explicaré como usar las funciones que proporciona el APC para almacenar datos de aplicación en cache.

Etiquetas:
Abr 25

Una de las técnicas más habituales en el desarrollo de aplicaciones PHP orientadas a objetos es crear un archivo fuente para cada definición de clase. Con esta técnica es necesario hacer require de muchos archivos en el código para cada ejecución. Nuestro fantástico PHP5 nos ofrece la posibilidad de cargar automáticamente las clases que necesita bajo demanda definiendo la función __autoload().

Otra gran ventaja de usar autoload es que nos aseguramos de que sólo incluimos las definiciones de las clases que realmente usamos. Es frecuente en la vida de una aplicación que debido a actualizaciones se nos queden requires de clases que ya no usamos o que por ejemplo tengamos la costumbre de hacer todos los requires que va a usar una clase en la cabecera, entonces para ejecutar un sólo método que quizás sólo usa un par de clases estamos cargando clases innecesarias.

A pesar de que normalmente el uso de autoload provoca una mejora importante en el rendimiento ya que sólo se cargan las clases que usamos se ha de tener en cuenta que el código que tengamos en la función __autoload() debe ser lo más ligero posible ya que dependiendo del tamaño de la aplicación se va a ejecutar muchísimas veces.

Una función __autoload() de ejemplo podría ser algo como:

function __autoload($class_name) {
  $file = '/classes/'.$class_name.'.php';
  if (file_exists($file)) require_once $file;
}

Si tiras de file_exists() y constantemente estás añadiendo y quitando clases quizás te resulta interesante usar clearstatcache() antes para limpiar la cache de estados de archivo.

Como es necesario definir el autoload al principio de cada script resulta útil usar la directiva auto_prepend_file, si no tienes acceso a tu PHP.INI puedes añadir lo siguiente en tu .htaccess:

php_value auto_prepend_file "funcion_autoload.php"

Una última consideración es que en teoría no se pueden lanzar exceptions desde dentro de la función __autoload(), aunque usando eval() para cargar la definición de la clase exception deseada lo puedes conseguir (en las notas del manual tienes varios ejemplos).

Etiquetas:
preload preload preload