PHP

Integración continua: phpcpd, detectando el código duplicado

0

PHPCPD es una aplicación que nos permite detectar fragmentos de código duplicado o muy parecido, de tal forma que podamos optimizarlo creando métodos o funciones que eliminen esa duplicidad.

Su instalación es simple:

pear channel−discover pear.phpunit.de
pear channel−discover components.ez.no
pear channel−discover pear.symfony.com
pear channel−discover pear.netpirates.net
pear install phpunit/phpcpd

Para ejecutar usamos:
phpcpd −−log−pmd ruta−donde−se−guardará−el−resultado−del−analisis/cpd.xml directorio−o−archivo−a−analizar

Integración continua: Coding standards para PHP

0

¿Qué son los coding standards?

Los coding standards son estándares de código, dicho de otra manera, son reglas que indican como debe escribirse el código, cuantos espacios debe tener una tabulación, en qué línea debe ubicarse un “else”, cómo debe escribir una función, método o clase, etc.

Los coding standards se utilizan principalmente en los CMS como WordPress o Drupal, esto permite que el código sea mucho más legible por diferentes personas.

La aplicación que vamos a instalar es PHPCS que nos permitirá revisar el código y comprobar si estamos siguiendo el standard correctamente.

Instalación

La instalación es sencilla, como prácticamente la mayoría de herramientas que utilizaremos para nuestro servidor de integración.

sudo apt−get install php−pear

sudo pear install php_CodeSniffer

En Ubuntu la aplicación se instala en /usr/share/php/PHP.

Una vez instalado, será necesario dar permisos de lectura al usuario www-data:

cd /usr/share/php/PHP
sudo chown −R www−data:www−data CodeSniffer
sudo chown www−data:www−data CodeSniffer.php
sudo chmod −R 775 CodeSniffer
sudo chmod −R 775 CodeSniffer.php

Con esto ya lo tenemos instalado.

En futuros post, instalaremos algunos Coding Standards y veremos el funcionamiento de esta herramienta.

Integración continua: Instalando y configurando Redmine

0

¿Qué es Redmine?

Redmine es una herramienta de gestión de proyectos, nos permite mantener un control de las tareas y errores a solucionar en ellos. Esta herramienta nos permite organizar los proyectos por versiones, o por springs si utilizas alguna metodología ágil. Dispone de gráficos Grantt, wiki, acceso al repositorio del proyecto, etc.; muchísimas características que nos facilitan el día a día en el desarrollo de proyectos.

Instalando Redmine

La instalación de Redmine es bastante sencilla, utiliza el siguiente comando:

apt−get install redmine redmine−mysql libapache2−mod−passenger

Una vez instalado y configurado creamos una carpeta en /var/www llamada redmine:
mkdir /var/www/redmine

A continuación, creamos un enlace simbólico con el contenido que se mostrará en el navegador:
ln −s /usr/share/redmine/public /var/www/redmine

Accediendo a Redmine desde un subdominio

Ahora configuraremos un subdominio para poder acceder a Redmine.

Comprueba que tienes activado el módulo passenger en apache2:

a2enmod passenger

Instala “bundle”, una gema de Ruby:
gem install bundle

Da permisos al usuario www-data:
usermod −aG root www−data
chmod 775 −R /usr/share/redmine

Modifica el archivo /etc/apache2/mods-available/passenger.conf añadiendo la siguiente línea:
PassengerDefaultUser www−data

Crea el VirtualHost en /etc/apache2/sites-available, con el siguiente contenido:

        ServerName redmine.ic.net

        DocumentRoot /var/www/redmine
        
                AllowOverride all
                Options −MultiViews
                PassengerResolveSymlinksInDocumentRoot on
        

Reinicia apache:
/etc/init.d/apache2 restart

Al igual que hicimos con Jenkins, será necesario que modifiques tu archivos hosts y añadas la siguiente línea:
192.168.1.40 redmine.ic.net

Sustituye la IP por la tuya.

Ahora, si escribes la dirección de Redmine en tu navegador podrás acceder a tu gestor de proyectos. El usuario para el administrador es “admin” y su contraseña “admin”.

A partir de aquí, es cosa tuya. Crea algunos proyectos, añade algunas versiones, tareas, errores, etc.

Integración continua: instalando y configurando Jenkins

0

Ya vistes la checklist del último post: http://www.interadictos.es/2014/08/11/integracion-continua-primeros-pasos/, ahora vamos a empezar a instalar y configurar algunas herramientas, para ir poco a poco uniéndolas.

¿Qué es Jenkins?

Jenkins es nuestro servidor de integración continua. Es una aplicación que permite automatizar una lista de tareas que le indiquemos. Estas tareas pueden ser iniciadas de forma manual, pero lo habitual será llamar a la primera tarea de la lista cada vez que se realice un commit en el repositorio, e ir concatenando tareas hasta finalizar la lista completa. Tranquilo si no te haces aun a la idea, a medida que vayamos avanzando con esta guía lo iras entendiendo.

Instalando Jenkins

Primero descargamos la key del servidor de Jenkins, que nos permitirá añadirlo al archivo sources.list:

wget −q −O − http://pkg.jenkins−ci.org/debian/jenkins−ci.org.key | sudo apt−key add −

A continuación lo añadimos al archivo sources.list. Puedes añadirlo de esta manera o añadirlo a mano:
sudo sh −c 'echo deb http://pkg.jenkins−ci.org/debian binary/ > /etc/apt/sources.list.d/jenkins.list'

Actuliazamos la lista de paquetes e instalamos:
sudo apt−get update
sudo apt−get install jenkins

Ahora necesitamos poder acceder a él a través de la red, para ello vamos a crear el subdominio jenkins.ic.net. Para poder acceder por el subdominio jenkins.ic.net es necesario crear un virtual host con proxy inverso, para ello instalamos varios módulos de Apache2 necesarios:
sudo a2enmod proxy
sudo a2enmod headers
sudo a2enmod deflate
sudo a2enmod proxy_http

