Archivo de 'desarrollo'

Bibliotecas JavaScript: Motores de física

Hoy hemos visto en el Google I/O la noticia de que Angry Birds estará disponible para el navegador Google Chrome. ¿Cómo es esto posible? Gracias al objeto canvas :D Ahora que HTML5 ha llegado para quedarse, y que incluso Internet Explorer ofrece soporte para el elemento canvas, poco a poco se irán popularizando las animaciones y juegos en el navegador, sin necesidad de Flash. Uno de los elementos clave son los motores de física; consiste en una serie de bibliotecas que permiten modelar sistemas donde exista gravedad, y se puedan definir objetos rígidos o dinámicos, con coeficientes de fricción, elasticidad, etc. Suena bien, ¿verdad? :)

Uno de los motores más populares es box2d. De hecho, es el que utiliza el propio Angry Birds. Y por supuesto existen ports de esta biblioteca para muchísimos lenguajes de programación, incluido, claro está, el pequeño gran JavaScript. En concreto la versión JavaScript está extraída automáticamente de la versión de ActionScript. Si quieres ver algunos ejemplos en vivo, entra a la web de box2d-js. Lo que ahora vamos a hacer, lo puedes ver aquí abajo (prueba a hacer clic sobre el lienzo).

Dejemos de hablar, y vamos a mancharnos las manos :)

Aviso sobre este artículo

El código en el que he basado este artículo está extraído de savagelook.com. Te recomiendo que entres y lo revises, ya que su ejemplo es más completo que la versión que yo aquí expongo.

Del mismo modo, es muy recomendable el manual de uso de box2d que han hecho en Box2DFlash.

Paso 1 – Preparando el entorno

Lo primero que tenemos que hacer es crear el código HTML. box2djs depende de dos bibliotecas:

  1. Prototype, como framework javascript.
  2. excanvas, para añadir soporte canvas a Internet Explorer.

Además, crearemos ya el objeto canvas, al que le estableceremos el ancho y alto, y sus propiedades CSS. Nos quedará el siguiente código:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<!--[if IE]><script type="text/javascript" src="lib/excanvas.js"></script><![endif]-->
<script src="lib/prototype-1.6.0.2.js"></script>
<script src="lib/box2djs.min.js"></script>
</head>
<body>
<canvas id="canvas-world" style="position:absolute; top:0; left:0;background-color:#F5F5F5;width:800px; height:500px;"></canvas>
</body>
</html>

Ver demo. Como vemos, no ocurre gran cosa. Tan sólo vemos una gran caja gris

Paso 2 – Crear el mundo

En este paso vamos a crear el mundo en el que todo sucederá. Vamos a ver el código JavaScript que hace lo define:

            var world;
            var ctx;
            var canvasWidth;
            var canvasHeight;

            // Función que crea el mundo.
            function createWorld() {
                var worldAABB = new b2AABB();
                worldAABB.minVertex.Set(-1000, -1000);
                worldAABB.maxVertex.Set(1000, 1000);
                var gravity = new b2Vec2(0, 300);
                var doSleep = true;
                world = new b2World(worldAABB, gravity, doSleep);

                return world;
            }

            // Punto de entrada principal. Cuando se cargue la ventana:
            Event.observe(window, 'load', function() {
                world = createWorld();
       		ctx = $('canvas-world').getContext('2d');

       		var canvasElm = $('canvas-world');
       		canvasWidth = parseInt(canvasElm.width);
       		canvasHeight = parseInt(canvasElm.height);
       		var canvasTop = parseInt(canvasElm.style.top);
       		var canvasLeft = parseInt(canvasElm.style.left);
            });

En primer lugar definimos cuatro variables globales que utilizaremos en el resto de la aplicación. Después, definimos la función createWorld(), y finalmente vinculamos el evento “load” de la ventana, al código de la función anónima. Este código por ahora hace una llamada a createWorld(), extrae el lienzo 2d del canvas, y calcula su ancho/alto y sus coordenadas. Pero vamos a ver qué hace createWorld():

La primera línea crea un nuevo objeto de tipo b2AABB. Estos objetos definen un espacio en 2 dimensiones. En las dos líneas siguientes establecemos los extremos donde este espacio termina. En concreto nuestro espacio, worldAABB, va desde las coordenadas (-1000,-1000) hasta las coordenadas (1000,1000).

Después creamos un vector gravitacional. El primer valor, 0, corresponde a la gravedad horizontal. El segundo, 300, la gravedad vertical. Es decir, podemos crear una gravedad lateral :D

Otra opción que definimos es la posibilidad de que los objetos se “duerman” cuando entren en reposo. Esto lo hacemos asignando true a la variabel doSleep.

Finalmente creamos el objeto mundo, de la clase b2World, pasándole como parámetros al constructor las variables que acabábamos de crear.

Ver demo. En este caso, de nuevo no ocurre nada. Tan sólo hemos definido el modelo virtual, sin atarlo al canvas.

Paso 3 – Crear un suelo

En nuestro ejemplo va a haber un suelo. En resumen, tan sólo es una caja con posición fija ocupando toda la parte inferior visible. Este es el código que lo define:

            // Añade un suelo al mundo
            function createGround(world) {
                var groundSd = new b2BoxDef();
                groundSd.extents.Set(400, 30);
                groundSd.restitution = 0.0;
                var groundBd = new b2BodyDef();
                groundBd.AddShape(groundSd);
                groundBd.position.Set(400, 470);
                return world.CreateBody(groundBd);
            }

Además, añadimos una llamada a createGround(world) antes del return de la función createWorld(). Vamos a ver en qué consiste la creación del suelo:

Primero creamos un objeto b2BoxDef, que no es más que un objeto que define una forma rectangular. A continuación establecemos su tamaño (400 x 30), y su elasticidad (0.0). El factor de elasticidad 0.0 es el mínimo. Cuanto más alto, más elástico será el material de la forma que estamos definiendo.

A continuación creamos un cuerpo físico, al que le añadimos la forma que acabamos de crear. Por último establecemos las coordenadas en las que lo colocamos (400, 470), y lo añadimos al mundo.

Ver demo. Todavía no ocurre nada. Tan sólo hemos añadido la definición y las características del suelo, pero nuestro mundo todavía no se ha arrancado, ni se ha vinculado al canvas.

Paso 4 – Dibujando en el lienzo

