Javascript in extremis

Hace unos meses, recogí a través de Twitter la siguiente idea: Desarrollar una pequeña aplicación que localizase la ubicación del usuario, y mostrase los resultados de las elecciones generales del 20 de Noviembre de 2011. A esto, había que añadir el siguiente hándicap: Dado que no tengo contratado ningún tipo de hosting, disponía solamente del subdominio que ofrece GitHub gratis a sus usuarios. La consecuencia de esto es que no tenía la posibilidad de tener código en el servidor, con lo cual, toda la lógica debía implementarla con JavaScript.

Me pareció una gran propuesta, ya que de entrada era un reto para enfrentarme a todo esto con las limitaciones que supone disponer sólo de código en el cliente. Pero al final, con más o menos arte, conseguí llegar a algo parecido a lo que pretendía.

El motivo para escribir este post es mostrar qué herramientas tuve que usar para enfrentarme a lo siguiente:

  • Geolocalizar la posición del cliente
  • Pasar de tener una latitud y longitud, al nombre concreto del municipio correspondiente.
  • Obtener el resultado electoral para dicho municipio.

Y también de paso, justificar por qué el resultado no es tan bueno como podía esperarse.

La parte correspondiente a geolocalización es posible con no mucha dificultad haciendo uso de la API que ofrece HTML5 para ello, junto con la V3 de la API de Google Maps:

$(function(){
	var geocoder = new google.maps.Geocoder();
	if (geocoder && navigator.geolocation) {
		navigator.geolocation.getCurrentPosition(function(pos){
			var latlng = new google.maps.LatLng(pos.coords.latitude, pos.coords.longitude);
			geocoder.geocode({ 'location': latlng }, procesarLocalizacion);
		});
	} else {
		$('#contenido').html('Su navegador no ofrece soporte para geolocalización.');
	}
});

El siguiente paso es el que requiere obtener el nombre del municipio en cuestión. Con la llamada a geocoder.geocode, es posible realizar la geolocalización inversa, pasando a la función de callback array donde cada elemento es un objeto con los 'address_components', indicando comunidad autónoma, provincia y municipio.

function obtenerComponente(obj) {
	return obj['address_components'][0]['long_name'];
}
function procesarLocalizacion(results, status) {
	if (status == google.maps.GeocoderStatus.OK) {
		// ...
		var comunidad = obtenerComponente(results[4]);
		var provincia = obtenerComponente(results[3]);
		// ...
	}
}

Problema final: Obtener los resultados electorales para el municipio concreto. Por desgracia, el Gobierno de España no ha sido tan gentil de ofrecerme personalmente otra API en la que todo encaje mágicamente. En ese momento, ya había tanteado algunas páginas web en las que se ofreciesen los resultados electorales, y afortunadamente la de RTVE.es los ofrecía en un formato muy decente: una tabla con partidos y porcentajes, donde la URL estaba formada por la comunidad autónoma, provincia y localidad. Las dos únicas pegas eran que no todos los nombres coincidían con los que ofrece la API de Google Maps, y el formato de las URLs variaba para comunidades autónomas uniprovinciales.

Pero he aquí el gran marrón: Esto es código JavaScript, en el lado del cliente por tanto, y debido a la Same oring policy no puedo acceder a este contenido. Para los dos inconvenientes anteriores, chapuza arriba, chapuza abajo, podía hacer algo. Sin embargo, este último problema presentaba menos margen de maniobra.

En este momento, Yahoo! me pone en bandeja la solución con su Yahoo! Query Language: Mediante un plugin de jQuery, esta auténtica maravilla me permitía salvar este último impedimento

if (status == google.maps.GeocoderStatus.OK) {
	var href, baseUrl = 'http://resultados-elecciones.rtve.es/generales/2011/congreso';
	// ...
	if(esComunidadUniprovincial(comunidad)) {
		href = [baseUrl, comunidad].join('/');
	} else {
		href = [baseUrl, comunidad, provincia, municipio].join('/');
	}
	$.ajax({
		url: href,
		type: 'GET',
		success: function(res) {
			var rows = $(res.responseText).find('tbody:first > tr');
			rows = $(rows).filter(function(index) {
				return rows.length - index > 3;
			});
			var tabla = $('<table></table>').html(rows);
			$('#contenido').html(tabla);
		}
	});
}

Y sí, ese $.ajax funciona, y gracias al mencionado cross-domain-ajax para jQuery, al cual recomiendo que echéis un vistazo cuando podáis.