Ahora creamos un virtualhost en /etc/apache2/sites-available llamado 001-jenkins.ic.net.conf (Podeis usar el nombre que quieras), con el siguiente contenido

        ServerName jenkins.ic.net  

        
                Order deny,allow
                Allow from all
         

        ProxyPreserveHost On
        ProxyPass / http://localhost:8080/
        ProxyPassReverse / http://localhost:8080/


La ruta habitual para acceder a Jenkins sería http://localhost:8080, si pudieramos acceder desde el mismo servidor de integración continua, si lo hicieramos desde fuera sería http://ic.net:8080. Para evitar tener que recordar el número de puerto utilizamos el archivo anterior. Nos facilitará la vida.

Ahora creamos un enlace simbólico de este archivo en /etc/apache2/sites-enabled.

sudo ln −s /etc/apache2/sites−available/001−jenkins.ic.net.conf /etc/apache2/sites−enabled/001−jenkins.ic.net.conf

Reiniciamos apache con:
sudo /etc/init.d/apache2 reload

En nuestro ordenador tendremos que editar el archivo hosts, para indicarle al pc a qué ip debe dirigirse para encontrar el alojamiento de jenkins.ic.net. Añadimos la siguiente línea al archivo (sustituye la IP por la de tu servidor de integración continua):
192.168.1.40 jenkins.ic.net

Ahora podrás escribir el subdominio en el navegador para acceder a la página principal de Jenkins.

A partir de aquí te recomiendo que explores todas las opciones de Jenkins, además de echarle un ojo a la documentación: https://wiki.jenkins-ci.org/display/JENKINS/Use+Jenkins

En la administración de Jenkins puedes acceder al apartado de plugins, revísalos y podrás hacerte una idea de las posibilidades de esta herramienta.

Integración continua: primeros pasos

0

Ya expliqué la semana pasada qué era eso de la integración continua, pues bien, ahora toca dar los primeros pasos. Obviamente necesitamos un ordenador que haga de servidor, una máquina virtual, o bien un servidor de verdad si lo que pretendes es utilizar de forma profesional.

Como sistema operativo para esta guía utilizaré Ubuntu 14.04. Tú puedes usar el que quieras pero tal vez te resulte más complicado encontrar algún software, con lo que tendrás que investigar más.

El hardware a usar dependerá de las necesidades que tengas, o para lo que quieras un servidor de integración continua. En mi caso utilizo un pc de gama baja con micro AMD, 8 GB de RAM, sin teclado, ratón, ni monitor (y por tanto sin escritorio gráfico), y con una tarjeta WiFi para conectar a internet. Y esto, para mí, es suficiente. Si lo fueses a usar en un grupo de desarrollo, lo mejor es usar un VPS o un servidor dedicado, ya que mejorará en gran medida el rendimiento.

Ya tenemos el hardware listo y nuestro sistema operativo está instalado, por tanto vamos a iniciar los primeros pasos para crear nuestro servidor de integración continua.

Continue reading “Integración continua: primeros pasos” »

Integración continua: Introducción

0

Supongo que os preguntaréis qué es eso de la integración continua, pues bien, os explico: se considera Integración Continua al conjunto de metodologías y procesos que se ejecutan durante el desarrollo de un proyecto de programación, con la diferencia de que estos procesos están centralizados y automatizados, lo que permite ejecutarlos cada pocas horas.

Dentro de los procesos automatizados se encuentran la descarga de los repositorios, la comprobación de los estándares de codificación, la ejecución de los test unitarios, la actualización de la base de datos, el despliegue al servidor de producción, etc. Cualquier proceso que se ejecute durante el desarrollo de un proyecto se puede integrar en este sistema.

Esta forma de trabajar nos permite a los desarrolladores detectar, antes de poner en producción el proyecto, posibles errores o ineficiencias, pudiendo solucionarlos y mejorando con ello la calidad del software que generamos.

Sirva este pequeño artículo como introducción a esta nueva sección del blog, ya que durante las próximas semanas iré publicando nuevos artículos para que podáis montar vuestro propio servidor de integración continua, me centraré en la automatización de procesos en proyectos PHP, ya sean basados en Frameworks (Symfony, Zend, etc.), CMS (WordPress, PrestaShop, etc.) o creados desde cero, aunque es muy posible que muchas cosas de las que os explique podáis utilizarlas para otros lenguajes de programación. Os explicaré tanto la instalación del software necesario como su configuración, además de como unir todo el software para que se ejecute de forma automática.

De momento os dejo con varios links para que le vayáis echando un ojo a eso de la Integración Continua:

– http://es.wikipedia.org/wiki/Integraci%C3%B3n_continua

Desarrollo PHP avanzado

Logo Cassandra

Cassandra 1.x y PHP para desarrolladores SQL: Clusters

2

Cassandra permite crear anillos o clusters de servidores de una forma muy sencilla, esto nos permitirá levantar nuevos servidores dentro de un cluster en cuestión de varios minutos.

 

Para ello tendremos que modificar la configuración de la BD.

En /etc/cassandra modificamos el archivo cassandra.yaml. Buscaremos la siguiente línea:

- seeds: "localhost"

Y sustituimos localhost por la ip local del servidor, en mi caso 192.168.1.10.

- seeds: "192.168.1.10"

A continuación modificamos las siguientes líneas:

listen_address: localhost

[...]

rpc_address: localhost

Por:

listen_address: 192.168.1.10

[...]

rpc_address: 192.168.1.10

Guardamos y reiniciamos el servidor.

Ahora el servidor con esta configuración será al que se conecten el resto de servidores del cluster.

Para el resto de servidores la configuración es parecida. En seeds mantenemos la ip del servidor principal y en listen_address y rpc_address ponemos la ip del servidor a unir al cluster:

- seeds: "192.168.1.10"

[...]

listen_address: 192.168.1.103

[...]

rpc_address: 192.168.1.103

Utilizo la ip 192.168.1.103 ya que es la que tengo configurada en mi cluster de prueba, tu deberías poner la ip correspondiente a la máquina donde estés configurando Cassandra.

Guardamos y reiniciamos la BD.

Esto es todo lo que hay que hacer para crear un cluster con Cassandra

Logo Cassandra

Cassandra 1.x y PHP para desarrolladores SQL: phpCassa (III)