Bien, ya vamos avanzando :) En este paso vamos a implementar el código encargado de dibujar en el canvas nuestro mundo virtual. Veamos cómo es:

            function drawWorld(world, context) {
                for (var b = world.m_bodyList; b; b = b.m_next) {
                    for (var s = b.GetShapeList(); s != null; s = s.GetNext()) {
                        drawShape(s, context);
                    }
                }
            }

            function drawShape(shape, context) {
                context.strokeStyle = '#ffffff';
                context.fillStyle = "black";
                context.beginPath();
                switch (shape.m_type) {
                    case b2Shape.e_polyShape:
                        {
                            var poly = shape;
    				        var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0]));
                            context.moveTo(tV.x, tV.y);

                            for (var i = 0; i < poly.m_vertexCount; i++) {
                                var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i]));
                                context.lineTo(v.x, v.y);
                            }
                            context.lineTo(tV.x, tV.y);
                        }
                        break;
                }

                context.fill();
                context.stroke();
            }

Si nunca has trabajado con canvas, este es un buen momento para que revises algún artículo. Por ejemplo, este de Mozilla, o este de Thinkvitamin.

Por un lado tenemos la función drawWorld, que se encarga de recorrer cada uno de los objetos del mundo, y cada una de las formas que contienen los objetos, para finalmente llamar a la función drawShape, que dibujará en el lienzo canvas la forma.

Por otro lado tenemos drawShape, que es la función que dibuja cada forma. En primer lugar establecemos los colores de línea (stroke) y de relleno (fill). Posteriormente indicamos que vamos a comenzar a dibujar en el lienzo. A continuación dibujamos la forma que corresponda; por ahora tan sólo dibujamos polígonos.

Para cada polígono, calculamos las coordenadas de su primer vértice. A continuación, dibujamos líneas entre cada uno de los siguientes vértices que encontremos, hasta finalmente, cerrar el polígono.

Y por último indicamos al contexto del lienzo que renderice el relleno y las líneas.

Ver demo. ¡Cómo es posible! ¡Todavía no ocurre nada! Esto es porque falta la función que da vida y que arranca al sistema. ¡Vamos al siguiente paso!

Paso 5 – Arrancando el sistema

Ya ha llegado el momento; vamos a unir nuestro sistema, con nuestro canvas, y a hacer que funcione en el tiempo. Para ello vamos a añadir una nueva función:

            function step(cnt) {
                var timeStep = 1.0/60;
                var iteration = 1;
                world.Step(timeStep, iteration);
                ctx.clearRect(0, 0, canvasWidth, canvasHeight);
                drawWorld(world, ctx);
                setTimeout('step(' + (cnt || 0) + ')', 10);
            }

Y vamos a hacer que la función anónima que se ejecuta al cargar la página, invoque a esta función, añadiendo al final:

step();

¿Qué hace step()? Cada vez que se invoca, realiza una nueva iteración en el sistema. En primer lugar establece los frames per second del sistema físico. Por convenio suele utilizarse a 60Hz, es decir, 1.0/60. En cada uno de estos instantes, se realizará una simulación con las equaciones físicas.

A continuación definimos el número de iteraciones por cada instante. Cuanto más bajo sea el valor, más precisión, así como mayor consumo de recursos.

En la tercera línea, indicamos al mundo que avance un nuevo paso. Es en este momento cuando se realizan todos los cálculos para ver en qué estado se encuentra cada objeto del sistema.

Después, limpiamos el lienzo, ya que en la siguiente línea hacemos una llamada a drawWorld para dibujar todo el sistema.

Finalmente establecemos un timeout para volver a ejecutar un paso (step()) en los siguientes 10 milisegundos.

Ver demo. ¡Viva! Al fin se ve algo :D No es gran cosa, pero ya tenemos dibujado el suelo en nuestro lienzo.

Paso 6 – Añadiendo más objetos al mundo

El paso anterior ha estado bien, pero vamos a hacer que nuestro mundo sea más rico en detalles. En concreto vamos a añadir unos polígonos que escriban el mensaje “Hola Mundo!” :) Esta es la función que lo crea:

            function createBox(world, x, y, width, height, fixed) {
                if (typeof(fixed) == 'undefined') fixed = true;
                var boxSd = new b2BoxDef();
                if (!fixed) boxSd.density = 1.0;
                boxSd.restitution = 0.0;
                boxSd.friction = 1.0;
                boxSd.extents.Set(width, height);
                var boxBd = new b2BodyDef();
                boxBd.AddShape(boxSd);
                boxBd.position.Set(x,y);
                return world.CreateBody(boxBd);
            }

            function createHelloWorld() {
                // H
                createBox(world, 50, 420, 10, 20, false);
                createBox(world, 90, 420, 10, 20, false);
                createBox(world, 70, 395, 30, 5, false);
                createBox(world, 50, 370, 10, 20, false);
                createBox(world, 90, 370, 10, 20, false);

                // O
        	createBox(world, 140, 435, 20, 5, false);
        	createBox(world, 155, 405, 5, 25, false);
       		createBox(world, 125, 405, 5, 25, false);
        	createBox(world, 140, 375, 20, 5, false);

        	// L
       		createBox(world, 200, 435, 20, 5, false);
       		createBox(world, 185, 400, 5, 30, false);

       		// A
       		createBox(world, 240, 410, 5, 30, false);
       		createBox(world, 278, 425, 5, 15, false);
       		createBox(world, 265, 405, 20, 5, false);
       		createBox(world, 280, 390, 5, 10, false);
       		createBox(world, 260, 375, 25, 5, false);

        	// M
        	createBox(world, 390, 355, 40, 5, false);
       		createBox(world, 360, 400, 10, 40, false);
       		createBox(world, 420, 400, 10, 40, false);
       		createBox(world, 390, 400, 5, 40, false);

       		// U
       		createBox(world, 460, 435, 20, 5, false);
       		createBox(world, 445, 405, 5, 30, false);
       		createBox(world, 475, 405, 5, 30, false);

       		// N
                createBox(world, 495, 415, 5, 30, false);
                createBox(world, 525, 415, 5, 30, false);
        	createBox(world, 510, 375, 20, 5, false);

       		// D
       		createBox(world, 558, 435, 18, 5, false);
       		createBox(world, 545, 405, 5, 25, false);
       		createBox(world, 575, 405, 5, 25, false);
       		createBox(world, 558, 375, 18, 5, false);

       		// O
        	createBox(world, 610, 435, 20, 5, false);
        	createBox(world, 595, 405, 5, 25, false);
       		createBox(world, 625, 405, 5, 25, false);
       		createBox(world, 610, 375, 20, 5, false);

       		// !
       		createBox(world, 650, 430, 10, 10, false);
       		createBox(world, 650, 380, 10, 40, false);
            }

