martes, 17 de febrero de 2015

Yii Framework 2 - User (Registro de usuarios)




En este capítulo empezamos a ver como implementar un sistema de autenticación de usuarios, Yii2 en su aplicación esqueleto nos muestra un ejemplo de como loguear a los usuarios, pero no nos muestra como implementar el registro de usuarios.

En este caso vamos a crear uno, con un formulario de registro y que solicite la activación de la cuenta de usuario mediante correo electrónico. Entre los objetivos de este ejemplo es mostrar como utilizar la tecnología ajax para impedir que un usuario se pueda registrar si el username o el email existen en la tabla users, ésto ya lo vimos en un capítulo anterior pero es interesante que lo volvamos a repasar.

Para que el usuario finalice su activación tendrá que hacer click en un enlace que previamente hemos enviado al usuario por correo electrónico, si los datos son correctos el usuario será activado y podrá iniciar sesión (la configuración del inicio de sesión lo veremos en el siguiente capítulo).

El código de este capítulo se puede ver a continuación:

1 - Instalar la tabla users.sql

CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) AUTO_INCREMENT NOT NULL,
  `username` varchar(50) NOT NULL,
  `email` varchar(80) NOT NULL,
  `password` varchar(250) NOT NULL,
  `authKey` varchar(250) NOT NULL,
  `accessToken` varchar(250) NOT NULL,
  `activate` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
);


2 - Crear la vista register.php donde está el formulario de registro ...

<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
?>

<h3><?= $msg ?></h3>

<h1>Register</h1>
<?php $form = ActiveForm::begin([
    'method' => 'post',
 'id' => 'formulario',
 'enableClientValidation' => false,
 'enableAjaxValidation' => true,
]);
?>
<div class="form-group">
 <?= $form->field($model, "username")->input("text") ?>   
</div>

<div class="form-group">
 <?= $form->field($model, "email")->input("email") ?>   
</div>

<div class="form-group">
 <?= $form->field($model, "password")->input("password") ?>   
</div>

<div class="form-group">
 <?= $form->field($model, "password_repeat")->input("password") ?>   
</div>

<?= Html::submitButton("Register", ["class" => "btn btn-primary"]) ?>

<?php $form->end() ?>


3 - Crear el modelo de validación para el formulario de registro anterior, FormRegister.php

<?php

namespace app\models;
use Yii;
use yii\base\model;
use app\models\Users;

class FormRegister extends model{
 
    public $username;
    public $email;
    public $password;
    public $password_repeat;
    
    public function rules()
    {
        return [
            [['username', 'email', 'password', 'password_repeat'], 'required', 'message' => 'Campo requerido'],
            ['username', 'match', 'pattern' => "/^.{3,50}$/", 'message' => 'Mínimo 3 y máximo 50 caracteres'],
            ['username', 'match', 'pattern' => "/^[0-9a-z]+$/i", 'message' => 'Sólo se aceptan letras y números'],
            ['username', 'username_existe'],
            ['email', 'match', 'pattern' => "/^.{5,80}$/", 'message' => 'Mínimo 5 y máximo 80 caracteres'],
            ['email', 'email', 'message' => 'Formato no válido'],
            ['email', 'email_existe'],
            ['password', 'match', 'pattern' => "/^.{8,16}$/", 'message' => 'Mínimo 6 y máximo 16 caracteres'],
            ['password_repeat', 'compare', 'compareAttribute' => 'password', 'message' => 'Los passwords no coinciden'],
        ];
    }
    
    public function email_existe($attribute, $params)
    {
  
  //Buscar el email en la tabla
  $table = Users::find()->where("email=:email", [":email" => $this->email]);
  
  //Si el email existe mostrar el error
  if ($table->count() == 1)
  {
                $this->addError($attribute, "El email seleccionado existe");
  }
    }
 