4

Ya he tratado casi en su totalidad las funciones más básicas de PHPCassa y Cassandra, con lo que ya tendrás un conocimiento suficientemente amplio de lo que se puede hacer con ellos, el resto dependerá de la evolución de la BD y de las librerías (y de la experiencia que tengas xP).

Hoy trataré varios temas que por las consultas realizadas por varios usuarios se merece un post aparte en el que se pueda tratar de una forma más amplia. Estos temas son: ordenar registros, crear keys para los registros y contar registros.

 

ORDENAR REGISTROS

Cassandra dispone de varios tipos de comparadores y subcomparadores para ordenar los registros. El más habitual es UTF-8 pero hay varios más. Si vamos a Cassandra Cluster Admin y accedemos al formulario de creación de columns family, podremos pasar el ratón por los interrogantes que hay a la izquierda de los campos para poder ver los diferentes tipos de comparadores:

Tipos de comparadores

Tipos de comparadores

Como puedes ver, estos comparadores le dicen a Cassandra como ordenar los registros. Si has seguido este tutorial ya conocerás al menos uno de los comparadores (utf-8) por tanto vamos a ver como ordena Cassandra los registros con este comparador.

Para este primer ejemplo vamos a crear una column family llamada column_family_order_utf8 de tipo standard y el tipo de comparador UTF8Type.

Vamos a nuestro archivo test.php y escribimos el siguiente código:

[codesyntax lang="php"]

<?php

$data = array();
$data[5] = array(
    'title' => 'Apache Cassandra',
    'license' => 'Open Source',
    'category' => 'no-sql',
);
$data[3] = array(
    'title' => 'MongoDB',
    'license' => 'Open Source',
    'category' => 'no-sql'
);
$data[1] = array(
    'title' => 'Neo4j',
    'license' => 'Open Source',
    'category' => 'no-sql',
);
$data[4] = array(
    'title' => 'MySQL',
    'license' => 'Open Source',
    'category' => 'sql'
);
$data[2] = array(
    'title' => 'MariaDB',
    'license' => 'Open Source',
    'category' => 'sql',
);

foreach($data as $key => $value){
  $cass->guardar('column_family_order_utf8', $key, $value);
}

[/codesyntax]

Este código guardará un array desordenado en nuestra column family. Una vez ejecutado el script accedemos a Cassandra Cluster Admin y visualizamos los registros de column_family_order_utf8, nos mostrará el siguiente resultado:

Listado de los registros guardados

Listado de los registros guardados

Como puedes observar los registros no se han ordenado, sin embargo cada una de las columns de cada registro sí están ordenadas por orden alfabético. Esto sucede porque los comparadores no ordenan por keys o claves de los registros sino por columns, aunque hay un caso especial, si la column family es super los registros si estarán ordenados. Veamoslo.

Creamos una column family llamada column_family_utf8_super con el tipo de comparador UTF8Type y el tipo de column family como super. En nuestro archivo test.php añadimos el siguiente código:

[codesyntax lang="php"]

<?php

$data = array();
$data[5] = array(
    'title' => 'Apache Cassandra',
    'license' => 'Open Source',
    'category' => 'no-sql',
);
$data[3] = array(
    'title' => 'MongoDB',
    'license' => 'Open Source',
    'category' => 'no-sql'
);
$data[1] = array(
    'title' => 'Neo4j',
    'license' => 'Open Source',
    'category' => 'no-sql',
);
$data[4] = array(
    'title' => 'MySQL',
    'license' => 'Open Source',
    'category' => 'sql'
);
$data[2] = array(
    'title' => 'MariaDB',
    'license' => 'Open Source',
    'category' => 'sql',
);

$cass->guardar('column_family_order_utf8_super', 'databases', $data);

[/codesyntax]

Una vez ejecutado este script nos vamos a nuestra column family y nos mostrará lo siguiente:

Listado de registros en la column family

Listado de registros en la column family

Como puedes observar ahora sí están ordenados los registros, pero claro ahora están dentro de una super column.

En Cassandra la ordenación se realiza a las columnas del registro o los registros dentro de las super columns, si creasemos otra super column, esta no se ordenaría con respecto a la ya creada, ‘databases’.

Aunque esta ordenación puede ser útil y totalmente funcional, para los desarrolladores que vengan de las bases de datos relacionales les puede resultar confuso. Por ejemplo, para guardar los comentarios de un post de un blog podríamos utilizar este tipo de ordenación perfectamente, ya que podemos utilizar el id del post como key de la super column (el ‘databases’ de la imagen anterior se sustituiría por el id del post) y el timestamp (como este 1337787675.32246500) para la key de los registros. De esta forma tendríamos ordenados los comentarios por tiempo y por post. Si quisieramos recuperar los comentarios de un post concreto solo tendríamos que pasarle al método correspondiente el id del post como key de la super column.

¿Y si quisieramos guardar los post del blog? En este caso una column family super nos lo podría resolver indicando como key de la super columnposts‘  y utilizando como key en los registros el timestamp. Pero si quisieramos hacer busquedas utilizando indices secundarios una column family super no nos valdría, ya que los indices secundarios no funcionan en ellas.

Para ello habría que crear una column family standard para los post, que nos permitirá usar los indices secundarios, y una column family super para guardar las keys de los post dentro de una super column con key, por ejemplo, ‘key_posts‘. Para hacer la consulta habría que recuperar las keys de los posts a mostrar en primer lugar, y después, utilizando un método de PHPCassa que aun no hemos usado recuperar los registros correspondientes a esas keys.

El método que deberíamos añadir a nuestro archivo cassandra.php sería el siguiente:

 

[codesyntax lang="php"]

<?php

  function obtenerMultiplesRegistros($name_columnFamily, $multiKeys, $super_column = ''){

      if (!empty($super_column)){
        $column_family = new SuperColumnFamily($this->conexion, $name_columnFamily);
        $result = $column_family->multiget_super_column($multiKeys, $super_column);
      }
      else{
        $column_family = new ColumnFamily($this->conexion, $name_columnFamily); 
        $result = $column_family->multiget($multiKeys);
      }

      return $result;

  }