Definimos en primer lugar la función createBox(). A partir de 6 parámetros crea una nueva caja en el mundo. En concreto los parámetros son: (1) objeto mundo, (2) coordenada x, (3) coordenada y, (4) ancho, (5) altura, y (6) si tiene o no posición fija.

Su código es prácticamente equivalente al código que usamos para crear el objeto suelo. Además, establecemos la densidad de los objetos que no tienen posición fija, y un coeficiente de rozamiento.

Luego tenemos la función createHelloWorld, que tan sólo crea rectángulos.

Por último, vamos a añadir una llamada a createHelloWorld(); antes de step(), en la función anónima que se ejecuta al cargar la ventana.

Ver demo. La cosa ya va cambiando :) Ahora tenemos ya un montón de cajas, que hasta vemos cómo se tambalean.

Paso 7 – Creando círculos

Nuestro programa ya sabe cómo crear rectángulos en el sistema (createBox()), y cómo dibujarlos en el canvas. Ahora vamos a añadir la opción de crear círculos, y de dibujarlos. Primero, vamos a añadir esta función:

            function createBall(world, x, y) {
                var ballSd = new b2CircleDef();
                ballSd.density = 1.0;
                ballSd.radius = 20;
                ballSd.restitution = 0.5;
                ballSd.friction = 0.5;
                var ballBd = new b2BodyDef();
                ballBd.AddShape(ballSd);
                ballBd.position.Set(x,y);
                return world.CreateBody(ballBd);
            }

Como vemos, es prácticamente igual que createBox(). En este caso al crear el objeto forma, no hemos elegido la clase b2BoxDef, sino b2CircleDef. Y además no tenemos la opción de especificar el tamaño, sino que todas las esferas tendrán el mismo radio, como vemos en “ballSd.radius = 20;”.

El sistema ya sabe añadir y reconocer círculos. Ahora vamos a indicarle cómo dibujarlos. En el switch de la función drawShape() que añadimos en el paso 4, vamos a añadir un nuevo caso:

                    case b2Shape.e_circleShape:
                        {
            				var circle = shape;
            				var pos = circle.m_position;
            				var r = circle.m_radius;
            				var segments = 16.0;
            				var theta = 0.0;
            				var dtheta = 2.0 * Math.PI / segments;
    				        context.moveTo(pos.x + r, pos.y);

    				        for (var i = 0; i <= segments; i++) {
            					var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta));
            					var v = b2Math.AddVV(pos, d);
            					context.lineTo(v.x, v.y);
            					theta += dtheta;
                            }
                            context.moveTo(pos.x, pos.y);
                            var ax = circle.m_R.col1;
                            var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y);
                            context.lineTo(pos2.x, pos2.y);
                        }
                        break;

Ahora en la función drawShape(), en caso de que le proporcionen un círculo, sabrá cómo dibujarlo. En concreto lo hará dividiéndolo en 16 segmentos, y con un poco de trigonometría, se calculará el vector de cada segmento, y sus coordenadas finales.

Además, tras dibujar el círculo, se añadirá un radio, que nos permitirá observar cómo realmente gira el círculo.

Ver demo. Nada nuevo, ya que todavía no hemos creado en ningún momento un círculo.

Paso 8 – Creando objetos con el ratón

Ahora vamos a ver cómo crear nuevos objetos al vuelto, haciendo clic con el ratón. Veremos también cómo impactan con los que ya hay, y cómo efectivamente se ve que se respetan las leyes físicas ;)

Vamos a añadir este código antes de la llamada a step() en la función anónima:

                Event.observe('canvas-world', 'click', function(e) {
                    if (Math.random() > 0.5) {
                        createBox(world, e.clientX, e.clientY, 10, 10, false);
                    } else {
                        createBall(world, Event.pointerX(e), Event.pointerY(e));
                    }
                });

Al hacer clic en el canvas, habrá un 50% de probabilidades de que se cree o bien una caja, o bien un círculo. Lo interesante es que se creará donde hayamos hecho clic, y desde ahí caerá :)

Ver demo final. Si quieres, también puedes descargarte el código completo, con el ejemplo final documentando cada línea.

Por supuesto, ahora se pueden añadir imágenes de fondo al canvas, o añadir sprites a nuestros objetos. Y desde luego también se pueden añadir sonidos. Aquí tienes un ejemplo prácticamente igual que el que acabamos de hacer, pero con sonidos y texturas:

Introducción a Doctrine 2

¡Hola! Últimamente sigo liado sumergiéndome en el mundo de ASP.NET, de ADO.NET, y sacando tiempo de debajo de las piedras para un pequeño proyecto en mi querido Zend Framework en PHP. Tras ver ADO.NET Entity Framework sentí la necesidad de usar algo igual en PHP; la solución se llama Doctrine :) La primera pregunta que nos deberíamos hacer es, qué es exactamente Doctrine. Respuesta rápida: Es un ORM de PHP. Pero analicémoslo un poco más.

En primer lugar, ¿qué diantres es un ORM? Viene de las siglas Object-relational mapping, que viene a traducirse como Mapeo objeto-relacional. En concreto es una técnica, o patrón arquitectónico, que permite comunicar dos sistemas distintos, típicamente una base de datos relacional con objetos de un lenguaje orientado a objetos, mediante un sistema que “mapea” (vincula) ambos sistemas. Mucha gente (yo mismo) ha creado a mano sus objetos mapeadores que, junto con objetos usando el patrón Active Record, daban un resultado muy similar a lo que un ORM nos ofrece. Pero oye, un ORM hace todo el trabajo sucio por nosotros ;)

Existen ORMs para la mayoría de lenguajes de programación. Por ejemplo, en Java es muy popular Hibernate, en .NET se utiliza ADO.NET Entity Framework, y en PHP el más usado es Doctrine (hay más). Doctrine está inspirado en Hibernate.

Pero veamos algunas de las características que nos ofrece un ORM como Doctrine 2:

  • Muy sencillo de configurar. De hecho, no es necesario mantener complejos ficheros de configuración XML.
  • Tiene su propio dialecto de SQL, llamado DQL (Doctrine Query Language).
  • Puede generar los modelos (las clases PHP que representan una fila de la BD) a partir de la base de datos. Sólo habría que definir las dependencias entre ellos
  • Del mismo modo, también puede generar la base de datos a partir de los modelos.

¡Fantástico! Él puede crear las tablas, y las consultas por nosotros. Pero veamos ahora lo sencillo que es usarlo:

