PHP, PEAR y PHPUnit para Yii en Mac OS X Lion
He tenido que montar un entorno de desarrollo para Yii, un framework de PHP, en mi Mac OS X Lion. Como he tenido que hacer varios apaños he recopilado todos los ajustes en esta entrada.
Antes de nada enumero las versiones del software utilizadas:
- Mac OS X 10.7.2
- PHP 5.3.6
- PEAR 1.9.4
- PHPUnit 3.5.15
- Selenium Server 2.8.0
- Yii 1.1.8
- Firefox 7.0.1
La mayoría de las acciones que comento han de realizarse con permisos de administrador.
Activar el servidor web con PHP
Mac OS X Lion incluye Apache con soporte para PHP. Sin embargo es necesario activarlo. Para ello en /etc/apache2/httpd.conf descomentamos:
LoadModule php5_module libexec/apache2/libphp5.so
Y en /etc copiamos el fichero de configuración de fábrica:
# cp /etc/php.ini.default /etc/php.ini
Nota: personalmente prefiero que mis entornos de desarrollo y pruebas sean lo más privados posibles. Por ello modifico el Listen de /etc/apache2/httpd.conf a
Listen 127.0.0.1:80
Solamente resta activar el servidor web desde System Preferences / Sharing / Web Sharing.
El directorio que se publica como página principal está ubicado en /Library/WebServer/Documents/ podemos utilizar directamente esta carpeta o bien utilizar carpetas personales ( http://localhost/~usuario/ ). La opción de carpetas personales busca el directorio Sites en el home del usuario, que se puede crear a partir del cuadro de configuración anterior.
Para probar que todo está funcionando lo más recomendable es publicar un fichero .php con un getinfo():
<?php phpinfo(); ?>
Instalar PEAR y PHPUnit
En las versiones anteriores de Mac OS X PEAR está directamente instalado, en Lion es necesario hacer un paso previo:
# php /usr/lib/php/install-pear-nozlib.phar
Con esto ya tenemos PEAR instalado. Actualizamos:
# pear upgrade-all
configuramos el autodiscover (para hacernos las cosas más fáciles) e instalamos PHPUnit:
# pear config-set auto_discover 1 # pear install pear.phpunit.de/PHPUnit
Solamente nos falta un detalle más, añadir el software instalado por PEAR a la configuración de PHP. Añadimos al /etc/php.ini:
include_path = "/usr/lib/php/pear/"
Para comprobar que todo va bien comprobamos la versión de PHPUnit
# phpunit --version
Montar y ejecutar las pruebas de un proyecto Yii
A partir de aquí lidiaremos con los problemas que surgen con Yii. Primero nos descargamos Yii y creamos un proyecto de prueba:
$ yii/framework/yiic webapp demo
Problema con date.timezone
Al acceder a la demo puede que nos muestre un mensaje de error como el siguiente:
It is not safe to rely on the system’s timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function.
La solución más sencilla es configurar en /etc/php.ini el timezone, por ejemplo:
date.timezone = "Europe/Madrid"
Problema con Selenium, arrancar Firefox
Configuramos la URL para las pruebas en el fichero protected/tests/WebTestCase.php:
define('TEST_BASE_URL','http://localhost/~chernando/demo/index-test.php/');
Descargamos Selenium y lo dejamos ejecutando:
$ java -jar selenium-server-standalone-2.8.0.jar
e intentamos lanzar las pruebas funcionales del proyecto yii:
$ cd demo/protected/tests/ $ phpunit functional/SiteTest.php
Lo más probable es que falle el lanzamiento de iexplorer (bastante razonable en Mac OS X
) o que no encuentre firefox-bin ya que no está en el path. Para solucionarlos editamos protected/tests/phpunit.xml, eliminamos:
<browser name="Internet Explorer" browser="*iexplore" />
y modificamos la entrada del Firefox a nuestro, en mi caso es el siguiente:
<browser name="Firefox" browser="*firefox /Users/chernando/Applications/Firefox.app/Contents/MacOS/firefox-bin" />
Con esto podremos ejecutar las pruebas funcionales.
Problema con Selenium, fallan los test
Después de llegar hasta aquí me sorprendió bastante que de los tres tests que vienen de fábrica fallaran dos
Concretamente estos dos fallos:
Body cannot be blank.
Failed asserting that <boolean:false> is true.
Password cannot be blank.
Failed asserting that <boolean:false> is true.
Por suerte es un problema conocido y ya está corregido en el repositorio.
El problema es que no se espera a que se ejecuten las validaciones con JavaScript. El parche publicado corrige un par de líneas en el fichero protected/tests/functional/SiteTest.php que cambia los dos assert que fallan por waitFor, consiguiendo el comportamiento esperado.
Homebrew, gestión de paquetes en Mac OS X
Homebrew es un sistema de gestión de paquetes de software para Mac OS X como podría ser un apt en sistemas GNU/Linux pero sin distribución de paquetes precompilados.
Gestión de paquetes en Mac OS X
Apple propone el App Store como sistema para obtener software y mantenerlo actualizado. Evidentemente solo las aplicaciones publicadas en el Apple Store pueden ser mantenidas de esta manera. Y aún así la oferta es bastante limitada si nos fijamos en software Unix.
Otras opciones disponibles son MacPorts o Fink. Todos ellas tienen un planteamiento parecido y diferente al mismo tiempo. MacPorts y Fink plantean mantener todo el software a parte de la base de Mac OS X mientras que Homebrew prefiere utilizar al máximo la base propuesta por el sistema y construir únicamente lo que falte.
Este planteamiento tiene sus ventajas y sus inconvenientes:
- Al necesitar menos dependencias externas es más rápido.
- Al utilizar las dependencias del sistema las cosas se pueden romper cuando el sistema las actualice.
Por otro lado MacPorts y Fink, aunque más laboriosos tienen un entorno más controlado y por tanto más estable.
En resumen, depende de las necesidades de cada uno. En este artículo veremos el uso de habitual de Homebrew que probablemente sea el más ligero para empezar a probar.
Requisitos
Necesitamos el entorno de compilación de Mac OS X. Aunque algunas utilidades vienen ya instaladas de fábrica en el sistema, es recomendable (y en algunos casos indispensable) instalar Xcode. Lo podemos encontrar en el App Store.
Instalación de Homebrew
Desde la web oficial podemos acceder a la guía de instalación. En la práctica se reduce a ejecutar (a fecha de este artículo):
/usr/bin/ruby -e "$(curl -fsSL https://raw.github.com/gist/323731)"
La instalación se realiza en /usr/local por lo que o bien lo ejecutamos como administrador o bien nos damos de permisos para escribir en ese directorio.
Actualizar el listado de fórmulas
Homebrew utiliza el concepto de fórmula. Este concepto transmite la idea de cómo se obtiene un programa, es decir, sus dependencias y sus pasos de compilación.
El listado de fórmulas se mantiene en los repositorios de GitHub. Esto nos permite crear forks del proyecto, añadir nuestras fórmulas y demás. Veamos cómo se actualiza el listado:
$ brew update remote: Counting objects: 141, done. remote: Compressing objects: 100% (63/63), done. remote: Total 122 (delta 93), reused 88 (delta 59) Receiving objects: 100% (122/122), 13.71 KiB, done. Resolving deltas: 100% (93/93), completed with 18 local objects. From http://github.com/mxcl/homebrew f34cda7..09ad71b master -> origin/master Updated Homebrew from f34cda74 to 09ad71bd. [...]
Es la salida típica de git. A partir de ahí veremos un resumen de las nuevas fórmulas añadidas y de las actualizadas.
Buscar fórmulas
brew search PATRÓN
Mostrar información de una fórmula
Tenemos la opción clásica:
brew info FÓRMULA
y la curiosa opción de visitar la página del proyecto:
brew home FÓRMULA
Instalación de fórmulas
brew install FÓRMULA
Listar fórmulas antiguas
En el proceso de actualización del listado de fórmulas se detectarán nuevas versiones de fórmulas ya instaladas:
brew outdated
Actualización de paquetes
Tenemos dos opciones
brew upgrade
que actualiza todos las fórmulas con nuevas versiones o
brew upgrade FÓRMULA
que actualiza únicamente la fórmula especificada.
Eliminación de versiones antiguas
Una curiosidad de Homebrew es que no desinstala versiones anticuadas para ello usamos:
brew cleanup
Crear nuevas fórmulas
Es bastante sencillo crear una fórmula nueva. Aunque el Formula Cookbook es bastante claro, veremos brevemente los paso principales:
- brew create URL (con la URL de la descarga)
- Editar el fichero, creado automáticamente, en /usr/local/Library/Formula/FÓRMULA.rb
- Añadir las dependencias con depends_on.
- Añadir los comandos system necesarios para la compilación
- Probar la fórmula con brew install -vd FÓRMULA
Más información
NTP Sincronización de tiempos
Uno de los principios que debemos mantener en nuestros equipos es mantenerlos en hora. En algunos es un requisito, por ejemplo sistemas de autenticación dependientes del tiempo, y en otros para mantener la cordura, por ejemplo para realizar un seguimiento de logs en distintas máquinas.
La solución es el uso de NTP (Network Time Protocol) que nos permite sincronizar los relojes de nuestros equipos y mantenerlos en hora con el paso del tiempo. Vamos a ver las configuraciones típicas usando ntpdate y el demonio ntpd.
Pero antes un breve comentario sobre el stratum. El número del stratum nos indica el número de pasos a los que estamos de un reloj atómico (nivel 0). Los relojes atómicos son muy precisos (no como los relojes de cuarzo) pero también muy caros, por lo que es habitual que se utilicen relojes de GPS como origen.
Existen multitud de servidores públicos de NTP. Una iniciativa que mantiene un pool abierto y que además realiza balanceo es NTP pool project. Para este artículo utilizaremos de ejemplo 2.es.pool.ntp.org.
ntpdate, para equipos itinerantes
ntpdate está pensando para utilizarse una sola vez. Lo lanzamos, obtiene el tiempo del servidor remoto y sincroniza la hora local. Por ejemplo:
# ntpdate 2.es.pool.ntp.org 14 Sep 12:18:03 ntpdate[3359]: adjust time server 158.227.98.15 offset 0.000969 sec
El uso más habitual es para equipos portátiles u ordenadores que no tienen conexión permanente a Internet. Los equipos esperan a tener conexión y efectúan la sincronización.
Un error habitual es que el socket esté en uso, normalmente por el demonio ntpd, que exige cerrar primero ntpd y luego lanzar ntpdate:
# ntpdate 2.es.pool.ntp.org 14 Sep 12:21:57 ntpdate[3392]: the NTP socket is in use, exiting
La configuración de ntpdate como servicio es muy sencilla, podemos echar un vistazo en /etc/default/ntpdate (sistemas Debian).
Demonio ntpd, equipos con conexión permanente
Si el equipo está conectado la gran parte del tiempo lo recomendable es utilizar el demonio ntpd. Evidentemente se encargará de mantener el equipo sincronizado con el paso del tiempo.
Existe una diferencia importante entre ntpdate y ntpd. ntpd realiza un ajuste sutil sobre la velocidad del reloj para compensar la deriva natural del reloj (drift). Esto tiene una repercusión: el tiempo es continuo (obviemos cuestiones metafísicas
).
Por ejemplo imaginemos que nuestro equipo está 5 minutos atrasado. ntpd intentará que el reloj “corra” hasta llegar a la sincronización sin saltarse ni un segundo (simplemente “cuenta los segundos más rápido”). ntpdate por otro lado al ver que la diferencia es mayor a medio segundo directamente “saltará” en el tiempo fijando la hora actual inmediatamente.
La práctica recomendada es arrancar el equipo con ntpdate y dejar que el demonio ntpd se haga cargo de mantener la hora.
La configuración es un poco más elaborada, ya que podemos configurarlo como servidor de NTP. El fichero de configuración /etc/ntp.conf está muy bien comentado y básicamente cambiando las directivas server por servidores locales (o del NTP pool project) sería suficiente.
Prestando servicio interno
Si queremos sincronizar varios equipos dentro de nuestra red haremos que un par de equipos se sincronicen con servidores externos y al mismo tiempo presten ese servicio a nivel local. De esta forma, incluso si se cae la conexión, todos los equipos estarán sincronizados contra la misma fuente de referencia. Además es más educado y consume menos recursos
Para ello simplemente es necesario revisar las directivas restrict.
Broadcast
La última modalidad es la sincronización por broadcast. En esta configuración un servidor local de NTP emite de forma regular la hora actual, los equipos que escuchen actualizan la hora. Es una configuración muy típica para desplegar equipos con conexión a red como impresoras, cámaras y dispositivos similares.
Para configurar al servidor para que emita hay que hacer uso de la directiva broadcast.
Para hacer que un equipo escuche las emisiones la directiva es broadcastclient.
Más información
Python generators
Todos conocemos las listas de Python mientras que sus hermanos los generadores son menos conocidos. Este breve artículo es un recordatorio
El concepto de generador
La idea de los Generators se basa en dos conceptos: los iteradores y la evaluación perezosa.
Python utiliza el concepto de iterador de una forma muy natural, por ejemplo:
for i in elementos:
print i
Por debajo el intérprete lo que hace es crear un iterador mediante el método iter() y avanzar posiciones con el método next() del iterador devuelto. Cualquier objeto que cubra estos métodos puede ser tratado como un iterador y por tanto trabajar de forma transparente con el resto de la API.
El segundo concepto es la evaluación perezosa (lazy evaluation), que en pocas palabras significa que una expresión se resuelve únicamente cuando se la necesita, no antes.
Uniendo ambos conceptos tenemos el generador: una estructura, sobre la que podemos iterar, cuyos componentes se van obteniendo según se va avanzando.
La diferencia fundamental con la lista es que la lista tiene una evaluación ansiosa/codiciosa (eager evaluation). Esto implica que cuando queremos manejar una lista de 10 elementos la lista necesita disponer de esos 10 elementos en el momento de su declaración. Sin embargo el generador solamente necesita saber cómo generar el siguiente valor por lo que no necesita ni reservar espacio ni conocer a priori ningún elemento.
Declaración de un método generador
Un método que utilice la palabra reservada yield se trata como un generador. yield puede devolver un valor, como si fuera un return (cuidado, return se utiliza para terminar el generador).
Por ejemplo definimos la función naturales() que devuelve un iterador de todos los números naturales:
def naturales():
n = 1
while True:
yield n
n = n + 1
i = naturales()
print i # <generator object naturales at 0x10f1a3050>
print i.next() # 1
print i.next() # 2
Vemos que la primera ventaja de la evaluación perezosa es que podemos tratar estructuras infinitas.
Emulemos el método enumerate(list) nativo de Python utilizando el generador como una lista (o mejor dicho su iterador):
ls = ['a', 'b', 'c'] print zip(naturales(), ls) # [(1, 'a'), (2, 'b'), (3, 'c')]
Declaración de una expresión generadora
Otra forma de declarar un generador es exactamente igual que una expresión de lista pero con paréntesis. Por ejemplo los números pares:
print [p for p in range(10) if p % 2 == 0] # [0, 2, 4, 6, 8] print (p for p in range(10) if p % 2 == 0) # <generator object <genexpr> at 0x10f1a3190>
En este caso vemos que la lista está completa mientras que el generador se queda a la espera. A partir de este ejemplo podemos definir otro generador de números naturales pares:
i = (p for p in naturales() if p % 2 == 0) print i.next() # 2 print i.next() # 4
Es importante encadenar generadores con generadores, en caso contrario la evaluación de la lista obligaría el recorrido completo del generador. Si la estructura fuera infinita, como el ejemplo, provocaría la muerte del intérprete:
[p for p in naturales() if p % 2 == 0]
Conclusiones
Evidentemente utilizar los generadores no es la solución para todos los problemas.
Hay unos escenarios concretos en los que merece la pena considerados:
- Estructuras infinitas.
- Estructuras que ocupen mucha memoria, reducimos así el espacio de memoria a reservar.
- La generación de los elementos es costosa, podemos retrasar su cálculo hasta el último momento.
- Sabemos que la estructura no se va a consumir completamente, una mezcla de las dos anteriores.
- Cuando trabajamos con otros generadores.
Más información
Wargames
Aquí tenéis otra entrada baja en contenidos para estar en forma este verano
Aprovechando estos ratos muertos que tienen las vacaciones, he estado reuniendo estos enlaces de Wargames y retos similares.
Para aquellos que quieran practicar técnicas de seguridad, criptografía, ingeniería inversa en un entorno cómodo y con todas las garantías higénicas o bien pasar el tiempo con algo más que un sudoku, aquí tenéis la lista:
- http://warzone.elhacker.net/ WarZone de elhacker.net
- http://www.hackthissite.org/ Hack This Site!
- http://www.mod-x.co.uk/ Mod-X Security Challenge
- http://hackquest.com/ HackQuest
- http://www.informatica64.com/RetosHacking.aspx Retos Hacking Informática 64
- http://www.hellboundhackers.org/ HellBound Hackers
- http://crackmes.de/
- http://bright-shadows.net/ TheBlacksheep
- http://www.try2hack.nl/
- http://www.hackertest.net/ Hacker Test
- http://www.smashthestack.org/ SmashTheStack Wargamming Network
- http://www.overthewire.org/wargames/ OverTheWire
- http://www.yashira.org/
Además http://www.wechall.net/ mantiene un listado de wargames activos, con puntuaciones y estadísticas de jugadores entre juegos. Es una lista mucho más seria
Enjoy!