[/codesyntax]

 

A este método le pasamos el nombre de la column family standard donde hemos guardado los posts y un array con las keys a recuperar, nos devolverá un array con los datos de cada uno de los posts que le hemos pedido… y en el orden en el que le hayamos pasado las keys a recuperar.

Existe otra forma de ordenar registros, pero además de que en versiones posteriores de Cassandra desaparecerá y provoca problemas de rendimiento, yo no la recomiendo, ya que pierdes todos los beneficios que ofrece Cassandra. Aun así os dejo un link para que le echéis un ojo xD:

http://ria101.wordpress.com/2010/02/22/cassandra-randompartitioner-vs-orderpreservingpartitioner/

Y os dejo otro link para que tengais algo más de información respecto a ordenar registros en Cassandra:

http://ayogo.com/blog/2010/04/09/sorting-in-cassandra/

 

CREAR CLAVES O KEYS PARA LOS REGISTROS

Como has podido ver en el apartado anterior, para ordenar los registros es muy importante tener una key de cada registro que sea única y siempre vaya en orden. Esto las bases de datos relacionales nos lo dan ya hecho, sin embargo en las bases de datos no-sql esto no es así.

Las bases de datos no-sql se crearon para optimizar la lectura y escritura de los registros en la base de datos, esto provocó que algunas funcionalidades muy útiles en las bases relaciones no tuvieran cabida en las no-sql. Por tanto para mantener en orden los registros debemos crear nosotros nuestras propias claves.

PHPCassa dispone de un método para crear una clave aleatoria basada en el timestamp.

Para utilizarla primero añadiremos la clase a nuestra lista:

[codesyntax lang="php" highlight_lines="11"]

<?php

use phpcassa\Connection\ConnectionPool;
use phpcassa\ColumnFamily;
use phpcassa\SuperColumnFamily;
use phpcassa\ColumnSlice;
use phpcassa\SystemManager;
use phpcassa\Schema\StrategyClass;
use phpcassa\Index\IndexExpression;
use phpcassa\Index\IndexClause;
use phpcassa\UUID;

[/codesyntax]

Ahora crearemos un nuevo método en nuestro archivo cassamdra.php que devolverá la clave:

 

[codesyntax lang="php"]

<?php

  public function obtenerUuid(){

      $util = new UUID;

      return $util->import($util->uuid1());
  }

[/codesyntax]

 

Como me está quedando un post muy largo os voy a dejar algo de deberes xD. Sería interesante probarlo tanto con column families standard y super, además de con un tipo de comparador UTF8Type y TimeUUIDType.

Como el chorro de letras y números que devuelve es muy poco legible yo suelo utilizar mi propio sistema de claves, normalmente utilizo el microtime() de php y un par de ids de lo que se esté guardando: el id del usuario, el id de lo que se ha creado, etc. Algo que lo diferencie del resto. Para la clave primero pongo los segundos que devuelve microtime() y después los microsegundos. Para que os hagais una idea sería algo así:

$key = $segundos . ‘.’ . $microsegundos . ‘_’ . $id_post . ‘_’ . $id_usuario;

De esta forma le estamos dando una clave para los ejemplos del anterior apartado, más concretamente para los comentarios en un post.

 

CONTAR REGISTROS

El último apartado de este post. Contar registros. Esto es algo que Cassandra no lleva muy bien, sobre todo con grandes cantidades de información.

Cassandra cuando recibe una petición de lectura/escritura utiliza unas tablas en memoria llamadas memtables (el que le puso el nombre de rompió la cabeza pensando). Cuantas más peticiones reciba Cassandra más memtables creará, de esta forma permitirá una cierta consistencia de la información almacenada en la base de datos.

Cuando le pedimos a phpCassa que nos devuelva el número de registros en una column family o en una super column concreta, lo que está haciendo en realidad es hacer una petición a Cassandra de todos los registros que haya en la column family o en la super column. Cuando esto ocurre Cassandra guardará estos registros en la RAM del servidor antes de enviarlos a phpCassa, y sí Cassandra no cuenta los registros, porque no está pensada para eso, es phpCassa la que cuenta los registros y te devuelve el número concreto.

El método que utiliza phpCassa para contar registros es el que os muestro a continuación, dentro de un método para añadir al archivo cassandra.php:

 

[codesyntax lang="php"]

<?php

  public function contar_registros($name_columnFamily, $key, $super_column='', $range_start='', $range_end=''){

      $column_slice = new ColumnSlice($range_start, $range_end);

      if (!empty($super_column)){
        $column_family = new SuperColumnFamily($this->conexion, $name_columnFamily);
        $result = $column_family->get_subcolumn_count($key, $super_column, $column_slice);
      }
      else{
        $column_family = new ColumnFamily($this->conexion, $name_columnFamily); 
        $result = $column_family->get_count($key, $column_slice);
      }

      return $result;

  }

[/codesyntax]

 

Esta forma de contar registros provoca que Cassandra consuma todos los recursos de RAM del servidor, con lo que no os la recomiendo.

La mejor manera de contar registros con Cassandra es utilizar una column family con contadores, de tal forma que en nuestro código cada vez que se guarde un registro en la bd se aumente el contador que lleva la cuenta de los registros guardados. En el caso de que se eliminen, habría que disminuirlo. Y en el caso de que el registro se esté actualizando controlar que no aumente el contador xD.

Cualquier duda que tengais intentaré responderla lo mejor posible en los comentarios.

Logo Cassandra

Cassandra 1.x y PHP para desarrolladores SQL: phpCassa (II)

0

En el anterior post traté las acciones habituales que realizamos con Cassandra: guardar, actualizar y eliminar. Sin embargo aun quedan dos acciones muy útiles por tratar: los contadores y los indices secundarios.

 

CONTADORES

Los contadores son en realidad un tipo especial de validador de Column Family.

 

CREAR UN CONTADOR 

Para crearla procedemos de la siguiente manera:

– Accedemos a Cassandra Cluster Admin:

Página principal de Cassandra Cluster Admin

– Entramos en el keyspace con el que estamos trabajando:

Detalle del keyspace my_keyspace