Finalmente, dejo el link de mi obra, así como el enlace al repo de GitHub por si queréis ver el código de una forma más noble que con el Ctrl+U:

Saludos desde Diemen.

PD: Esta chapuzada fue realizada hace unos meses, con lo cual no tengo demasiado reciente el código citando y es posible que no hable con la propiedad suficiente. Pido disculpas a los damnificados por adelantado.

Anuncios

Listbox con scroll en Tkinter

Dentro de los widgets que contiene la librería Tkinter, uno de ellos es Listbox, que es el típico listado que aparece en casi cualquier interfaz de usuario. Sin embargo, cuando usamos este widget no tenemos un scroll que nos permita desplazarnos por los elementos que no quepan en la lista.

La forma de solucionar este inconveniente es crear una subclase que incorpore esta funcionalidad:

# tkhelpers.py
from Tkinter import *

class ScrollableListbox(Listbox):
    def __init__(self, master, *arg, **key):
        self.yscroll = Scrollbar(master, orient=VERTICAL)
        Listbox.__init__(self, master, yscrollcommand=self.yscroll.set, *arg, **key)
        self.yscroll['command'] = self.yview

Aunque esto presenta un gran inconveniente: Cuando llamemos a grid() para maquetar el listado, tendríamos que añadir una llamada distina a self.yscroll.grid() modificando la columna para que el scroll aparezca al lado. Esto dificulta el cálculo de los parámetros de grid() para el resto de widgets, por ejemplo:

# ...
label1.grid(row=0, column=0)
slb.grid(row=1, column=0)
slb.yscroll(row=1, column=1)

label2.grid(row=0, column=2)
other.grid(row=1, column=2)
# ...

Como veis, el scroll ha hecho desplazarse a los widgets label2 y other a la columna 2, cuando si están justo a la derecha lo lógico es que fuese la 1. Para solucionar el hecho de que el scroll “ocupe” una columna él sólo, la modificación es meter un Frame dentro de la subclase, de forma que el método grid trate a todo como un mismo bloque.

# tkhelpers.py
from Tkinter import *

class ScrollableListbox(Listbox):
    def __init__(self, master, *arg, **key):
        self.frame = Frame(master)
        self.yscroll = Scrollbar(self.frame, orient=VERTICAL)
        Listbox.__init__(self, self.frame, yscrollcommand=self.yscroll.set, *arg, **key)
        self.yscroll['command'] = self.yview
    def grid(self, *arg, **key):
        self.frame.grid(*arg, **key)
        Listbox.grid(self, row=0, column=0, sticky=N+S+E+W)
        self.yscroll.grid(row=0, column=1, sticky=N+S)

Listo. Una clase de 10 líneas con la que podemos tratar de forma transparente un ScrollableListbox como si fuese un Listbox. En el siguiente ejemplo, si cambiáis una clase por la otra veréis que no hay ningún error:

from Tkinter import *
from tkhelpers import ScrollableListbox

def onselect(event):
    widget = event.widget
    index = int(widget.curselection()[0])
    print 'Has seleccionado:\t%s' % widget.get(index)

root = Tk()

lb = Label(root, text=u'Selecciona un número')
slb = ScrollableListbox(root, width=20, height=5)
slb.bind('<<ListboxSelect>>', onselect)
for number in xrange(1, 15):
    slb.insert(END, str(number))
lb.grid(row=0, column=0)
slb.grid(row=1, column=0, padx=5, pady=5)

root.mainloop()

Ejecutando este código obtenemos el siguiente resultado (Con Lisbox nos aparecería la anterior imagen):

Espero que os sea útil. ¡Saludos!

Google, NYT y pastillas azules

Si preguntase qué tienen en común las empresas que venden una conocida pastillita azul con Google y el New York Times, costaría ver la conexión. Curiosamente, tienen algo bastante en común: una invención llamada reCaptcha.

Mientras navegamos por Internet, en numerosas ocasiones se nos muestra una imagen y nos piden introducir el texto que aparece. Suele ocurrir al crearnos una cuenta en alguna web, o al publicar comentarios en lugares donde no hace falta estar registrado. Esto ocurre porque ciertas empresas se dedican a crear “robots” (por ponerle algún nombre guay) que van de acá para allá intentando publicar comentarios con enlaces a páginas poco fiables para vendernos vaya a saber usted qué. Con el paso del tiempo, estos bichos se han ido sofisticando, y por tanto estos textos, llamados captchas, se han ido complicando e han ido introduciendo tal ruido a las imágenes que cuesta la misma vida descifrarlas. Robots para vender viagra e imágenes raras, lo típico que inventa el ser humano.

