Laravel 5 Fast Tutorial

Index

  1. Estructura
  2. Uso de Namespaces y PSR-4
  3. Creación de un nuevo proyecto
  4. Composer
  5. Rutas
  6. Expresiones regulares para rutas
  7. Migraciones
  8. Seeders
  9. Seeders y el componente Faker de Laravel 5
  10. Fluent: el constructor de consultas SQL de Laravel
  11. Eloquent
  12. Blade, el motor de plantillas de Laravel
  13. Laravel Collective, integración de los componentes Form y Html
  14. Listado y Paginación
  15. Creación de usuarios
  16. Edición de usuarios
  17. Validación. Parte I
  18. Validación. Parte II
  19. Eliminar registros
  20. Internacionalización
  21. Principios del diseño DRY
  22. AJAX con Laravel
  23. Búsquedas y filtros con Laravel y Eloquent (Query scopes)
  24. Filtros de búsqueda y paginación
  25. Proteger el acceso mediante los Middleware
  26. Creando nuestros propios Middleware

Estructura

  • Controllers: /app/Http/Controllers/
  • Vistas: /resources/views/
  • Paquetes de idiomas: /resources/
  • Modelos: se pueden organizar dentro de /app/ en la forma que tengan más sentido en la aplicación
  • Configuración de la aplicación: /app/providers/ para dar un enfoque más orientado a objetos a la configuración de la aplicación
  • Otras carpetas como config y database (que alojan migraciones y seeders) ahora estan el el directorio principal en vez de /app/

Uso de Namespaces y PSR-4

Los controladores, modelos y demás clases de la aplicación estarán en el directorio /app/ y usarán almenos un namespace principal. Por defecto es «App» per se puede cambiar con un simple comando:

php artisan app:name Cms

Recomendación: Utilizar el nombre de la aplicación como namespace

Composer carga automáticamente todas las clases de la aplicación, gracias a que Laravel viene con el estandar PSR-4 configurado por defecto (es un estandar que organiza las clases en directorios y archivos)

Si tienes una clase \Cms\User va a estar en /app/Cms/User.php

La clase \Http\Controllers\HomeController va a estar en app/Http/Controllers/HomeController.php, etc.

Composer nos permite manejar las dependencias de nuestros proyectos, ya sea la descarga de un framework completo como Laravel, o más senzillos como un generador de PDFs

Creación de un nuevo proyecto

Vía composer:

composer create-project --prefer-dist laravel/laravel project_name "5.4.*" 

Composer

Uso de composer, independientemente, sin usar laravel u otro framework.

Para todos los proyectos de composer, crear un archivo composer.json. Aquí debemos insertar información útil para composer, en formato JSON:

{
    "name" : "ruta/al/proyecto/",
    "description" : "descripción del proyecto",
    "type" : "project",
    "authors" : [{
        "name" : "",
        "email" : "",
    }],
    "require" : { //paquetes de los cuales depende la aplicación
        "php" : ">=5.3.0",
    },
    "autoload" : {  //autoload de las clases que vamos a utilizar
    },
    "psr-4" : { //protocolo }
    }
}

Para instalar composer dentro del proyecto:

composer install

packagist.org: repositorio de packages de composer.

Rutas

Se definen en ./routes/web.php

Para rutas de páginas:

Route::get('ruta/a/pagina','ControllerName@action');

Para módulos CRUD:

Route::resource('admin/news','AdminNewsController');

Para crear este tipo de controladores hacer uso de la consola:

php artisan make:controller AdminNewsController

Este comando nos genera una serie de rutas asociadas al controlador

Para saber las diferentes rutas que tenemos definidas en laravel:

php artisan route:list

Con funciones anónimas. Totalmente desaconsejado pero útil al inicio para definir rutas rápidamente:

Route::get('ruta/a/pagina',function()
{
    //return View::make('hello');
    return "hola";
});

Expresiones regulares para rutas

Un ejemplo:

Route::get('admin/news/{id}', function($id){
    return $id;
})->where('id','\d+');

o

Route::pattern('id','\d+');
Route::get('admin/news/{id}', function($id){
    return $id;
});

Migraciones

Son la forma en como definimos las tablas en Laravel. Son una herramienta que nos permite crear una especie de sistema de control de versiones de bases de datos donde podemos definir tablas con POO en vez de SQL. Son ideales para trabajar en equipo, dado que elimina la necesidad de enviar fragmentos de SQL a través de email, mensajería, etc. Los programadores simplemente necesitan generar una nueva migración, escribir los cambios a la base de datos en ella y el resto del equipo puede simplemente ejecutar la migración.

[PROJECT]\database\migrations\2014_10_12_000000_create_users_table.php


[PROJECT]\database\migrations\2014_10_12_100000_create_password_resets_table.php

Continen los scripts para la creación de las tablas de usuarios en la BD

Ejecutar migración:

php artisan migrate

Para deshacer la última migración / volver a la migración anterior

php artisan migrate:rollback

¿Como crear una nueva migración?

Imáginemos que queremos crear una nueva tabla de etiquetas en la BD.

A la nueva migración la llamaremos «create_tags_table»

php artisan make:migration create_tags_table

Para completar mejor el comando anterior, le podemos añadir un flag con el nombre de la tabla que vamos a crear

php artisan make:migration create_tags_table --create="tags"

Esto nos genera un nuevo archivo para nuestra migración. Gracias al flag, se nos ha añadido código en el archivo para la creación de la tabla.

class CreateTableTags extends Migration {

