msgbartop
Desarrollador web y android, con todo lo que ello implica
msgbarbottom

12 Nov 11 Redireccionar a página de mantenimiento con htaccess, manteniendo los estilos css y las imágenes

Si vamos a realizar cambios en nuestra web y se tratan de cambios que pueden afectar al funcionamiento de la página (cambios en la base de datos, cambio de funcionalidades, etc), lo ideal es redirigir a una página de mantenimiento para informar a los usuarios mientras se realizan los cambios. En este caso lo vamos a hacer editando el archivo .htaccess y dando permiso a nuestra ip para acceder a la web normalmente y poder ver los cambios. El resto de usuarios verán la página de mantenimiento a la que les redirigiremos.

1
2
3
4
5
6
<IfModule mod_rewrite.c>
 RewriteEngine on
 RewriteCond %{REMOTE_ADDR} !^999\.999\.999\.999
 RewriteCond %{REQUEST_URI} !/mantenimiento.html$ [NC]
 RewriteRule .* /mantenimiento.html [R=302,L]
</IfModule>

En la línea 3, cambiaremos la ip por la nuestra propia para poder acceder normalmente.
La línea 4 indica que no redirija si ya estamos en la página de mantenimiento (para evitar un bucle infinito).
Y en la línea 5 indicamos la ruta de la propia página de mantenimiento con una redirección 302 (redirección temporal).

Hasta aquí todo bien. El problema es que desde la página de mantenimiento no podemos acceder a las imágenes o estilos css porque también se ejecuta la redirección. Para evitar esto, añadiremos una línea más al código anterior:

1
2
3
4
5
6
7
<IfModule mod_rewrite.c>
 RewriteEngine on
 RewriteCond %{REMOTE_ADDR} !^999\.999\.999\.999
 RewriteCond %{REQUEST_URI} !/mantenimiento.html$ [NC]
 RewriteCond %{REQUEST_URI} !\.(jpe?g?|png|gif|css) [NC]
 RewriteRule .* /mantenimiento.html [R=302,L]
</IfModule>

Ahora todo debería funcionar correctamente. Cuando hayamos terminado de hacer los cambios, solo tendremos que comentar las líneas anteriores.

Etiquetas: , , , , , ,

05 Nov 11 Funciones anónimas (closures) en PHP 5.3

Según la documentación oficial de PHP, las funciones anónimas (o closures) permiten la creación de funciones que no tienen un nombre especificado.

Ejemplo sencillo de lo que quiere decir:

$saludo = function($nombre)
{
    printf("Hola %s\r\n", $nombre);
};
 
$saludo('Mundo');
$saludo('PHP');

Pero en este ejemplo vamos a ir un poco más allá.
Imaginemos que tenemos este array:

$usuarios = array(
             array('id' => 1, 'nombre' => 'Jon', 'cumple' => '1984-02-17'),
             array('id' => 2, 'nombre' => 'Luis', 'cumple' => '1985-07-07'),
             array('id' => 3, 'nombre' => 'Antonio', 'cumple' => '1974-11-17')
         );

Si quisiéramos obtener los ids de cada usuario, haríamos algo parecido a esto:

$ids = array();
foreach ($usuarios as $usuario) {
    $ids[] = $usuario['id'];
}

Pero hay una forma mucho más elegante de hacerlo utilizando las funciones anónimas que nos proporciona PHP:

$ids = array_map(function ($usuario) {
    return $usuario['id'];
}, $usuarios);

Mucho más limpio y elegante… Vamos a rizar más el rizo. Ahora queremos obtener los usuario que hayan nacido a partir de 1980. Normalmente haríamos algo así:

$usuarios_filtrados = array();
$fecha_inicial = strtotime('1980-01-01');
foreach ($usuarios as $usuario) {
    if (strtotime($usuario['cumple']) >= $fecha_inicial) {
        $usuarios_filtrados[] = $usuario;
    }
}

En esta ocasión lo haremos utilizando de nuevo las funciones anónimas o closures:

$fecha_inicial = strtotime('1980-01-01');
$usuarios_filtrados = array_filter($usuarios, function($usuario) {
    global $fecha_inicial;
    return strtotime($usuario['cumple']) >= $fecha_inicial;
});

