Crear extensiones de PHP (I)

Luego de estar un tiempo sin postear en el blog, vuelvo con una serie de post relacionados con la creación de extensiones para PHP.

Crear extensiones de PHP no es un tema muy popular entre los desarrolladores web, siempre usamos extensiones creadas por los demás pero ¿a nadie le pico el bichito de saber como crear una propia?

¡A mi! y la verdad que no es nada complicado hacer una.

Que ventaja brinda una extensión

Principalmente rapidez, al estar escrita en C y correr en el núcleo de PHP su rendiemiento es mucho mayor.

Existen dos timpos de extensiones:  extensiones PHP y extensiones Zend. Las primeras agregan funcionalidad al lenguaje (por ejemplo Mysqli) y las segundas son extensiones de bajo nivel que modifican el núcleo del lenguaje (por ejemplo xdebug, APC).

Lo que necesitamos

Primero debemos instalar las herramientas para trabajar con C (bueno, también podemos escribir la extension en C++ pero ese será otro post) y alguna otra más:

sudo apt-get install libc6-dev
sudo apt-get install gcc
sudo apt-get install autoconf
sudo apt-get install automake
sudo apt-get install libtool
sudo apt-get install bison
sudo apt-get install flex
sudo apt-get install re2x

Obviamente que tambien debes tener instalado una versión de PHP (5.2+).

Como la creamos

Cada extensión posee al menos los siguientes archivos

- config.m4 es el archivo de configuración para la compilación, indica que archivos se deben compilar y que librerías externas se necesitan

- php_miLibreria.h y miLibreria.c son los archivos de código/funcionalidad de la extensión.

Veamos un ejemplo

Como ejemplo crearemos una extensión “fredddy” que provea una función llamada freddy_hola a PHP y que imprime un simple saludo

config.m4


PHP_ARG_ENABLE(freddy,
[Whether to enable the "freddy" extension],
[  --enable-freddy       Enable "freddy" extension support])

if test $PHP_FREDDY != "no"; then
PHP_SUBST(FREDDY_SHARED_LIBADD)
PHP_NEW_EXTENSION(freddy, freddy.c, $ext_shared)
fi

Lo anterior es la mínima configuración necesaria que necesita la extensión, veamos de que trata:

  • El primer parámetro de PHP_ARG_ENABLE setea en ./configure una opción llamada -enable-freddy
  • El segundo parámetro de PHP_ARG_ENABLE se mostrara durante el proceso de configuración, indicando que se habilito la extensión
  • El tercer parámetro de PHP_ARG_ENABLE se mostrara si se visualiza la ayuda de ./configure (./configure -help)
  • Para la compilación se deben seguir 3 pasos: phpize, ./configure -enable-freddy, make
  • Cuando se invoca a ./configure -enable-freddy se crea una variable  local llamada PHP_FREDDY y se setea en ‘yes’
  • PHP_SUBST es un macro similar a AC_SUBST() de C y es necesaria para crear la extensión
  • PHP_NEW_EXTENSION declara el módulo y el archivo fuente para la compilacion. $ext_shared es similar a PHP_SUBST y es necesaria para crear la extensión como modulo