/* Código de inicialización ... */
$user = new User();
$user->name = "john";
$user->password = "doe";
$em->persist($user);
$em->flush();

Para que este código funcione sólo tenemos que hacer dos cosas:

  1. Creamos el código de inicialización que cree el EntityManager $em. Es el objeto que en última instancia lleva a cabo las acciones.
  2. Definimos las entidades (módulos) especificando en comentarios (o en XML o en YAML) cómo se relaciona con la base de dato

Vamos a ver cómo instalarlo y después haremos un ejemplo sencillo en SQLite.

Instalación

Si bien podemos instalarlo a mano descargándolo desde la página de Doctrine, para esta guía asumiré que se ha instalado con PEAR. Para ello tenemos primer que instalar el canal:

sudo pear channel-discover pear.doctrine-project.org

Y después instalamos el ORM:

sudo pear install pear.doctrine-project.org/DoctrineORM-2.0.1

Ahora lo tenemos instalado. Podemos probar a ejecutar su utilidad de línea de comandos, y nos mostrará esto:

$ doctrine
Doctrine Command Line Interface version 2.0.1
Usage:
[options] command [arguments]
Options:
--help           -h Display this help message.
--quiet          -q Do not output any message.
--verbose        -v Increase verbosity of messages.
--version        -V Display this program version.
--ansi           -a Force ANSI output.
--no-interaction -n Do not ask any interactive question.
Available commands:
help                         Displays help for a command (?)
list                         Lists commands
dbal
:import                      Import SQL file(s) directly to Database.
:run-sql                     Executes arbitrary SQL directly from the command line.
orm
:convert-d1-schema           Converts Doctrine 1.X schema into a Doctrine 2.X schema.
:convert-mapping             Convert mapping information between supported formats.
:ensure-production-settings  Verify that Doctrine is properly configured for a production environment.
:generate-entities           Generate entity classes and method stubs from your mapping information.
:generate-proxies            Generates proxy classes for entity classes.
:generate-repositories       Generate repository classes from your mapping information.
:run-dql                     Executes arbitrary DQL directly from the command line.
:validate-schema             Validate that the mapping files.
orm:clear-cache
:metadata                    Clear all metadata cache of the various cache drivers.
:query                       Clear all query cache of the various cache drivers.
:result                      Clear result cache of the various cache drivers.
orm:schema-tool
:create                      Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output.
:drop                        Drop the complete database schema of EntityManager Storage Connection or generate the corresponding SQL output.
:update                      Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output.

Demo básica

Para este apartado, voy a utilizar una demo Sandbox (entorno de pruebas) prácticamente igual que la que usan en la documentación de Doctrine 2. En este artículo encontrarás el código de todos los ficheros necesarios. Además, al final del mismo encontrarás un enlace para poder descargarlo.

El proyecto consiste de sólo 4 ficheros:

  • Entities/Users.php – Modelo de usuarios
  • Entities/Address.php – Modelo de dirección
  • cli-config.php – Fichero PHP que contiene el código de inicialización necesario para usar la utilidad en linea de comandos ‘doctrine’. Cada vez que usemos la utilidad ‘doctrine’, tendremos que tener este fichero.
  • index.php – Código de inicialización típico de una aplicación web que utilice Doctrine 2.

Vamos a echar un vistazo a los ficheros. En primer lugar veamos cómo es el archivo cli-config.php:

<?php
// (1) Autocargamos clases
require_once 'Doctrine/Common/ClassLoader.php';
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine');
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Entities', __DIR__);
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Proxies', __DIR__);
$classLoader->register();

// (2) Configuración
$config = new \Doctrine\ORM\Configuration();

// (3) Caché
$cache = new \Doctrine\Common\Cache\ArrayCache();
$config->setMetadataCacheImpl($cache);
$config->setQueryCacheImpl($cache);

// (4) Driver
$driverImpl = $config->newDefaultAnnotationDriver(array(__DIR__."/Entities"));
$config->setMetadataDriverImpl($driverImpl);

// (5) Proxies
$config->setProxyDir(__DIR__ . '/Proxies');
$config->setProxyNamespace('Proxies');

// (6) Conexión
$connectionOptions = array(
'driver' => 'pdo_sqlite',
'path' => 'database.sqlite'
);

// (7) EntityManager
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);

// (8) HelperSet
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()),
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
));

¡Que nadie se asuste! Realmente es un código muy sencillo, y prácticamente siempre se usará algo igual o muy parecido. Para superar el medio inicial, vamos a ver detalladamente qué es lo que ha pasado. El proceso de inicialización consta básicamente de dos pasos: Por una parte hay que asegurarse de que las clases de Doctrine pueden cargarse, y por otra hay que crear una instancia del Entity Manager. Siguiendo paso a paso:

  1. En primer lugar autocargamos las clases necesarias (nota: para esto podríamos haber utilizado el autocargador de otro framework). En concreto autocargaremos 3 secciones:
    1. El propio Doctrine, que trae Common, DBAL, ORM y Symfony. Si lo hemos instalado con PEAR, podremos verlo en el directorio /usr/share/php/Doctrine.
    2. Entities, que es el directorio en el que almacenaremos nuestros modelos.
    3. Proxies, otro espacio que es necesario definir. Los proxies son subclases de nuestros modelos que el propio Doctrine crea automáticamente.
  2. Una vez que nos hemos asegurado de que todas las clases pueden cargarse automáticamente, comenzamos a crear nuestro objeto EntityManager. Para ello, lo primero es crear un objeto de tipo Configuration.
  3. El primer parámetro que vamos a configurar es la caché. Podríamos haber usado APC, el sistema de cachés de facto de PHP (aconsejable en un entorno en producción), pero para pruebas usaremos el método ArrayCache.
  4. A continuación establecemos el driver con el que mapearemos nuestra base de datos. Esto es necesario para  hacer que nuestros modelos se correlacionen con nuestras bases de datos. Existen tres drivers disponibles:
    1. XML, desde donde se puede definir como encajar el modelo con la base de datos mediante sintaxis XML.
    2. YAML, al igual que con XML, también se puede definir utilizando YAML.
    3. Anotaciones, que en mi opinión son una maravilla. Mediante etiquetas en comentarios PHP defines cómo se mapean los datos. Más abajo, cuando lleguemos a los modelos, veremos un ejemplo de uso.
  5. Ahora configuramos los proxies. Simplemente establecemos su directorio, y su espacio de nombres.
  6. Conexión. Es aquí donde establecemos dónde se se encuentra nuestra base de datos. Si ésta no existiera, Doctrine la crearía por nosotros. En nuestro ejemplo usaremos SQLite.
  7. Ya está todo listo: hemos configurado la Caché, el Driver para mapear, los proxies y la conexión. Ya podemos crear el objeto EntityManager.
  8. Por último, creamos un objeto de tipo HelperSet, que es el que utilizará la utilidad de línea de comandos para saber cómo conectarse. El objeto HelperSet debe tener dos helpers: db, para identificar la conexión, y em, el EntityManager.

