Códigos de Invitación con Laravel

Quizás en algún momento de nuestras divertidas vidas como desarrolladores podemos encontrarnos con una aplicación que requiera códigos de invitación para acceder a alguna sección, registro, módulo, etc.  Con Laravel podemos hacerlo simplemente usando un paquete y en este tutorial aprenderemos cómo con un ejemplo.

Gracias a un paquete llamado Doorman, generar códigos de invitación es muy simple. Entre las posibilidades que nos ofrece este paquete con los códigos tenemos:

  • Poder vincularlo a una dirección de correo electrónico específica.
  • Puede estar disponible para cualquier persona (ideal para compartir en las redes sociales).
  • Puede tener un número limitado de usos o ilimitado.
  • Puede tener una fecha de caducidad o nunca caducar.

Nuestro Miniproyecto

Para entender un poco mejor cómo podemos usar este paquete vamos a realizar un miniproyecto donde un usuario se pueda registrar únicamente con un código de invitación que no haya expirado y que no se haya usado más veces de lo que está permitido.

Para comenzar vamos a crear un proyecto de Laravel con Composer escribiendo en nuestra terminal lo siguiente:

composer create-project laravel/laravel invitation

Debemos crear una base de datos y asociarla a nuestro proyecto creando el archivo .env a partir del archivo .env.example que se encuentra en la raíz y añadiendo las credenciales de la base de datos.

Para nuestro ejemplo vamos a necesitar iniciar sesión; por tanto, generaremos la funcionalidad que trae Laravel por defecto con el comando de Artisan:

php artisan make:auth

Luego cambiemos el idioma de nuestra aplicación en el archivo config/app.php a español. Esto lo haremos para aprovechar que el paquete incluye, por defecto, la localización a español. Para cambiar el idioma simplemente editamos la clave locale en el archivo antes mencionado:

'locale' => 'es'

Instalación del paquete

La instalación del paquete es muy sencilla, con Composer podemos requerirlo en nuestro proyecto con el siguiente comando:

composer require clarkeash/doorman

El paquete requiere que tengas una versión de Laravel igual o mayor a la 5.6. Esto quiere decir que no necesitamos agregar de forma manual el Service Provider o los Facades necesarios.

El siguiente paso a seguir es recomendable, ya que el paquete necesita ejecutar una migración que creará una tabla para los códigos, pero en algunas ocasiones se puede necesitar algún campo extra.

Para tener la migración dentro de la carpeta database/migrations y así editarla (en caso de ser necesario) ejecutamos en la terminal lo siguiente:

php artisan vendor:publish --provider='Clarkeash\Doorman\Providers\DoormanServiceProvider'

Hace falta resaltar que luego de ejecutar este comando también fue agregado el archivo config/doorman.php el cual contiene la configuración necesaria del paquete si deseamos personalizarlo.  Otros archivos agregados están en el directorio resources/lang/vendor/doorman en varias carpetas que contienen los mensajes necesarios en distintos idiomas.

Para finalizar con la instalación debemos ejecutar las migraciones con el siguiente comando:

php artisan migrate

Para nuestro ejemplo de pruebas debes crear un usuario ahora ya que luego necesitaremos un código el cual no podremos crear si no tenemos algún usuario.

Rutas

Para el ejemplo de nuestro miniproyecto tendremos algunas rutas que manejarán los códigos de invitación. Nuestro archivo routes/web.php debe quedar de esta forma:

<?php

Route::get('/', function () {
  return view('welcome');
});

Route::group(['middleware' => 'auth'], function () {
  Route::resource('invitations', 'InviteController')->only(['index', 'create', 'store']);
});

Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');

Este es un proyecto de muestra, para ahorrar tiempo no vamos a colocar todo el CRUD completo sobre las invitaciones.

Controladores

Comenzaremos creando un nuevo controlador el cual será el encargado de manipular los códigos de invitación, ejecutando el siguiente comando en nuestra terminal:

