En las entidades de Symfony2 es sencillo relacionar un campo con otra tabla con una relación ManyToOne y que este campo solo pueda tomar un valor que esté disponible en la segunda entity. Si creamos un formulario para poder introducir nuevos registros en la base de datos, necesitamos un campo del tipo select para seleccionar uno de los datos de la segunda tabla y relacionarlos.
Deberemos modificar el formulario y añadirle un par de opciones más. En este ejemplo, vamos a relacionar un usuario con su país.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | namespace Acme\MiBundle\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; class PerfilType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add('pais', 'entity', array( 'class' => 'AcmeMiBundle:Pais' 'label' => 'Pais', ) ); } } |
Que no se nos olvide añadir el método __toString() a la entity Pais:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | namespace Acme\MiBundle\Entity; use Doctrine\ORM\Mapping as ORM; class Pais { [...] public function __toString() { return $this->getNombre(); } } |
Y así de sencillo, ahora en nuestra plantilla aparecerá automáticamente el campo de tipo select a la hora de seleccionar el país del usuario.
Etiquetas: AbstractType, entity, formularios, select, Symfony2
He estado desarrollando mi propio bundle para el registro de usuarios en Symfony2 y me he encontrado con el problema de querer añadir campos al formulario de registro que no he definido en el modelo. Uno de estos campos es un checkbox para aceptar los términos de uso de la aplicación. No me interesa guardar este campo en la base de datos por lo que no está definido en el modelo.
Así es como lo he hecho:
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 29 30 31 32 | class RegisterFormType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder ->add('email', 'email') ->add('password', 'repeated', array( 'type' => 'password', 'invalid_message' => 'Las dos contraseñas deben coincidir', 'options' => array('label' => 'Contraseña'), 'required' => false)) ->add("accept_tos", "checkbox", array( "property_path" => false, ) ); $builder ->addValidator(new CallbackValidator(function(FormInterface $form){ if (!$form["accept_tos"]->getData()) { $form->addError(new FormError('Debes aceptar los términos de uso')); } }) ); } } |
Como vemos, añadimos un campo checkbox con nombre “accept_tos” y le indicamos la opción “property_path” a false, por lo que no validará este campo con el modelo. Eso sí, debemos añadir un validador para este campo para asegurarnos de que el usuario acepta las condiciones de uso.
Fuente: http://www.richsage.co.uk/2011/07/20/adding-non-entity-fields-to-your-symfony2-forms/
Etiquetas: addValidator, buildForm, bundle, CallbackValidator, formularios, property_path, Symfony2
En el post anterior creamos un sistema de registro de usuarios básico para Symfony 1.4. Lo que vamos a hacer ahora es un sistema de login (y logout) para que el usuario pueda iniciar sesión en nuestra web.
Primero creamos el formulario de login, por lo que creamos un nuevo archivo:
/lib/form/LoginForm.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 | <?php class LoginForm extends BaseForm { public function configure() { parent::configure(); $this->setWidgets(array( 'email' => new sfWidgetFormInput(), 'password' => 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'=>'Escribe tu contraseña')), )); $this->widgetSchema->setNameFormat('login[%s]'); $this->widgetSchema->setFormFormatterName('list'); } } |
El formulario no tiene mayor misterior. Un input text para el email y un input password para la contraseña. Validamos que se hayan rellenado los dos campos.
Editamos las actions del módulo user que creamos en el post anterior para añadir el login.
/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 28 29 30 31 | <?php class userActions extends sfActions { public function executeLogin(sfWebRequest $request) { $this->form = new LoginForm(); if($request->isMethod("post")){ $this->form->bind($request->getParameter("login")); if($this->form->isValid()){ if(!$user = UserTable::login($this->form->getValue("email"), $this->form->getValue("password"))){ // No hemos conseguido loguear al usuario // Redirigimos de nuevo al login con un mensaje de error $this->getUser()->setFlash("error", "datos incorrectos"); $this->redirect("@user_login"); }else{ // Logueamos $this->getUser()->setAuthenticated(true); $this->getUser()->setAttribute("id",$user->id); // Comprobamos si tiene referer, si no, le llevamos a la homepage $url = $this->getUser()->getAttribute("referer",false)?:"@homepage"; $this->getUser()->setAttribute("referer", false); $this->redirect($url); } } } } |
Tenemos que comprobar si existe ese email junto con esa contraseña en nuestra base de datos. Si es así, logueamos al usuario y, si no, mostramos un mensaje de error.
La comprobación de si existe el usuario en la base de datos la hacemos en la linea 12, donde llamamos al método estático “login” del archivo UserTable.class.php del modelo.
/lib/model/doctrine/UserTable.class.php
1 2 3 4 5 6 7 8 9 10 11 | <?php class UserTable extends Doctrine_Table { public static function login($email,$password){ return Doctrine_Query::create() ->from('User u') ->where('u.email = ?', array($email)) ->andWhere('u.password = ?', array(md5($password))) // Podrimos usar otro algoritmo, en este caso utilizamos md5 ->fetchOne(); } } |
Y eso es todo, lo más sencillo para el final: la plantilla donde mostramos el formulario.
/apps/frontend/modules/user/templates/loginSuccess.php
1 2 3 4 5 6 7 | <?php if($sf_user->getFlash("error")): ?> <div class="error"><?php echo $sf_user->getFlash("error"); ?></div> <?php endif; ?> <form action="<?php echo url_for("@user_login"); ?>" method="post"> <?php echo $form; ?> <input type="submit" value="Entrar" /> </form> |
En este paso sería lógico desarrollar el logout para poder cerrar la sesión en la web. Para ello modificamos de nuevo la actions de nuestro módulo para añadir la nueva funcionalidad:
/apps/frontend/modules/user/actions/actions.class.php
1 2 3 4 5 6 7 8 9 | <?php class userActions extends sfActions { public function executeLogout(sfWebRequest $request){ $this->getUser()->setAuthenticated(false); $this->getUser()->getAttributeHolder()->clear(); $this->redirect("@homepage"); } } |
Para ejecutar el logout, solo debemos crear un enlace en nuestra web donde se llame a esta action. No necesitamos plantilla ya que después de logout siempre redirigiremos a la homepage.
<?php echo link_to("Cerrar sesión", '@user_logout'); ?> |
Debemos añadir las siguientes rutas en el archivo routing.yml:
user_login:
url: /login
param: { module: user, action: login }
user_logout:
url: /logout
param: { module: user, action: logout } |
Cualquier duda en los comentarios!
Etiquetas: formularios, login, logout, registro, Symfony
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: formularios, registro, Symfony