    public function up()
    {
        Schema::create('tags', function(Blueprint $table)
        {
            $table->increments('id');
            $table->string('name');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::drop('tags');
    }
}

Una vez hayamos completado el código (hayamos añadido las columnas deseadas), ejecutamos:

php artisan migrate

Más comandos

Deshacer todas las migraciones:

php artisan migrate:reset

Deshacer todas las migraciones y vovler a ejecutarlas:

php artisan migrate:refresh

MÁS:

Seeders

Herramienta que nos permite cargar datos de prueba en la BD. [PROJECT]\database\seeds

  1. Descomentar

    $this->call(‘UserTableSeeder’);

  2. Crear class UserTableSeeder en la misma carpeta

  3. Vamos a la consola para ejecutar el seeder

    php artisan db:seed

Nos arroja un error: Aún y que la tabla si existe en el script necesitamos ejecutar antes dump auto-load. En el composer.json tanto los seeders como las migraciones no se ejecutan bajo el PSR4 sinó bajo el classmap.

composer dump-autoload

Seeders y el componente Faker de Laravel 5

Para poblar las tablas de nuestra BD con datos ficticios.

Cuando queremos crear un usuario tipo «admin» podemos hacerlo de forma manual en un seed, pero cuando queremos insertar muchos usuarios podemos hacerlo de una forma más agradable mediante «Faker».

Los trabajamos desde /database/seeds

Las migraciones las trabajamos una a una por tablas; con los seeders podemos trabajar diferentes tablas a la vez.

Para cada tabla creamos una Seeder (UserTableSeeder, AdminTableSeeder,…) y desde DatabaseSeeder los referenciamos para cuando demos la orden desde la consola los datos sean cargados para todos los seeders.

Componente «Faker»

  1. Descargar el componente fzaninotto/faker desde packagist
  2. Incluir la clase de Faker en nuestro Seed (Faker\Factory) y realizar inserts en nuestra tabla mediante un bucle y haciendo uso del objeto $faker

    namespace Faker\Factory;

    class UserTableSeeder extends Controller {

    public function run() { for($i = 0; $i < 10; $i++) { $faker = Faker::create();

    $id = \DB::table('users')->insertGetId(array(
        'name'  =>  $faker->firstName(),
        'email' =>  $faker->email,
        'type'  =>  'admin',
        'password'  => \Hash::make($faker->word)
    ));
    
    \DB::table('user_profiles')->insert(array(
        'user_id' => $id,
        'pic' =>  'http://placehold.it/150x200'
    ));
    }
    

    }

Una vez programados los seeds correspondientes, desde la consola:

php artisan migrate:refresh --seed

MÁS:

Fluent: el constructor de consultas SQL de Laravel

Se trata de un constructor de rutas SQL

Primero hemos creado un controlador para el modelo:

namespace App\Http\Controllers;
class UsersController extends Controller {
    ...
}

Y hemos añadido el nuevo controlador al archivo de rutas

Route::controllers([
    'users' => 'UsersController',
    ...
]);

Finalmente en nuestro controlador hemos creado una función donde construimos la consulta

namespace App\Http\Controllers;

class UsersController extends Controller {

    public function getIndex()
    {
        //$result = \DB::table('users')->get(); //return ALL the users in JSON format

        $result = \DB::table('users')
            ->select('name','email')
            ->where('name','javi')
            ->orderBy('name','ASC')
            //->orderBy(\BD::raw('RAND()'))
            ->get();

        //dd($result);

        return $result;
    }
}

MÁS:

Eloquent

Nos permiten mapear datos de la base de datos para convertirlos a objetos y viceversa, tomar un objeto y guardarlo como un registro de la base de datos.

ORM: técnica de programación para convertir datos entre el sistema de tipos utilizado en un lenguaje de programación orientado a objetos y la utilización de una base de datos relacional como motor de persistencia

Siguiendo con el ejemplo anterior, imaginemos que deseamos realizar tareas sobre los datos obtenidos, como ahora obtener el nombre completo o la edad del usuario. Eso nos obligaría a post-procesar los datos obtenidos paras contruir los resultados:

namespace App\Http\Controllers;

class UsersController extends Controller {

    public function getIndex()
    {
        $result = \DB::table('users')->get(); //return ALL the users in JSON format

        foreach($result as $row)
        {
            $row->full_name = $row->first_name . ' ' . $row->last_name;
            $row->age = \Carbon\Carbon::parse($row->birthdate)->age;
        }

        //dd($result);

        return $result;
    }
}

Pero existe una forma más senzilla: aquí es donde entran los ORM y en este caso en particular, Eloquent. Con los ORM las tablas de la base de datos son presentadas como Clases, y los registros como objetos.

Si nos fijamos en nuestra carpeta /app/ existe una clase llamada User.php que extiende de Eloquent, y es una clase que representa a la tabla de usuarios.

Este modelo de Eloquent nos permite utilizar una serie de métodos que nos permite hacer consultas de una forma más senzilla de lo que hemos visto hasta ahora con Fluent.

Volvemos a la Clase UsersController y haremos uso de la clase User en lugar del constructor Fluent. Crearemos otra función para probar y comparar el código entre ámbos métodos:

namespace App\Http\Controllers;

use App\User;

class UsersController extends Controller {

    public function getOrm()
    {
        $result = User::first();

        //dd($result);
        return $result;
    }

}

Y en web.php:

Route::get('orm', 'UsersController@getOrm');

Nos devuelve un objeto con propiedades adicionales, ya que ya no es un objeto plano sino que es un objeto que forma parte del ORM Eloquent.

Entonces, ¿qué hacemos si queremos obtener el nombre completo como en el ejemplo anterior?

  1. Debemos crear un nuevo método dentro de nuestra Clase User

    public function getFullNameAttribute() { return $this->first_name . ‘ ‘ . $this->last_name; }

  2. En el UserController obtener el resultado de la siguiente manera:

    namespace App\Http\Controllers;

    use App\User;

    class UsersController extends Controller {

    public function getOrm()
    {
        $result = User::first(); 
    
        //dd($result);
        //return $result;
    
        //Magia!! Equivale a 'return ($result->getFullNameAttribute);'
        return $result->full_name;
    }
    

    }

Para est mágia, Laravel hace uso de los métodos y atributos mágicos de PHP.

Cada vez que creemos una nueva tabla en la base de datos que queramos representar através del ORM, debemos crear una nueva clase (como la de User). Siguiendo con el ejemplo, vamos a crear un nuevo método para calcular la edad. Debemos crear un nuevo modelo para UserProfile

Este proceso es muy senzillo. Desde la consola:

php artisan make:model UserProfile

Para obtener ayuda de como utilizar el comando para crear un nuevo modelo:

php artisan help make:model

Para obtener e un listado de comandos que nos trae laravel

php artisan list


namespace App;

use Illuminate\Database\Eloquent\Model;

class UserProfileController extends Model {

}

Creamos nuestro método para obtener la edad:

namespace App;

use Illuminate\Database\Eloquent\Model;

class UserProfileController extends Model {

    public function getAgeAttribute()
    {
        return \Carbon\Carbon::parse($this->birthdate)->age;
    }
}

Atención!! Si nos fijamos no hemos especificado el nombre de la tabla. (protected $table = ‘user_profiles’;). Esto es debido a que el ORM adivina el nombre de la tabla a través del nombre de la clase (UserProfile => user_profiles)

Entonces, ¿cómo podemos hacer para traernos el perfil del usuario junto con el usuario? Con Eloquent, generando relaciones: desde la clase del módelo User, generamos un nuevo método (con el nombre deseado) donde generamos la relación:

public function profile()
{
    //en este caso hasOne (método que viene incluido en el ORM) porque cada usuario tiene un solo perfil
    return $this->hasOne('App\UserProfile'); 
}

Y de vuelta en el controlador

namespace App\Http\Controllers;

use App\User;

class UsersController extends Controller {

    public function getOrm()
    {
        $result = User::first();

        return ($result->profile->age); //Magia!!
        //que es lo mismo que 'return ($result->profile->getAgeAttribute);'.
    }

}

En conclusión, se ha trabajado mucho más limpiamente; no ha hecho falta realizar ningún JOIN.

La ventaja de utilizar un ORM no es dejar de escribir SQL. La ventaja es que permite convertir nuestros objetos a consultas SQL.

Compatibilidad con Fluent

El ORM Eloquent es compatible con el generador de consultas Fluent. Por ejemplo:

namespace App\Http\Controllers;

use App\User;

class UsersController extends Controller {

    public function getOrm()
    {
        $users = User::get();

        dd($users);
    }

}

Esto nos devuelve una Collection. Si preferimos una array:

namespace App\Http\Controllers;

use App\User;

class UsersController extends Controller {

    public function getOrm()
    {
        $users = User::get();
        //dd($users->toArray());
        return $users;
    }

}

Y si deseamos refinar la consulta:

namespace App\Http\Controllers;

use App\User;

class UsersController extends Controller {

    public function getOrm()
    {
        $users = User::select('id','first_name')
            ->where('first_name','<>','javi')
            ->orderBy('first_name','ASC')
            ->get();

        //dd($users->toArray());
        return $users;
    }
}

Y siguiendo con el primer ejemplo, si también deseamos traer los perfiles de usuario, ya no hacemos uso del join(), sinó de with, especificando el método profile que generamos en la Clase User:

namespace App\Http\Controllers;

use App\User;

class UsersController extends Controller {

    public function getOrm()
    {
        $users = User::select('id','first_name')
            ->with('profile')
            ->where('first_name','<>','javi')
            ->orderBy('first_name','ASC')
            ->get();

        return $users;
    }
}

MÁS

Blade, el motor de plantillas de Laravel

Las vistas se encuentran en /resources/views/

¿Como pasar datos de nuestros controladores a las vistas?

Ejemplo:

En web.php

Route::get('example', function(){

    $user = 'John';

    // el nombre de nuestro template es 'template' que se encuentra dentro de /resources/views/examples
    // compact permite pasar la variable como un array asociativo
    return view('example.template', compact('user'));
});

Y en nuestro template:

<p>Bienvenido <?php echo $user; ?></p>

Así es como funciona con PHP. Ahora vamos a utilizar el motor de plantillas «Blade». para ello debemos renombrar nuestro template de [template].php a [template].blade.php, y utilizar las llaves de blade para encapsular nuestro código PHP:

{% raw %}
<p>Bienvenido {{$user}}</p>
{% endraw %}

¿Como hacemos con el resto de código (ifs, issets,…)?

Ejemplo:

<?php if(isset($user)): ?>
    <p>Bienvenido <?php echo $user; ?></p>
<?php else ?>
    <p>Unauthenticated</p>
<?php endif; ?>

La sintaxisis de Blade es muy senzilla y lo único que necesitamos es eliminar las etiquetas de php y reemplazarlas por @:

@if( isset($user) )
    <p>Bienvenido <?php echo $user; ?></p>
@else
    <p>Unauthenticated</p>
@endif

Layouts

Con php normalmente dividiamos los contenidos del header y footer en archivos independientes y los referenciabamos en nuestros archivos de contenido.

Con Blade hacemos uso de Layouts:

  1. Creamos un archivo de layout que contenga el header y footer (por ejemplo layout.blade.php):

    ...
    
  2. En medio de ambos debemos de escribir la sintaxis de Blade para manejar el layout. En este caso solo hacemos uso de una función llamada yield que nos permite especificar secciones diferentes para cada plantilla. Para nuestro ejemplo, la sección «content» (el nombre es totalmente opcional)

    @yield('content')
    
  3. Y finalmente en nuestra plantilla para agregar el layout:

    @extends(‘example.layout’)

    @section(‘content’)

    @if(isset($user))
    <p>Bienvenido <?php echo $user; ?></p>
    @else
    <p>Unauthenticated</p>
    @endif
    

    @endsection

Para complicarlo un poco más, imaginemos que queremos que el «title» de nuestra página lo queremos hacer dinámico. Haríamos:

<html>
    <head>
        <title>
            @yield('title','Titulo por defecto')
        </title>

        ...

    </head>
</html>

Y en nuestra plantilla:

        @extends('example.layout')

        @section('title')
            Título personalizado
        @endsection

        @section('content')

          @if(isset($user))
            <p>Bienvenido <?php echo $user; ?></p>
          @else
            <p>Unauthenticated</p>
          @endif

        @endsection

MÁS

Laravel Collective, integración de los componentes Form y Html

Veremos ejemplos detallados de vistas con HTML plano y la diferencia de utilizar el motor de plantilla Blade, también se mostrará el uso del helper asset() y el error más común a la hora de instalar Laravel 5 (Que las vistas se muestren sin estilo).

Por otro lado instalaremos el componente de Laravel Collective para utilizar las etiquetas dinámicas de Form y HTML como por ejemplo: Html::style() y Form::text(),que fueron eliminados del núcleo del framework (Illuminate) y ahora los mantiene este grupo.

Así mismo vamos a trabajar con el framework Twitter Bootstrap en Laravel y resaltaremos el uso correcto en cuanto al “escape de datos” en Laravel 5 y su diferencia con respecto a las versiones anteriores.

El desarrollador FRONTEND debe trabajar en el directorio /resources. Y los recursos públicos dentro de /public. Por ejemplo, si está trabajando con LESS, los archivos deben estar dentro de /resources, y una vez los compile, ubicarlos dentro de /public.

El helper {{asset}} permite solucionar los problemas con las rutas hacia el directorio raiz:

{% raw %}
<link href="{{ asset('/css/app.css') }}" rel="stylesheet" >
{% endraw %}

También existe un helper para crear enlaces a hojas de estilo:

{% raw %}
{{ Html::style('/css/app.css') }}
{% endraw %}

Este helper fué extraído del núcleo y ahora es mantenido por el grupo Laravel Collective, por lo que es necesario instalarlo para poder usarlo, mediante Composer.

Una vez realizado esto, es posible que no nos funcione y veamos la etiqueta imprimida en el navegador, escapando las etiquetas e interpretandola como texto. Si queremos que Laravel no escape los datos y los imprima como tal debemos:

{% raw %}
{!! Html::style('/css/app.css') !!}
{% endraw %}

Otras etiquetas adicionales:

{% raw %}      

{!! Form::text('email', null, ['class' => 'form-control', 'type' => 'email']) !!}
{% endraw %}      

es equivalente a

<input type="email" class="form-control" name="email" value="{{ old('email') }}"/>

o

{% raw %}      
{!! Form::password('password', ['class' => 'form-control']) !!}
{% endraw %}      

es equivalente a

<input type="password" class="form-control" name="password"/>

MÁS

Listado y Paginación

Para este ejemplo, vamos a crear un módulo de usuario.

Primero vamos a crear una ruta para el módulo de usuario:

Route::resource('users','UsersController');

Esto va a colisionar con las rutas que habíamos definido previamente (10. Fluent: el constructor de consultas SQL de Laravel), así que vamos a escribir rutas para el administrador. Creamos un grupo de rutas:

Route::group(['prefix' => 'admin'], function(){
    Route::resource('users','UsersController');
});

Para el namespace también debemos hacerlo de una forma similar. Debemos añadir el namespace del controller ‘UsersController’ y añadirlo al archivo de rutas, añadiendo el namespace de ‘Admin’:

Route::group(['prefix' => 'admin', 'namespace' => 'App\Http\Controllers\Admin'], function(){
    Route::resource('users','UsersController');
});

Ahora dentro de la estructura de directorios vamos a generar el controlador de usuarios en la subcarpeta ‘Admin’ (generada manualmente dentro de Http\Controllers), y con la ayuda de la consola generamos el controlador:

php artisan help make:controller UsersController

Obtendremos un error debido a que actualmente ya tenemos un UsersControllers; volvemos a ejecutar el comando especificando otro nombre para el controlador, y una vez generado y reubicado dentro de la carpeta ‘Admin’, le modificamos el nombre manualmente a UsersController, actualizando también dentro del archivo las referencias al nombre del controlador, y por supuesto, su nuevo namespace.

Automáticamente, dentro de controlador se nos han generado una serie de recursos para funciones tipo CRUD, que además utilizan el protocolo REST.

Centrándonos tan solo en el ejemplo de index(), si implementamos dentro del método:

public function index()
{
    $users = User::paginate();
    return $users;
}

Así de simple. Es posible que al ejecutar obtengámos un error con el namespace, que se están concatenando; en el RouteServiceProvider, como ya vimos en una clase anterior, ya se está concatenando el namespace, por lo que en nuestro archivo de rutas deberíamos modificar el código a:

Route::group(['prefix' => 'admin', 'namespace' => 'Admin'], function(){

    Route::resource('users','UsersController');
});

¿Como podemos utilizar esto en nuestras vistas? Dentro de /resources/views/ creamos un subdiretorio /admin/users/ y dentro creamos los archivos de vistas para el administrador, en esto caso el archivo index.blade.php.

Y para enviar los datos a la vista, dentro de la función index() del controlador:

public function index()
{
    $users = User::paginate();

    return view('admin.users.index', compact('users'));
}

Y en la vista:

@extends('app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-10 col-md-offset-1">
            <div class="panel panel-default">
                <div class="panel-heading">Usuarios</div>

                <div class="panel-body">

                <p>Hay {{ $users->total() }} registros</p>

                <table class="table table-striped">
                    <tr>
                    <th>id</th>
                    <th>name</th>
                    <th>email</th>
                    <th>type</th>
                    <th>actions</th>
                    </tr>
                    @foreach ($users as $user)
                    <tr>
                    <td>{{ $user->id }}</td>
                    <td>{{ $user->full_name }}</td>
                    <td>{{ $user->email }}</td>
                    <td>{{ $user->type }}</td>
                    <td>
                        <a href="#">Editar</a> 
                        <a href="#">Eliminar</a>
                    </td>
                    </tr>
                    @endforeach
                </table>

                {!! $users->render() !!}

                </div>
            </div>
        </div>
    </div>
</div>
@endsection

MÁS

Creación de usuarios

Mediante el comando

php artisan route:list

obtenemos un listado de todas las rutas definidas.

Las rutas también tienes un nombre: admin/users es equivalente a admin.users.create. Podemos utilizar ambas. En nuestro código:

<p>
    <a href="{{route('admin.users.create')}}">Crear Usuario</a>
</p>

<table>
    <tr>
    <th>ID</th>
    <th>NOMBRE</th>
    <th>EMAIL</th>
    <th>ACCIONES</th>
    </tr>
    @foreach($users as $user)
    <tr>
    <td>{{ $user->id }}</td>
    <td>{{ $user->full_name }}</td>
    <td>{{ $user->email }}</td>
    <td>
        <a href="#">Editar</a>
        <a href="#">Eliminar</a>
    </td>
    </tr>
    @endforeach
<table>
{!! $users->render() !!} 

En el controlador:

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

Y en nuestra plantilla /admin/users/create.blade.php:

<div class="panel-body">

    <p>Nuevo usuario</p>

    {!! Form::open(['route' => 'admin.users.store', 'method' => 'POST']) !!}

    <div class="form-group">
    {!! Form::label('name','Nombre') !!}
    {!! Form::text('name',null,['class'=>'form-control','placeholder'=>'']) !!}
    </div>

    <div class="form-group">
    {!! Form::label('surname','Apellidos') !!}
    {!! Form::text('surname',null,['class'=>'form-control','placeholder'=>'']) !!}
    </div>

    <div class="form-group">
    {!! Form::label('email','Correo electrónico') !!}
    {!! Form::text('email',null,['class'=>'form-control','placeholder'=>'Introduzca su email']) !!}
    </div>

    <div class="form-group">
    {!! Form::label('password','Contraseña') !!}
    {!! Form::password('password',['class'=>'form-control']) !!}
    </div>

    <div class="form-group">
    {!! Form::label('type') !!}
    {!! Form::select('type',[''=>'Seleccione el tipo de usuario', 'user'=>'usuario', 'admin'=>'administrador'], null, ['class'=>'form-control']) !!}
    </div>

    <button type="submit">Crear Usuario</button>

    {!! Form::close() !!}

</div>

Ahora ya podemos proceder al método store para almacenar en la base de datos. Debemos primero aprender como obtener los datos con Laravel; cuando trabajamos sin framework normalmente trabajamos con el método $_POST, pero con Laravel utilizamos la clase Request. Existen varias maneras de utilizar esta clase:

Forma I: Facades de Laravel

use Illuminate\Support\Facades\Request;

public function store()
{
    dd(Request::all());
}

Forma II: Importando la clase con inyección de dependencias

use Illuminate\Http\Request;

protected $request;

public function __construct(Request $request)
{
    $this->request = $request;
}

public function store()
{
    dd(Request->all());
}      

Forma III: Con inyección de dependencias pero dentro del propio método:

public function store(Request $request)
{
    dd($request->all());
}

Para el actual ejemplo continuaremos con la Forma III. Guardamos los datos:

public function store(Request $request)
{
    //dd($request->all());

    $user = news User($request->all());
    $user->save();
}

Si realizamos la anterior acción, veremos que se ha guardado un nuevo usuario pero con una serie de problemas: los campos referentes al nombre aparecen vacios, la contraseña no se encuentra encriptada,… ¿Por qué se produce esto? Si nos dirijimos al modelo de User, veremos la propiedad fillable. Aquí se encuentran los atributos que el modelo va a aceptar cuando queremos pasarle los datos por lote. Aquí debemos agregar los atributos que nosostros realmente tenemos en nuestro modelo.

$fillable = ['first_name', 'last_name', 'email', 'password', 'type'];

Para solucionar la encriptación del password, en el mismo modelo de User definimos la siguiente función, que va a ser llamada cada vez que alguien quiera asignar una nueva contraseña al modelo de usuario:

public function setPasswordAttribute($value)
{
    if(!empty($value))
    {
    $this->attributes['password'] = \Hash::make($value);
    }
}

Para finalizar vamos a implmentar una acción para una vez se haya creado un nuevo usuario:

public function store(Request $request)
{
    $user = news User($request->all());
    $user->save();

    return \Redirect::route('admin.users.index');
}

Otra forma de realizar este últmo paso:

public function store(Request $request)
{
    $user = news User($request->all());
    $user->save();

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

Y una tercera forma a través de la inyección de dependencias:

public function store(Request $request, Redirect $redirect)
{
    $user = news User($request->all());
    $user->save();

    return $redirect->route('admin.users.index');
}

MÁS

Edición de usuarios

La edición de registros se realiza mediante el método edit del controlador:

public function edit($id)
{

    $user = User::findOrFail($id); //este método, si no encuentra el registro devuelve un 404

    return view('admin.users.edit', compact('user'));
}

Necesitamos crear la vista /admin/users/edit para poder editar el usuario, y modificar la ruta de destino del formulario:

<div class="panel-body">

    <p>Editar usuario</p>

    {!! Form::open(['route' => 'admin.users.update', 'method' => 'PUT']) !!}

        <div class="form-group">
            {!! Form::label('name','Nombre') !!}
            {!! Form::text('name',null,['class'=>'form-control','placeholder'=>'']) !!}
        </div>

        <div class="form-group">
            {!! Form::label('surname','Apellidos') !!}
            {!! Form::text('surname',null,['class'=>'form-control','placeholder'=>'']) !!}
        </div>

        <div class="form-group">
            {!! Form::label('email','Correo electrónico') !!}
            {!! Form::text('email',null,['class'=>'form-control','placeholder'=>'Introduzca su email']) !!}
        </div>

        <div class="form-group">
            {!! Form::label('password','Contraseña') !!}
            {!! Form::password('password',['class'=>'form-control']) !!}
        </div>

        <div class="form-group">
            {!! Form::label('type') !!}
            {!! Form::select('type',[''=>'Seleccione el tipo de usuario', 'user'=>'usuario', 'admin'=>'administrador'], null, ['class'=>'form-control']) !!}
        </div>

        <button type="submit">Actualizar Usuario</button>

        {!! Form::close() !!}        
</div>

En este caso, tanto para la vista de crear como la de editar necesitamos duplicar el formulario. Para evitar duplicar el código, cortamos el código que es común a todos y los importamos a un nuevo archivo. En este caso en /admin/users/partials/fields.blade.php.

<div class="form-group">
    {!! Form::label('name','Nombre') !!}
    {!! Form::text('name',null,['class'=>'form-control','placeholder'=>'']) !!}
</div>

<div class="form-group">
    {!! Form::label('surname','Apellidos') !!}
    {!! Form::text('surname',null,['class'=>'form-control','placeholder'=>'']) !!}
</div>

<div class="form-group">
    {!! Form::label('email','Correo electrónico') !!}
    {!! Form::text('email',null,['class'=>'form-control','placeholder'=>'Introduzca su email']) !!}
</div>

<div class="form-group">
    {!! Form::label('password','Contraseña') !!}
    {!! Form::password('password',['class'=>'form-control']) !!}
</div>

<div class="form-group">
    {!! Form::label('type') !!}
    {!! Form::select('type',[''=>'Seleccione el tipo de usuario', 'user'=>'usuario', 'admin'=>'administrador'], null, ['class'=>'form-control']) !!}
</div>

Y en nuestras plantillas de crear y editar usuarios, respectivamente:

<div class="panel-body">

    <p>Nuevo usuario</p>

    {!! Form::open(['route' => 'admin.users.store', 'method' => 'POST']) !!}

    @include('admin.users.partials.fields')

    <button type="submit">Crear Usuario</button>

    {!! Form::close() !!}

</div>

y

<div class="panel-body">

    <p>Editar usuario</p>

    {!! Form::open(['route' => 'admin.users.update', 'method' => 'PUT']) !!}

    @include('admin.users.partials.fields')

    <button type="submit">Actualizar Usuario</button>

    {!! Form::close() !!}

</div>

También necesitamos añadir el enlace a editar en la página de índice:

<p>
    <a href="{{route('admin.users.create')}}">Crear Usuario</a>
</p>

<table>
    <tr>
    <th>ID</th>
    <th>NOMBRE</th>
    <th>EMAIL</th>
    <th>ACCIONES</th>
    </tr>
    @foreach($users as $user)
    <tr>
    <td>{{ $user->id }}</td>
    <td>{{ $user->full_name }}</td>
    <td>{{ $user->email }}</td>
    <td>
        <a href="{{route('admin.users.edit', user)}}">Editar</a>
        <a href="#">Eliminar</a>
    </td>
    </tr>
    @endforeach
<table>
{!! $users->render() !!} 

Si ejecutásemos ahora, veríamos que se cargaría el formulario pero sin los datos. Para solucionar esto, en el formulario de editar deberemos usar Form:model en lugar de Form:open, pasándole la variable $user (recordemos que en el controlador pasamos la variable user hacia la vista):

<p>
    Editar usuario {{ $user->first_name }}
</p>

<div>
    {!! Form::model([$user, 'route' => 'admin.users.update', 'method' => 'PUT']) !!}

    @include('admin.users.partials.fields')   
    <button type="submit">Actualizar Usuario</button>

    {!! Form:close() !!}
</div>

Ahora toca implementar la función update del controlador:

use Illuminate\Support\Facades\Request;

...

public function update($id)
{
    $user = User::findOrFail($id);

    //manera alternativa de guardar a la ya vista
    $user->fill(Request::all());
    $user->save();

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

Pero obtenemos un error… debemos pasar el usuario en la ruta!!

<p>
    Editar usuario {{ $user->first_name }}
</p>

<div>
    {!! Form::model($user, ['route' => ['admin.users.update', $user], 'method' => 'PUT']) !!}

    @include('admin.users.partials.fields')   
    <button type="submit">Actualizar Usuario</button>

    {!! Form:close() !!}
</div>

Validación. Parte I

Laravel maneja la validación de manera muy sencilla, siguiendo un patrón para definir las reglas de validación con cadenas en un array asociativo.

Básicamente usamos el patrón Factory para crear un nuevo validador, esto es lo que hace Validator::make y luego tenemos algunos métodos muy fáciles de usar como $validator->fails que comprueba si la validación falló y $validator->messages() que nos devuelve los errores de validación.

En Laravel 5, Taylor Otwell incorporó 2 métodos para hacer la validación aún más fácil, que en las versiones anteriores. Estos son: el método validate dentro del controlador con el trait Illuminate\Foundation\Validation\ValidatesRequests y los Form Requests.

Forma I

use Illuminate\Support\Facades\Validator;

...

public function store()
{
    $data = Request::all();

    $rules = array (
    'name' => 'required',
    'surname' => 'required',
    'email' => 'required',
    'password' => 'required',
    'type' => 'required',
    );

    $v = Validator::make($data, $rules);

    if($v->fails())
    {
    return redirect()->back()
        ->withErrors($v->errors())
        ->withInput(Request::except('password'));
    }

    $user = User::create($data);

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

Ahora necesitamos mostrar los errores en la vista. Volviendo al formulario de creación de nuevo usuario:

<div class="panel-body">

    <p>Nuevo usuario</p>

    @include('admin.users.partials.errors')

    {!! Form::open(['route' => 'admin.users.store', 'method' => 'POST']) !!}

    @include('admin.users.partials.fields')

    <button type="submit">Crear Usuario</button>

    {!! Form::close() !!}

</div>

<p>
    Editar usuario {{ $user->first_name }}
</p>

<div>
    {!! Form::model($user, ['route' => ['admin.users.update', $user], 'method' => 'PUT']) !!}

    @include('admin.users.partials.fields')   
    <button type="submit">Actualizar Usuario</button>

    {!! Form:close() !!}
</div>

Y añadimos el partial admin.users.partials.errors:

@if($errors->any())

    <div class="alert alert-danger" role="alert">

    <p>Por favor corrige los errores</p>
    <br/>
    <ul>
        @foreach($errors->all() as $error)

        <li>{{$error}}</li>

        @endforeach
    </ul>

    </div>

@endif

Forma II. Método Validate, incluído con Laravel 5

Utilizando el método Validate incluído con Laravel 5. Debemos estar pendientes de que nuestro controlador incluya el trait ValidateRequest.

namespace App\Http\Controllers;

use Illuminate\Foundation\Bus\DispatchesCommands;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;

abstract class Controller extends BaseController {

    use DispatchesCommands, ValidatesRequests;

}

El método validate acepta como primer parámetro la request como inyección de dependencias, por lo que podemos prescindir del facade.

use Illuminate\Http\Request;

...

function store(Request $request)
{
    $rules = array (
    'name' => 'required',
    'surname' => 'required',
    'email' => 'required',
    'password' => 'required',
    'type' => 'required',
    );

    $this->validate($request, $rules);

    $user = User::create($request->all());
    return redirect()->route('admin.users.index');
}

Como vemos hemos reducido considerablemente la cantidad de código.

La vista se mantiene igual.

Forma III. Creando un FormRequest, incluído con Laravel 5.

Este método nos permite extraer la validación fuera del controlador. Con Laravel 5 viene de forma automática dentro del framework. Desde la consola creamos un FormRequest, con un nombre característico:

php artisan make:request CreateUserRequest

Esta nueva clase se encuentra en /Http/Requests/. La idea de esta clase es pasarle las reglas que tenemos en el método store() del controlador.

class CreateUserRequest extends Request {

    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'name' => 'required',
            'surname' => 'required',
            'email' => 'required',
            'password' => 'required',
            'type' => 'required',
        ];
    }
}

Y de vuelta al controlador:

use App\Http\Requests\CreateUserRequest;

(...)

public function store(CreateUserRequest $request)
{
    $user = User::create($request->all());
    return redirect()->route('admin.users.index');
}

Y esto es todo, de esta manera se maneja la validación con Laravel. La vista se mantiene igual.

Para finalizar, vamos a añadir más validaciones al FormRequest:

class CreateUserRequest extends Request {

    ...

    public function rules()
    {
        return [
        'name' => 'required',
        'surname' => 'required',
        'email' => 'required|unique:users,email',
        'password' => 'required',
        'type' => 'required',
        ];
    }
}

MÁS

Validación. Parte II

Continuaremos aprendiendo cómo validar los datos que envían los usuarios, esta vez para el formulario que nos permite editar y actualizar registros.

Aprenderemos más sobre la regla unique de Laravel, además veremos la regla in

Primero desde la consola creamos un nuevo FormRequest en este caso para la edición de usuarios.

php artisan make:request EditUserRequest


//originalmente a false. Por ahora devolvemos true
public function authorize()
{
    return true;
}

public function rules()
{
    return [
        'first_name' => 'required',
        'last_name' => 'required',
        'email' => 'required|unique:users,email', //también validamos que el email sea unico en la tabla users
        'password' => 'required',
        'type' => 'required'
    ];
}


public function update(EditUserRequest $request, $id)
{
    $user = User::findOrFail($id);

    //manera alternativa de guardar a la ya vista
    $user->fill($request->all());
    $user->save();

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


<p>
    Editar usuario {{ $user->first_name }}
</p>

<div>

    @include('admin.users.partials.errors')

    {!! Form::model($user, ['route' => ['admin.users.update', $user], 'method' => 'PUT']) !!}

    @include('admin.users.partials.fields')   
    <button type="submit">Actualizar Usuario</button>

    {!! Form:close() !!}

</div>

En este caso no necesitamos que el password sea obligatorio, por lo que podemos eliminar la validación. Tampoco nos interesa que nos salte la validación de unique del email, pero tampoco podemos eliminar la validación, por lo que necesitamos indicarle al validador que ignore mi registro. Una forma es importando el objeto de rutas al objeto FormRequest

use Illuminate\Routing\Route;

...

private $route;

public function __construct(Route $route)
{
    $this->route = $route;
}

//originalmente a false. Por ahora devolvemos true
public function authorize()
{
    return true;
}

public function rules()
{
    return [
        'first_name' => 'required',
        'last_name' => 'required',
        'email' => 'required|unique:users,email,' . $this->route->getParameter('users'),
        'type' => 'required'
    ];
}

También añadimos otra validación: para el selector, forzar que el usuario solo pueda escoger entre los valores deseados. Esto es necesario hacerlo para los dos FormRequests que hemos trabajado:

public function rules()
{
    return [
    'first_name' => 'required',
    'last_name' => 'required',
    'email' => 'required|unique:users,email,' . $this->route->getParameter('users'),
    'type' => 'required|in:user,admin'
    ];
}

Eliminar registros

Eliminaremos registros en la BD y notificaremos al usuario de que el registro ha sido eliminado.

Los registros se pueden eliminar mediante el método destroy($id), y mediante el método delete() que necesita cargar antes el objeto.

User::destroy($id);


$user = User::find($id);
$user->delete();

Primero en el formulario de edición añadimos un nuevo partial para añadir el botón de eliminar usuario.

<p>
    Editar usuario {{ $user->first_name }}
</p>

<div>

    include('admin.users.partials.errors')

    {!! Form:model($user, ['route' => ['admin.users.update',$user], 'method' => 'PUT'], 'class' => '') !!}

    @include('admin.users.partials.fields')   
    <button type="submit">Actualizar Usuario</button>

    {!! Form:close() !!}
</div>

@include('admin.users.partials.delete')

Creamos un nuevo partial. Observar que en la ruta añadimos el objeto $user que tenemos disponible en nuestro formulario de edit. Es necesario añadirlo ya que el método destroy() espera el identificador del registro a eliminar:

{!! Form:open(['route' => ['admin.users.destroy', $user], 'method' => 'DELETE']) !!}

<button type="submit" class="btn btn-danger">Eliminar usuario</button>

{!! Form:close() !!}

Ahora para eliminar el registro debemos implementar la función destroy() en el controlador. A continuación se muestra las dos posibilidades: destroy($id), y delete(). También vemos la manera de mostrar un usuario un mensaje como que el registro ha sido elimninado.

use Illuminate\Support\Facades\Session;

...

public function destroy($id)
{
    $user = User::findOrFail($id);
    $user->delete();

    Session::flash('message', $user->full_name . ' ha sido eliminado');

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

El método flash envía el mensaje solo la primera vez que se ejecuta; si quisieramos mostrar un mensaje persiste, deberíamos hacer uso de la función set

use Illuminate\Support\Facades\Session;

...

public function destroy($id)
{
    $user = User::findOrFail($id);
    $user->delete();

    Session::flash('message', $user->full_name . ' ha sido eliminado');

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

Y preparamos la vista index para poder mostrar el mensaje:

@if(Session::has('message'))
    <p class="alert alert-success"> {{ Session::get('message') }} </p>
@endif

<p>
    <a href="{{route('admin.users.create')}}">Crear Usuario</a>
</p>

<table>
    <tr>
        <th>ID</th>
        <th>NOMBRE</th>
        <th>EMAIL</th>
        <th>ACCIONES</th>
    </tr>
    @foreach($users as $user)
    <tr>
        <td>{{ $user->id }}</td>
        <td>{{ $user->full_name }}</td>
        <td>{{ $user->email }}</td>
        <td>
            <a href="{{route('admin.users.edit', $user)}}">Editar</a>
            <a href="#">Eliminar</a>
        </td>
    </tr>
    @endforeach
<table>
{!! $users->render() !!} 

Para finalizar vamos a insertar un poco de javascript para confirmar que se desea eliminar el usuario:

{!! Form:open(['route' => ['admin.users.destroy', $user], 'method' => 'DELETE']) !!}

<button type="submit" onClick="return confirm('Seguro que deseas eliminar el registro?');" class="btn btn-danger">Eliminar usuario</button>

{!! Form:close() !!}

Internacionalización

Aprenderemos como traducir los mensajes de error y notificaciones de nuestra aplicación a cualquier idioma.

Primero buscaremos en Google y buscar algo como «laravel 5 spanish translation» y veremos que uno de los resultados corresponde a un paquete «caouecs/Laravel4-lang». Deberemos descargar los paquetes y agregar los que nos interesen a nuestro proyecto al directorio «/resources/lang».

Veremos que ha habido un pequeño cambio durante el desarrollo del framework y es que el archivo «remiders» ha pasado a llamarse «passwords», de modo que debemos renombrar el archivo a «passwords.php»

A continuación nos dirigimos al archivo /config/app.php y modificamos el valor de locale a es.

Y esto es todo para que nuestra aplicación se muestre en otro idioma.

Dentro de la aplicación encontraremos textos que se encuentran incrustrados literalmente, sin utilizar el módulo de idiomas. Pero podemos prepararlos para que sean traducibles.

Por ejemplo, si nos dirigimos a /Http/Controllers/Auth/AuthController.php y de aquí nos dirigimos a la declaración de AuthenticatesAndRegistersUsers veremos un método llamado getFailedLoginMessage que devuelve un mensage de error explícitamente:

public getFailedLoginMessage()
{
    return 'These credentials does not match our records.';
}

Haciendo uso del método de traducir de Laravel podemos modificar el método de la siguiente forma:

public getFailedLoginMessage()
{
    return trans('passwords.invalid_credentials');
}

Ahora volvemos a nuestro archivo de idioma «passwords.php» y añadimos una nueva entrada con el texto en español:

    "invalid_credentials" => "El usuario y/o la contraseña no son válidos",

Obviamente, también necesitaremos crear la nueva entrada en el archivo passwords.php para inglés.

Para los nombres de los labels, los atributos, debemos dirigirnos al archivo validation.php y al final veremos un array attributes que debemos poblarla con el nombre de los diferentes atributos

'attributes' => array(
    'email' => 'Correo electrònico',
    'password' => 'Contraseña'
);

Y en la vista disponer el texto con la función trans():

{% raw %}  
<label>{{ trans('validation.attributes.email') }}</label>
<label>{{ trans('validation.attributes.password') }}</label>
{% endraw %}  

MÁS

Principios del diseño DRY

Patrón de diseño aplicabre a Laravel. DRY: no te repitas o «don’t repeat yourself».

Este principio consiste en la idea de evitar la duplicación de código tanto como sea posible, duplicar código hace a nuestra aplicación más difícil de mantener, brinda la posibilidad de crear muchas inconsistencias y conlleva a la larga a un mayor esfuerzo y tiempo de desarrollo.

En Laravel tenemos beforeFilter() que lo podemos especificar dentro del controlador.

public function __construct()
{
    //beforeFilter nos indica que antes de ejecutar los métodos show, edit, update y destroy, vamos a ejecutar el método findUser
    //Como findUser es un método del controlador debe ir precedido por @
    $this->beforeFilter('@findUser', ['only' => ['show', 'edit', 'update', 'destroy']]);
}

Como primer parámetro indicamos el nombre del método que quermos llamar; al ser un método dentro del mismo controlador debe ser precedido por @. Y como segundo parámetro podemos especificar a que métodos queremos aplicarlo.

A continuación definimos la función findUser.

use Illuminate\Routing\Redirector;
use Illuminate\Routing\Route;

...

public function findUser(Route $route)
{
    $this->user = User::findOrFail($route->getParameter('users'));
}

Aquí no tiene sentido trabajar con la variable User, sinó que utilizamos la propiedad user. Tampoco tenemos el id disponible, pero podemos obtenerlo mediante el componente de rutas. En este caso, el parámetro con el que se pasa el usuario se llama users en el controlador Resource. Esto se puede verificar en la consola mediante el comando:

php artisan route:list

De este modo, ya tenemos la propiedad users para los métodos show, edit, update y destroy, y no necesitamos estar llamando a findOrFail para cada una de ellas:

//ANTES
public function edit($id)
{
    return view('admin.users.edit', compact('user'));
}

//AHORA
public function edit($id)
{
    return view('admin.users.edit')->with('user', $this->user); 
}


//ANTES
public function update(EditUserRequest $request, $id)
{
    $user = User::findOrFail($id);
    $user->fill($request->all());
    $user->save();

    return redirect()->back();
}

//AHORA
public function update(EditUserRequest $request, $id)
{
    $this->user->fill($request->all());
    $this->user->save();

    return redirect()->back();
}


//ANTES
public function destroy($id, Request $request)
{
    $user = User::findOrFail($id);
    $user->delete();

    Session::flash('message', $user->full_name . ' fue eliminado de nuestros registros');
    return redirect()->route('admin.users.index');
}

//AHORA
public function destroy($id, Request $request)
{
    $this->user->delete();

    Session::flash('message', $this->user->full_name . ' fue eliminado de nuestros registros');
    return redirect()->route('admin.users.index');
}

La ventaja es que si más tarde queremos actualizar nuestro controlador para que interactue con el nombre de usuario en vez de con el identificador, solamente deberemos cambiar el método beforeFilter:

public function findUser(Route $route)
{
    $this->user = User::where('username', $route->getParameter('users'))->firstOrFail();
    //$this->user = User::findOrFail($route->getParameter('users'));
}

MÁS

AJAX con Laravel

Aprenderemos cómo interactuar desde Javascript con un backend en Laravel, tal como lo haríamos con un API; pero en este caso haremos un ejemplo sencillo dentro de nuestra aplicación, para eliminar filas utilizando jQuery y una petición POST con AJAX.

A pesar de que hoy en día existen frameworks de Javascript más potentes como AngularJS, jQuery aún es útil si sólo necesitamos agregar contenido dinámico para algún widget dentro de nuestra aplicación o sitio web, por ejemplo un campo de auto-completado o combos dependientes.

Agregamos una class al botón de eliminar fila y añadimos un atributo a las filas para poder diferenciarlas del resto de filas

@if(Session::has('message'))
    <p class="alert alert-success"> {{ Session::get('message') }} </p>
@endif

<p>
    <a href="{{route('admin.users.create')}}">Crear Usuario</a>
</p>

<table>
    <tr>
        <th>ID</th>
        <th>NOMBRE</th>
        <th>EMAIL</th>
        <th>ACCIONES</th>
    </tr>
    @foreach($users as $user)
    <tr data-id="{{ $user->id }}" >
        <td>{{ $user->id }}</td>
        <td>{{ $user->full_name }}</td>
        <td>{{ $user->email }}</td>
        <td>
            <a href="{{route('admin.users.edit', $user)}}">Editar</a>
            <a href="#" class="btn-delete" >Eliminar</a>
        </td>
    </tr>
    @endforeach
<table>
{!! $users->render() !!}

Necesitamos un punto donde podamos añadir nuestro javascript. Una opción es añadirlo al final de nuestro app.blade.php. La otra opción que vamos a utilizar es crear una nueva section para los scripts

{% raw %}
    ...

        @yield('content')

        <!-- Scripts -->
        <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.1/js/bootstrap.min.js"></script>
    @yield('scripts')
</body>
{% endraw %}

Al final de nuestro index.blade.php de admin/users/ definimos la nueva section:

@section('scripts')        
<script>
$(document).ready(function(){
    $('.btn-delete').click(function(){

    var row = $(this).parents('tr');
    var id = row.data('id');

    });
});
</script>
@endsection        

Para eliminar necesitamos algunos datos que nos provee el formulario de Laravel, como la ruta y los campos ocultos que se encuentran en el formulario.

Copiamos el formulario del partial y nos lo llevamos a nuestro index.blade.php de admin/users/, pero como es un formulario oculto (necesitamos los datos pero no interactuar con él), eliminamos el contenido de dentro del formulario. Tampoco necesitamos la variable $user porque no trabajamos con un usuario en concreto, sinó que trabajamos con usuarios dinámicos. A cambio, utilizaremos un placeholder USER_ID que nos permitirá con el javascript substituirlo por el usuario dinámico que hayamos seleccionado. Al formulario también le asignamos un id para poder acceder a él mediante el javascript. El resultado sería:

@section('content')
    ...

    {!! Form::open(['route' => ['admin.users.destroy', ':USER_ID'], 'method' => 'DELETE', 'id' => 'form-delete']) !!}
    {!! Form:close() !!}

    @endsection

    @section('scripts')        
    <script>
    $(document).ready(function(){
        $('.btn-delete').click(function(){

        var row = $(this).parents('tr');
        var id = row.data('id');
        var form = $('#form-delete');
        var url = form.attr('action').replace(':USER_ID', id);

        });
    });
    </script>
@endsection        

Para enviar la petición con AJAX necesitamos los datos ocultos del formulario; si inspeccionamos mediante el navegador veremos que el formulario oculto contiene dos campos:

{% raw %}
<input name="_method" type="hidden" value="DELETE" > <input name="_token" type="hidden" value="AjkjguioasldnaladASsdaAdsdhjg" >
{% endraw %}

Para obtener estos datos serializamos el formulario:

@section('scripts')        
<script>
$(document).ready(function(){
    $('.btn-delete').click(function(){

    var row = $(this).parents('tr');
    var id = row.data('id');
    var form = $('#form-delete');
    var url = form.attr('action').replace(':USER_ID', id);

    var data = form.serialize();
    });
});
</script>
@endsection        

Ahora enviamos la petición con AJAX:

@section('scripts')        
<script>
$(document).ready(function(){
    $('.btn-delete').click(function(event){

    event.preventDefault();

    var row = $(this).parents('tr');
    var id = row.data('id');
    var form = $('#form-delete');
    var url = form.attr('action').replace(':USER_ID', id);

    var data = form.serialize();

    $.post(url, data, function(){

    });
    });
});
</script>
@endsection        

También debemos preparar el método del controlador para procesar este tipo de peticiones, ya que actualmente se encuentra preparada para hacer redirecciones, que en este caso al ser peticiones AJAX no nos interesan.

public function destroy($id, Request $request)
{
    $this->user->delete();

    $message = $this->user->full_name . ' fue eliminado de nuestros registros';

    if($request->ajax())
    {
        return $message;
    }

    Session::flash('message', $message);
    return redirect()->route('admin.users.index');
}


@section('scripts')        
<script>
$(document).ready(function(){
    $('.btn-delete').click(function(event){

    event.preventDefault();

    var row = $(this).parents('tr');
    var id = row.data('id');
    var form = $('#form-delete');
    var url = form.attr('action').replace(':USER_ID', id);

    var data = form.serialize();

    $.post(url, data, function(result){
        console.log(result); //mostrará el mensaje de retorno
        row.fadeOut(); //desvanecerá la fila
    });
    });
});
</script>
@endsection        

Podemos aún mejorar más la funcionalidad, contemplando la posibilidad de que haya habido un error y el usuario no haya sido eliminado:

@section('scripts')
<script>
    $(document).ready(function(){
    $('.btn-delete').click(function(event){

        event.preventDefault();

        var row = $(this).parents('tr');
        var id = row.data('id');
        var form = $('#form-delete');
        var url = form.attr('action').replace(':USER_ID', id);

        var data = form.serialize();

        $.post(url, data, function(result){
            console.log(result); //mostrará el mensaje de retorno
            row.fadeOut(); //desvanecerá la fila
        }).fail(function(){
            alert("El usuario NO ha sido eliminado");
        });;
    });
    });
</script>
@endsection

Otro tipo de mensaje que podemos devolver con AJAX son con formato JSON, que es el más utilizado actualmente, que por ejemplo, nos permitiría devolver datos extras:

public function destroy($id, Request $request)
{
    $this->user->delete();

    $message = $this->user->full_name . ' fue eliminado de nuestros registros';

    if($request->ajax())
    {
    return response()->json([
        'id' => $this->user->id,
        'message' => $message
    ]);
    }

    Session::flash('message', $message);
    return redirect()->route('admin.users.index');
}

Si utilizamos esta opción, también deberemos modificar la forma de recibir los datos en el template:

@section('scripts')        
<script>
$(document).ready(function(){
    $('.btn-delete').click(function(event){

    event.preventDefault();

    var row = $(this).parents('tr');
    var id = row.data('id');
    var form = $('#form-delete');
    var url = form.attr('action').replace(':USER_ID', id);

    var data = form.serialize();

    $.post(url, data, function(result){
        console.log(result.message); //mostrará el mensaje de retorno
        row.fadeOut(); //desvanecerá la fila
    }).fail(function(){
        alert("El usuario NO ha sido eliminado");
    });
    });
});
</script>
@endsection        

Búsquedas y filtros con Laravel y Eloquent (Query scopes)

Imaginemos que de pronto tenemos 1000 usuarios o más en nuestra base de datos y queremos saber si un usuario determinado se registró en nuestra aplicación. Tal como está el módulo ahora tendríamos que buscar registro por registro lo cual no es nada práctico.

Aprenderemos cómo buscar o filtrar registros en la base de datos usando el ORM Eloquent y una características incluida en dicho ORM llamada «query scopes»

Primero de todo, a nuestra vista con la tabla de usuarios le añadimos el formulario para buscar usuarios:

{!! Form::open(['route'=>'admin.users.index', 'method'=>'GET', 'class' => 'navbar-form navbar-left pull-right', 'role'=>'search']) !!}
    <div class="form-group">
    {!! Form::text('name', null, ['class' => 'form-control', 'placeholder'=>'Nombre de usuario']) !!}
    </div>
    <button type="submit" class="btn btn-default">Buscar</button>
{!! Form::close() !!}

<p>
    <a href="{{route('admin.users.create')}}">Crear Usuario</a>
</p>

<table>
    <tr>
    <th>ID</th>
    <th>NOMBRE</th>
    <th>EMAIL</th>
    <th>ACCIONES</th>
    </tr>
    @foreach($users as $user)
    <tr>
    <td>{{ $user->id }}</td>
    <td>{{ $user->full_name }}</td>
    <td>{{ $user->email }}</td>
    <td>
        <a href="#">Editar</a>
        <a href="#">Eliminar</a>
    </td>
    </tr>
    @endforeach
<table>
{!! $users->render() !!} 

Veamos como podemos obtener el parámetro introducido por el usuario en el buscador en el controlador.

En el método index() utilizaremos inyección de dependencias para poder obtener el parámetro:

//ANTES
public function index()
{
    $users = User::orderBy('id', 'DESC')->paginate();

    return view('admin.users.index',compact('users'))
}

//DESPUES
public function index(Request $request)
{
    //la función name() hace referencia a scopeName() (MAGIA!!)
    $users = User::name($request->get('name'))->orderBy('id', 'DESC')->paginate();

    return view('admin.users.index',compact('users'))
}     

Y en el modelo de User, utilizando el método scope() del ORM de Eloquent, implementamos la siguiente función:

public function scopeName($query, $name)
{
    if(trim($name) != "")
    {
        $query->where('first_name', $name);
    }
}

Notemos que el método se encuentra precedido por el prefijo scope.

Para mejorarlo aún más, podemos concatenar el apellido, para el caso de que el usuario introduzca el nombre completo del usuario en el buscador

public function scopeName($query, $name)
{
    if(trim($name) != "")
    {
        $query->where(\DB::raw("CONCAT(name,' ', surname)"), $name);
    }
}

Y para mejorarlo aún más, contemplamos la posibilidad de búsquedas parciales (nombre incompleto):

public function scopeName($query, $name)
{
    if(trim($name) != "")
    {
        $query->where(\DB::raw("CONCAT(name,' ', surname)"), "LIKE", "%$name%");
    }
}

MÁS

Filtros de búsqueda y paginación

Aprenderemos cómo filtrar usuarios con 2 o más campos de búsqueda, además veremos cómo combinar la paginación de Laravel con los filtros que agregamos al módulo.

También aprenderemos a mantener los valores de búsqueda dentro del formulario, y a mover parte de la lógica al modelo para simplificar nuestro controlador y aplicar nuevamente el principio DRY.

Primero a la vista index le agregamos un selector para seleccionar el tipo de usuario. Utilizamos el método de configuración de Laravel para definir los tipos de usuario:

return array(

    'user_types' =>  [
            ''          =>  'Tipo de usuario',
            'admin'     =>  'Administrador',
            'user'      =>  'Usuario'
    ],

);


{!! Form::open(['route'=>'admin.users.index', 'method'=>'GET', 'class' => 'navbar-form navbar-left pull-right', 'role'=>'search']) !!}
    <div class="form-group">
        {!! Form::text('name', null, ['class' => 'form-control', 'placeholder'=>'Nombre de usuario']) !!}
        {!! Form::select('type', config('options.user_types'), null, ['class' => 'form-control']) !!}
    </div>
    <button type="submit" class="btn btn-default">Buscar</button>
{!! Form::close() !!}
...

Añadimos un nuevo scope en el model User:

public function scopeType($query, $type)
{
    $types = config('options.user_types');

    if(trim($type) != "" && isset($types[$type]))
    {
        $query->where('type', $type);
    }
}

Y referenciamos el nuevo scope en el controlador:

public function index(Request $request)
{
    //la función name() hace referencia a scopeName() y type() a scopeType() (MAGIA!!)
    $users = User::name($request->get('name'))->type($request->get('type'))->orderBy('id', 'ASC')->paginate();

    return view('admin.users.index', compact('users'));
}

Ahora ya nuestro filtro funciona, pero vemos que al refrescar la pantalla los valores no se mantienen en el filtro. Para solucionar esto tenemos varias opciones.

Aunque no tengamos un modelo de Eloquent podemos utilizar toda la Request:

    {!! Form::model(Request::all(), ['route' => 'admin.users.index', 'method'=>'GET', 'class' => 'navbar-form navbar-left pull-right', 'role'=>'search']) !!}

    ...

También podríamos utilizar only para pasar solo los valores que nos interesan:

    {!! Form::model(Request::only(['name','type']), ['route' => 'admin.users.index', 'method'=>'GET', 'class' => 'navbar-form navbar-left pull-right', 'role'=>'search']) !!}

    ...

Tal y como lo tenemos ahora tenemos el siguiente problema: si después de aplicar un filtro el usuario cambia de página, los valores del filtro se pierden en la URL. Para solucionar esto agregamos los filtros al paginador:

    {!! $users->append(Requests::only(['name','type']))->render() !!}

Volviendo al controlador, vemos que la función de buscar nos ha quedado muy larga. Solucionamos esto creando una función en el modelo de User y realizando una llamada desde el controlador:

//Si no utilizasemos static, en el controlador necesitariamos instanciar un módelo para luego llamar a la función
public static function filterAndPaginate($user, $type)
{
    //la función name() hace referencia a scopeName() (MAGIA!!)
    return User::name($user)->type($type)->orderBy('id', 'ASC')->paginate();
}


public function index(Request $request)
{
    //ANTES
    //$users = User::name($request->get('name'))->type($request->get('type'))->orderBy('id', 'ASC')->paginate();

    //DESPUÉS
    $users = User::filterAndPaginate($request->get('name'), $request->get('type'));

    return view('admin.users.index', compact('users'));
}

MÁS

Proteger el acceso mediante los Middleware

Los middleware nos permiten proteger rutas y acciones de acceso no autorizado. Se situa en el medio entre la petición del usuario (Request) y las acciones del controlador que arman y envían la respuesta (Response).

Existen varias maneras de definir un middleware.

A nivel de controlador:

class UsersController extends Controller {

    public function __construct()
    {
        //le indica a laravel que solo los usuarios autenticados, pueden acceder a las acciones dentro del controlador
        $this->middleware('auth');

        $this->beforeFilter('@findUser', ['only' => ['show', 'edit', 'update', 'destroy']]);
    }

Pero la mayoría de las veces muchas de nuestras rutas o controladores o acciones va a tener características comunes. En el archivo de rutas, utilizamos el grupo de rutas para indicarle a Laravel que todas ls rutas que tengan prefijo de admin y el namespace Admin utilicen el middleare de auth:

Route::group(['prefix' => 'admin', 'middleware' => 'auth' ,'namespace' => 'Admin'], function(){
    Route::resource('users','UsersController');
});

¿Cómo funciona? Dentro del archivo Kernel.php se definen los middleare de la aplicación que actuan de manera global y también los que actuan a nivel de la ruta

MÁS

Creando nuestros propios Middleware

La verdadera utilidad de los Middleware llega cuando podemos generar nuevos que nos ayuden a proteger nuestra aplicación de acuerdo a la lógica que queramos implementar.

Cada aplicación es diferente, en todas la seguridad es un tema crucial, sin embargo hay aplicaciones que sólo tienen un tipo de usuario, otras que sólo tienen un par de tipos y otras que requieren sistemas de permisos complejos como listas de control de acceso o ACL

Tener un par de tipos de usuarios con permisos bien definidos es suficiente, y es lo que aprenderemos en esta lección.

En el capitulo anterior implementamos que solo los usuarios autenticados pudiesen acceder al panel de administración; imáginemos que solo queremos que los usuarios con el perfil admin puedan hacerlo; para ello necesitamos crear nuestro propio middleware.

Desde la consola generamos nuestro propio middleware:

php artisan make:middleware isAdmin

En el directorio /app/Http/Middleware/ se ha generado nuestro middleware. El método handle() es obligatorio; es el que laravel va a ejectuar cuando hayamos registrado el middleware. Para registrarlo, vamos al archivo Kernel.php y añadimos nuestro middleware:

protected $routeMiddleware = [
    'auth' => 'App\Http\Middleware\Authenticate',
    'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth',
    'guest' => 'App\Http\Middleware\RedirectIfAuthenticated',
    'is_admin'  => 'App\Http\Middleware\IsAdmin'
];

Ahora, dentro del Middleware vamos a implementar la lógica que nos diga si un usuario es o no administrador. para ello necesitamos tomar el usuario, y para ello necesitamos la clase auth de laravel, que se puede cargar con inyección de dependencias

use Illuminate\Contracts\Auth\Guard;

class IsAdmin {

    public function __construct(Guard $auth)
    {
        $this->auth = $auth;
    }
    ...
}

De esta manera ya podemos acceder al usuario desde el handle.

public function handle($request, Closure $next)
{
    if($this->auth->user()->type != 'superadmin' && $this->auth->user()->type != 'admin')
    {
        $this->auth->logout();

        if ($this->auth->guest())
        {
            if ($request->ajax())
            {
                return response('Unauthorized.', 401);
            }
            else
            {
                return redirect()->guest('auth/login');
            }
        }
    }

    return $next($request);
}

No olvidemos añadir nuestro middleware al archivo de rutas:

//los middleware se ejecutan en el orden en que los especifiquemos en la array
Route::group(['prefix' => 'admin', 'middleware' => ['auth','is_admin'] ,'namespace' => 'Admin'], function(){

    Route::resource('users','UsersController');

});

Vamos a extraer la comprobación del tipo de usuario y la vamos a llevar al modelo de usuario, ya que es posible que la comprobación del tipo de usuario la tengamos que hacer en diversos sitios a lo largo de la aplicación.

En el modelo:

public function isAdmin()
{
    return $this->type === 'admin';
}

public function isSuperAdmin()
{
    return $this->type === 'superadmin';
}

Y en el método handle:

public function handle($request, Closure $next)
{
    if(!$this->auth->user()->isAdmin() && !$this->auth->user()->isSuperAdmin())
    {
    $this->auth->logout();

    if ($this->auth->guest())
    {
        if ($request->ajax())
        {
        return response('Unauthorized.', 401);
        }
        else
        {
        return redirect()->to('auth/login');
        }
    }
    }

    return $next($request);
}

Para optimizar aún más, podríamos crear un middleware para cada tipo de usuario, pero quizás nos encontremos que estamos repitiendo mucha lógica. Una opción sería renombrar la class IsAdmin por isType.

La class la definiremos como abstracta, y dentro creamos otro método abstracto isType()

abstract class IsType {

    abstract public function getType();

    public function handle($request, Closure $next)
    {
    //Al convertirlo en abstracto, aquí ya no podemos utilizar la clase directamente
    //ANTES:
    //if(!$this->auth->user()->isAdmin() && !$this->auth->user()->isSuperAdmin())
    //AHORA:
    if(!$this->auth->user()->is($this->getType()) && !$this->auth->user()->isSuperAdmin())
    {
        if ($this->auth->guest())
        {
        if ($request->ajax())
        {
            return response('Unauthorized.', 401);
        }
        else
        {
            return redirect()->to('auth/login');
        }
        }
    }

    return $next($request);
    }
}

Por supuesto, necesitamos definir el método is dentro del modelo de usuario

class User extends Model implements AuthenticatableContract, CanResetPasswordContract {

    ...

    public function is($type)
    {
        return $this->type === $type;
    }
}

Ahora, si queremos podemos crear un middleware para cada tipo de usuario, que extienda de la clase isType que acabamos de crear

class IsSuperAdmin extends IsType
{
    public function getType()
    {
        return 'superadmin';
    }
}


class IsAdmin extends IsType
{
    public function getType()
    {
        return 'admin';
    }
}

De esta forma hemos creado un middleware para cada tipo sin repetir mucha lógica.