– Pulsamos en Create New Column Family y escribimos los siguientes datos:

Datos para crear una Column Family con contadores

– En Default Validation Class el texto completo a escribir es:

org.apache.cassandra.db.marshal.CounterColumnType

– Y pulsamos el botón Create Column Family.

Varias cosas sobre los CounterColumn:
– Para crear los CounterColumns es obligatorio indicar en Default Validation Class la clase de validador correcto. Indicado más arriba.
– Los contadores puedes tener números positivos o negativos.
– Los CounterColumns no son un sustituto de el autoincrement de las bases relacionales.

Ahora que ya está creada la Column Family es hora de programar.

 

GUARDAR Y/O ACTUALIZAR UN CONTADOR 

En nuestra clase en cassandra.php creamos el siguiente método:

 

[codesyntax lang="php"]

<?php

  public function guardarContador($name_columnFamily, $key, $column, $value=1, $super_column=NULL){

    try {
      if (!is_null($super_column)){
        $column_family = new SuperColumnFamily($this->conexion, $name_columnFamily);
        $column_family->add($key, $super_column, $column, $value);
      }
      else{
        $column_family = new ColumnFamily($this->conexion, $name_columnFamily);
        $column_family->add($key, $column, $value);
      }
      return true;
    }
    catch(Exception $e){
      return false;
    }
  }

[/codesyntax]

 

Con este método podremos incrementar o disminuir el contador.


$name_columnFamily
: Es el nombre de la column family donde se encuentra el contador a modificar.

$key: Clave del registro donde se encuentra el contador.
$column: Nombre del contador.
$value: Valor a añadir al contador.
$super_column: Clave de la super column donde se encuentra el contador.

Añadimos el siguiente código a nuestro archivo test.php:

[codesyntax lang="php"]

<?php

if ( $cass->guardarContador('column_family_counter', 'post', 'num_total_post', 1)){
  print "El contador se ha actualizado correctamente<br />";
}else{
  print "Error al actualizar el contador<br />";
}

$result = $cass->obtener('column_family_counter', 'post');

print_r($result);

[/codesyntax]

Como ves, para recuperar un contador se utiliza el mismo método que para obtener cualquier otro registro. El resultado de este código sería:

El contador se ha actualizado correctamente
 Array ( [num_total_post] => 1 )

 

PONER A CERO EL CONTADOR

Para poner a cero un contador hay que restarle el valor que tenga en ese momento el contador. En test.php escribiríamos lo siguiente:

[codesyntax lang="php"]

<?php

if ( $cass->guardarContador('column_family_counter', 'post', 'num_total_post', ($result['num_total_post']*-1))){
  print "El contador se ha actualizado correctamente<br />";
}else{
  print "Error al actualizar el contador<br />";
}

$result = $cass->obtener('column_family_counter', 'post');

print_r($result);

[/codesyntax]

Y el resultado:

El contador se ha actualizado correctamente
 Array ( [num_total_post] => 0 )

 

INDICES SECUNDARIOS

Los indices secundarios son un objeto especial en Cassandra que nos permite realizar búsquedas por columnas concretas.

Estos índices solo funcionan en las column family standard, con lo que su uso se ve bastante limitado.

 

 

CREAR UN INDICE SECUNDARIO

Para ello vamos a nuestro Cassandra Cluster Admin y en la column family my_column_family_standard pulsamos en Create Secondary Index:

Detalle de la ubicación del botón Create Secondary Index

Detalle de la ubicación del botón Create Secondary Index

– Nos aparecerá el siguiente formulario:

Detalle del formulario para crear un índice secundario

Detalle del formulario para crear un índice secundario

– Para nuestro cometido vamos a crear el siguiente índice secundario:

Formulario con los datos para el ejemplo

Formulario con los datos para el ejemplo

–  Pulsamos en Add Secondary Index.

 

REALIZAR UNA CONSULTA CON UN INDICE SECUNDARIO 

Nos vamos a nuestro archivo cassandra.php y añadimos el siguiente método:

[codesyntax lang="php"]

<?php

  private function getIndexOperator($value){
    switch ($value){
      case '=':
        $operator = 'EQ';
        break;
      case '>=':
        $operator = 'GTE';
        break;
      case '>':
        $operator = 'GT';
        break;
      case '<=':
        $operator = 'LTE';
        break;
      case '<':
        $operator = 'LT';
        break;
      default:
        $operator = 'EQ';
        break;
    }

    return $operator;
  }

[/codesyntax]

Este método nos permitirá trabajar con operadores diferentes a la igualdad, pudiendo utilizar >,<,>=,<=.

Pero para poder trabajar con índices es necesario incluir en nuestro listado de namespaces dos nuevas clases, tal y como se muestra en el siguiente código:

[codesyntax lang="php" highlight_lines="9,10"]

<?php

use phpcassa\Connection\ConnectionPool;
use phpcassa\ColumnFamily;
use phpcassa\SuperColumnFamily;
use phpcassa\ColumnSlice;
use phpcassa\SystemManager;
use phpcassa\Schema\StrategyClass;
use phpcassa\Index\IndexExpression;
use phpcassa\Index\IndexClause;

[/codesyntax]

A continuación creamos el método que generará la consulta con los índices secundarios.

 

[codesyntax lang="php"]

<?php

  public function obtenerPorIndices($name_columnFamily, $arrayColumnsValues, $range_start = "", $range_end = '', $column_count = 100, $invertir_orden = false){

    try{
      // Creamos el objeto
      $column_family = new ColumnFamily($this->conexion, $name_columnFamily);

      // Inicializamos las variables
      $index_exp = array();
      $registros = array();

      // Creamos un array de index_expression
      foreach($arrayColumnsValues as $key => $value){
        if (array_key_exists('operator', $value)){ 
          $op = $this->getIndexOperator($value['operator']); 
        }
        else{ 
          $this->getIndexOperator('='); 
        }

        $column = $value['values'];
        $key_column = key($column);

        $index_exp[] = new IndexExpression($key_column, $column[$key_column], $op);            
      }

      // Creamos la index_clause
      $index_clause = new IndexClause($index_exp, $range_start, $column_count);

      // Creamos la column_slice
      $column_slice = new ColumnSlice($range_start, $range_end, $column_count, $invertir_orden);

      // Recuperamos los registros
      $rows = $column_family->get_indexed_slices($index_clause, $column_slice);

      foreach($rows as $key => $columns){
        if (!array_key_exists($key, $registros)){ $registros[$key] = null; }
        $registros[$key] = $columns;
      }
      return $registros;
    }catch (Exception $e){
      return false;
    }
  }