Ya tenemos listo el fichero cli-config.php, que como dijimos, es el que se utiliza para conectar la utilidad de consola ‘doctrine’ con nuestro proyecto.

Ahora vamos a ver los modelos.

Entities/Adress.php
<?php
namespace Entities;

/** @Entity @Table(name="addresses") */
class Address
{
   /**
    * @Id @Column(type="integer")
    * @GeneratedValue(strategy="AUTO")
    */
   private $id;

   /** @Column(type="string", length=255) */
   private $street;

   /** @OneToOne(targetEntity="User", mappedBy="address") */
   private $user;

   public function getId()
   {
      return $this->id;
   }

   public function getStreet()
   {
      return $this->street;
   }

   public function setStreet($street)
   {
      $this->street = $street;
   }

   public function getUser()
   {
      return $this->user;
   }

   public function setUser(User $user)
   {
      if ($this->user !== $user) {
         $this->user = $user;
         $user->setAddress($this);
      }
   }
}
Entities/User.php
<?php
namespace Entities;

/** @Entity @Table(name="users") */
class User
{
   /**
    * @Id @Column(type="integer")
    * @GeneratedValue(strategy="AUTO")
    */
   private $id;

   /** @Column(type="string", length=50) */
   private $name;

   /**
    * @OneToOne(targetEntity="Address", inversedBy="user")
    * @JoinColumn(name="address_id", referencedColumnName="id")
    */
   private $address;

   public function getId()
   {
      return $this->id;
   }

   public function getName()
   {
      return $this->name;
   }

   public function setName($name)
   {
      $this->name = $name;
   }

   public function getAddress()
   {
      return $this->address;
   }

   public function setAddress(Address $address)
   {
      if ($this->address !== $address) {
         $this->address = $address;
         $address->setUser($this);
      }
   }
}

No me detendré a explicar cómo se definen los modelos. En la propia documentación de Doctrine puedes ver una introducción al mapeo. Lo importante aquí es que, mediante etiquetas en los comentarios, hemos definido la relación entre la entidad User y la entidad Address, y también hemos definido cada una de sus columnas (¡hasta podemos indicar que una columna sea autoincremental!).

Llegados a este punto, tenemos 3 ficheros ya creados: cli-config.php, Entity/User.php y Entity/Address.php. Desde el raíz de nuestro proyecto vamos a ejecutar lo siguiente:

$ doctrine orm:schema-tool:create

Si todo ha ido bien, nos saldrá un mensaje indicándonos que el esquema de base de datos se ha creado correctamente. Y si comprobamos el directorio, veremos que se ha creado un nuevo fichero, database.sqlite, que contiene la definición de nuestras dos tablas.

Usándolo en una aplicación web

Bien, ya hemos definido las entidades, hemos creado la base de datos, y sabemos cómo funciona el código de inicialización de Doctrine 2. Ahora vamos a ver el fichero index.php:

<?php

// (1) Autocargamos clases
require_once 'Doctrine/Common/ClassLoader.php';
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine');
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Entities', __DIR__);
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Proxies', __DIR__);
$classLoader->register();

// (2) Configuración
$config = new \Doctrine\ORM\Configuration();

// (3) Caché
$cache = new \Doctrine\Common\Cache\ArrayCache();
$config->setMetadataCacheImpl($cache);
$config->setQueryCacheImpl($cache);

// (4) Driver
$driverImpl = $config->newDefaultAnnotationDriver(array(__DIR__."/Entities"));
$config->setMetadataDriverImpl($driverImpl);

// (5) Proxies
$config->setProxyDir(__DIR__ . '/Proxies');
$config->setProxyNamespace('Proxies');

// (6) Conexión
$connectionOptions = array(
    'driver' => 'pdo_sqlite',
    'path' => 'database.sqlite'
);

// (7) EntityManager
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);

// (8) Código de prueba
$user = new \Entities\User();
$address = new \Entities\Address();

echo 'Hola mundo!' . PHP_EOL;

// Introduce aquí el código que maneje $user y $address

Si nos fijamos, los 7 primeros puntos son exactamente iguales que en cli-config.php. En el punto 8 creamos dos objetos de nuestro modelo, ya listos para usar. ¡Pero cuidado! Antes de poder hacer cambios en la base de datos SQLite, debes asegúrate de que el usuario de tu servidor web tiene permisos de escritura en el fichero database.sqlite. Ahora podríamos añadir el siguiente código de ejemplo:

$address->setStreet("Calle Río tinto, 12");
$em->persist($address);
$em->flush();

echo "Insertamos dirección<br />" . PHP_EOL;

$user->setName("Pedro");
$user->setAddress($address);
$em->persist($user);
$em->flush();

echo "Insertamos usuario<br />" . PHP_EOL;

Simplemente tenemos que acceder desde nuestro navegador a ese fichero (o también mediante “php index.php”) para que PHP ejecute el código. Una vez hecho, podemos consultar la base de datos mediante sqlite3:

$ sqlite3 database.sqlite
SQLite version 3.6.22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> select * from addresses;
1|Calle Río tinto, 12
sqlite> select * from users;
1|1|Pedro

¡Lo ha hecho todo él sólo por nosotros! Así de sencillo resulta trabajar con bases de datos con un ORM :) A partir de aquí empieza lo bueno: ya puedes guardar tus objetos de forma persistente en base de datos, sin tener preocuparte ni por crear las tablas, ni por SQL, ni nada. Céntrate en PHP :)

¡Gracias por leer!

Descargar el código fuente

Capturador de enlaces de Películas Yonkis

AVISO IMPORTANTE: Script obsoleto

Desde que el diseño de Películas Yonkis cambió, el script que desarrollé y que hay aquí colgado, se ha quedado obsoleto. Sería necesario reajustar las expresiones regulares. Ignoro si hay más cambios (como nuevas “trampas” en JavaScript).

Por favor, si has creado una versión actualizada, compártelo y añado una referencia a la URL donde lo tengas alojado.