Y esto solo son algunos ejemplos. Las herramientas las tenemos ahí, depende de nosotros si las utilizamos o no :)

Etiquetas: , , ,

25 Oct 11 Desloguear (logout) a un usuario mediante programación en Symfony2

¿Cómo se puede desloguear un usuario sin tener que acceder a la ruta correspondiente del logout? Para algunas funcionalidades que queramos añadir a nuestro proyecto, es posible que necesitemos desloguear al usuario. Un ejemplo podría ser el caso de desactivar/borrar su cuenta, pero puede que haya más (todo depende de nuestra imaginación ;) ). Vamos a ver como hacerlo con Symfony2.

$this->get("request")->getSession()->invalidate();
$this->get("security.context")->setToken(null);

Como extra, podemos informar al usuario mediante un mensaje flash y le redirigimos a la página que queramos:

$this->get("session")->setFlash('message.success', true);
return new RedirectResponse($this->generateUrl('homepage'));

Evidentemente, la página a la que vamos a redirigir tiene que poder ser vista por usuarios anónimos (no debe necesitar autentificación).

Pd.: Ya escribí como Instalar Symfony2 desde cero

Etiquetas: , , , ,

02 Sep 11 Error svn: Directory .svn containing working copy admin area is missing

El motivo por el que nos puede aparecer este error (utilizando el sistema de control de versiones svn) al intentar hacer un commit, es porque hemos borrado (o ha desaparecido misteriosamente ;) ) la carpeta .svn de alguno de los directorios del proyecto.

svn: Directory .svn containing working copy admin area is missing

Hay dos soluciones:

1) Descargarnos de nuevo el proyecto completo (checkout).

svn co http://servidorsvn/proyecto/trunk proyecto

2) Si la carpeta .svn ha desaparecido solamente de uno de los directorios del proyecto, podemos bajarnos solo esa carpeta y mover el archivo .svn a nuestro proyecto (a la carpeta a la que falta el .svn).

En este ejemplo, se ha borrado la carpeta .svn del directorio css.

svn co http://servidorsvn/proyecto/trunk/css css_copia
mv css_copia/.svn /ruta_al_proyecto/proyecto/css/
rm -rf css_copia

Etiquetas: , ,

22 Jul 11 Integrar Sphinx en Symfony2 con Doctrine2

En el grupo de Symfony2 hay una gran cantidad de preguntas entre las que, de vez en cuando, podemos encontrar un buen hilo donde se trata un tema interesante. Entre tanta “paja” (preguntas fáciles de responder solamente leyendo la documentación) es posible que pasemos por alto alguna de estas buenas preguntas junto a sus respuestas.

Voy a intentar recopilar este tipo de hilos y traducirlos al castellano, siempre dejando el enlace al hilo original del grupo de Symfony2 por si hay alguna nueva respuesta.

Para empezar, podemos encontrar como utilizar Sphinx haciendo uso de la api como un servicio en Symfony2.

1) Nos descargamos la api de Sphinx de la página oficial http://sphinxsearch.com/, colocándolo en la carpeta vendor/sphinx-client. Debemos renombrar la clase a SphinxClient.

2) Lo añadimos en el autoload.php

  $loader->registerPrefixes(array(
   // ...
   'Sphinx'           => __DIR__.'/../vendor/sphinx-client',
  ));

3) Añadimos Sphinx como servicio de Symfony2 en el fichero config.yml

 services:
     search:
         class: SphinxClient
         calls:
             - [setArrayResult, [true]]
             - [setLimits, [0, 20, 1000]]

4) Por último, podemos hacer uso del servicio desde el controlador de esta forma:

$search  = $this->get('search');
$results = $search->Query($search_terms, 'myindex');

Evidentemente deberemos leer la documentación de Sphinx para instarlo y configurarlo correctamente. La página oficial es bastante completa en ese sentido.

Hilo original: doctrine2 and sphinx

Etiquetas: , , , , ,

06 Jul 11 Instalar Symfony2 desde cero