[/codesyntax]

 

Con este método podremos buscar registros con varios índices secundarios.

Lo primero que hacemos en este método es crear el objeto ColumnFamily que nos proveerá de los métodos para hacer la consulta con indices secundarios. A continuación inicializamos varias variables necesarias para el método.

Dentro de un bucle creamos un array de IndexExpression(). Este objeto le indicará a Cassandra qué indices, con qué valores y qué operador se deben buscar los registros.

Seguidamente creamos la iIndexClause(), que nos permite indicarle la key desde la que comenzará a recuperar registros y el número de registros a obtener.

Por último recuperamos los registros utilizando el método get_indexed_slices().

Para probar el método vamos a crear datos de prueba en test.php y haremos la consulta:

 

[codesyntax lang="php"]

<?php

$data[] = array(
    'title' => 'Apache Cassandra',
    'license' => 'Open Source',
    'category' => 'no-sql',
);

$data[] = array(
    'title' => 'MongoDB',
    'license' => 'Open Source',
    'category' => 'no-sql',
);

$data[] = array(
    'title' => 'MySQL',
    'license' => 'Open Source',
    'category' => 'sql',
);

foreach($data as $key => $value){
  $cass->guardar('my_column_family_standard', $key, $value);
}

$arrayColumnsValues[] = array(
    'values' => array(
      'category' => 'no-sql'
    ),
    'operator' => '='
);

$result = $cass->obtenerPorIndices('my_column_family_standard', $arrayColumnsValues);

print_r($result);

[/codesyntax]

 

Si ejecutamos el código anterior Cassandra nos devolverá:

Array ( [0] => Array ( [title] => Apache Cassandra [license] => Open Source [category] => no-sql ) [1] => Array ( [title] => MongoDB [license] => Open Source [category] => no-sql ) )

[ci-box type="warning"]Al utilizar un operador diferente al = puede que Cassandra te devuelva un error. Para evitarlo añade otro indice secundario a la consulta pero con el operador =, siendo este la primera columna que Cassandra procese. Por alguna razón Cassandra devuelve un error si no se hace así. [/ci-box]

Con este post concluye la parte básica de PHPCassa, Cassandra y Cassandra Cluster Admin. En el próximo post y siguientes trataré varios temas que se han quedado un poco colgados o faltan por explicar como ordenar registros, cómo crear claves, mejorar el rendimiento de Cassandra, crear clusters, etc.

 

Logo Cassandra

Cassandra 1.x y PHP para desarrolladores SQL: PHPCassa (I)

0

¡Por fin llego el día! ¡Hoy toca programar!

Para ello vamos a utilizar las clases de abstracción de la base de datos PHPCassa que nos ahorrará bastante tiempo para trabajar con Cassandra. Puedes descargarlo desde https://github.com/thobbs/phpcassa.

Ubicamos PHPCassa en una carpeta dentro de nuestro servidor web y creamos dos archivos. Yo los he llamado test.php y cassandra.php. El segundo será una clase que nos ahorrará algunas lineas de código con las tareas habituales. El primero lo utilizaré para testear la clase y sus respectivos métodos.

Bien empecemos.

 

PRIMEROS PASOS

En primer lugar necesitamos incluir un archivo de PHPCassa en cassandra.php:

[codesyntax lang="php"]

<?php

// CLASES NECESARIAS PARA CONECTAR CON CASSANDRA
require_once('phpcassa/lib/autoload.php');

[/codesyntax]

He inmediatamente llamamos a los namespace mínimos para trabajar con PHPCassa:

[codesyntax lang="php"]

<?php

use phpcassa\Connection\ConnectionPool;
use phpcassa\ColumnFamily;
use phpcassa\SystemManager;
use phpcassa\Schema\StrategyClass;

[/codesyntax]

 

Justo debajo creamos la clase:

[codesyntax lang="php"]

<?php

class cassandra {

}

[/codesyntax]

 

CONECTANDO

Para realizar la conexión a la BD, PHPCassa nos pide tan solo un dato obligatorio: el nombre del keyspace con el que vamos a trabajar. También nos permite añadir las ips de los nodos con los que queramos trabajar, aunque por defecto su valor es localhost. Por tanto podemos crear un constructor como el siguiente:

[codesyntax lang="php"]

<?php

  function __construct($keyspace, $nodos = array('localhost')){
    if (!empty($keyspace)){
      $this->conexion = new ConnectionPool($keyspace, $nodos);
    }else{
      print "El keyspace está vacío";
    }
  }

[/codesyntax]

Este constructor nos permite indicarle a la clase el keyspace con el que queremos trabajar y la ip o nombre de servidor donde se encuentre nuestra instancia de Cassandra. Por defecto el puerto al que se va a conectar es el 9160. Si hubieses configurado Cassandra para escuchar en otro puerto deberás especificarlo: localhost:9160.

Este código nos crea una variable llamada conexion que será una instancia del objeto ConnectionPool, necesario para todas las consultas a la BD

 

GUARDANDO REGISTROS

Ahora procedemos a crear el método que guardará los registros. Para ello primero crearemos un objeto ColumnFamily al que le pasaremos el nombre de la column family en la que queremos trabajar:

[codesyntax lang="php"]

<?php

  public function guardar($name_columnFamily, $key, $data = array()){
    try {
      $column_family = new ColumnFamily($this->conexion, $name_columnFamily);
      $column_family->insert($key, $data);
      return true;
    }catch(Exception $e){
     return false;
    }
  }

[/codesyntax]