¡Cuantísimo tiempo sin escribir! Por suerte o por desgracia, últimamente tengo muy poco tiempo para mí. Recientemente se han visto varias noticias sobre cómo dejar de depender de páginas de enlaces como Series Yonkis y compañía. Al hilo de ese asunto, alguien redactó el Proyecto Communis. En resumen consiste en crear una base de datos única con todos los enlaces a descargas. De esa base de datos podrán beber todos los servicios que lo deseen. En mi opinión, ése no es el camino a seguir; seguiría siendo un sistema centralizado, en tanto que tanto los ficheros como la base de datos estarían alojadas en algún servidor en particular. Es un sistema muy débil, pero es el que venimos usando ahora. La solución pasa por volver a un sistema completamente descentralizado como es el P2P.

Ahora bien, las páginas de descargas son hoy por hoy la solución más eficaz para acceder a los contenidos. En concreto, la web que más tráfico tiene (y entiendo que la mayor base de datos también) es Películas Yonkis. Y no sé a vosotros, pero a mí no me gusta, no me gusta nada. Está inundada de screeners, audios en latino, o versiones originales. No hay nada malo en ello, siempre y cuando hubiera alguna forma eficaz de poder filtrar contenidos. Y como no existe, pues me he hecho un programa (sí, otro más) para descargar su base de datos. Luego tengo una web de uso personal para yo (y mi señor padre) poder localizar esos enlaces.

Os presento mi script capturador de Películas Yonkis: crawler.php

Funciona igual que el que desarrolló Carlos Capote para Series Yonkis: Un script se encarga de descargar documentos web, y mediante expresiones regulares, captura su contenido. Finalmente, se salta la protección javascript ejecutando javascript en consola (con SpiderMonkey).

Características

Como principales características tiene:

  • Funciona en PHP, de modo que se puede ejecutar en prácticamente cualquier servidor web.
  • Es únicamente para Películas Yonkis, aunque está hecho de forma que sea fácilmente extensible para añadir otros portales (próximamente, CineTube).
  • Guarda una copia local en SQLite.
  • Puede ejecutarse en busca de nuevos enlaces. Sólo lleva unos segundos, ya que mediante peticiones HEAD comprueba si la URL ya se ha analizado: para ello, almacena una lista de URLs y sus cabeceras ETag y last-modified.
  • Evita duplicados, ya que almacena en base de datos cada URL de descarga.

Únicamente tiene dos dependencias: SpiderMonkey y cURL para PHP.

Base de datos

La base de datos tiene dos tablas, Movies y Movie_Links:

Movies:

  • id – identificador numérico.
  • title – Título de la película.
  • year – Valor numérico para el año.
  • description – Sinopsis.
  • genres – Lista separada por comas de géneros.

Movie_Links:

  • id – Identificador numérico.
  • movie_id – Identificador de la película a la que pertenece.
  • urls – Lista de URLs de esta descarga. En la mayoría de los casos será sólo una, pero hay películas que están divididas en partes.
  • lang – Idioma.
  • subs – Indica si tiene o no subtítulos.
  • quality – Calidad, de 1 a 5.
  • length – Duración en minutos.
  • enc – Método de codificación, por ejemplo DVD-Rip o Screener.
  • size – Tamaño total en MB.
  • format – Formato del fichero, por ejemplo AVI.
  • resolution – Resolución de salida.
  • type – Indica si es descarga directa o visualización online.
  • service – Almacena el servicio utilizado, por ejemplo, megaupload, megavideo, veoh, fileserve, etc.
  • broken – No usado todavía, sirve para notificar si un enlace está roto.

Y aquí dejo una copia de la base de datos (14.85MB). Puedes descargarla y ejecutar:
$ sqlite3 peliculas.db

Supongamos que queremos consultar todas las pelis de ciencia ficción que estén en megaupload, en DVD-Rip y en español de España, podríamos ejecutar:

$ sqlite3 peliculas.db
SQLite version 3.6.12
Enter SQL statements terminated with a ";"
sqlite> select distinct(m.title)
from movies m, movie_links ml where
ml.movie_id = m.id
and enc="DVD-Rip"
and genres like "%ciencia%"
and lang="spanish"
and service="Megaupload";

Mejorar el rendimiento de javascript con timers

Uno de mis últimos encargos consiste en una aplicación financiera para el navegador. En resumen, se trata de “traducir” una serie de hojas en Excel a javascript. El cómo lo he implementado da para otro artículo.

Básicamente el funcionamiento es el siguiente:

  • Creamos un campo de texto por cada celda del fichero Excel.
  • Muchas celdas tienen valores dinámicos, es decir, se generan a partir del valor de otras.
  • Definimos la fórmula que genera el valor de cada celda dinámica, y esa fórmula la añadimos a la pila de callbacks del evento ‘change’ de todas las celdas de las que depende.

Problema: Hay miles de celdas, prácticamente todas dinámicas. Esto implica que a menudo, al cambiar un sólo valor, se generan cientos o miles de cálculos. Javascript es single threaded, es decir, sólo se ejecuta una única hebra (para realizar cálculos, modificar el árbol DOM, gestionar eventos, o redibujar la pantalla). Si una operación muy costa en cálculos conlleva 8 segundos, la interfaz de usuario se quedará bloqueada hasta que finalice. Mal asunto.

El experto en usabilidad Jakob Nielsen dijo:

0,1 segundos es el límite para que el usuario perciba que el sistema responde inmediatamente.

¿Son los Web Workers la solución?

Quizás te preguntes qué son los Web Workers. Se trata de una especificación de WHATWG, el equipo que ha dado forma a lo que hoy conocemos como HTML5, para permitir programar aplicaciones multihebra en javascript, es decir, correr procesos en segundo plano.

Si bien es una funcionalidad que han empezando a incorporar recientemente los navegadores (a excepción de IE9), no es nada nuevo; el paquete Google Gears ya ofrecía estos mecanismos. De hecho, los web workers están basados en la API WorkerPool de Google Gears.

Por tanto, ¿son la solución al problema? Sí, pero…

A pesar de que una hoja de cálculo puede ser un ejemplo de libro para el uso de Web Workers, finalmente no los he utilizado. Existen algunas limitaciones, por cuestiones de seguridad, a la hora de usar Web Workers. A destacar, que no tiene acceso a:

  1. DOM.
  2. objeto window.
  3. objeto document.
  4. objeto parent.

Toda la comunicación con la hebra principal se realiza mediante mensajes. Esto interfiere con el código que ya tenía, de modo que implicaría reescribirlo en gran parte. No es una opción por desgracia.

