Hace unas semanas fue publicado un tutorial en un nivel intermedio para aprender Laravel desde la documentación del framework como lo hicieron con la guía rápida para principiantes. En esta oportunidad, este tutorial abarca relaciones con Eloquent, rutas, autenticación, políticas de acceso, inyección de dependencias, Route Model Binding, entre otros detalles que veremos a continuación, siendo un interesante recurso si ya conoces lo básico para seguir aprendiendo sobre Laravel.
En este tutorial se desarrollará una lista de tareas que permitirá el registro de usuarios. Un avance a lo que pudimos ver en la guía rápida para principiantes.
Como siempre debes tener un entorno de desarrollo donde instalar el proyecto de manera local y configurado según el sistema operativo que uses. Nuestras series Instalación y configuración de entornos y Vagrant y Homestead pueden ayudarte en ello.
También puedes descargar el proyecto completo desde su repositorio en GitHub y seguir nuestra guía de cómo instalar proyectos existentes de laravel
Preparando la base de datos
Las migraciones son la manera para definir la estructura de la base de datos y poder modificarla usando código PHP. De esta manera se facilita el trabajo permitiendo llevar un mejor control de la base de datos. Para trabajar con las migraciones debemos confirmar que tenemos el archivo de configuración .env con las credenciales de la base de datos que se va a usar.
Tabla users
Debido a que la aplicación permitirá el registro de usuarios se creará esta tabla para almacenar su información. Laravel trae por defecto una migración para la tabla de usuarios (users), ésta se encuentra en el directorio database/migrations.
Tabla tasks
Para guardar la información de las tareas. Se crea ejecutando por consola:
php artisan make:migration create_tasks_table --create=tasks
Esto genera un archivo llamado create_tasks_table.php en el directorio database/migrations y en su contenido ya tiene establecido crear el campo de Id incremental y el timestamps, solo se necesita agregar los relacionados con el nombre de la tarea: un campo tipo string denominado name y uno llamado user_id para vincular la tabla tasks con la tabla users:
<?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateTasksTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('tasks', function (Blueprint $table) { $table->increments('id'); $table->integer('user_id')->index(); $table->string('name'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('tasks'); } }
Para ejecutar las migraciones hacemos php artisan migrate y las tablas se crearán en la base de datos con los respectivos que se definieron en ellas.
Modelos de Eloquent
Eloquent es el ORM que viene por defecto en Laravel (Object-Relational Mapper) que nos permite interactuar con nuestras bases de datos de forma sencilla, nuevamente usando código PHP. Nos permite recuperar y almacenar datos por medio de “modelos”. Por lo general, cada modelo Eloquent corresponde a una tabla de la base de datos.
Modelo User
Para trabajar con los usuarios en la base de datos se necesita un modelo. Laravel por defecto ya tiene definido uno en el directorio app.
Modelo Task
Para definir el modelo Task que corresponde a la tabla tasks de la base de datos se ejecuta por consola:
php artisan make:model Task
Se creará un nuevo archivo en el directorio app llamado Task.php que por defecto está vacío. Por los momentos solo agregamos que el atributo name pueda ser asignado masivamente:
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Task extends Model { protected $fillable = ['name']; }
Relaciones Eloquent
Ya teniendo los modelos definidos podemos vincularlos entre sí para trabajar con ellos de una manera más fluida. Para este caso se tendría que: un usuario (user) puede tener muchas tareas (tasks) mientras que una tarea (task) es asignada a un usuario (user)
Relaciones en el modelo User
Se modifica el modelo User.php que se halla en el directorio app para incluir la relación de uno a muchos (hasMany) con respecto al modelo Task, creando un método llamado tasks, así:
<?php namespace App; class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract { use Authenticatable, Authorizable, CanResetPassword; public function tasks() { return $this->hasMany(Task::class); } }
Relaciones en el modelo Task
También se define la relación con el modelo User de esta manera:
<?php namespace App; use App\User; use Illuminate\Database\Eloquent\Model; class Task extends Model { protected $fillable = ['name']; /** * Get the user that owns the task. */ public function user() { return $this->belongsTo(User::class); } }
belongsTo es el método de eloquent para definir esta relación.
Para aprender más sobre Eloquent te recomendamos el siguiente material:
- Fluent y Eloquent en Laravel 5 – Parte 1
- Tablas pivote con Eloquent en Laravel
- Aprende a usar Eloquent el ORM de Laravel
Rutas
En la guía rápida para principiantes se usó solo Closures para definir las rutas del proyecto, sin embargo, para la mayoría de las aplicación es recomendable usar controladores para organizar las rutas.
Autenticación
Laravel proporciona un controlador para trabajar con la autenticación de una aplicación: app/Http/Controllers/AuthController , este controlador usa un especial trait app/Http/Controllers/AuthController que contiene toda la lógica necesaria para crear y autenticar a los usuarios.
Rutas de autenticación
Para trabajar con el controlador se necesitan definir las siguientes rutas en app/Http/routes.php:
// rutas para autenticar Route::get('auth/login', 'Auth\AuthController@getLogin'); Route::post('auth/login', 'Auth\AuthController@postLogin'); Route::get('auth/logout', 'Auth\AuthController@getLogout'); // rutas para registrar Route::get('auth/register', 'Auth\AuthController@getRegister'); Route::post('auth/register', 'Auth\AuthController@postRegister');
Vistas de autenticación
Se requiere crear las vistas login.blade.php y register.blade.php en el directorio resources/views/auth .
El archivo register.blade.php debería contener un formulario que incluya los campos de email y contraseña y confirmación de contraseña y realice una solicitud POST a la ruta /auth/register .
El archivo login.blade.php debería contener un formulario que incluya los campos de email y contraseña y realiza un solicitud de tipo POST a la ruta /auth/login.
Para profundizar sobre el login y registro de usuarios en Laravel 5.1:
Controlador para Task
Para poder recuperar y almacenar tareas (tasks) se necesita crear el controlador TaskController en el directorio app/Http/Controllers con el siguiente comando ejecutado por consola:
php artisan make:controller TaskController --plain
y crear las rutas a las que apuntará el controlador en el archivo app/Http/routes.php:
Route::get('/tasks', 'TaskController@index'); Route::post('/task', 'TaskController@store'); Route::delete('/task/{task}', 'TaskController@destroy');
Autenticación de todas las rutas de Tasks
Para esta aplicación, las rutas de las tareas se requiere de un usuario autenticado, es decir, el usuario debe estar registrado para crear una tarea. Para hacer posible esto se tiene que restringir el acceso a esas rutas y Laravel nos proporciona los middleware para ese propósito.
En este caso vamos a restringir el acceso a todas las acciones del controlador por lo que incluiremos el middleware en el método constructor de la clase TaskController:
<?php namespace App\Http\Controllers; use App\Http\Requests; use Illuminate\Http\Request; use App\Http\Controllers\Controller; class TaskController extends Controller { /** * Create a new controller instance. * * @return void */ public function __construct() { $this->middleware('auth'); } }
Se usó el middleware auth para hacer la restricción. Todos los middleware de rutas estan definidos en app/Http/Kernel.php
Para conocer más sobre middleware:
- Protege el acceso a tu aplicación con los Middleware de Laravel 5
- Middleware en Laravel 5.1
- Cómo crear y usar los Middleware en Laravel 5.1
Creación de Layout y vistas
Esta aplicación solo tiene una vista que contiene un formulario para agregar las nuevas tareas así como la lista de las tareas actuales. El resultado es algo como la siguiente imagen donde se usó Bootstrap para los estilos de CSS.
Definición del layout
Laravel usa el motor de plantillas Blade para renderizar las vistas como se vio en la guía rápida para principiantes.
Un layout es un marco base que comparten varias páginas de una aplicación. Por ejemplo para ésta se tiene una barra de navegación en parte superior que usualmente puede estar en todas sus vistas. Por tanto para esta aplicación el archivo app.blade.php ubicado en el resources/view/layout/ contiene lo siguiente:
<!DOCTYPE html> <html lang="en"> <head> <title>Laravel Quickstart - Intermediate</title> <!-- CSS And JavaScript --> </head> <body> <div class="container"> <nav class="navbar navbar-default"> <!-- Navbar Contents --> </nav> </div> @yield('content') </body> </html>
@yield(‘content’) es una directiva especial de Blade que permite extender el layout para inyectar el contenido en otras vistas.
Creación de vista index.blade.php
Hay vista que puede extender a otras, en este caso index.blade.php extiende de layout.blade.php . Esto se hace con la directiva de Blade @extends , la cual informa que plantilla está extendiendo; todo el contenido que se encuentre entre @section(‘content’) y @endsection será inyectado donde se ubique @yield(‘content’) en el layout app.blade.php para así renderizar la vista completa.
Para esta aplicación creamos una vista que muestre el formulario para crear una nueva tarea index.blade.php y lo ubicamos en el directorio resources/views/tasks/
@extends('layouts.app') @section('content') <!-- Bootstrap Boilerplate... --> <div class="panel-body"> <!-- Display Validation Errors --> @include('common.errors') <!-- New Task Form --> <form action="/task" method="POST" class="form-horizontal"> {{ csrf_field() }} <!-- Task Name --> <div class="form-group"> <label for="task-name" class="col-sm-3 control-label">Task</label> <div class="col-sm-6"> <input type="text" name="name" id="task-name" class="form-control"> </div> </div> <!-- Add Task Button --> <div class="form-group"> <div class="col-sm-offset-3 col-sm-6"> <button type="submit" class="btn btn-default"> <i class="fa fa-plus"></i> Add Task </button> </div> </div> </form> </div> <!-- TODO: Current Tasks --> @endsection
En esta vista fueron omitidas las partes de Bootstrap CSS para ver el código completo en GitHub
Para devolver esta vista debemos crear un método index en TaskController.php con lo siguiente:
/** * Display a list of all of the user's task. * * @param Request $request * @return Response */ public function index(Request $request) { return view('tasks.index'); }
Adición de Tareas
Ya teniendo las vistas lista falta trabajar con la ruta POST para las tareas, es decir, crear el método del controlador que manejará la entrada de datos desde el formulario y su almacenamiento en la base de datos. Pero primero vamos a validar la entrada.
Validación
Para este formulario se establece que el campo name sea obligatorio y que contenga un máximo de 255 caracteres. Si la validación falla se redireccionará al usuario a la URL /tasks así como los datos introducidos y los errores obtenidos. De esta manera en el archivo TaskController.php:
/** * Create a new task. * * @param Request $request * @return Response */ public function store(Request $request) { $this->validate($request, [ 'name' => 'required|max:255', ]); // Create The Task... }
A diferencia de la guía rápida para principiantes aquí la validación se realiza en el controlador por lo que se hace uso del trait ValidatesRequests a través del método validate incluido en la clase Controller, el cual acepta como parámetros al request y un array con las reglas de validación. La redirección hacía atrás se hace automáticamente.
Para conocer más sobre validaciones:
Creación de la tarea
Ahora que ya se maneja la validación de la entrada de datos, se crea realmente una nueva tarea y luego se redirecciona al usuario al URL /task. Para crear la tarea se hará uso del poder de las relaciones en Eloquent:
/** * Create a new task. * * @param Request $request * @return Response */ public function store(Request $request) { $this->validate($request, [ 'name' => 'required|max:255', ]); $request->user()->tasks()->create([ 'name' => $request->name, ]); return redirect('/tasks'); }
La mayoría de las relaciones de Laravel exponen un método save, el cual acepta una instancia del modelo relacionado y automáticamente establece un valor para la clave foránea en el modelo relacionado antes de ser almacenado en la base de datos. En este caso el método save establecerá automáticamente la propiedad user_id de la tarea dada con el ID del usuario actual autenticado que se obtiene usando $request->user() .
Mostrando las tareas existentes
Primero se necesita edita el método TaskController@index para pasar todas las tareas existentes a la vista. La función view acepta como segundo argumento un array con los datos que estarán disponibles en la vista, donde cada clave en el array será una variable en la vista. Por ejemplo:
/** * Display a list of all of the user's task. * * @param Request $request * @return Response */ public function index(Request $request) { $tasks = Task::where('user_id', $request->user()->id)->get(); return view('tasks.index', [ 'tasks' => $tasks, ]); }
Sin embargo, se puede hacer uso de las capacidades de inyección de dependencias de Laravel para inyectar TaskRepository en el TaskController, el cual será usado para todo nuestro acceso a datos.
Inyección de dependencias
El contenedor de servicios de Laravel es una de las características más poderosas del framework.
Creación de un repositorio
Se define un repositorio TaskRepository para mantener toda la lógica de acceso de datos para el modelo Task. Algo especialmente útil si la aplicación crece y se necesita compartir algunas consultas de Eloquent a través de la aplicación. Se crea un directorio app/Repositories y se agrega la clase TaskRepository con lo siguiente:
<?php namespace App\Repositories; use App\User; use App\Task; class TaskRepository { /** * Get all of the tasks for a given user. * * @param User $user * @return Collection */ public function forUser(User $user) { return Task::where('user_id', $user->id) ->orderBy('created_at', 'asc') ->get(); } }
Inyección de repositorio
Una vez que el repositorio está definido, se puede inyectar en el controlador TaskController a través de su constructor y luego usarlo dentro del método index.
<?php namespace App\Http\Controllers; use App\Task; use App\Http\Requests; use Illuminate\Http\Request; use App\Http\Controllers\Controller; use App\Repositories\TaskRepository; class TaskController extends Controller { /** * The task repository instance. * * @var TaskRepository */ protected $tasks; /** * Create a new controller instance. * * @param TaskRepository $tasks * @return void */ public function __construct(TaskRepository $tasks) { $this->middleware('auth'); $this->tasks = $tasks; } /** * Display a list of all of the user's task. * * @param Request $request * @return Response */ public function index(Request $request) { return view('tasks.index', [ 'tasks' => $this->tasks->forUser($request->user()), ]); } }
Mostrando las tareas
Una vez que los datos son pasados, se pueden procesar las tareas en la vista tasks/index.blade.php para ser mostradas en una tabla, haciendo uso de la directiva de Blade @foreach
@extends('layouts.app') @section('content') <!-- Create Task Form... --> <!-- Current Tasks --> @if (count($tasks) > 0) <div class="panel panel-default"> <div class="panel-heading"> Current Tasks </div> <div class="panel-body"> <table class="table table-striped task-table"> <!-- Table Headings --> <thead> <th>Task</th> <th> </th> </thead> <!-- Table Body --> <tbody> @foreach ($tasks as $task) <tr> <!-- Task Name --> <td class="table-text"> <div>{{ $task->name }}</div> </td> <td> <!-- TODO: Delete Button --> </td> </tr> @endforeach </tbody> </table> </div> </div> @endif @endsection
Si quieres conocer más sobre estos temas puedes consultar:
Si quieres aprender a crear código profesionalmente con PHP usando programación orientada a objetos y además entender cómo funciona Laravel:
Eliminación de tareas
Añadir el botón de eliminar
Hay que agregar un botón de eliminar por cada tarea listada en la vista task/index.blade.php. Se creará un formulario con un botón por cada tarea de la lista. Cuando se hace clic en el botón una solicitud DELETE /task será enviada a la aplicación que activará el método TaskController@destroy
<tr> <!-- Task Name --> <td class="table-text"> <div>{{ $task->name }}</div> </td> <!-- Delete Button --> <td> <form action="/task/{{ $task->id }}" method="POST"> {{ csrf_field() }} {{ method_field('DELETE') }} <button>Delete Task</button> </form> </td> </tr>
Como se puede apreciar el método del formulario para el botón es de tipo POST aunque esté respondiendo a una solicitud usando una ruta Route::delete . Los formularios de HTML solo admiten las peticiones de tipo GET y POST, por lo que se necesita una manera de pasar la petición DELETE desde el formulario. Esto se hace colocando method_field(‘DELETE’) dentro del formulario el cual generará un campo oculto que sobreescribirá el método para colocar el verdadero.
<input type="hidden" name="_method" value="DELETE">
Route Model Binding
Antes de hacer el método destroy en TaskController , primero revisaremos la declaración para esta ruta:
Route::delete('/task/{task}', 'TaskController@destroy');
Sin añadir código adicional, Laravel podría inyectar el ID de la tarea dada en el método TaskController@destroy de esta manera:
/** * Destroy the given task. * * @param Request $request * @param string $taskId * @return Response */ public function destroy(Request $request, $taskId) { // }
Sin embargo lo primero que se debe hacer es recuperar la instancia de la tarea desde la base de datos a partir del ID dado. Pero mejor es inyectar la instancia de la tarea que coincide con la ID de una sola vez. Para esto se debe agregar en el método boot del archivo app/Providers/RouteServiceProvider.php lo siguiente:
$router->model('task', 'App\Task');
Con esto Laravel recupera el modelo Task que corresponde con el ID dado, siempre que vea {task} en la declaración de la ruta. Ahora el método destroy se puede definir así:
/** * Destroy the given task. * * @param Request $request * @param Task $task * @return Response */ public function destroy(Request $request, Task $task) { // }
Políticas de acceso
Ya tenemos la instancia de la tarea inyectada en el método destroy, pero no hay garantía que el usuario autenticado sea realmente el dueño de la tarea dada. Por ejemplo cuando por medio de una solicitud maliciosa se intente eliminar las tareas de un usuario pasando un ID de tarea aleatorio a la ruta /tasks/{task} . Para ello se necesitará hacer uso de las capacidades de las politicas de acceso de Laravel para garantizar que el usuario que sea el real dueño de la instancia de la tarea que fue inyectada en la ruta.
Creación de una política
Laravel usa policies para organizar la lógica de autorización en clases sencillas y simples. Tipicamente cada política corresponde a un modelo. Para crear una política se ejecuta por consola:
php artisan make:policy TaskPolicy
Este comando generará el archivo app/Policies/TaskPolicy.php
Ahora se agrega el método destroy a la política. Este método recupera la instancia del usuario y la instancia de la tarea y comprueba si el ID de usuario coincide con el user_id de la tarea. De hecho, todos los métodos de la política deberían retornar un valor true o false.
<?php namespace App\Policies; use App\User; use App\Task; use Illuminate\Auth\Access\HandlesAuthorization; class TaskPolicy { use HandlesAuthorization; /** * Determine if the given user can delete the given task. * * @param User $user * @param Task $task * @return bool */ public function destroy(User $user, Task $task) { return $user->id === $task->user_id; } }
Por último, se necesita asociar el modelo Task con TaskPolicy. Lo hacemos agregando una línea en la propiedad $policies del archivo app/Providers/AuthServiceProvider.php para informarle a Laravel cuál política debería ser usada cuando se intenta autorizar una acción en una instancia de la tarea.
/** * The policy mappings for the application. * * @var array */ protected $policies = [ Task::class => TaskPolicy::class, ];
Autorización de la acción
Luego que la política está definida se puede usar en el método destroy. Todos los controladores de Laravel pueden llamar a un método authorize que se encuentra en el trait AuthorizesRequest
/** * Destroy the given task. * * @param Request $request * @param Task $task * @return Response */ public function destroy(Request $request, Task $task) { $this->authorize('destroy', $task); // Delete The Task... }
En el llamado al método authorize se pasa como primer argumento el nombre del método de la política que se desea llamar y como segundo argumento la instancia del modelo que se está tratando. Como Laravel asocia un modelo con una política, ya sabe que el modelo Task corresponde con TaskPolicy y su método destroy. El usuario actual se envía automáticamente por lo cual no necesita ser pasado manualmente.
Si la acción es autorizada, el código continuará ejecutándose normalmente. Sin embargo, si la acción no es autorizada (es decir, el método destroy de la política devuelve false) se lanzará una excepción 403 y una página de error será mostrada al usuario.
Para conocer más sobre autorización y políticas de acceso a Laravel puedes consultar:
- Autorización y Controles de Acceso (Parte 4 de Curso introductorio a Laravel 5.1)
Eliminación de la tarea
Finalmente, toca agregar la lógica al método destroy para realmente eliminar la tarea dada. Se usa el método delete de Eloquent para eliminar la instancia del modelo dado en la base de datos. Una vez que el registro es eliminado, se redirecciona el usuario a la URL /task
/** * Destroy the given task. * * @param Request $request * @param Task $task * @return Response */ public function destroy(Request $request, Task $task) { $this->authorize('destroy', $task); $task->delete(); return redirect('/tasks'); }
Recuerda que el proyecto completo se encuentra disponible en GitHub.
Si deseas aprender más sobre este framewok te invitamos a adquirir una membresía en Styde.Net para acceder a todo el contenido exclusivo de nuestras Series y Cursos.
Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.
Lección anterior Rutas con filtros en Laravel 5.1 Lección siguiente Cómo funcionan los Service Providers en Laravel 5.1