    public function username_existe($attribute, $params)
    {
  //Buscar el username en la tabla
  $table = Users::find()->where("username=:username", [":username" => $this->username]);
  
  //Si el username existe mostrar el error
  if ($table->count() == 1)
  {
                $this->addError($attribute, "El usuario seleccionado existe");
  }
    }
 
}


4 - Crear el modelo de registro activos (ActiveRecord) para la tabla users, Users.php

<?php

namespace app\models;
use Yii;
use yii\db\ActiveRecord;

class Users extends ActiveRecord{
    
    public static function getDb()
    {
        return Yii::$app->db;
    }
    
    public static function tableName()
    {
        return 'users';
    }
    
}


5 - Abrir el archivo de configuración config/params.php para incluir un título para la aplicación Yii, este título lo enviaremos en el correo electrónico al usuario, también agregaremos un salt para encriptar las contraseñas de usuario.

<?php
return[
'adminEmail' => 'tuemail@gmail.com',
'title' => 'Aplicación Yii',
'salt' => 'fsddsflj38343lj0',
];


6 - Agregar las clases FormRegister y Users creadas anteriormente al controlador

use app\models\FormRegister;
use app\models\Users;


7 - Código para el controlador con la acción Register que utilizaremos para registrar al usuario, la acción Confirm que permitirá activar al usuario cuando haga click en el enlace adjunto en el correo electrónico y el método privado randKey($str, $long) para generar claves aleatorias para las columnas authKey y accessToken

<?php
 
 private function randKey($str='', $long=0)
    {
        $key = null;
        $str = str_split($str);
        $start = 0;
        $limit = count($str)-1;
        for($x=0; $x<$long; $x++)
        {
            $key .= $str[rand($start, $limit)];
        }
        return $key;
    }
  
 public function actionConfirm()
 {
    $table = new Users;
    if (Yii::$app->request->get())
    {
   
        //Obtenemos el valor de los parámetros get
        $id = Html::encode($_GET["id"]);
        $authKey = $_GET["authKey"];
    
        if ((int) $id)
        {
            //Realizamos la consulta para obtener el registro
            $model = $table
            ->find()
            ->where("id=:id", [":id" => $id])
            ->andWhere("authKey=:authKey", [":authKey" => $authKey]);
 
            //Si el registro existe
            if ($model->count() == 1)
            {
                $activar = Users::findOne($id);
                $activar->activate = 1;
                if ($activar->update())
                {
                    echo "Enhorabuena registro llevado a cabo correctamente, redireccionando ...";
                    echo "<meta http-equiv='refresh' content='8; ".Url::toRoute("site/login")."'>";
                }
                else
                {
                    echo "Ha ocurrido un error al realizar el registro, redireccionando ...";
                    echo "<meta http-equiv='refresh' content='8; ".Url::toRoute("site/login")."'>";
                }
             }
            else //Si no existe redireccionamos a login
            {
                return $this->redirect(["site/login"]);
            }
        }
        else //Si id no es un número entero redireccionamos a login
        {
            return $this->redirect(["site/login"]);
        }
    }
 }
 
 public function actionRegister()
 {
  //Creamos la instancia con el model de validación
  $model = new FormRegister;
   
  //Mostrará un mensaje en la vista cuando el usuario se haya registrado
  $msg = null;
   
  //Validación mediante ajax
  if ($model->load(Yii::$app->request->post()) && Yii::$app->request->isAjax)
        {
            Yii::$app->response->format = Response::FORMAT_JSON;
            return ActiveForm::validate($model);
        }
   
  //Validación cuando el formulario es enviado vía post
  //Esto sucede cuando la validación ajax se ha llevado a cabo correctamente
  //También previene por si el usuario tiene desactivado javascript y la
  //validación mediante ajax no puede ser llevada a cabo
  if ($model->load(Yii::$app->request->post()))
  {
   if($model->validate())
   {
    //Preparamos la consulta para guardar el usuario
    $table = new Users;
    $table->username = $model->username;
    $table->email = $model->email;
    //Encriptamos el password
    $table->password = crypt($model->password, Yii::$app->params["salt"]);
    //Creamos una cookie para autenticar al usuario cuando decida recordar la sesión, esta misma
    //clave será utilizada para activar el usuario
    $table->authKey = $this->randKey("abcdef0123456789", 200);
    //Creamos un token de acceso único para el usuario
    $table->accessToken = $this->randKey("abcdef0123456789", 200);
     
    //Si el registro es guardado correctamente
    if ($table->insert())
    {
     //Nueva consulta para obtener el id del usuario
     //Para confirmar al usuario se requiere su id y su authKey
     $user = $table->find()->where(["email" => $model->email])->one();
     $id = urlencode($user->id);
     $authKey = urlencode($user->authKey);
      
     $subject = "Confirmar registro";
     $body = "<h1>Haga click en el siguiente enlace para finalizar tu registro</h1>";
     $body .= "<a href='http://yii.local/index.php?r=site/confirm&id=".$id."&authKey=".$authKey."'>Confirmar</a>";
      
     //Enviamos el correo
     Yii::$app->mailer->compose()
     ->setTo($user->email)
     ->setFrom([Yii::$app->params["adminEmail"] => Yii::$app->params["title"]])
     ->setSubject($subject)
     ->setHtmlBody($body)
     ->send();
     
     $model->username = null;
     $model->email = null;
     $model->password = null;
     $model->password_repeat = null;
     
     $msg = "Enhorabuena, ahora sólo falta que confirmes tu registro en tu cuenta de correo";
    }
    else
    {
     $msg = "Ha ocurrido un error al llevar a cabo tu registro";
    }
     
   }
   else
   {
    $model->getErrors();
   }
  }
  return $this->render("register", ["model" => $model, "msg" => $msg]);
 }