Luego está el motivo de que no funcionan ni funcionarán en Internet Explorer.

Timers al rescate

Repasemos conceptos de sistemas operativos. En un sistema multitarea, en un momento dado no se pueden ejecutar más tareas que procesadores tiene el equipo. Por eso existe el planificador de procesos, que es la parte que se encarga de repartir el tiempo disponible entre los distintos procesos en ejecución. Si se asignan tiempos bajos de ejecución, el efecto será que están funcionando varios procesos concurrentemente. Sintetizando muchísimo, podríamos decir que el planificador de procesos se encarga de asignar bloques de tiempo a cada proceso.

Ahora repasemos los timers. ¿Qué hacen exactamente? Posponen la ejecución de un comando. Esto significa que, mientras se espera ese tiempo, el navegador queda libre para hacer otras cosas.

¿Y si añadimos pausas voluntarias en la propagación de eventos ‘change’ de las celdas? De esta forma, habrá pausas que permitirán “descongelar” la interfaz de usuario, emulando que los cálculos se están ejecutando en un segundo plano, haciendo que la interfaz de usuario sea fluida. Miremos el siguiente gráfico:

Gráfico de relación de tiempos con y sin timers

El color azul representa los cálculos. El verde, es cuando no realizamos cálculos y la interfaz responde a eventos (el equivalente a este proceso en sistemas operativos se llama idle).

En el primer caso no utilizamos timers. Lo que ocurre es que el navegador se queda congelado durante más de tres segundos.

Sin embargo, en el segundo caso añadimos pausas voluntarias, con lo que percibimos que en todo momento el navegador reacciona inmediatamente a nuestras acciones.

Demo

He hecho una sencilla demo. El script se encarga de calcular los números primos menores de 300.000. En un caso con timers y en otro sin él:

  1. Sin timers. La página se quedará congelada hasta que finalice el cálculo.
  2. Con timers. Podrás interactuar con la página mientras hace los cálculos.

Si miramos el código, veremos que hay algunas diferencias. En el primer caso, esto es lo que ocurre:

jQuery("#button").bind('click', function() {
  for (var i=1; i<=300000; i++) {
    if(isPrime(i)) {
      jQuery('#prime').append(" "+ i);
    }
  }

Comprobamos uno a uno cada número desde un bucle, y si es primo, lo mostramos en pantalla. Seguramente haga saltar un aviso en el navegador de que el script está llevando demasiado tiempo.

El segundo ejemplo es un poco más elaborado:

function comprobarPrimo(numero, fin,total) {
  var delay = 10;
  var heap = 500;
  if (isPrime(numero)) {
    jQuery('#prime').append(" "+ numero);
  }
  if(++numero < fin) {
    if (total%heap === 0) {
      setTimeout("comprobarPrimo("+numero+","+fin+","+(total+1)+")", delay);
    } else {
      comprobarPrimo(numero, fin, (total+1));
    }
  }
}

jQuery("#button").bind('click', function() {
  comprobarPrimo(1,300000,0);
});

En lugar de usar un bucle, usamos una función recursiva. Esto lo hacemos porque, cada vez que llamamos a setTimeout, se ejecuta el código que le sigue sin esperar el delay. Es decir, que al añadir un timer no pararíamos realmente toda la ejecución. Con una función recursiva corregimos esto.

Y definimos dos variables: delay, que es el retardo en milisegundos que se aplicará, y heap, que define cada cuántas llamadas se aplica delay. En este ejemplo se establece un delay de 20 milisegundos que se aplica cada 500 números primos. Los otros 499 se ejecutan sin retardo.

El resultado es completamente distinto al del primer ejemplo. Y únicamente añadimos pausas de 20 milisegundos cada 500 números primos. Por supuesto el tiempo de ejecución se ve incrementado.

Desarrollo web: de Unix a Windows

Un cliente me ha propuesto colaborar con el proyecto que están desarrollando. Tras decirme que utilizan .NET, le comenté que no lo conozco y que generalmente trabajo en entornos LAMP, a lo que me comentó que no cree que me fuera a suponer ningún problema familiarizarme con C#. ¿Y por qué no? Mentiría si dijera que no me apetecía aprender un poco de Windows y de sus tecnologías. La última vez que utilicé un sistema operativo de Microsoft para trabajar o estudiar fue a mediados de 2003. Desde entonces sólo he tenido sistemas Unix (Linux y Mac).

El equipo

Lo primero era hacerme con un equipo –la virtualización está muy bien para poder hacer pruebas en otros sistemas, pero no para utilizar estos sistemas como herramientas de trabajo. Por suerte, tenía uno acumulando polvo. Pero no me apetecía ni comprar ni tener 2 monitores, 2 ratones y 2 teclados, uno para el servidor Linux y otro para Windows, de modo que me compré un conmutador.

Con este fantástico cacharrito puedo compartir monitor, teclado y ratón con dos equipos. Simplemente tengo que usar un atajo del teclado para alternar entre uno y otro.

Instalación de Windows 7

El siguiente paso era instalar Windows 7 Professional. He elegido Windows 7 porque ya que me pongo, que al menos sea con lo último. ¡Faltaría más! La instalación ha sido muy sencilla, más que la de cualquier programa o driver de Windows :) Además, y como punto a favor, ha detectado e instalado prácticamente todo el hardware, y lo que no ha detectado, ha utilizado drivers compatibles (como el caso de la tarjeta gráfica).

Una vez instalado, el arranque ha sido rapidísimo, y el funcionamiento una vez iniciada sesión también muy aceptable. Sinceramente, no me lo esperaba.

Instalación del servidor web

El siguiente paso era instalar Internet Information Services (el servidor web por defecto de Windows). De nuevo, bastante sencillo. Desde el panel de control, en el apartado “Programs and Features”, accedemos a “Turn Windows features on or off”, y una vez ahí se selecciona “Internet Information Services”. Damos a “Ok” y listo, ya tenemos un servidor web. Vamos a localhost para comprobarlo:

Web por defecto al instalar IIS

Por lo que he podido ver, no es del todo imprescindible correr IIS, ya que Visual Studio crear un servidor ad-hoc en un puerto aleatorio a la hora de desarrollar una aplicación web.

Escritorio remoto

Afortunadamente trabajo desde distintos equipos: Linux, Mac y, ahora también, Windows. Al servidor Linux ya tengo acceso remoto gracias a ssh. No es necesario tener acceso al escritorio (a pesar de que lo tengo habilitado). Sin embargo en Windows sí lo necesito. De modo que ahora toca activar el escritorio remoto (Remote Desktop Services):

  1. Vamos a Control Panel.
  2. System and Security.
  3. System.
  4. Remote Settings.
  5. Se nos abre una nueva ventana. En el apartado “Remote Desktop” activamos la opción “Allow connections from computers running any version of Remote Desktop (less secure)”.
  6. Pulsamos en Apply, y ya está hecho.

Además, al tratarse de un servidor, he cambiado la política de ahorro de energía para que el equipo no se suspenda nunca, de modo que siempre pueda conectar a él.

Conectar desde KDE

Para conectar desde KDE/Linux es muy sencillo. Por defecto viene instalada la aplicación KRDC para conectar a escritorios remotos, bien por vnc, o por rdp. Éste último, es el protocolo propietario de Microsoft para escritorios remotos, pero además, está también implementado en sistemas Linux, por lo que es también posible compartir un escritorio en Linux con este mismo sistema. Pero volviendo al ajo, KRDC es un avanzado cliente de escritorio remoto, y al iniciar sesión podremos especificar rango de colores, resolución de pantalla, compartir sonidos, etc.

Escritorio remoto RDC en KDE

Configuración de KRDC

Conectar desde Mac

Actualización: Si vas a conectar a un Windows desde Mac con un teclado que no sea de EEUU, no uses el cliente de Microsoft. Echa un vistazo a este artículo para ver porqué, y cuál usar en su lugar.

Desde Mac es también sencillo. Microsoft tiene un cliente nativo de plataformas Mac para conectar a escritorios remotos. Descargamos la versión 2.0.1, la instalamos, y lo ejecutamos :) Desde el panel de preferencias también podemos configurar la conexión (resolución, sonidos, colores, distribución del teclado, etc.):