Nos vamos acercando inevitablemente al cambio de Symfony 1.x a Symfony2 y la conclusión que he sacado de mi asistencia al evento #desymfony es que cuanto antes nos pongamos con ello, mucho mejor. Symfony2 mejora a su predecesor en numerosos aspectos y se convertirá en el framework definitivo para el desarrollo de aplicaciones web en el futuro. Si quieres saber todas las nuevas características de esta versión, te recomiendo que visites la web oficial de Symfony2: http://symfony.com/.

En este primer post procederemos a su instalación. Personalmente, me gusta programar en local (en mi propio ordenador) y una vez haya acabado con el desarrollo o quiera probarlo en un entorno de producción, hacer el deploy a un servidor real. Por ello, primero voy a explicar como configurar un servidor web en nuestro ordenador y después procederemos con la instalación de Symfony2.

La instalación la voy a hacer en un entorno Linux con la distribución Ubuntu 11.04. Adaptar los comandos según nuestra distribución (o sistema operativo).

1) Primero instalamos Apache

# apt-get install apache2 apache2.2-common apache2-utils ssl-cert  apache2-mpm-prefork

2) Instalamos y habilitamos el módulo para utilizar modrewrite

# apt-get install libapache2-mod-proxy-html

Para habilitar el modulo en apache, ejecutamos:

# a2enmod rewrite

3) Instalamos PHP5

# apt-get install php5 libapache2-mod-php5 php5-common php5-intl

4) Instalamos Mysql

# apt-get install mysql-server mysql-client php5-mysql

5) Por último instalamos phpmyadmin (opcional)

# apt-get install phpmyadmin

Con estos sencillos pasos ya tendremos un servidor web instalado en nuestro ordenador.

Ahora continuamos con la instalación de Symfony2. Mi IDE favorito para programar es Netbeans, por lo que es el que utilizaré en este ejemplo. Si no lo hemos cambiado, nuestro workspace (donde se encuentran los proyectos a los que podemos acceder desde Netbeans) lo encontraremos en la carpeta NetBeansProjects en nuestra carpeta Home.

Procedemos a crear una nueva carpeta con el nombre de nuestro proyecto en el workspace de Netbeans:

$ mkdir $HOME/NetBeansProjects/miproyecto

Entramos en la carpeta que acabamos de crear:

$ cd $HOME/NetBeansProjects/miproyecto