captcha facebook
Captcha de Facebook

Por otra parte, las herramientas de reconocimiento óptico de caracteres (OCR) han ido avanzando con el fin de conseguir una mejor calidad en la digitalización de textos. Sin embargo, eso de coger un impreso de hace 200 años y que un ordenador reconozca cada palabra escrita en él tiene una gran dificultad, a pesar de los avances que hay en los algoritmos que usan estas herramientas. Por lo general, en textos de baja calidad se obtienen unos resultados pésimos.

De esta forma, a un señor se le ocurrió una solución bastante ingeniosa: emplear una tarea para resolver otra, y así matar dos pájaros de un tiro. El funcionamiento de este nuevo tipo de captcha, llamado reCaptcha, es el siguiente: De las dos palabras que aparecen, una tiene la misma función que el captcha tradicional. Es decir, una vez enviado el formulario se comprueba que coinciden para verificar que no se trata de un robot. Sin embargo, la segunda es una palabra que el OCR no ha podido descrifrar, con lo cual la usa en el captcha para que un ser humano, que reconociendo palabras es un fuera de serie, escriba la palabra correcta. Tan ingeniosa la idea, que Google la adquirió para digitalizar los libros de Google Books y ediciones antiguas del New York Times.

recaptcha
Figura explicativa del sitio web de reCaptcha

En el ejemplo anterior, vemos como el OCR no ha podido leer la palabra morning, con lo cual le añade un poco de ruido y la usa para generar captchas. Obviamente, no toma como buena la primera, sino que la acepta cuando han coincidido suficientes veces.

De hecho, si escribiésemos margning upon, nos lo reconocería como válido aunque no esté bien, ya que el reCaptcha no tiene forma de saber que nos estamos equivocando (Si lo supiese, significaría que no tiene necesidad ninguna de adivinarla). Como mucho, estaríamos fastidiando un poco a los señores del New York Times, obligando al captcha a seguir haciendo comprobaciones para determinar cual es la palabra.

Como conclusión, he de decir que por desgracia se ofrece el reCaptcha para integrarlo con tu sitio web, pero no para usarlo como herramienta complementaria al OCR. Sería un detallazo por parte de Google para que organizaciones pudiesen usarlo.

Saludos,

Alex Rodas

Introducción a Yii

Yii es un framework MVC para el desarrollo de aplicaciones web en PHP. En la definición anterior podría cambiar sin problemas Yii por Zend, CakePHP, CodeIgniter, por citar algunos otros. Lo que me hace decantarme por Yii es mi experiencia con cada uno de ellos, y las comparativas que he encontrado cuando me informé en el tema.

Antes de profundizar en los pros y contras, algunos más subjetivos que otros, os dejo esta introducción para crear nuestra primera aplicación con Yii. Adelanto que mi experiencia ha sido muy positiva, y como veréis, apenas se tarda en tener algo tangible.

Paso 1: Instalación y requisitos

El primer paso es descargar el framework de la página oficial. Una vez descargado, descomprimirlo en la carpeta webroot del servidor. En mi caso, al utilizar XAMPP en Windows, es C:\xampp\htdocs. Para simplificar, renombro el directorio “yii-X.Y.Z.r” por “yii”. De esta forma, accediendo en la URL de nuestro navegador a localhost/yii/requirements veremos que pasa todos los requisitos, o como mucho dan un aviso en aquellos que no nos serán necesarios.

Paso 2: Webapp

Haciendo uso del script yiic en yii/framework, crearemos el esqueleto de nuestra aplicación “holamundo”.

% cd webroot
% yii\framework\yiic webapp holamundo

Listo. La estructura de directorios que nos genera es la siguiente:

webroot/
|- yii/
  |- framework/
  |- ...
|- holamundo/
  |- assets/
  |- css/
  |- images/
  |- protected/
    |- config/
    |- ...
  |- themes/
  |- ...

De los directorios de holamundo, el uso de css/images/  y themes/ resulta bastante evidente. En protected/ se encuentra el código PHP de nuestra aplicación, y dentro de config/ los ficheros de configuración de la misma.

Paso 3: Primer vistazo

Introduciendo en la URL de nuestro navegador localhost/holamundo estaremos en el index de nuestra recién estrenada aplicación. Si hacemos click en cualquier enlace del menú de navegación, veremos que se añade index.php?r=X/Y, siendo X e Y un controlador y una acción, respectivamente. Saltándonos por un momento lo que significa esto último, podemos transformar esta URL en una mucho más amigable, de la forma http://localhost/holamundo/X/Y. Esto lo realizaremos en el siguiente paso.

