4. Helper Phoogle Maps para CakePHP

Hoy vamos a dar un paso importante con respecto a los dos posts anteriores y vamos a ver cómo integrar un ayudante Ajax en CakePHP para trabajar con Google Maps.

Justin Johnson es un desarrollador web que trabaja en www.systemsevendesigns.com. Ha programado una clase en PHP, Phoogle Maps (pronunciado Foogle Maps), que se integra con el API de Google y Yahoo! y facilita la visualización de mapas. Puedes descargarte esta clase aquí, haciendo clic en el enlace Download Phoogle Maps. Phoogle es el nombre de la clase PHP; para adaptar esta clase al framework CakePHP y convertirla en helper, o ayudante, hay que seguir estas explicaciones -hay dos-. Yo me descargué la versión 2 de Phoogle y seguí esta.

Mis pasos fueron estos:

1. Después de obtener la clase comprimida, copié el archivo phoogle.php en la carpeta views/helpers de mi aplicación Cake.

2. Edité el archivo phoogle.php y cambié la declaración de la clase:

Donde ponía esto:


class PhoogleMap{

escribí esto otro:


class PhoogleHelper extends Helper{

3. A continuación, edité el método de mi controlador, asociado al mapa, y cargué el ayudante Phoogle:


var $helpers = array('Phoogle'); 

Inicialicé la variable googleApiKey en este método, así:


$this->set('googleApiKey','tu_clave');

4. Edité el layout y puse esto, que escribe el código JavaScript que carga el API de Google:


if(isset($phoogle))
{
    $phoogle->setAPIKey($googleApiKey);
    $phoogle->printGoogleJS();
}

Este fragmento hay que insertarlo entre las etiquetas <head> y </head> del layout y sirve para cargar la versión 2 del API Google Maps -la versión se especifica en el parámetro v del URL-. El código que imprime el método printGoogleJS de la clase Phoogle es este:


<script src="http://maps.google.com/maps?file=api&v=2&key=tu_clave" type="text/javascript"></script>

5. Finalmente, escribí este fragmento de código en mi vista, dentro de la etiqueta:

<div id="map" style="width: 500px; height: 300px">


$phoogle->addAddress("Barcelona gaudi 2");

$phoogle->showMap();

Esto escribe en la vista el siguiente JavaScript:


      function showmap(){
            //<!&#91;CDATA&#91;

         if (GBrowserIsCompatible()) {

            var map = new GMap(document.getElementById("map"));
map.centerAndZoom(new GPoint(2.174498,41.404616,0,2.174498,41.404616,0), 4);
}

         var icon = new GIcon();
         icon.image = "http://labs.google.com/ridefinder/images/mm_20_red.png";
         icon.shadow = "http://labs.google.com/ridefinder/images/mm_20_shadow.png";
         icon.iconSize = new GSize(12, 20);
         icon.shadowSize = new GSize(22, 20);
         icon.iconAnchor = new GPoint(6, 20);
         icon.infoWindowAnchor = new GPoint(5, 1);

         map.addControl(new GSmallMapControl());
map.addControl(new GMapTypeControl());
var point0 = new GPoint(2.174498,41.404616,0,2.174498,41.404616,0)
;
              var marker0 = new GMarker(point0);

              map.addOverlay(marker0)

              GEvent.addListener(marker0, "click", function() {
marker0.openInfoWindowHtml("Barcelona gaudi 2");
});
      //&#93;&#93;>

      }
      window.onload = showmap;

Phoogle ha geocodificado la dirección “Barcelona gaudi 2”; es decir, ha calculado los valores 2.174498 y 41.404616, las coordenadas geográficas de esta dirección, mediante su método addAddress:


/**
* @function     addAddress
* @param        $address:string
* @returns      Boolean True:False (True if address has long/lat, false if it doesn't)
* @description  Add's an address to be displayed on the Google Map
*               (thus eliminating the need for two different show methods from version 1.0)
*/
   function addAddress($address,$htmlMessage=null){
    if (!is_string($address)){
      die("All Addresses must be passed as a string");
     }
      $apiURL = "http://maps.google.com/maps/geo?&output=xml&key=".$this->apiKey."&q=";
      $addressData = file_get_contents($apiURL.urlencode($address));

      $results = $this->xml2array(utf8_encode($addressData));
      if (empty($results['kml'][Response]['Placemark']['Point']['coordinates'])){
         $pointer = count($this->invalidPoints);
         $this->invalidPoints[$pointer]['lat']= $results['kml'][Response]['Placemark']['Point']['coordinates'][0];
         $this->invalidPoints[$pointer]['long']= $results['kml'][Response]['Placemark']['Point']['coordinates'][1];
         $this->invalidPoints[$pointer]['passedAddress'] = $address;
         $this->invalidPoints[$pointer]['htmlMessage'] = $htmlMessage;
        }else{
         $pointer = count($this->validPoints);
         $this->validPoints[$pointer]['lat']= $results['kml'][Response]['Placemark']['Point']['coordinates'];
         $this->validPoints[$pointer]['long']= $results['kml'][Response]['Placemark']['Point']['coordinates'];
         $this->validPoints[$pointer]['passedAddress'] = $address;
         $this->validPoints[$pointer]['htmlMessage'] = $htmlMessage;
      }

   }

Este método hace una petición HTTP al geocodificador de Google, especificando los parámetros q -dirección que se quiere geocodificar-, key -clave API Google Maps- y output -formato de la respuesta-. La respuesta que se obtiene de Google es un documento XML parecido a este, que el método addAddress analiza sintácticamente:

<?xml version=”1.0″ encoding=”UTF-8″ ?>

<Response>
  <name>“Barcelona gaudi 2”</name>
<Status>
  <code>200</code>
  <request>geocode</request>
  </Status>
<Placemark id=”p1>
  <address>Avinguda de Gaudí, 2, 08025, Barcelona, España</address>
<AddressDetails Accuracy=”8 xmlns=”urn:oasis:names:tc:ciq:xsdschema:xAL:2.0>
<Country>
  <CountryNameCode>ES</CountryNameCode>
  <CountryName>España</CountryName>
<AdministrativeArea>
  <AdministrativeAreaName>CT</AdministrativeAreaName>
<SubAdministrativeArea>
  <SubAdministrativeAreaName>Barcelona</SubAdministrativeAreaName>
<Locality>
  <LocalityName>Barcelona</LocalityName>
<Thoroughfare>
  <ThoroughfareName>Avinguda de Gaudí 2</ThoroughfareName>
  </Thoroughfare>
<PostalCode>
  <PostalCodeNumber>08025</PostalCodeNumber>
  </PostalCode>
  </Locality>
  </SubAdministrativeArea>
  </AdministrativeArea>
  </Country>
  </AddressDetails>
<Point>
  <coordinates>2.174498,41.404616,0</coordinates>
  </Point>
  </Placemark>
  </Response>
  </kml>

Si todo sale bien, debería obtenerse un mapa de l’avinguda Gaudí de Barcelona, con la Sagrada Família al lado. Si encuentras algún error en este post o tienes algún problema instalando Phoogle puedes dejar aquí un comentario. ¡Suerte!

Anuncios

12. Validación de datos en CakePHP 1.1

Hasta ahora hemos dado nuestros primeros pasos en CakePHP de la mano del ejemplo de la base de datos de la discografía y ha llegado el momento de despedirnos de él, aunque siempre puede uno releer los posts anteriores para repasar.

Vamos a ver cómo se validan los datos en CakePHP a través de otro ejemplo que trabaja con una tabla de usuarios y que se puede crear con el siguiente script MySQL:


create table usuarios (

id mediumint unsigned NOT NULL auto_increment,

nombre varchar(100) NOT NULL,

telefono varchar(9) NOT NULL,

email varchar(100),

primary key(id)

);

Una vez tenemos la tabla, lo primero que debemos hacer es definir las reglas de validación en el modelo.


class Usuario extends AppModel{