php artisan make:controller InviteController

En este controlador vamos a trabajar con 3 métodos que se encargan de listar todos los códigos, mostrar un formulario de creación y guardarlos. El código del controlador es el siguiente:

<?php

namespace App\Http\Controllers;

use Carbon\Carbon;
use Illuminate\Http\Request;
use Clarkeash\Doorman\Models\Invite;
use Clarkeash\Doorman\Facades\Doorman;

class InviteController extends Controller
{
  public function index()
  {
    $invitations = Invite::all();

    return view('invitations.index', compact('invitations'));
  }

  public function create()
  {
    return view('invitations.create');
  }

  public function store(Request $request)
  {
    // Creamos una instancia de carbon
    $date = Carbon::createFromFormat('Y-m-d', $request->get('valid_until'));

    // Generamos el código
    Doorman::generate()
      ->uses($request->get('max'))
      ->expiresOn($date)
      ->make();

    return redirect()->route('invitations.index');
  }
}

Con el Facade Doorman generamos un nuevo código, especificamos el límite de usos y la fecha de expiración.

Como lo has notado el paquete ya contiene un modelo llamado Invitation el cual funciona como cualquier otro modelo de Laravel y hace referencia a la tabla invitations. Si deseas cambiar la tabla debes dirigirte a config/doorman.php.

La idea de nuestro proyecto es que no pueda registrarse cualquier persona, sino que para hacerlo es necesario que el usuario indique un código de invitación activo y en el caso de que no lo esté mostrar cuál es el error.

Mejora tus habilidades con Blade, Eloquent ORM y aprende a desarrollar módulos avanzados con Laravel

Ver más

Anteriormente, hemos ejecutado un comando que nos generó todo lo necesario para registro, login, recuperación de contraseña, etc. El punto es que tendremos que editar lo que viene por defecto. El controlador que se encarga del registro está ubicado en app/Http/Controllers/Auth/RegisterController.php.

Para comenzar a trabajar con este controlador necesitaremos sobreescribir el método register el cual está contenido en el trait RegistersUsers, agregar el requerimiento del campo code e importar todas las clases necesarias:

<?php

namespace App\Http\Controllers\Auth;

use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Http\Controllers\Controller;
use Clarkeash\Doorman\Facades\Doorman;
use Illuminate\Auth\Events\Registered;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;
use Clarkeash\Doorman\Exceptions\DoormanException;

class RegisterController extends Controller
{
  use RegistersUsers;

  protected $redirectTo = '/home';

  public function __construct()
  {
    $this->middleware('guest');
  }

  protected function validator(array $data)
  {
    return Validator::make($data, [
      'code' => 'required',
      'name' => 'required|string|max:255',
      'email' => 'required|string|email|max:255|unique:users',
      'password' => 'required|string|min:6|confirmed',
    ]);
  }

  protected function create(array $data)
  {
    return User::create([
      'name' => $data['name'],
      'email' => $data['email'],
      'password' => Hash::make($data['password']),
    ]);
  }

  public function register(Request $request)
  {
    $this->validator($request->all())->validate();

    try {
      Doorman::redeem($request->get('code'));
    } catch (DoormanException $e) {
      return back()->withErrors(['code' => $e->getMessage()]);
    }

    event(new Registered($user = $this->create($request->all())));

    $this->guard()->login($user);

    return $this->registered($request, $user)
      ?: redirect($this->redirectPath());
  }
}

Utilizando un try catch podemos capturar una excepción en el caso de que el código no sea válido, haya expirado o sus usos llegaron al límite. En caso de que el código sea válido se contará el uso. En caso de capturar un error retornamos un error con la llave code y el mensaje capturado en español.

Vistas

Solo necesitaremos agregar un par de vistas, una para listar todos los códigos y la otra para crear uno nuevo. Comenzaremos creando la vista para listar todos los códigos en resources/views/invitations/index.blade.php con este contenido:

@extends('layouts.app')