Paso 4: Editando el fichero de configuración

A diferencia otras soluciones como el XML de configuración de Zend, Yii hace uso de algo que para mi gusto es mucho más natural: Utilizar como configuración un array de arrays, “simulando” la estructura de un XML. Adelanto que no es un mega-array-cajondesastre, sino que se encuentra organizado por complementos, conexión a la base de datos, idioma, etc.

Este script se encuentra, dentro de la carpeta nuestro proyecto, en protected\config\main.php. Tiene una pinta tal que así:

<!--?php // uncomment the following to define a path alias // Yii::setPathOfAlias('local','path/to/local-folder'); // This is the main Web application configuration. Any writable // CWebApplication properties can be configured here. return array(   'basePath'=-->dirname(__FILE__).DIRECTORY_SEPARATOR.'..',
  'language'=>'es',
  #...
  // application components
  'components'=>array(
    'user'=>array(
      // enable cookie-based authentication
      'allowAutoLogin'=>true,
    ),
    // uncomment the following to enable URLs in path-format
    'urlManager'=>array(
      'urlFormat'=>'path',
      'showScriptName'=>false,
      'rules'=>array(
        '/'=>'/view',
        '//'=>'/',
        '/'=>'/',
      ),
    ),
    #...
  ),
  #...
);

Las líneas resaltadas son aquellas que he añadido. Exactamente, lo que se ha hecho es:

  • En la línea 10, establecer el idioma como español. Ojo, no se traduce toda la aplicación, sino mensajes como por ejemplo los que muestran los widgets.
  • En la línea 21, una vez descomentado el array de 'urlManager', añadir 'showScriptName'=>false,. Esto hace que no se muestre en los enlaces el bootstrap (el index.php al inicio de cada URL)

Además, es necesario crear nuestro .htaccess en el directorio del proyecto con el siguiente contenido

RewriteEngine on
# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

# otherwise forward it to index.php
RewriteRule . index.php

Listo también. Dentro del array de 'components' se encuentra en la variable 'db' , donde se especifica la conexión a la base de datos. Por defecto aparece para una base de datos SQLite dentro del directorio holamundo\protected\data. Justo debajo aparece comentado el código para hacerse con un SGBD como MySQL, indicando nombre de usuario y contraseña.

Conclusión

Siguiendo estos pasos, tenemos el esqueleto de nuestra aplicación con el componente de la URL amigable funcionando. Los requisitos más destacables son tener PHP >= 5.0 y Apache con mod_rewrite. En siguientes tutoriales explicaré cómo generar automáticamente el código de los modelos y las operaciones CRUD de los mismos, así como la autenticación de usuarios y la integración de los muchos complementos que se encuentran disponibles. Y cómo no, todo feedback es bien recibido.

¡Saludos!

“Syntactic sugar” y otros inventos

Hace ya bastantes años, cuando en la facultad nos enseñaban lo básico de Java y programación orientada a objectos, de manera anecdótica se mencionaba (incluso diría que de una forma un poco peyorativa) el término “azúcar sintáctico”. Este término sirve para referirse a determinados atajos, que funcionalmente no suponen nada, pero al programador le resultan más cómodos. Por poner un ejemplo, sustituir i = i+1; por i++;

Conforme he ido abandonando Java, me he ido dando cuenta del valor que tiene ya no sólo el azúcar sintáctico, sino en general aprovechar cualquier artificio que nos proporcione un lenguaje de programación para escribir programas más concisos. Un ingeniero, o en general cualquier programador, no debe escribir código para que lo entienda una máquina, sino para que lo entienda otra persona.

Ejemplo práctico: Implementar la clase Punto, con getters y setters para sus coordenadas X e Y, y también un método para sumar dos puntos, el cual devolverá un punto con la suma de sus coordenadas cartesianas. También una pruebecilla para crear dos puntos, sumarlos, y ver que todo va bien. En Java, como de costumbre, haríamos nuestro double getX(); y toda la pesca. Luego su correspondiente main haciendo su llamada a Punto p3 = p1.add(p2);. Nada nuevo bajo el Sol. Nótese el spanglish que sutilmente introducimos, haciendo uso de las palabras Punto, get, set y add.

Vámonos a otro lenguaje de programación, como Ruby. Nuestra clase Punto queda así:

class Punto
  attr_accessor :x, :y
  def initialize(x, y)
    @x, @y = x, y
  end
  def +(p)
    Point.new(@x + p.x, @y + p.y)
  end
end

p1 = Punto.new(0, 1)
p2 = Punto.new(2, 1)
p1 += p2

puts "P1 = #{p1.x},#{p1.y}"

¡Que tipo de brujería es esta! Ni más ni menos que el “azúcar sintáctico” transformado en código intuitivo. Algo de spanglish queda, pero es ya por las palabras reservadas del lenguaje. Pasemos a un lenguaje compilado como C#, para que no quede el argumento de que sólo los lenguajes interpretados se prestan a virguerías:

class Punto
{
  private double x;
  public double X
  {
    get { return x; }
    set { this.x = value; }
  }
  private double y;
  public double Y
  {
    get { return y; }
    set { this.y = value; }
  }
  public Punto(double x, double y)
  {
    this.X = x;
    this.Y = y;
  }
  public static Punto operator+(Punto point1, Punto point2)
  {
    return new Punto(point1.X + point2.X, point1.Y + point2.Y);
  }
}

Y para probarlo:

static void Main(string[] args)
{
  Punto p1 = new Punto(0, 1);
  Punto p2 = new Punto(2, 1);
  p1 += p2;
  Console.WriteLine("P1 = {0},{1}", p1.X, p1.Y);
}

En ambos casos, ese azúcar sintáctico nos permite acercarnos al lenguaje de la persona que lee el código. En este caso lo de menos es el spanglish, las convenciones de nombres, o lo avanzados que tengan que ser los conocimientos para saber cómo expresarlo en Ruby o C#. Lo importante es que semánticamente se expresa de una manera clara al programador, y sintácticamente es válido la máquina.

Tristemente, lo normal es que únicamente se valore la funcionalidad del código (compila o no), y no su expresividad. Java no es que se preste precisamente a esta clave de finuras. Sin embargo, para muchos es poco menos que el “lenguaje definitivo”, del que no se sale, y creo que nunca se le ha dado la suficiente importancia a, al menos, contrastarlo con cómo se podrían hacer las cosas en otro lenguaje.

También quiero dejar claro que esta no es una reflexión a lo loco, como si me pongo a hablar sobre el sexo de los ángeles. Detrás de todo esto hay cosas muy materiales, a fin de cuentas el software es tiempo y dinero. Como muy acertadamente se sostiene en este artículo, todos los lenguajes difieren en azúcar sintáctico del GOTO, y la clave está en que se es más productivo escribiendo en un lenguaje que encaja con la manera de pensar del ser humano.

Pongo como último ejemplo una versión simplificada de un ejercicio que se propuso para los cursos de Stanford sobre Software as a Service: comprobar si una cadena es un palíndromo en Ruby. Creo que la elegancia de las 5 líneas que ocupa hablan por sí solas

class String
  def palindrome?
    self.eql? reverse
  end
end

"saippuakauppias".palindrome?  # => true

Saludos,

Alex Rodas

“Clean Code”, de Robert C. Martin

Una percepción bastante extendida, sobre todo entre personas “atascadas” en los metodologías de desarrollo del software en cascada, es que dado un diseño, la implementación es trivial. Suponiendo que para ellos el significado de trivial es el mismo que para la RAE, me atrevo a matizar la frase: Una vez dado el diseño, una implementación con una seguridad prácticamente nula, inmantenible, etcétera, es trivial.

Por poner un ejemplo real, os presento el siguiente script para validar un email. Esto, damas y caballeros, está hecho por una persona que ha terminado una Ingeniería Técnica en Informática:

function esCorreoValido (cadenaEmail) {
  var ult = cadenaEmail.length - 1;
  var caracteresValidos = "abcdefghijklmnñopqrstuvwxyzABCDEFGHIJKLMNÑOPQRTSUVWXYZ1234567890@._-";
  if (cadenaValida (cadenaEmail, caracteresValidos) 
      &amp;&amp; cadenaEmail.indexOf('@',0)==cadenaEmail.lastIndexOf('@') 
      &amp;&amp; cadenaEmail.indexOf('@',0)!=-1 &amp;&amp; cadenaEmail.indexOf('.',0)!=-1 
      &amp;&amp; cadenaEmail.indexOf('@',0)!=0 &amp;&amp; cadenaEmail.indexOf('.',0)!=0 
      &amp;&amp; cadenaEmail.lastIndexOf("@")!=ult &amp;&amp; cadenaEmail.lastIndexOf(".")!=ult)
    return true;
  else
    return false;
}