Instalación de ASP.NET

Ya está casi todo listo. Me dirijo a www.asp.net, y desde ahí accedo a la sección “Get Started“. Nos muestra una serie de pasos, en concreto uno para instalar ASP.NET y Visual Studio Express Tools. De nuevo bastante sencillo; descargamos el instalador, y éste se encarga de obtener todos los ficheros necesarios para la instalación. Una vez finalizado, ya tendremos instalado:

  • ASP.NET – Es el framework de desarrollo de .NET para crear aplicaciones web. Contiene toda la biblioteca .NET, y permite utilizar cualquiera de sus lenguajes.
  • ASP.NET MVC – Framework, basado en ASP.NET, que implementa el patrón MVC. Similar a la mayoría de frameworks MVC, con algunas diferencias (esto da para otro artículo).
  • Visual Web Developer 2010 Express – Es la versión freeware diseñada para estudiantes y aficionados. Existen muchas otras versiones de Visual Studio (Professional, Premium, Ultimate…), siendo la más barata de ellas 550€, aunque a través del programa DreamSpark se puede adquirir una licencia de 3 años por 100 dólares. Incluye también SQL Server.
  • SQL Server Express Database – Es la versión gratuita. Contiene algunas limitaciones, generalmente de rendimiento y de volumen de datos.

Hola mundo en ASP.NET MVC

Para finalizar el primer paso en mi cruzada, sólo queda hacer un “Hola mundo” en ASP.NET MVC. Como ahora vamos a ver, este punto no tiene mucho misterio. Tal y como ocurre con la mayor parte de frameworks de desarrollo web MVC, ASP.NET también tiene un generador inicial de código: se encarga de crear la estructura básica de directorios, layout global (Site.Master), controladores de inicio, vistas para cada acción de los controladores, bootstrap (Global.asax), modelos, etc. Además, utiliza por defecto ficheros de configuración.

Pero veamos cómo crear un nuevo proyecto:

  1. Arrancamos Microsoft Visual Web Developer 2010 Express.
  2. File > New Project
  3. En la columna izquierda seleccionamos “Visual C#”, y posteriormente “ASP.NET MVC 2 Web Application”. De este modo nuestro proyecto utilizará el framework MVC.
  4. Le damos un nombre a nuestro proyecto en el campo “Name”.
  5. Pulsamos “Ok”.

El proyecto ya está creado. Sólo tenemos que ejecutarlo accediendo a “Debug” > “Start Debugging” (también mediante el atajo de teclado F5). Se abrirá la página en un puerto aleatorio del navegador:

proyecto por defecto de ASP.NET MVC

¡Y con esto finaliza el primer salto de Unix a Windows!

Limitar la velocidad de descarga en PHP

Una de las cosas que tienen muchas páginas de descarga directa es la velocidad limitada para los clientes no premium. ¿Cómo lo hacen? Vamos cómo.

En este ejemplo vamos a suponer que queremos limitar la velocidad de descarga a 5kB/s. Para ello utilizaremos la extensión HTTP de pecl, en concreto la magia reside en la función http_throttle. Esta función recibe dos parámetros:

  1. número de segundos entre cada envío de bloques de datos.
  2. tamaño en bytes de cada bloque de datos.

De este modo, si le decimos que cada bloque de datos tenga un tamaño de 5000 bytes, y que entre cada petición debe transcurrir un segundo, conseguiremos que las descargas se realicen a 5kB/s. Realmente no es una velocidad estable, pero tiene muy pocas oscilaciones, la mayoría debidas a la conexión más que al servidor. Este es el código de ejemplo:

<?php http_throttle(1, 5000); http_send_content_type('application/pdf'); http_send_content_disposition('test.pdf'); http_send_file('test.pdf'); ?>

Como veis, únicamente establecemos la velocidad, las cabeceras para que el navegador sepa cómo manejar la petición, y finalmente mandamos un fichero de prueba llamado test.pdf que se encuentra en el directorio de trabajo actual.

El único problema está en la disponibilidad de la extensión HTTP. No creo que muchos servicios de alojamiento la tengan activada, aunque siempre se les puede pedir que lo instalen. Si quieres saber cómo hacerlo, simplemente tienes que hacer lo siguiente (en sistemas debian):

$ sudo pecl install http_pecl

Si te devolviera un código de error, quizás sea porque no tienes instaladas las herramientas de desarrollo de PHP, o alguna de las bibliotecas necesarias para compilar esta extensión. Tendrás que hacer lo siguiente:

$ sudo aptitude install php5-dev
$ sudo aptitude install libcurl3-openssl-dev

Ahora ya podrás compilarla. Sólo queda activarla en tu fichero php.ini. Añade esta línea:

extension=http.so

Y reinicia Apache con

$ sudo /etc/init.d/apache2 restart