Ver el vídeo tutorial de Yii Framework 2 en Youtube


8 comentarios:

Nomada Digital dijo...

hola disculpe tengo un problema, hize todo exactamente como el video y tengo problemas al presionar el boton registrar, se queda ahi no hace practiacamente nada y no me sale ningun error tampoco
Saludos

Admin dijo...

Hola, me sucedio lo mismo.
básicamente el error sucede por que se usa las clases ActiveForm y Response en el SiteController y no estan siendo importadas.
solo agrega en SiteController.php:

use yii\widgets\ActiveForm;
use yii\web\Response;

para que funcione.
Saludos

Admin dijo...

Hola José, el error básicamente es por que se esta haciendo uso las clases ActiveRecord, Response, Url y Html en el SiteController.php y estos no fueron importados, agregalos y con eso funciona, deberia quedarte asi:

use app\models\FormRegister;
use app\models\Users;
use yii\widgets\ActiveForm;
use yii\web\Response;
use yii\helpers\Url;
use yii\helpers\Html;

Para el caso del archivo SiteController.php
Saludos.

Unknown dijo...

Jose me ocurrio lo mismo que a ti y agregue "use yii\widgets\ActiveForm;" al principio del archivo "SiteController.php"

Unknown dijo...

no me manda el correo de confirmacion

Unknown dijo...

Um detalhe amigos. A legenda da validação do campo password diz que é necessário colocar entre 6 a 16 caracteres, porém no arquivo Formregister.php a regra está 8 conforme abaixo:

['password', 'match', 'pattern' => "/^.{8,16}$/", 'message' => 'Mínimo 6 y máximo 16 caracteres'],

Pode ser que estejam com dificuldades pois o botão "Register" não será ativado com senha menor que 8 números!

Paola Botero dijo...

hola, me funciono casi todo, lo único que me ha fallado es que no envía los mensajes a los correos, ya he probado con distintos usuarios y correos y nada, en la bd si se crean los usuarios, que podría ser??

Unknown dijo...

Todo perfecto, solo que implemente la validación de si un dato existe, en mi caso particular la cedula de un cliente, funciona bien cuando es crear un nuevo registro pero en el momento de actualizar tambien valida y pues no realiza actualización de datos, ¿como podria solucionar esto?