   var $name = 'Usuario';

   var $validate = array(  'nombre'=>VALID_NOT_EMPTY,
                           'telefono'=>VALID_NOT_EMPTY,
                           'email'=>VALID_EMAIL,
                           );

}

En este ejemplo hemos utilizado las validaciones predeterminadas de CakePHP VALID_NOT_EMPTY y VALID_EMAIL. Además de estos dos tipos de validación, CakePHP 1.1 cuenta con otros dos: VALID_NUMBER y VALID_YEAR.

Vamos a crear esta vista para ver el efecto de las dos validaciones que acabamos de crear:

<h1>Nuevo Usuario</h1>
<form method="post" action="<?php echo $html->url('/usuarios/anadir_usuario')?>">
   <fieldset>
      <div class="required">
         <label for="usuario_nombre">Nombre:</label>
         <?php echo $html->input('Usuario/nombre', array('size' => '40'))?>
         <?php echo $html->tagErrorMsg('Usuario/nombre', 'El nombre es obligatorio'); ?>
      </div>
      <div class="required">
         <label for="usuario_telefono">Telefono:</label>
         <?php echo $html->input('Usuario/telefono', array('size' => '40'))?>
      </div>
      <div class="required">
         <label for="usuario_email">E-mail:</label>
         <?php echo $html->input('Usuario/email', array('size' => '40'))?>
         <?php echo $html->tagErrorMsg('Usuario/email', 'El e-mail es obligatorio'); ?>
      </div>
</fieldset>
<?php echo $html->submit('Añadir usuario') ?>
</form>

En la vista hemos especificado los campos obligatorios -“required”-. Por una parte, para que la magia de CakePHP funcione, el método validates de la clase Controller comprueba las validaciones que se añaden al modelo, y, por otra, el método validationErrors() de Controller devuelve los mensajes de error que lanza el modelo. El método tagErrorMsg del objeto $html muestra estos errores en la vista. Puedes repasar cómo funcionan los ayudantes HTML aquí.

3. Mi primer mapa Google

Hoy crearemos nuestro primer mapa. Para ello, debemos copiar el código de ejemplo del post anterior -facilitado por Google en el momento de solicitar una clave para trabajar con el API-, pegarlo en un editor, y guardarlo con el nombre EjemploGoogle.html. Sólo hay que hacer un pequeño cambio: introducir nuestra clave en esta parte:


<script src="http://maps.google.com/maps?file=api&v=2&key=tuclave"

type="text/javascript">

</script>

Si cargamos la página EjemploGoogle.html en un navegador, veremos este mapa:

Esta es la Universidad de Stanford. Aquí se conocieron los creadores de Google, Larry Page y Sergey Brin.

Ahora vamos a editar el archivo EjemploGoogle.html y nos vamos a centrar en esta parte del código JavaScript, que pertenece a la función load():

var map = new GMap2(document.getElementById("map"));
map.setCenter(new GLatLng(37.4419, -122.1419), 13);

Hacemos este pequeño cambio y lo dejamos así:

var map = new GMap2(document.getElementById("map"));
map.setCenter(new GLatLng(41.387917,2.169919), 13);

¡Ya estamos trabajando con el API de Google! La primera línea crea un objeto de la clase JavaScript GMap2 y representa un mapa único en la página. El constructor de la clase GMap2 necesita un contenedor HTML para crear el objeto, que suele ser un elemento DIV. En otras palabras, decimos a Google: “Oye, por favor, créame un mapa y pónmelo en el “map” del HTML. Gracias.”.

La segunda línea sirve para inicializar el mapa. La clase JavaScript GMap2 implementa el método setCenter() y necesita una coordenada de tipo GLatLng. Nosotros hemos puesto aquí las coordenadas geográficas de la ciudad de Barcelona:

¿Que eres de Bilbao? Pues no pasa nada, escribes las coordenadas de tu ciudad en el código de ejemplo:

var map = new GMap2(document.getElementById("map"));
map.setCenter(new GLatLng(43.256963,-2.923441), 13);

Y cargas el archivo EjemploGoogle.html en tu navegador:

¿Prefieres Madrid? Escribimos sus coordenadas geográficas en el código JavaScript:

var map = new GMap2(document.getElementById("map"));
map.setCenter(new GLatLng(40.416741,-3.70325), 13);

 

Todo esto y más se explica aquí; nosotros continuamos en próximos posts.

2. Consigue una clave para trabajar con la API Google Maps

Para poder trabajar con la API tenemos que darnos de alta en el servicio y conseguir una clave, que, como veremos en futuros posts, se utiliza para identificar las peticiones. La clave se obtiene en la página de Inscripción para el API de Google Maps -hace falta tener una cuenta Google-.

Después de leer y aceptar las condiciones de uso podemos escribir el URL de nuestro sitio web: yo escribí la dirección de este blog, www.tutorialcakephp.wordpress.com. A continuación, en la página siguiente, tenemos que introducir nuestra cuenta: yo aquí puse la mía, jordicakephp@gmail.com.

Con estos datos Google genera la clave y nos da la bienvenida: “Thank you for signing up for a Google Maps API key!”; también nos facilita un código HTML de ejemplo para ver cómo se utiliza el script:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    <title>Google Maps JavaScript API Example</title>
    <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=tu clave"
      type="text/javascript"></script>
    <script type="text/javascript">
    //<![CDATA[
    function load() {
      if (GBrowserIsCompatible()) {
        var map = new GMap2(document.getElementById("map"));
        map.setCenter(new GLatLng(37.4419, -122.1419), 13);
      }
    }
    //]]>
    </script>
  </head>
  <body onload="load()" onunload="GUnload()">
    <div id="map" style="width: 500px; height: 300px"></div>
  </body>
</html>

La página de bienvenida a la documentación para programadores del API de Google Maps explica cómo utilizar las instrucciones del API. Nosotros lo hacemos en próximos posts porque antes nos gustaría recordar, aquí, cómo funcionan las coordenadas geográficas; también dejamos esta tabla con las coordenadas de algunas ciudades españolas:

Ciudad

N

E

Barcelona

41.387917

2.169919

Bilbao

43.256963

-2.923441

Madrid

40.416741

-3.70325

Sevilla

37.38264

-5.996295

1. Incrusta Google Maps en tu sitio web o blog

Vamos a seguir los pasos de Google para incrustar un mapa en una página web sin necesidad de programar ni utilizar la API.

1. Vamos a http://maps.google.es y escribimos la dirección que queramos en el cuadro de búsqueda -yo aquí puse la Plaça Catalunya de Barcelona-.

2. A continuación, cuando aparece el mapa, hacemos clic en Enlazar, en la parte derecha de la pantalla.

3. Aparece un cuadro con la opción Pegar HTML para insertar en sitio web: seleccionamos el texto que hay dentro y lo copiamos.

¡Ya podemos pegar el código en el HTML de nuestro sitio web para ver el mapa!

Ejercicio 2. Sesiones con CakePHP

Vamos a ver cómo se utiliza el componente de sesiones a través de un sencillo ejemplo en que un usuario del sistema introduce su nombre y su contraseña en un formulario de acceso. Este ejemplo es el de la documentación oficial, que puedes ver aquí.

Objetivos

1. Repasar la forma en que CakePHP ejecuta las acciones de los controladores.

2. Conocer los métodos mágicos de tipo FindBy<campo> de los modelos.

3. Conocer el componente de sesiones de CakePHP.

Notas

Se utiliza la primera instalación de CakePHP y no se cambia el nombre de la carpeta c:\wamp\www\discografia

Resolución

Creamos la base de datos ejercicio02 y la tabla usuarios:

create table usuarios (
id int(11) not null auto_increment,
login varchar(255) not null,
password varchar(32) not null,
nombre varchar(255) not null,
primary key(id)
);

Configuramos el archivo database.php para que CakePHP trabaje con la base de datos que acabamos de crear:

class DATABASE_CONFIG {
var $default = array(
'driver' => 'mysql',
'persistent' => false,
'host' => 'localhost',
'login' => 'root',
'password' => '',
'database' => 'ejercicio02',
'prefix' => '',
);
var $test = array(
'driver' => 'mysql',
'persistent' => false,
'host' => 'localhost',
'login' => 'root',
'password' => '',
'database' => 'ejercicio02',
'prefix' => '',
);
}

Creamos el modelo para la tabla usuarios:

class Usuario extends AppModel
{
   var $name = 'Usuario';
}

Creamos la vista login.thtml, que se ha traducido de la documentación oficial:

<?php if ($error): ?>

El nombre de usuario o la contraseña no son correctos: inténtalo otra vez.
<?php endif; ?>
<form action="<?php echo $html->url('/usuarios/login'); ?>" method="post">
   <fieldset>
      <label for="login">Usuario:</label>
      <?php echo $html->input('Usuario/login', array('size' => 20)); ?>
      <label for="password">Contraseña:</label>
      <?php echo $html->password('Usuario/password', array('size' => 20)); ?>
      <?php echo $html->submit('Entrar'); ?>
   </fieldset>
</form>

Creamos el archivo usuarios_controller.php y escribimos la lógica de la función login():

class UsuariosController extends AppController{
   function login(){
         $this->set('error', false);
         if (!empty($this->data)){
            $someone = $this->Usuario->findByLogin($this->data['Usuario']['login']);
            if(!empty($someone['Usuario']['password']) && $someone['Usuario']['password'] == $this->data['Usuario']['password']){
                  $this->Session->write('Usuario', $someone['Usuario']);
                  $this->redirect('/empresas/anadir_empresa');
               }
               else{
                  $this->set('error', true);
               }
            }
         }
}

Conclusiones

1. Sobre el método login() de UsuariosController. Los controladores se utilizan para manejar la lógica de un modelo y pueden tener cualquier número de acciones, que ejecuta automáticamente el despachador de CakePHP -generalmente se utilizan para mostrar vistas-. Cuando escribimos, en la barra de direcciones, http://localhost/discografia/usuarios/login, el despachador de Cake pasa la petición al controlador UsuariosController y éste ejecuta la acción.

2. Sobre el método mágico findByLogin() de Usuario. Hasta ahora, para recuperar los datos de las tablas, hemos utilizado el método findAll, que recupera todas las filas. En este ejercicio se utiliza por primera vez el método mágico findByLogin -de tipo findBy<nombreCampo>-, que se utiliza para recuperar los registros de la tabla usuarios cuyo login coincide con el nombre que introduce el usuario en el formulario. Como dice la documentación oficial, “estas funciones mágicas se utilizan como atajo para recuperar un registro, a partir de una tabla, un campo y un valor”. Disponemos de un método findBy para cada campo del modelo.

3. Sobre el componente de sesiones de Cake. “El componente de sesiones se utiliza para interactuar con la información de la sesión, e incluye la lectura y escritura básicas. El componente de sesiones está disponible de forma predterminada en los controladores”, dice la documentación oficial aquí. Vamos a comprobar esto, yendo a esta línea de código del método login() de UsuariosController:

   $this->Session->write(‘Usuario’, $someone[‘Usuario’]);

Si el usuario introduce datos en el campo login y la contraseña de la tabla usuarios coincide con la del formulario, entonces, gracias al método write() de la clase Session, se escribe una variable de sesión.

El componente de sesiones de Cake se encuentra en su núcleo, en la carpeta c:\wamp\www\discografia\cake\libs\controller\components\session.php, y extiende la clase CakeSession, que está en c:\wamp\www\discografia\cake\libs\session.php. El código del método write() de la clase SessionComponent es este:

 

function write($name, $value = null) {
if ($this->__active === true) {
$this->__start();
if (is_array($name)) {
foreach ($name as $key => $value) {
if (parent::write($key, $value) === false) {
return false;
}
}
return true;
}
if (parent::write($name, $value) === false) {
return false;
}
return true;
}
return false;
}

Ya para acabar, se puede contrastar esta forma de trabajar con las sesiones, mediante esta clase de CakePHP, con esta otra, de PHP, que hace uso de la matriz asociativa superglobar $_SESSION[].

 

Ejercicio 1. Base de datos con relación de tipo muchos a muchos. Andamiaje.

En este ejercicio trabajamos con una base de datos muy sencilla que tiene sólo dos tablas -articulos y clientes- entre las que se establece una relación de tipo muchos a muchos; se inspira en este ejercicio para Access de www.superalumnos.net. Se utiliza la técnica del andamiaje para comprobar el correcto funcionamiento y para insertar algunos datos de prueba.

Objetivos

1. Conocer el funcionamiento de las relaciones de tipo muchos a muchos en CakePHP y las convenciones que hay que utilizar para trabajar con ellas.

2. Utilizar la técnica del scaffolding para comprobar el correcto funcionamiento de la recién implementada base de datos.

Notas

Se utiliza la primera instalación de CakePHP y no se cambia el nombre de la carpeta c:\wamp\www\discografia

Resolución

Arrancamos el monitor MySQL, creamos la base de datos pedidos y las tablas clientes, articulos y articulos_clientes:

create table clientes (
id int unsigned auto_increment,
nombre varchar(50) not null,
telefono varchar(9) not null,
primary key (id) );
 
create table articulos (
id int unsigned auto_increment,
descripcion varchar(50) not null,
pvp decimal(4,2) null,
primary key (id) );
 
create table articulos_clientes (
id int unsigned auto_increment,
articulo_id int unsigned not null,
cliente_id int unsigned not null,
primary key (id) );

Configuramos el archivo database.php para que CakePHP se pueda conectar a la base de datos que acabamos de crear:

class DATABASE_CONFIG {
var $default = array(
'driver' => 'mysql',
'persistent' => false,
'host' => 'localhost',
'login' => 'root',
'password' => '',
'database' => 'pedidos',
'prefix' => '',
);
var $test = array(
'driver' => 'mysql',
'persistent' => false,
'host' => 'localhost',
'login' => 'root',
'password' => '',
'database' => 'pedidos',
'prefix' => '',
);
}

Los modelos y controladores para los clientes y los artículos son estos:

<?php
class Cliente extends AppModel{
        var $name = 'Cliente';      
}
?>
<?php
class Articulo extends AppModel{
        var $name = 'Articulo';
        var $hasAndBelongsToMany = array('Cliente'=>array('className'=>'Cliente'));
}
?>
 
<?php 
class ClientesController extends AppController{ 
      var $name = 'Clientes';
      var $scaffold; 
?>
<?php 
class ArticulosController extends AppController{ 
      var $name = 'Articulos';
      var $scaffold; 
?>

 

Conclusiones

 

1. Para asociar las tablas clientes y pedidos necesitamos una tabla de unión HABTM (hasAndBelongsToMany), cuyo nombre debe seguir esta convención: se escribe el nombre de la primera tabla, un guión bajo, y el nombre de la segunda tabla, en orden alfabético. En nuestro ejemplo, como tenemos las tablas articulos y clientes, el nombre de la tabla de unión debe ser articulos_clientes.

 

2. Para definir la relación entre las dos tablas debemos añadir un array en el modelo Articulo.

 

3. Utilizamos la característica del andamiaje de CakePHP para comprobar el funcionamiento del esquema de la base de datos: estamos en la primera etapa del proyecto, en que se pueden producir cambios en el diseño.