Descargamos Symfony2 (comprueba la última versión en http://symfony.com/download):

$ wget http://symfony.com/download?v=Symfony_Standard_Vendors_2.0.0-RC4.tgz

Y descomprimimos:

$ tar zxvf Symfony_Standard_Vendors_2.0.0-RC4.tgz

Borramos el archivo comprimido que hemos descargado:

$ rm Symfony_Standard_Vendors_2.0.0-RC4.tgz

Ya tenemos instalado un proyecto básico en Symfony2. Ahora vamos a configurar Apache para poder acceder desde nuestro navegador favorito.

Creamos un nuevo archivo donde irá la configuración de nuestra web:

# vi /etc/apache2/sites-available/miproyecto

Que contendrá el siguiente contenido (cambia “jonseg” por el nombre de tu carpeta Home):

<VirtualHost *:80>
   ServerAdmin email@email.com
   DocumentRoot /home/jonseg/NetBeansProjects/miproyecto/Symfony/web
   ServerName miproyecto.com
   ServerAlias *.miproyecto.com
   ErrorLog /var/log/apache2/miproyecto_error.log
   CustomLog /var/log/apache2/miproyecto_access.log common
   DirectoryIndex index.php
   <Directory "/home/jonseg/NetBeansProjects/miproyecto/Symfony/web">
        Options Indexes FollowSymLinks
        Order Allow,Deny
        Allow from all
        AllowOverride all
        <IfModule mod_php5.c>
           php_admin_flag engine on
           php_admin_flag safe_mode off
           php_admin_value open_basedir none
        </ifModule>
   </Directory>
</VirtualHost>

Creamos un enlace simbólico de este archivo en sites-enabled para indicar que es una web activa:

# ln -s /etc/apache2/sites-available/miproyecto /etc/apache2/sites-enabled/miproyecto

Para que se apliquen los cambios, reiniciamos el servidor Apache:

# /etc/init.d/apache2 restart

Por último, forzamos en el archivo /etc/hosts la ip de nuestra web, que en este caso será localhost o 127.0.0.1

# vim /etc/hosts

Y añadimos esta línea al final del fichero:

127.0.0.1 www.miproyecto.com miproyecto.com

Después de todo esto, ya podremos acceder a nuestro nuevo proyecto en Symfony2 desde el navegador escribiendo la url que hemos configurado.

Como primer paso, debemos comprobar si cumplimos con la configuración básica para que funcione nuestro proyecto. Lo haremos desde la siguiente url:

http://miproyecto.com/config.php

Symfony2 config

Como vemos, nos encontramos con dos problemas (podríamos tener más, deberemos resolverlos según sea necesario). En este caso el error es que las carpetas app/cache y app/logs no tienen los permisos necesarios.

Lo arreglaremos así:

chmod 777 $HOME/NetBeansProjects/miproyecto/Symfony/app/cache
chmod 777 $HOME/NetBeansProjects/miproyecto/Symfony/app/logs

Una vez arreglados los problemas, ya podemos acceder a nuestro proyecto web con Symfony2 mediante esta url:

http://miproyecto.com/app_dev.php/

Symfony2 home

En próximos artículos nos iremos adentrando poco a poco en el desarrollo de Bundles, las plantillas con Twig, etc.

Cualquier duda, en los comentarios!

Etiquetas: , , , , ,

17 Jun 11 Relaciones en Symfony 1.4 con Doctrine

Una de las dificultades que podemos encontrar al utilizar el ORM Doctrine (por ejemplo, en Symfony) es configurar las relaciones entre tablas en el schema.yml que define nuestra base de datos. A modo de ejemplo, voy a mostrar los tres tipos de relaciones que podemos querer configurar:

Relaciones uno a uno

Un caso típico para este tipo de relación es el de un usuario y su perfil. Un usuario solamente tiene un perfil y un perfil solo puede pertenecer a un usuario.

User:
  tableName: user
  columns:
    id:
      type: integer(4)
      primary: true
      notnull: true
      autoincrement: true
    email:
      type: string(255)
      notnull: true
      unique: true
    password:
      type: string(45)
 
Profile:
  tableName: profile
  columns:
    user_id:
      type: integer(4)
      primary: true
      notnull: true
      autoincrement: false
    nickname: string(255)
  relations:
    User:
      class: User
      local: user_id
      foreign: id
      foreignAlias: Profile
      type: one
      foreignType: one

Con esta relación, podremos acceder al perfil de un usuario así:

$user->Profile->nickname // obtenemos el nickname del perfil del usuario

Relaciones uno a muchos:

Un ejemplo de este tipo de relación es el de los usuarios y sus números de teléfono. Un usuario puede tener varios números de teléfono pero un teléfono solo puede pertenecer a un usuario.

User:
  tableName: user
  columns:
    id:
      type: integer(4)
      primary: true
      notnull: true
      autoincrement: true
    email:
      type: string(255)
      notnull: true
      unique: true
    password:
      type: string(45)
 
Telephone:
  tableName: telephone
  columns:
    id:
      type: integer(4)
      primary: true
      notnull: true
      autoincrement: true
    user_id:
      type: integer(4)
      notnull: true
    number:
      type: string(255)
  relations:
    User:
      class: User
      local: user_id
      foreign: id
      foreignAlias: Telephones
      type: one
      foreignType: many

Para acceder a los números de teléfono de un usuario, podemos hacer:

foreach($user->Telephones as $telephone){
  echo $telephone->number;
}

Relaciones muchos a muchos

Para terminar, un caso típico de esta relación es la que podemos encontrar entre los artículos de un blog y sus etiquetas. Un artículo puede tener varias etiquetas y una etiqueta puede asignarse a varios artículos. En esta relación necesitaremos obligatoriamente una tercera tabla (article_tags) para poder relacionar la tabla articles y la tabla tags.

Article:
  tableName: articles
  columns:
    id:
      type: integer(4)
      primary: true
      notnull: true
      autoincrement: true
    title:
      type: string(100)
      notnull: true
 
Tag:
  tableName: tags
  columns:
    id:
      type: integer(4)
      primary: true
      notnull: true
      autoincrement: true
    name:
      type: string(100)
      notnull: true
  relations:
    Article:
      foreignAlias: Tags
      class: Article
      refClass: ArticleTag
 
ArticleTag:
  tableName: article_tags
  columns:
    article_id:
      type: integer(4)
      primary: true
    tag_id:
      type: integer(4)
      primary: true
  relations:
    Article:
      foreignAlias: ArticleTags
    Tag:
      foreignAlias: ArticleTags

Podremos obtener el nombre de todos los tags de un artículo de la siguiente manera:

foreach($article->ArticleTags as $article_tag){
      echo $article_tag->Tag->name;
}

¡Cualquier duda, en los comentarios!

Etiquetas: , , ,

12 May 11 Symfony 1.4 task error: The default context does not exist

Existe un problema bastante frecuente cuando ejecutamos un task en Symfony 1.4, pareciéndonos el siguiente error:

“The default context does not exist”

El problema es que estás utilizando un método de un objeto que utiliza sfContext (para recuperar valores del archivo app.yml, por ejemplo). Parece ser que Symfony no crea una instancia de sfContext y nos devuelve este error.

La siguiente línea debería resolver el problema. La colocaremos en primer lugar dentro de la función execute:

sfContext::createInstance($this->configuration);

Por ejemplo:

1
2
3
4
5
protected function execute ($arguments = array(), $options = array())
{
  sfContext::createInstance($this->configuration);
  // Aqui nuestro codigo
}

Etiquetas: , , , ,

01 Mar 11 Formulario de registro básico en Symfony 1.4

Es dificil no desarrollar una web sin tener registro de usuarios. Para Symfony 1.4 podemos encontrar gran cantidad de plugins que se encargan de esta función como pueden ser sfGuardPlugin (para Propel) y sfDoctrineGuardPlugin (para Doctrine). Estos plugins son muy completos y útiles, pudiéndolos usar en cualquiera de nuestros proyectos donde necesitemos las operaciones básicas con usuarios (registro, login, etc).

Para este post, la intención es crear un formulario de registro de usuarios sencillo desde cero para Symfony 1.4, sin la complejidad de un plugin ya que muchas veces tienen funcionalidades que no necesitaremos en nuestro proyecto.

Antes de empezar, evidentemente, crearemos un proyecto nuevo y generamos un modulo llamado “user”.

Primero modificamos el archivo schema.yml, añadiendo una tabla user básica y sencilla.

/config/doctrine/schema.yml

1
2
3
4
5
6
7
8
9
User:
  tableName: user
  actAs:
    Timestampable: ~
  columns:
    email:
      type: string(255)
    password:
      type: string(45)

Indicamos que se comporte como Timestampable para que se creen automáticamente los campos created_at y updated_at. No nos olvidemos generar las consultas sql y ejecutarlas en nuestra base de datos.

En el segundo paso, vamos a crear el formulario para el registro del usuario. Creamos un archivo nuevo en /lib/form/ llamado RegisterForm.class.php

/lib/form/RegisterForm.class.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
class RegisterForm extends BaseForm
{
    public function configure()
    {
        parent::configure();
 
        $this->setWidgets(array(
	    'email'     => new sfWidgetFormInput(),
	    'password'  => new sfWidgetFormInputPassword(),
            'password2' => new sfWidgetFormInputPassword(),
	));
 
	$this->setValidators(array(
            'email'        => new sfValidatorEmail(array('required'=>true), array('required'=> "El email es obligatorio")),
            'password'  => new sfValidatorString(array('required'=>true), array('required'=> "La contraseña es obligatoria")),
            'password2' => new sfValidatorString(array('required'=>true), array('required'=> "La contraseña es obligatoria")),
	));
 
	$this->widgetSchema->setNameFormat('register[%s]');
        $this->widgetSchema->setFormFormatterName('list');
 
        $this->validatorSchema->setPostValidator(new sfValidatorAnd(array(
            new sfValidatorSchemaCompare('password2', sfValidatorSchemaCompare::EQUAL, 'password', array('throw_global_error' => true), array('invalid' => "Las dos contraseñas no coinciden")),
            new sfValidatorDoctrineUnique(array('model' => 'User', 'column' => array('email')), array('invalid'=> "Este email ya está en uso"))
        )));
    }
}

Los formularios pueden darnos muchos quebraderos de cabeza al principio ya que es algo dificil de entender a la primera, por lo que voy a explicarlo un poco más en detalle.

1
2
3
4
5
$this->setWidgets(array(
    'email'     => new sfWidgetFormInput(),
    'password'  => new sfWidgetFormInputPassword(),
    'password2' => new sfWidgetFormInputPassword(),
));

Indicamos que para el campo email queremos un input normal. Como se puede apreciar, le decimos que para los campos contraseña queremos un campo input password.

1
2
3
4
5
$this->setValidators(array(
    'email'        => new sfValidatorEmail(array('required'=>true), array('required'=> "El email es obligatorio")),
    'password'  => new sfValidatorString(array('required'=>true), array('required'=> "La contraseña es obligatoria")),
    'password2' => new sfValidatorString(array('required'=>true), array('required'=> "La contraseña es obligatoria")),
));

Ahora los validadores. El campo email, evidentemente, debe ser un email. El validador sfValidatorEmail se encarga de comprobar que sea un formato de email válido.
Los campos password son obligatorios y no pueden estar vacíos. Podríamos decirle que tienen que tener una longitud mínima y máxima con las opciones max_length y min_length.

1
2
3
4
$this->validatorSchema->setPostValidator(new sfValidatorAnd(array(
    new sfValidatorSchemaCompare('password2', sfValidatorSchemaCompare::EQUAL, 'password', array('throw_global_error' => true), array('invalid' => "Las dos contraseñas no coinciden")),
    new sfValidatorDoctrineUnique(array('model' => 'User', 'column' => array('email')), array('invalid'=> "Este email ya está en uso"))
)));

Vamos a añadir unos validadores un poco más complejos. En el primero, comprobamos que los dos campos de contraseña deben ser iguales. Para ello utilizamos el validador sfValidatorSchemaCompare. Es típico hacer esta comprobación en todos los formularios de registro de usuarios.
En el segundo caso, comprobamos que el email introducido por el usuario no esté ya introducido en la base de datos y, por tanto, en uso por otro usuario. Utilizamos el validador sfValidatorDoctrineUnique.

Ya tenemos el formulario preparado para su uso. Editamos el archivo actions.class.php de nuestro módulo “user” y añadimos la acción “Register”:

/apps/frontend/modules/user/actions/actions.class.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
class userActions extends sfActions
{
  public function executeRegister(sfWebRequest $request)
  {
 
    $this->redirectIf($this->getUser()->isAuthenticated(), "@homepage");
 
    $this->form = new RegisterForm();
 
    if($request->isMethod("post")){
 
       $this->form->bind($request->getParameter("register"));
       if($this->form->isValid()){
 
           $user = new User();
           $user->email = $this->form->getValue("email");
           $user->password = md5($this->form->getValue("password")); // podriamos utilizar otro sistema para encriptar la contraseña
           $user->save();
 
           $url = $this->getUser()->getAttribute("referer",false)?:"@homepage";
           $this->getUser()->setAttribute("referer",false);
           $this->redirect($url);
       }
    }
  }
}

En la línea 7 le indicamos que si el usuario ya está logueado le redirija a la homepage.

Hemos utilizado el algoritmo md5 para encriptar la contraseña antes de guardarla en la base de datos. Podríamos utilizar otro algoritmo como sha1 o uno propio. Es totalmente recomendable encriptar las contraseñas y no guardarlas en texto plano en nuestra base de datos por motivos de seguridad.

Por último, creamos la plantilla donde se mostrará el formulario de registro.

/apps/frontend/modules/user/templates/registerSuccess.php

1
2
3
4
<form action="<?php echo url_for("@user_register"); ?>" method="post">
  <?php echo $form; ?>
  <input type="submit" value="Registrarse" />
</form>

Debemos añadir la siguiente ruta en el archivo routing.yml:

user_register:
  url:   /register
  param: { module: user, action: register }

Y eso es todo, ya hemos creado un sistema muy básico de registro de usuarios en Symfony 1.4. A partir de aquí se puede expandir e incluir las funcionalidades que queramos.
En un próximo post, realizaremos el login y el logout.

Cualquier duda o si hay que explicar algo mejor, en los comentarios :)

Etiquetas: , ,