@section('content')
  <div class="container">
    <div class="row justify-content-center">
      <div class="col-md-8">
        <a href="{{ route('invitations.create') }}" class="btn btn-primary mb-4">
          Crear nuevo código
        </a>

        <div class="card">
          <div class="card-header">Códigos de Invitación</div>

          <div class="card-body">
            <table class="table table-bordered">
              <thead>
                <tr>
                  <th>ID</th>
                  <th>Código</th>
                  <th>Usos</th>
                  <th>Limite de Usos</th>
                  <th>Expiración</th>
                </tr>
              </thead>
              <tbody>
                @foreach ($invitations as $invitation)
                  <tr>
                    <td>{{ $invitation->id }}</td>
                    <td>{{ $invitation->code }}</td>
                    <td>{{ $invitation->uses }}</td>
                    <td>{{ $invitation->max }}</td>
                    <td>{{ $invitation->valid_until->format('d/m/Y') }}</td>
                  </tr>
                  @endforeach
              </tbody>
            </table>
          </div>
        </div>
      </div>
    </div>
  </div>
@endsection

Procedemos ahora a crear la vista para crear un nuevo código de invitación en la dirección resources/views/invitations/create.blade.php con el siguiente código:

@extends('layouts.app')

@section('content')
  <div class="container">
    <div class="row justify-content-center">
      <div class="col-md-8">
        <div class="card">
          <div class="card-header">Crear Código de Invitación</div>

          <div class="card-body">
            <form action="{{ route('invitations.store') }}" method="POST">
              @csrf
              <div class="form-group">
                <label for="max">Limite de Usos</label>
                <input type="number" name="max" class="form-control" required>
              </div>

              <div class="form-group">
                <label for="valid_until">Fecha de Expiración</label>
                <input type="date" name="valid_until" class="form-control" required>
              </div>

              <div class="form-group">
                <button type="submit" class="btn btn-success">Registrar</button>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
@endsection

Aún debemos editar la vista del registro para colocar el campo code al cual hacemos referencia en el controlador RegisterController que hemos visto anteriormente. La vista se encuentra en resources/views/auth/register.blade.php y simplemente agregaremos lo siguiente abajo de la directiva @csrf:

<div class="form-group row">
  <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Code') }}</label>

  <div class="col-md-6">
    <input id="code" 
           type="text" 
           class="form-control{{ $errors->has('code') ? ' is-invalid' : '' }}"
           name="code" 
           value="{{ old('code') }}" 
           required 
           autofocus>

    @if ($errors->has('code'))
      <span class="invalid-feedback" role="alert">
        <strong>{{ $errors->first('code') }}</strong>
      </span>
    @endif
  </div>
</div>

Finalmente, podemos acceder a la ruta donde se listan todos los códigos y podemos observar algo como esto:

Resultado final del tutorial

Ahora si queremos registrar un nuevo usuario necesitaremos un código activo, en la imagen anterior se muestra que existe un código que llegó al límite de usos y al intentar usarlo tendremos algo como esto:

Registro para nuevos usuarios

¡Todo listo!

Ya hemos terminado nuestro proyecto, ahora un usuario no se podrá registrar sin un código de invitación.

Si deseas conocer mucho más sobre este paquete te invito a que ingreses al repositorio oficial del paquete y explores cuáles son las posibilidades que ofrece ademas de las que ya hemos visto con este ejemplo.

El código utilizado en este ejemplo lo puedes encontrar en este repositorio. Si deseas hacer algunos cambios o has encontrado algún error puedes hacer un pull request. Te invito a que compartas este tutorial en tus redes sociales y no olvides comentar las dudas que tengas o el resultado obtenido después de seguir este tutorial.

Espero que te haya gustado este material. No olvides seguirnos en Twitter y suscribirte a nuestro boletín:

Suscríbete a nuestro boletín

Te enviaremos publicaciones con consejos útiles y múltiples recursos para que sigas aprendiendo.

Material relacionado

Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.