Para qué perder el tiempo con expresiones regulares, habiendo un if interminable y cadenas tochas de las que a todos nos gustan. Dicho sea de paso, esta persona trabaja actualmente de analista para una empresa, que por respeto no mencionaré, y en su CV osa decir que tiene conocimientos altos de JavaScript.

¿Y a santo de qué viene esto? El “clean code” importa. El código bello, limpio, mantenible, no es una tarea para nada trivial. Afirmando los propios profesionales que la implementación es una tarea inferior, relegádola a la industria cárnica del intrusismo y la explotación de los recién titulados, se explica la calidad del software que al final acaban sufriendo los usuarios.

“Clean Code”, de Robert C. Martin, vale su peso en oro. Este libro, en mi opinión de lectura altamente recomendable, por no decir obligatoria, defiende las buenas prácticas de programación. Justifica, basándose en la experiencia de desarrolladores de prestigio internacional (¡gente que escribe libros y pica código!), el por qué es conveniente hacer las cosas de una manera y no de otra. La misma introducción es una lección de cómo el software implementado a la ligera hace quebrar una empresa.

En definitiva, detalla en sus capítulos cada mínimo detalle para conseguir un código lo más conciso y legible, desde comentarios hasta la indentación. Una función de 400 líneas debe causar bastante rechazo, y si no lo hará cuando uno tenga que debuguearla. De la misma forma, nombres de variables como boolean b = true;, o Integer e = 3;, no dicen absolutamente nada a alguien que no sea el que haya escrito el código, y serían fácilmente reemplazables por boolean isAuthenticatedUser = true; o Integer ellapsedTimeInDays = 3;.

Curiosamente, hace un tiempo leí este artículo en Forbes resaltando el valor de los buenos desarrolladores, recomendado por el propio Robert Martin.

Finalizo, después de todo este tocho, con una frase muy apropiada del señor Steve McConnell:

It’s OK to figure out murder mysteries, but you shouldn’t need to figure out code. You should be able to read it.

 

Añadir un captcha a un formulario con Yii

Hace unos meses hice una aplicación web con Yii, para mi gusto uno de los mejores framewoks MVC de PHP: Permite generar una gran cantidad de código a partir de la base de datos, no requiere ninguna configuración (como mucho el .htaccess si quieres ocultar el index.php), y hay todo tipo de widgets y componentes para “enchufar” directamente a nuestra aplicación.

Un ejemplo de esto último es cómo añadir un captcha a un formulario, para lo cual hace falta (y no miento) unas 20 líneas de código, incluyendo el HTML.

Lo primero es añadir en el modelo el campo de verificación:

class Foo extends CActiveRecord {
  public $verifyCode;
  public function rules() {
    return array(
      // ...
      array('verifyCode','captcha',
        'allowEmpty'=&gt;!CCaptcha::checkRequirements()),
    );
  }
  public function attributeLabels() {
    return array(
      // ...
      'verifyCode'=&gt;'Código de validación',
    );
  }
  // ...
}

Lo siguiente, añadir en el controlador donde se encuentra la acción que muestra el formulario:

public function actions() {
  return array(
    'captcha'=&gt;array(
      'class'=&gt;'CCaptchaAction',
      'backColor'=&gt;0xFFFFFF,
    ),
  );
}
public function accessRules() {
  return array(
    array('allow',
      'actions'=&gt;array('captcha'),
      'users'=&gt;array('*'),
    ),
    // ...
  );
}

Y por último, añadir en la vista:

<?php 
// ...
echo $this->renderPartial('_form', array('model'=>$model));
?>

Donde la vista parcial ‘_form’ contiene:

<pre><?php if(CCaptcha::checkRequirements()): ?>
  <div class="row">
    <?php echo $form->labelEx($model,'verifyCode'); ?>
    <div>
      <?php $this->widget('CCaptcha'); ?>
      <?php echo $form->textField($model,'verifyCode'); ?>
    </div>
    <div class="hint">Introduzca las letras que aparecen arriba.
    <br/>No hay distinción entre mayúsculas y minúsculas.</div>
    <?php echo $form->error($model,'verifyCode'); ?>
  </div>
<?php endif; ?>

De esta forma, nos queda el siguiente campo en el formulario:

captcha

Y todo esto, sin tener que preocuparnos por escribir código ninguno para que PHP cree una imagen aleatoria, ni implementar la verificación de que el texto sea correcto. Rápido y directo. Espero que os sea de ayuda.

¡Saludos!