Este método nos devolverá true si se ha guardado correctamente el registro, o false en caso de error.
Como puedes ver, al objeto ColumnFamily se le pasa como primer parámetro el objeto ConnectionPool que creamos en el constructor, después se le pasa el nombre de la column family.
Una vez creado el objeto ColumnFamily ya podemos utilizar el método insert para guardar el registro pasándole como parámetros la key y un array con las columnas y sus respectivos valores.

 

RECUPERANDO REGISTROS

Recuperar registros de Cassandra es algo más complejo que guardarlos:

Primero debemos añadir un nuevo namespace que nos permitirá usar una clase que nos será muy útil. El listado de namespaces nos quedaría así:

[codesyntax lang="php" highlight_lines="5"]

<?php

use phpcassa\Connection\ConnectionPool;
use phpcassa\ColumnFamily;
use phpcassa\ColumnSlice;
use phpcassa\SystemManager;
use phpcassa\Schema\StrategyClass;

[/codesyntax]

A continuación creamos el método en nuestro archivo cassandra.php:

 

[codesyntax lang="php"]

<?php

  public function obtener($name_columnFamily, $key, $column_names= NULL, $range_start = "", $range_end = "", $column_count = 100, $invertir_orden=false){

    try{
      $column_slices = new ColumnSlice($range_start, $range_end, $column_count, $invertir_orden);
      $column_family = new ColumnFamily($this->conexion, $name_columnFamily);
      $result = $column_family->get($key, $column_slices, $column_names);
    }catch(Exception $e){
      return false;
    }

    return $result;

  }

[/codesyntax]

 

Este método tiene algunos parámetros más que al guardar, pero tienen su razón de ser:

$columnFamily: Nombre de la column family donde buscar.
$key: Clave del registro donde buscar.
$columns: Columnas a buscar. No es obligatorio. Por defecto NULL.
$range_start: Key por la que Cassandra debe empezar a recuperar registros. No es obligatorio. Por defecto “”.$range_end: Key por la que Cassandra dejará de recuperar registros. No es obligatorio. Por defecto “”.
$column_count: Número de registros a obtener, Por defecto 100.
$invertir_orden: Invierte el orden en el que se recuperar los registros. De mayor a menor o viceversa, de la A-Z o viceversa, etc.

Como ves tienes bastantes opciones para recuperar registros. Este método devuelve un array con los registros.

 

PROBANDO LA CLASE

Vamos a probar el código a ver que tal funciona. Nos vamos al archivo test.php y escribimos el siguiente código:

[codesyntax lang="php"]

<?php

include_once "cassandra.php";

$cass = new cassandra('my_keyspace', array('192.168.1.10'));

$data = array(
    'nombre' => 'pepito',
    'ciudad' => 'Madrid',
    'vehiculo' => 'coche',
);

if ( $cass->guardar('my_column_family_standard', $key=1, $data)){
  print "El registro se ha guardado correctamente<br />";
}else{
  print "Error al guardar el registro<br />";
}

$result = $cass->obtener('my_column_family_standard', $key=1);

print_r($result);
?>

[/codesyntax]

Explico un poco el código anterior:

  1. Incluimos la clase en el archivo.
  2. Creamos el objeto indicándole el keyspace con el que trabajar (my_keyspace), y dentro de un array, la ip donde se encuentra en nodo de Cassandra. Si tuvieses Cassandra instalado en localhost no sería necesario indicar el segundo parámetro.
  3. Creamos el array que contendrá los datos a guardar.
  4. Creamos un condicional que guardará los datos en Cassandra y nos indicará si se han guardado correctamente o ha habido algún error.
  5. Guardamos en una variable ($result) los registros que recuperamos a través del método obtener del objeto $cass. A este método le indicamos la column family a buscar y la key a obtener.
  6. Mostramos los registros.

Si todo ha salido correctamente verás un texto como el siguiente cuando ejecutes el script:

El registro se ha guardado correctamente
 Array ( [ciudad] => Madrid [nombre] => pepito [vehiculo] => coche )

Si te aparecen más registros es posible que no vaciases las column family con la que trabajas. Simplemente ves a Cassandra Cluster Admin, entra en la column family y pulsa en Truncate Column Family. Así eliminarás todos los datos de la column family.

 

ACTUALIZAR UN REGISTRO

Ahora que ya funciona nuestra clase podemos ampliarla con nuevas características.
Aunque pienses que para actualizar un registro es necesario un nuevo método, en Cassandra no es necesario, simplemente utilizaremos el método guardar indicandole la key a modificar y el array con los datos a guardar.

Justo debajo del código que ya tenemos en el archivo test.php escribimos lo siguiente:

[codesyntax lang="php"]

<?php

$data = array(
    'ciudad' => 'Barcelona',
);

if ( $cass->guardar('my_column_family_standard', $key=1, $data)){
  print "El registro se ha guardado correctamente<br />";
}else{
  print "Error al guardar el registro<br />";
}

$result = $cass->obtener('my_column_family_standard', $key=1);

print_r($result);

[/codesyntax]

Como observarás el array no tiene todos los datos de la key que vamos a modificar, ya que no es necesario, tan solo pasaremos los datos que queremos actualizar. El resultado de este código es el siguiente:

El registro se ha guardado correctamente
 Array ( [ciudad] => Madrid [nombre] => pepito [vehiculo] => coche )
 El registro se ha guardado correctamente
 Array ( [ciudad] => Barcelona [nombre] => pepito [vehiculo] => coche )

La columna ciudad a cambiado, el resto sigue igual, la razón está en que cuando le pasas a Cassandra una key que ya existe, lo que hace es actualizar las columnas que se correspondan con las keys del array. Si en ese array hubiera keys que no existen como columnas en el registro, Cassandra simplemente las crearía nuevas.

 

GUARDANDO UNA SUPER COLUMN

Aquí tampoco nos hace falta crear un nuevo método ya que nos sirve perfectamente el método guardar, pero deberemos hacerle algunas modificaciones.

En primer lugar, para trabajar con column families super es necesario añadir una llamada al namespace concreto. La lista de namespaces quedaría de la siguiente manera:

[codesyntax lang="php" highlight_lines="5"]

<?php