Si la extensión contara con mas de un archivo fuente para la compilación se deben definir todos en PHP_NEW_EXTENSION separados por un espacio  (por ejempo: PHP_NEW_EXTENSION(freddy2, file1.c file2.c file3.c, $ext_sared)

php_freddy.h

Este archivo es el header del archivo .c que crearemos luego


#ifndef PHP_FREDDY_H
/* Prevenimos la doble inclucion */
#define PHP_FREDDY_H

/* Definimos las propiedades de la extension */
#define PHP_FREDDY_EXTNAME "freddy"
#define PHP_FREDDY_EXTVER "0.1"

/* Import configure options
 * when building outside of the
 * PHP source tree */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

/* Incluimos el header estandard de PHP */
#include "php.h"
/*
 * define the entry point symbole
 * Zend will use when loading this module
 */
extern zend_module_entry freddy_module_entry;
#define phpext_freddy_ptr &freddy_module_entry

#endif /* PHP_FREDDY_H */
  • config.h es incluido cuando se compila con phpize
  • Se incluye php.h que provee funcionalidades para usar en nuestra extension
  • zend_module_entry defina una estructura usada por Zend engine

freddy.c


#include "php_freddy.h"

PHP_FUNCTION(freddy_hola)
{
php_printf("Hola FreddY!!");
}

static function_entry php_freddy_functions[] = {
PHP_FE(freddy_hola, NULL)
{ NULL, NULL, NULL }
};

zend_module_entry freddy_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
PHP_FREDDY_EXTNAME,
php_freddy_functions, /* Functions */
NULL, /* MINIT */
NULL, /* MSHUTDOWN */
NULL, /* RINIT */
NULL, /* RSHUTDOWN */
NULL, /* MINFO */
#if ZEND_MODULE_API_NO >= 20010901
    PHP_FREDDY_EXTVER,
#endif
    STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_FREDDY
    ZEND_GET_MODULE(freddy)
#endif

Compilación

Ya tenemos todo listo para compilar nuestra primera extensión de PHP, para ello debemos hacer lo siguiente


phpize

./configure -enable-freddy

make

Se abra creado una carpeta llamada modules con el archivo freddy.so, este es el archivo de nuestra extensión y el que debemos cargar en PHP


touch /etc/php5/apache2/conf.d/freddy.ini

vim /etc/php5/apache2/conf.d/freddy.ini

Agregamos el siguiente código y reiniciamos apache


[freddy]
extension=/var/www/extensiones/freddy/modules/freddy.so

Listo!

Podemos crear un archivo php para ver como funciona el ejemplo


<?php
echo freddy_hola();

De a poco seguire subiendo más post y agregando nuevos conceptos de como crear extensiones para PHP.

Espero que les sea de utilidad.

Clip – trabajando desde linea de comando con PHP

Clip es un nuevo proyecto que estoy comenzando que facilita la creación de script PHP para correrlos desde la shell o línea de comando.

La funcionalidad que esta implementada hasta ahora es la siguiente:

- Recupera los parámetros de entrada del script.
- Identifica el SO que se utiliza y usa los comandos adecuados para cada SO.
- Funcionalidad para imprimir y recuperar datos desde la consola.
- Permite cambiar de color y fondo a los datos que se imprimen en la consola.
- Validación automatica de los parametros de entrada.
- Invocación al método help cuando se usa -h o —help como parámetro.
- Invocación al método version cuando se usa —version como parámetro.

Veamos unos ejemplos de su uso: (el ejemplo completo se puede ver en github)

1) Imprimir la típica ayuda de los scripts de shell cuando ponemos -h o –help

Para esto debemos crear una clase que extienda de Clip (sisi, usa namespaces de PHP 5.3+)

#!/usr/bin/php
<?php
class Test extends ClipClip
{
    protected $name = '';

    protected function help()
    {
        $this->writer()->write('Esta es la ayuda');
    }
}
$test = new Test();

Luego abrimos una consola y ponemos

> ./test.php -h
> Esta es la ayuda

2) Mostrar la versión de nuestro script

    protected function version()
    {
        $this->writer()->write('Esta es la version');
    }
> ./test.php -version
> Esta es la version

3) Perdir datos al usuario

    public function getName()
    {
        $this->name = $this->writer()->prompt('Escribi tu nombre por favor', null, null, true);
        $this->writer()->newLine();
    }

4) Validar los datos de entrada

Para validar los datos de entrada hay que crear un método que se llame validate que recibe como parametro un objeto del tipo ClipClipOpts. Este objeto tiene 3 atributos:

- short: son las opciones que constan de una sola letra.
- long: son los parámetros del tipo –key o –key=valor.
- input: son los demás parámetros.

Por ejemplo:

    protected function validate(ClipClipOpts $opts)
    {
        if (!in_array('v', $opts->short)) {
            return "Error: Debe agregar la opcion 'v'";
        }
        return true;
    }

Para ver el mensaje de error, usamos el script así

./test.php -t --clave=valor /home/Bart
> Error: Debe agregar la opcion 'v'

En cambio si se invoca de alguna de las siguientes nameras no mostrará el error

./test.php -v --clave=valor /home/Bart
./test.php -vt --clave=valor /home/Bart

Espero que les sea de utilidad!

Benchmark PHP: call_user_func vs direct method call

Estuve probando un script de benchmark de Rob Allen y la verdad que afirmo lo que sospecha y sabia

1) call_user_func consume 2 veces mas de tiempo que llamando directamente al método

2) Un código corrido en php 5.3 es mas eficiente que el mismo código corrido en PHP 5.2

Les dejo el código de Rob con una pequeña modificación, si pueden corran el script en distintas versiones de PHP, pero ojo!! córranlo en la misma pc porque sino las pruebas no serán objetivas.


<?php
class A
{
    function b($a)
    {
        return;
    }
}

define ('ITERATIONS', 10000000);
$start = microtime(true);
$a = new A();
for ($i = 0; $i < ITERATIONS; ++$i) {
   $a->b(1);
}
$stop = microtime(true);
echo  'Direct method call: ' . ($stop - $start) . ' seconds' . PHP_EOL;

$start = microtime(true);
$b = 'b';
for ($i = 0; $i < ITERATIONS; ++$i) {
   $a->$b(1);
}
$stop = microtime(true);
echo  'Direct method variable call: ' . ($stop - $start) . ' seconds' . PHP_EOL;

$start = microtime(true);
for ($i = 0; $i < ITERATIONS; ++$i) {
   call_user_func_array(array($a, 'b'), array(1));
}
$stop = microtime(true);
echo  'call_user_func_array call: ' . ($stop - $start) . ' seconds' . PHP_EOL;

Presentaciones de PHP Barcelona 2010

Les dejo 2 muy buenas presentaciones realizadas en PHP Barcelona 2010

APC & Memcache the High Performance Duo Slides

Hidden Features of PHP Slides

Les recomiendo leer principalmente la segunda

Configurar PHPUnit facilmente

Bueno, lo reconozco… este post es un clásico post para recordar como hacerlo en otro momento, de todas formas estoy seguro que a muchos les resultara útil.

La idea que tenía en mente era poder configurar todo lo relacionado a phpunit en un xml, la suerte es que phpunit posee este feature!

En este XML podemos indicar un monton de cosas, entre ellas definir los distintos suites de pruebas que tendrá nuestro proyecto, que tipo de log va a exportar, si colorea el código o no, si utiliza colores para mostrar el resultado en consola, el path del archivo bootStarp, etc.

El formato del xml es muy sencillo, veamos

<phpunit
         backupGlobals="false"
         backupStaticAttributes="true"
         bootstrap="/path/to/bootstrap.php"
         colors="false"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="true"
         stopOnFailure="true"
         syntaxCheck="false"
         testSuiteLoaderClass="PHPUnit_Runner_StandardTestSuiteLoader">
    <testsuites>
        <testsuite name="Nombre de la Suite 1">
            <file>pathTo/classTest.php</file>
        </testsuite>
    </testsuites>
    <logging>
        <log type="coverage-html" target="/tmp/coverage.html" charset="UTF-8" yui="true" highlight="false" lowUpperBound="35" highLowerBound="70"/>
    </logging>
    <php>
        <ini name="memory_limit" value="256"/>
        <const name="foo" value="bar"/>
        <var name="foo" value="bar"/>
    </php>
</phpunit>

Y la forma de indicarle al phpunit que archivo de configuracion usar es:


phpunit --configuration pathTo/config.xml

Como puede verse  en el nodo phpunit cada atributo representa una configuración de phpunit para su ejecución.

Luego existen otros nodos

- testsuites: aca definimos los distintos suites que tendra nuestro proyecto y las clases de teste que componen cada uno

- logging: indica que tipo log exportara phpunit, hay varias opciones:

  • coverage-html
  • coverage-xml
  • json
  • tap
  • junit

- php: aca podemos definir distintas cosas

  • variables de php.ini
  • constantes
  • variables globales

Bueno, con esto a mi me alcanza, existen otros nodos (para definir listeners por ejemplo) que los pueden buscar en el Apéndice C de la documentación de PHPUnit

PHPUnit y Code Coverage

Esta semana comencé a utilizar un feature de PHPUnit que nunca le había prestado atención, el Code Coverage (cobertura de código/código cubierto ¿?), la cosa es que un compañero de trabajo la estaba usando y me llama mucho la atención.

¿Por que? porque este feature indica el porcentaje de código que fue cubierto por el test unitario (wow!) esto sumado a una UI agradable y de fácil compresión (doblemente wow!) logra maravillas.

Una de las cosas que mas me agrado es que no solo muestra el % de código testeado, sino que muestra el código de la clase coloreada en verde las lineas testeadas y en rojo las no testeadas.

En fin, excelente feature de PHPUnit para los que realizamos TDD o tenemos que testear código legacy

Calidad de software en PHP (VIII) – PHP Depend

PHP Depend es una herramienta de control de métricas para desarrollos en PHP.

Para instalarla primero debemos instalar sus dependencias

sudo apt-get install php-pear
sudo apt-get install imagemagick
sudo apt-get install libmagick9-dev
sudo pecl install imagick

y luego si php depend

sudo pear channel-discover pear.pdepend.org
sudo pear install pdepend/PHP_Depend-beta