use phpcassa\Connection\ConnectionPool;
use phpcassa\ColumnFamily;
use phpcassa\SuperColumnFamily;
use phpcassa\ColumnSlice;
use phpcassa\SystemManager;
use phpcassa\Schema\StrategyClass;

[/codesyntax]

Ahora debemos modificar el método guardar y añadirle el parámetro $is_super_column, que nos permitirá crear un objeto SuperColumnFamily o ColumnFamily según corresponda. El método nos quedaría así:

[codesyntax lang="php"]

<?php

  public function guardar($name_columnFamily, $key, $data = array(), $is_super_column = false){

    try {
      if ($is_super_column){
        $column_family = new SuperColumnFamily($this->conexion, $name_columnFamily);
      }
      else{
        $column_family = new ColumnFamily($this->conexion, $name_columnFamily);        
      }

      $column_family->insert($key, $data);
      return true;
    }catch(Exception $e){
     return false; 
    }
  }

[/codesyntax]

Como ambas clases comparten el método insert no nos hace falta incluir esa linea dentro del condicional.

A continuación modificamos el método obtener con el mismo parámetro y utilizando el mismo sistema para crear el objeto correspondiente:

[codesyntax lang="php"]

<?php

  public function obtener($name_columnFamily, $key, $is_super_column=false, $column_names= NULL, $range_start = "", $range_end = "", $column_count = 100, $invertir_orden=false){

    try{
      if ($is_super_column){
        $column_family = new SuperColumnFamily($this->conexion, $name_columnFamily);
      }
      else{
        $column_family = new ColumnFamily($this->conexion, $name_columnFamily);        
      }
      $column_slices = new ColumnSlice($range_start, $range_end, $column_count, $invertir_orden);
      $result = $column_family->get($key, $column_slices, $column_names);
    }catch(Exception $e){
      return false;
    }

    return $result;

  }

[/codesyntax]

Ahora podemos añadir el siguiente código a nuestro archivo test.php:

[codesyntax lang="php"]

<?php

$data = array('vecino_1' => array(
    'nombre' => 'pepito',
    'ciudad' => 'Madrid',
    'vehiculo' => 'coche',
));

if ( $cass->guardar('my_column_family_super', $key=1, $data, $is_super_column=true)){
  print "El registro se ha guardado correctamente<br />";
}else{
  print "Error al guardar el registro<br />";
}

$result = $cass->obtener('my_column_family_super', $key=1, $is_super_column=true);

print_r($result);

[/codesyntax]

El único cambio además del nombre de la column family donde vamos a guardar el registro, se encuentra en el array con los datos. Este tiene un elemento que es la key de la super column, y como valor, el array de las columns a guardar.

Si ejecutamos el código anterior nos devolverá lo siguiente:

El registro se ha guardado correctamente
 Array ( [vecino_1] => Array ( [ciudad] => Madrid [nombre] => pepito [vehiculo] => coche ) )

[ci-box type="warning"]Al probar el código, PHP me devolvió el siguiente aviso:
Strict Standards: Declaration of phpcassa\SuperColumnFamily::add() should be compatible with that of phpcassa\ColumnFamily::add() in /var/www/prueba_cassandra/phpcassa/lib/phpcassa/SuperColumnFamily.php on line 491. 

No te preocupes no afecta ni a los ejemplos ni a la clase que funcionan correctamente.[/ci-box]

ACTUALIZANDO UNA SUPER COLUMN

La actualización de una super column se limita a las columnas que contienen los valores del registro no a su key. Si modificáramos la key , lo que en realidad estaríamos haciendo sería crear una nueva super column. Añadimos el siguiente código a nuestro archivo test.php:

[codesyntax lang="php"]

<?php

$data = array('vecino_1' => array(
    'nombre' => 'pepito',
    'ciudad' => 'Barcelona',
    'vehiculo' => 'coche',
));

if ( $cass->guardar('my_column_family_super', $key1, $data, $is_super_column=true)){
  print "El registro se ha guardado correctamente<br />";
}else{
  print "Error al guardar el registro<br />";
}

$result = $cass->obtener('my_column_family_super', $key=1, $is_super_column=true);

print_r($result);

[/codesyntax]

Y el resultado es:

El registro se ha guardado correctamente
 Array ( [vecino_1] => Array ( [ciudad] => Barcelona [nombre] => pepito [vehiculo] => coche ) )

 

ELIMINAR REGISTROS

Sí, aquí toca crear un nuevo método para nuestra clase en cassandra.php, aunque tampoco nos vamos a matar programando:

 

[codesyntax lang="php"]

<?php

  public function eliminar($name_columnFamily, $key, $is_super_column = false){

    try{
      if ($is_super_column){
        $column_family = new SuperColumnFamily($this->conexion, $name_columnFamily);
      }
      else{
        $column_family = new ColumnFamily($this->conexion, $name_columnFamily);
      }
      $column_family->remove($key);    
      return true;
    }catch(Exception $e){
      return false;
    }
  }

[/codesyntax]

 

El método es bastante sencillo: se le pasan tres parámetros que ya conocemos.

Dentro del método se crea un objeto ColumnFamily y se utiliza el método remove para eliminar el registro pasándole la key correspondiente, y si fuese una super column, la key de este.
El método remove tiene un segundo parámetro que por defecto tiene un valor null, en él se pueden añadir como array los nombres de las columnas a eliminar. Como no es habitual ese proceso he creído adecuado no utilizarlo.

Una vez añadido esté método a nuestra clase modificamos el archivo test.php con el siguiente código:

[codesyntax lang="php"]

<?php

if ( $cass->eliminar('my_column_family_standard', $key=1)){
  print "El registro se ha eliminado correctamente<br />";
}else{
  print "Error al eliminar el registro<br />";
}

$result = $cass->obtener('my_column_family_standard', $key=1);

print_r($result);

[/codesyntax]

Y el resultado es:

El registro se ha eliminado correctamente

 

De momento con esto es suficiente, dejo en tus manos estos métodos para que juegues con ellos todo lo que quieras y te familiarices con PHPCassa.

En el próximo post trataré las Counter Columns que nos servirán para contar registros, los Secondary Index que nos permitirán realizar búsquedas por columnas concretas, y algunas cosas más.

Ir arriba