Laravel 8.*

  • Laravel se ha convertido en el framework php más utilizado para desarrollo web.

  • En septiembre/2019 se publicón la versión 6.0

  • Desde entonces usa versionado semántico.

    • Febrero y agosto: versiones mayores: 6 (LTS), 7, ... (septiembre 2020: v8)

    • Semanalmente pueden aparecer versiones menores (segundo dígito) y de parcheo (tercero).

    • Las versiones mayores pueden incluír cambios de ruptura, el resto no.

  • Usa arquitectura MVC (Modelo Vista Controlador) y mucho más:

  • Sistema de enrutamiento potente

  • Usa Blade como gestor de plantillas

  • Su propio ORM: Eloquent (¿Adios SQL?)

  • Y muchos otros componentes que lo hacen muy interesante: Query Builder, Database Migrations, Seeders, Passport, Dusk, Socialite, ... y muchos otros componentes brindados por terceras partes.

  • Enlaces:

Instalación

Instalación via composer (directorio blog):

composer create-project --prefer-dist laravel/laravel blog
  • Esto implica:

    • Descargar fremwork original

    • Ejecutra composer install

    • Crear fichero de entorno (.env)

    • Generar clave de cifrado

Instalación via git

  • Partimos del código de GitHub o de otro proyecto Laravel. Aquí todas las fases son manuales:

    • Descarga

    git clone https://github.com/laravel/laravel.git nombreProyecto
    cd nombreProyecto
    • Instalación de dependencias

    composer install
  • Crear fichero de entorno a partir del de ejemplo:

    cp .env.example .env
  • Crearción de la clave de cifrado (ver .env).

    php artisan key:generate

NOTA

  • artisan se usa de forma generalizada. Por ejemplo, todas las clases deben crearse con ese comando.

  • Podemos desarrollar sin servidor ejecutando:

php artisan serve  # para iniciar el servicio
chrl+C             # para cerrar

Laravel en nuestro entornods:

  • Descargamos nuestro entornods y nos ponemos en la rama laravel

    git checkout --track origin/laravel
  • Añadimos al fichero hosts:

    127.0.0.1   laravel.local
  • Colocamos el directorio del proyecto dentro de data

  • Levantamos nuestro servicio:

      cd entornods 
      docker-compose up -d

OJO!!!

  • El entornods viene configurado para una aplicación ubicada en data/laravel

  • El resto de instrucciones también

  • Si usamos otro nombre deberemos ajustar alguno de estos comados y el contenido del fichero docker-compose.yml

  • El uso de la consola va a ser importante en el desarrollo con Laravel

  • Puede ser interesante definir aliases de comandos

  • Editamos el fichero ~/.bashrc

      #añadir lo siguiente al final: de  ~/.bashrc
      alias laraa='docker exec --user devuser laravel  php artisan '
      alias larac='docker exec --user devuser laravel  composer '
      alias larai='docker exec -it --user devuser laravel bash'
  • ejecutar lo siguiente si no queremos reiniciar:

      source ~/.bashrc
  • El fichero .env

    • El fichero .env define variables utilizadas únicamente en el entorno de desarrollo.

    • Cada sitio de desarrollo o producción mantiene su propio .env

    • Este fichero debe excluirse de git. Compruébalo en el fichero .gitignore.

    • Debemos ajustar la parte de BBDD y el APP_URL=http://laravel.local

  • El directorio de configuración es config. Buena parte de la configuración es condicionada al contenido del fichero .env

Repositorio de clase

  • Vamos a usar el repositorio: laravel20

  • Recomendación: hacer un fork del mismo:

  • Una vez hecho esto:

    • Ve a bitbucket y realiza un fork

    • Clona dentro de entornods/data tu repositorio

    • Instala dependencias

    • Obten una copia del fichero .env

    • Genera la clave de cifrado

    git clone ....
    cd laravel
    cp .env.example .env
    composer install
    php artisan key:generate
  • Es interesante poder descargar el código de clase. Para hacerlo añade el repositorio del profesor:

cd data/laravel
git remote add rafa git@bitbucket.org:rafacabeza/laravel20.git
  • Cuando quieras descargar la última clase:

git fetch rafa
git checkout --track rafa/claseXX #la rama que corresponda

Arquitectura

  • Configuramos nuestro servidor para que todas las peticiones lleguen a index.php (var https://laravel.com/docs/6.x/installation)

  • index.php sólo crea un objeto $app a partir del script bootstrap/app.php.

  • Tras esto la petición o request se pasa al kernel (nucleo) http o de consola:

  • Kernel Http:

      class Kernel extends HttpKernel
    • Carga clases bootstrapers que preparan la aplicación (heredado)

    • Define una serie de middlewares (filtros) que se ejecutan antes de tratar el request

    • Por fin, el kernel trata el request y devuelve un response (heredado)

  • Todas las clases dentro de Iluminate están dentro de vendor, son parte del framework y no debemos modificarlas en absoluto. Todo lo "heredado" está ahí.

  • La respuesta

    • Tras preparar la aplicación, la petición se pasa al router

    • De acuerdo a las rutas definidas se genera una respuesta o se delega en un controlador.

    • A las rutas y controladores se les puede asignar middlewares.

  • Los Service Providers

    • Son el elemento clave en el arrange (bootstraping) de la aplicación.

    • Definen los componentes disponibles en la aplicación.

    • El fichero config/app.php cuenta con un array donde definimos cuales otros queremos usar

      • Muchos de ellos están en el frameword (empiezan por Illuminate)

      • Otros están en app/Providers

      • Si añadimos librerías o creamos los nuestros propios (avanzado) deberemos añadir elementos a este array.

Rutas

  • En nuestro framework mvc "casero", un controlador era responsable de una tarea concreta, o conjunto de tareas relacionadas entre sí (CRUD sobre una tabla, por ejemplo).

  • Para nosotros el path o ruta de nuestra url representaba /controlador/metodo/arg1/arg2/argN.

  • Laravel tiene un sistema de enrutado mucho más flexible y potente.

  • Las rutas se definien en el directorio routes:

    • web.php define todas las rutas de la aplicación web

    • api.php define las rutas asociadas a un servicio web

    • console.php define rutas usadas desde la consola con php artisan

    • channels.php se usa en aplicaciones en tiempo real y websockets. No toca...

  • Para definir una ruta usamos una clase llamada Route.

  • Usamos una función que define el tipo de petición (get, post, ...)

  • Definimos una ruta

  • Definimos una función de callback o como veremos un controlador y un método

Route::get('user', function () {
 return 'hola mundo. Estamos en user';
});
  • Veamos algunos ejemplos ilustrativos:

Route::get('user', function () {
 return 'hola mundo. Estamos en user';
});

//prueba esta ruta creando un formulario.
Route::post('user', function () {
 return 'hola mundo. Estamos en user, por el método POST';
});

//podemos usar parámetros
Route::get('user/{id}', function ($id) {
 return 'Preguntando por user' . $id;
});

//podemos usar parámetros opcionales
Route::get('book/{id?}', function ($id = "desconocido") {
 return 'Preguntando por el libro ' . $id;
});

Route::get('user/delete/{id}', function ($id) {
 return 'Baja de usuario, argumento id:' . $id;
});

//Podemos filtrar los argumentos con expresiones regulares
Route::get('user/{name}', function ($name) { 
    // name debe ser texto
}) ->where('name', '[A-Za-z]+'); 

Route::get('user/{id}', function ($id) { 
    //  id debe ser un nº. natural
}) ->where('id', '[0-9]+')

Rutas y controladores

  • Las rutas anónimas son útiles para pequeñas tareas y pruebas.

  • Pero un trabajo serio necesita el uso de controladores (MVC)

use App\Http\Controllers\UserController;

//ruta prueba gestionada por el método "index" de PruebaController
Route::get('/photos', [PhotosController::class, 'index']);

Ojo! Esto ha cambiado en la versión 8. Anteriormente la sintáxis era:

Route::get('foo', 'Photos\AdminController@method');

Rutas resource: REST

  • Un CRUD necesita definir 7 rutas para 7 acciones.

    • Lectura: ruta de listado y de detalle

    • Creación: mostrar formulario de alta y procesarlo

    • Update: mostrar formulario de edición y procesarlo

    • Borrado

Route::get('photos/create', 'Photos\AdminController@create');
Route::post('photos', 'Photos\AdminController@store');
Route::get('photos', 'Photos\AdminController@index');
Route::get('photos/{id}', 'Photos\AdminController@show');
Route::get('photos/{id}/edit', 'Photos\AdminController@edit');
Route::put('photos/{id}', 'Photos\AdminController@update');
Route::delete('photos/{id}', 'Photos\AdminController@destroy');
  • Si definimos rutas de tipo resource se mapearán estas 7 rutas.

  • En la medida de lo posible estas rutas siguen el paradigma REST.

  • Son las acciones necesarias para un CRUD completo y usando los distintos verbos HTTP: get, post, put y delete.

  • Para ver como gestionar estas rutas y este tipo de controladores: https://laravel.com/docs/8.x/controllers#resource-controllers

  • Definición de una ruta

    Route::resource('photos', 'PhotoController');
  • Creación de un controlador resource:

    php artisan make:controller PhotoController --resource
  • Lista de rutas:

    Verb

    URI

    Action

    Route Name

    </tr>

    </thead>

    GET

    /photos

    index

    photos.index

    </tr>

    GET

    /photos/create

    create

    photos.create

    </tr>

    POST

    /photos

    store

    photos.store

    </tr>

    GET

    /photos/{photo}

    show

    photos.show

    </tr>

    GET

    /photos/{photo}/edit

    edit

    photos.edit

    </tr>

    PUT/PATCH

    /photos/{photo}

    update

    photos.update

    </tr>

    DELETE

    /photos/{photo}

    destroy

    photos.destroy

    </tr>

    </tbody>

    </table>

    </small>

MVC: Controladores & vistas

  • Laravel es mucho más que una arquitectura MVC

  • No obstante esta es una parte fundamental del framework

  • Veamos de momento como tratar controladores y vistas

Controladores

  • Su ubican en app/Http/Controllers

  • Para crear un controlador usaremos:

    php artisan make:controller ControllerName
    • Deberemos definir los métodos que necesitemos y asociarlos a una ruta del fichero de rutas (de momento, web.php).

  • Si queremos usar rutas de tipo resource:

    php artisan make:controller ControllerName --resource
    • Ya nos vendrá con los 7 métodos asociados a una ruta resource

  • OJO! Los formularios HTML no pueden usar PUT, PATCH ni DELETE. Podemos usar campos ocultos para falsear los verbos. El metodo helper method_field lo puede hacer por nosotros:

{{ method_field('PUT') }}
{{ method_field('DELETE') }}
  • Si en un método necesitamos hacer uso del objeto $request debemos incluírlo en su cabecera con type hinting:

    public function foo(Request $request)
    {
        //
    }
  • Podemos acceder a los parámetros GET y POST así:

    public function foo(Request $request)
    {
        $code = $request->code;
        $all = $request->all();
        dd($code);
    }

Vistas: Blade

  • Las vistas en php: php + html ==> código poco claro.

  • Mejor motores de plantillas que facilitan la inclusión de contenido variable en nuestro html.

  • Motores de plantillas son:

    • Blade, usado por Laravel

    • Smarty, muy habitual, no ceñido a ningún framework

    • Twig, usado por Simphony.

  • Código más limpio.

plantilla + compilación transparente = html + php
  • En laravel se devuelven con la función helper view()

  • Desde un controlador o ruta:

      return view('grupo.vista'); //estilo habitual
      return view('grupo/vista'); //también válido
    • Buscará una vista en el directorio resources/views/grupo

    • El fichero se llamará vista.blade.php o vista.php

  • Para llevar variables a la vista debemos hacer lo siguiente:

      return view('grupo.vista', $arrayAsociativo);
  • Ejemplos:

      return view('user.index', ['name' => 'James']);
      return view('blog.history', [
              'mes' => $mes,
              'ano' => $ano,
              'posts' => $posts
      ]);
  • Existen otras variantes:

    • Con with:

      return view('greeting')->with('name', 'Victoria');
    • Y por último usando compact.

      return view('users', compact('mes', 'ano', 'posts'));
      //equivale a:
      return view('blog.history', [
            'mes' => $mes,
            'ano' => $ano,
            'posts' => $posts
      ]);

Modelos y Bases de datos.

  • Un modelo se crea así:

    php artisan make:model Study
  • Pero para hablar de modelos debemos entender como gestionar las BBDD.

  • Para hacerlo Laravel nos brinda:

    • Migraciones

    • Seeders

    • Model Factrories

Migraciones

  • Laravel provee un sistema llamado "migraciones" que permite elaborar la estructura de la base de datos de un modo paulatino.

  • Las migraciones se asemejan a un control de versiones para la base de datos. Cada migración puede desplegarse o deshacerse según se precise.

  • Facilitan enormemente el trabajo en equipo y mediante SCV (git u otro).

  • Facilitan la migración de SGBD (Mysql, PostGres, ...).

  • Facilitan la implantación en nuevos equipos de desarrollo o en producción.

  • Debemos crear una migración por cada tabla.

  • Debemos crearla con artisan y editarla después.

  • Nota, en la versión 6 podemos crearla a la vez que el modelo:

php artisan make:migration create_studies_table  //crear migración
php artisan make:model -m Study //crear modelo y migración en un comando
  • Para modificar una tabla tenemos dos opciones:

    • Modificar la migración de creación. NO SIEMPRE POSIBLE

    • Crear una migración de modificación. Ejemplo:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class ModifyUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->bigInteger('role_id')->unsigned()->default(1)->after('id');
            $table->foreign('role_id')->references('id')->on('roles');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropForeign('users_role_id_foreign');
            $table->dropColumn('role_id');
        });        
    }
}

Creación de migraciones

  • Se usa artisan make:migration:

    // ej. comando base.
    php artisan make:migration create_users_table
    // ej. comando para creación de tabla. Añade código de creación.
    php artisan make:migration create_users_table --create=users
  • El resultado es una clase hija guardada database/migrations

  • El fichero se nombra usando el timestamp de creación para que sea ejecutado en el orden correcto.

Métodos de una migración

  • up() sirve para modificar la base de datos. Típicamente crear una tabla.

  • down() sirve para devolver la base de datos a su estado previo. Típicamente borrar una tabla.

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateStudiesTable extends Migration
{
    /**
     * Run the migrations.
     * Ejecutar migración
     * @return void
     */
    public function up()
    {
        //code
    }

    /**
     * Reverse the migrations.
     * Deshacer
     * @return void
     */
    public function down()
    {
        //code
    }
}

Código de las migraciones

  • Dentro de las funciones up y down usamos las funciones de la clase esquema para:

  • Crear tablas:

Schema::create('studies', function (Blueprint $table) {
    $table->id();
    ...
    $table->timestamps();
});
  • Modificar tablas.:

Schema::table('studies', function (Blueprint $table) {
    $table->string('name', 50)->change();
    $table->bigInteger('role_id')->unsigned()->default(1)->after('id');
    $table->string('description', 100)->after('name');
}
  • Borrar tablas:

public function down()
{
    Schema::dropIfExists('studies');
}
  • En las funciones anónimas (closure) de creación y modificación podemos hacer prácticamente cualquier cosa que soporte SQL. Debemos usar los métodos de la clase Blueprint aplicados a la variable $table:

    $table->string('email');  //añadir columna de texto VARCHAR(100). Hay métodos para muchos tipos de datos. Ver documentación.
    $table->string('name', 20);
    $table->tinyInteger('numbers');
    $table->float('amount');
    $table->double('column', 15, 8);
    $table->integer('votes');
    
    //podemos añadir modificadores e índices:
    $table->id();  //Clave principal, autoincremental, big integer.
    $table->char('codigo', 25)->primary(); //Otras claves principales
    $table->primary(['area', 'bloque']); // o así...
    $table->string('email')->nullable(); //añadir columna y permitir nulos.
    $table->timestamps(); //añade columnas create_at, update_at
    $table->string('email')->unique();  // columna con indice único
    $table->index(['account_id', 'created_at']); //índice multicolumna
    
    //claves ajenas: columna del tipo adecuado más f.k.
    $table->integer('user_id')->unsigned();
    $table->foreign('user_id')->references('id')->on('users');
    //podríamos añadir ->onDelete(cascade) ...
  • Los posibles modificadores son:

    ->first() | Place the column “first” in the table (MySQL Only)
    ->after('column') | Place the column “after” another column (MySQL Only)
    ->nullable() | Allow NULL values to be inserted into the column
    ->default($value) | Specify a “default” value for the column
    ->unsigned() | Set integer columns to UNSIGNED
    ->comment('my comment') | Add a comment to a column
  • Para otras operaciones consultar la documentación oficial.

Seeders (o sembradores)

  • Los seeders son clases usadas para:

  • Rellenar las tablas con datos de prueba

  • Cargar las tablas con datos iniciales.

  • Creación con artisan:

    php artisan make:seeder StudySeeder

  • Los seeders tienen un método run() donde se registra el contenido de los registros a insertar. Puede usarse sintáxis de Query Builder o de Eloquent

  • Ejemplo:

use Illuminate\Database\Seeder;
use App\Review;
class StudiesSeeder extends Seeder
{
    public function run()
    {
        //con eloquent
        Study::create([
        'code' => 'IFC303',
        'name' => 'Desarrollo de Aplicaciones Web',
        'abreviation' => 'DAW'
        ]);

        //lo mismo con query builder
        DB::table('studies')->insert([
        'code' => 'IFC303',
        'name' => 'Desarrollo de Aplicaciones Web',
        'abreviation' => 'DAW
        ]);
    }
}
  • El orden en que se ejecutan los seeders se establece manualmente. Debe rellenarse el método run() de la clase DatabaseSeeder ubicada en database/seeds

public function run()
{
    $this->call(StudySeeder::class);
    $this->call(ModuleSeeder::class);
}
  • Su ejecución es desde artisan:

    • Modo general

      php artisan db:seed

    • Junto a las migraciones:

      php artisan migrate:refresh --seed

    • De forma individual

      php artisan db:seed --class=ModuleSeeder

Model Factories

  • Un factory es una clase usada principalmente para desarrollo

  • Permite llenar nuestra base de datos con registros de prueba

  • Crear un factory:

php artisan make:factory PostFactory
  • Codificar un factory:

use Faker\Generator as Faker;

$factory->define(App\User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
        'remember_token' => str_random(10),
    ];
});
  • Usar el factory:

    • Crear un objeto con make() o con create(), el primero crea una variable, el segundo además la guarda en base de datos:

        $user = factory(App\User::class)->make();
        $user = factory(App\User::class)->create();
    • Podemos crear más de un registro en la misma orden:

      $users = factory(App\User::class, 3)->create();
  • Además podemos fijar o sobreescribir el valor de los campos:

$user = factory(App\User::class)->create([
    'name' => 'Abigail',
]);

Modelo

Bases de datos

  • Para acceder a bases de datos podemos (por orden):

    • ORM: Eloquent. Usado por Laravel y que usa una arquitectura Active Record.

    • Fluent Query Builder. Un sistema de creación de consultas independiente del gestor de bbdd utilizado.

    • SQL. Usar consultas de Raw Sql (sql en crudo o a pelo). Poco habitual

  • Bases de datos compatibles:

    • MySQL

    • Postgres

    • SQLite

    • SQL Server

  • Configuración:

    • Fichero config/database.php.

    • Pero basada en el .env.

  • Podemos usar las bases de datos de forma convencional, creadas previamente.

  • Más interesante usar migraciones como veremos.

Modelo

  • Las clases modelo extienden una superclase llamada Model

  • Heredan métodos muy interesantes.

  • Permiten realizar multitud de operaciones sin escribir una sóla línea de código a través de Eloquent.

  • Nomenclatura:

    • El nombre del modelo: tiene que ser en singular y mayúscula. P.ej. "User"

    • La la tabla asociada estará en plural y minúscula. P. ej. "users"

    • Clave primaria por defecho "id".

    • Eloquent usa las columnas created_at y updated_at para actualizar automáticamente esos valores de tiempo.

  • Artisan nos ayuda a crear los modelos:

    php artisan make:model Study    // sólo modelo
    php artisan make:model Study -m // modelo y migración
  • La migración es un fichero que nos permite ir creando a la BBDD a nuestra medida

    • Método up: crea o modifica una tabla

    • Método down: retrocede los cambios o borra la tabla

  • Si queremos cambiar los parámetros por defecto respecto a nombre de tabla, clave primaria y uso de columnas temporales:

    <?php
    namespace App;
    use Illuminate\Database\Eloquent\Model;
    class Study extends Model
    {
     protected $table = 'estudio';
     $primaryKey = 'id_estudio';
     public $timestamps = false;
    }
  • Veamos un ejemplo de CRUD Study. Vamos a ver el código de las clases Study y de StudyController.

    • Clase modelo. Muy sencillo porque Eloquent nos provee de los métodos que necesitamos.

<?php
namespace App;

use Illuminate\Database\Eloquent\Model;

class Study extends Model
{
    //$fillable indica que atributos se deben cargar en asignación masiva.
    protected $fillable = ['code', 'name', 'shortName', 'abreviation'];
}
  • Clase modelo. Usamos los métodos que nos facilita Eloquent como son:

    find($id) //Busca un registro
    findOrFail($id) //Similar pero lanza excepción si falla. 
    all() //Carga todos los registros.
    paginate() //Carga una página de registros. Ver paginación.
    save() //Guarda el objeto previamente cargado.
    push() //Guarda el objeto y si hay objtetos relacionados modificados también lo hace.
    delete() //Borra el objeto previamente cargado.
    destroy($id) //Localiza y borra un registro (la variable tb. podría ser un array de ids).
  • El controlador:

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request; 
use App\Http\Requests\StudyRequest; //usado para validación avanzada
use App\Study; //modelo Study

class StudyController extends Controller
{
    public function index()
    {
        $studies = \App\Study::all(); //toma todos los registros
        return view('study.index', ['studies' => $studies]);
    }

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

    public function store(Request $request)
    {
        $this->validate($request, [
            'code' => 'required|unique:studies',
            'abreviation' => 'required|unique:studies',
            'name' => 'required',
            'shortName' => 'required'
            ]);
        $study = new Study($request->all());
        $study->save();
        return redirect('/study');
    }

    public function delete($id)
    {
        // modo 1
        // $study = Study::find($id);
        // $study->delete();
        // modo 2
        Study::destroy($id);
        return redirect('/study'); //redirigir
    }

    public function update($id)
    {
        $study = Study::find($id);
        return view('study.update', ['study' => $study]);
    }

    public function save(StudyRequest $request)
    {
        $study = Study::find($request->id);
        $study->code = $request->code;
        $study->name = $request->name;
        $study->shortName = $request->shortName;
        $study->abreviation = $request->abreviation;
        $study->save();
        return redirect('/study');
    }
}

Response

  • Podemos reenviar la respuesta de diferentes modos:

  • Reenvio a una ruta concreta:

//fichero de rutas:
//ruta destino con nombre definido
Route::get('destino/{sitio}', function ($sitio) {
    return "Destino: $sitio";
})->name('destino');
  • Reenvío a rutas con nombre o con URL:

//código en el una fichero de rutas pero que podría ir en el controlador:
//reenvío ordinario por la URL
Route::get('teruel', function () {
    return redirect('/destino/Teruel');
});
//reenvío por nombre
Route::get('zaragoza', function () {
    return redirect()->route('destino', ['sitio' => 'Zaragoza']);
});
  • Reenvío a la misma ruta de la que venimos, e incluso añadir mensajes de error:

//redirección simple:
return Redirect::back();

//igual pero con errores
return Redirect::back()->withErrors(['msg', 'The Message']);

//en la vista podríamos hacer:

@if($errors->any())
<h4>{{$errors->first()}}</h4>
@endif
  • Reenviar a un método de un controlador:

    //a un método sin argumentos
    return redirect()->action('HomeController@index');
    //con argumentos
    return redirect()->action(
      'UserController@profile', ['id' => 1]
    );

Migraciones y Seeders

Migraciones

  • Laravel provee un sistema llamado "migraciones" que permite elaborar la estructura de la base de datos de un modo paulatino.

  • Las migraciones se asemejan a un control de versiones para la base de datos. Cada migración puede desplegarse o deshacerse según se precise.

  • Facilitan enormemente el trabajo en equipo y mediante SCV (git u otro).

  • Facilitan la migración de SGBD.

  • Facilitan la implantación en nuevos equipos de desarrollo o en producción.

  • Debemos crear una migración por cada tabla.

  • Debemos crearla con artisan y editarla después.

  • Nota, en la versión 6 podemos crearla a la vez que el modelo:

php artisan make:migration create_studies_table  //crear migración
php artisan make:model -m Study //crear modelo y migración en un comando
  • Ejemplo de migración de la tabla studies:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateStudiesTable extends Migration
{
    /**
     * up crea la tabla
     * down la elimina
     */
    public function up()
    {
        Schema::create('studies', function (Blueprint $table) {
            $table->id();
            $table->string('code')->unique();
            $table->string('name');
            $table->string('shortName');
            $table->string('abreviation');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('studies');
    }
}
  • Para modificar una tabla tenemos dos opciones:

    • Modificar la migración de creación. NO SIEMPRE POSIBLE

    • Crear una migración de modificación. Ejemplo:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class ModifyUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->bigInteger('role_id')->unsigned()->default(1)->after('id');
            $table->foreign('role_id')->references('id')->on('roles');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropForeign('users_role_id_foreign');
            $table->dropColumn('role_id');
        });        
    }
}

Ejecutar migraciones

*

  • Las migraciones se gestionan desde la consola mediante comandos de artisan.

    • El comando base es php artisan migrate. En los siguientes apartados vamos a ver las variantes.

    migrate:install //Crea una tabla en la base de
      datos que sirve para llevar el control de las migraciones realizadas y su orden.
    migrate:reset //Realiza una marcha atrás de todas las migraciones en el sistema, volviendo el schema de la
      base de datos al estado inicial.
    migrate:refresh //Vuelve atrás el sistema de migraciones y las vuelve a ejecutar todas.
    migrate:rollback //Vuelve hacia atrás el último lote de  migraciones ejecutadas.
    * \ //hp artisan migrate:rollback --step=2 Vuelve atrás las últimas migraciones, en este ccaso los últimos dos lotes.
    migrate:status //Te informa de las migraciones presentes en el sistema y si están ejecutadas o no.

Creación de migraciones

  • Se usa artisan make:migration:

    // ej. comando base.
    php artisan make:migration create_users_table
    // ej. comando para creación de tabla. Añade código de creación.
    php artisan make:migration create_users_table --create=users
  • El resultado es una clase hija guardada database/migrations

  • El fichero se nombra usando el timestamp de creación para que sea ejecutado en el orden correcto.

Métodos de una migración

  • up() sirve para modificar la base de datos. Típicamente crear una tabla.

  • down() sirve para devolver la base de datos a su estado previo. Típicamente borrar una tabla.

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateStudiesTable extends Migration
{
    /**
     * Run the migrations.
     * Ejecutar migración
     * @return void
     */
    public function up()
    {
        Schema::create('studies', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     * Deshacer
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('studies');
    }
}

Código de las migraciones

  • Dentro de las funciones up y down usamos las funciones de la clase esquema para:

    • Crear tablas: Schema::create('nommbreTabla', 'funcion_closure');

    • Renombrar tablas: Schema::rename\($original, $nuevo\);

    • Borrar tablas: Schema::drop('users'); o Schema::dropIfExists('users');

    • Modificación de tablas: Schema::table('nommbreTabla', 'funcion_closure');

  • En las funciones anónimas (closure) de creación y modificación podemos hacer prácticamente cualquier cosa que soporte SQL. Debemos usar los métodos de la clase Blueprint aplicados a la variable $table:

    $table->string('email');  //añadir columna de texto VARCHAR(100). Hay métodos para muchos tipos de datos. Ver documentación.
    $table->string('name', 20);
    $table->tinyInteger('numbers');
    $table->float('amount');
    $table->double('column', 15, 8);
    $table->integer('votes');
    
    //podemos añadir modificadores e índices:
    $table->id();  //Clave principal autoincremental usado por defecto.
    $table->char('codigo', 25)->primary(); //Otras claves principales
    $table->primary(['area', 'bloque']); // o así...
    $table->string('email')->nullable(); //añadir columna y permitir nulos.
    $table->timestamps(); //añade columnas create_at, update_at
    $table->string('email')->unique();  // columna con indice único
    $table->index(['account_id', 'created_at']); //índice multicolumna
    
    //claves ajenas: columna del tipo adecuado más f.k.
    $table->integer('user_id')->unsigned();
    $table->foreign('user_id')->references('id')->on('users');
    //podríamos añadir ->onDelete(cascade) ...
  • Los posibles modificadores son:

    ->first() | Place the column “first” in the table (MySQL Only)
    ->after('column') | Place the column “after” another column (MySQL Only)
    ->nullable() | Allow NULL values to be inserted into the column
    ->default($value) | Specify a “default” value for the column
    ->unsigned() | Set integer columns to UNSIGNED
    ->comment('my comment') | Add a comment to a column
  • Para otras operaciones consultar la documentación oficial.

Seeders (o sembradores)

  • Los seeders son clases usadas para:

  • Rellenar las tablas con datos de prueba

  • Cargar las tablas con datos iniciales.

  • Creación con artisan:

    php artisan make:seeder StudySeeder

  • Los seeders tienen un método run() donde se registra el contenido de los registros a insertar. Puede usarse sintáxis de Query Builder o de Eloquent

  • Ejemplo:

use Illuminate\Database\Seeder;
use App\Review;
class StudiesSeeder extends Seeder
{
    public function run()
    {
        //con eloquent
        Study::create([
        'code' => 'IFC303',
        'name' => 'Desarrollo de Aplicaciones Web',
        'abreviation' => 'DAW'
        ]);

        //lo mismo con query builder
        DB::table('studies')->insert([
        'code' => 'IFC303',
        'name' => 'Desarrollo de Aplicaciones Web',
        'abreviation' => 'DAW
        ]);
    }
}
  • El orden en que se ejecutan los seeders se establece manualmente. Debe rellenarse el método run() de la clase DatabaseSeeder ubicada en database/seeds

public function run()
{
    $this->call(StudySeeder::class);
    $this->call(ModuleSeeder::class);
}
  • Su ejecución es desde artisan:

    • Modo general

      php artisan db:seed

    • Junto a las migraciones:

      php artisan migrate:refresh --seed

    • De forma individual

      php artisan db:seed --class=ModuleSeeder

Model Factories

  • Crear un factory:

php artisan make:factory PostFactory
  • Codificar un factory:

use Faker\Generator as Faker;

$factory->define(App\User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
        'remember_token' => str_random(10),
    ];
});
  • Usar el factory:

    • Crear un objeto con make() o con create(), el primero crea una variable, el segundo además la guarda en base de datos:

        $user = factory(App\User::class)->make();
        $user = factory(App\User::class)->create();
    • Podemos crear más de un registro en la misma orden:

      $users = factory(App\User::class, 3)->create();
  • Además podemos fijar o sobreescribir el valor de los campos:

$user = factory(App\User::class)->create([
    'name' => 'Abigail',
]);

Relaciones entre tablas/modelos

  • El ORM Eloquent no pone fácil para tratar las relaciones entre tablas.

  • Basta añadir algunos métodos a los modelos implicados.

  • Vamos a ver únicamente las relaciones 1:N

  • Para relaciones 1:1 y N:M ver la documentación

  • Vamos a verlo con el ejemplo entre usuarios y roles: un Role puede tener varios Users y un User pertenece a un Role.

  • Crear modelo Role (y migración, seeder y controlador)

    • -m añade la migración

    • -s añade el seeder

    • -cr añade el controlador (c) tipo resource (r)

php artisan make:model Role -mscr
  • Métodos de la migración

      public function up()
      {
          Schema::create('roles', function (Blueprint $table) {
              $table->id();
              $table->string('name')->unique();
              $table->timestamps();
          });
      }
      public function down()
      {
          Schema::dropIfExists('roles');
      }
  • Modificación de la tabla users.

    • Es habitual tener que modificar tablas que ya existen. Para eso usamos migraciones un poco diferentes:

    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->unsignedBigInteger('role_id')->default(1);        
            $table->foreign('role_id')->references('id')->on('roles');
        });
    }
    public function down()
    {
        //las fk se mombran así: tabla_columna_foreign
        //esto se usa para eliminar la clave en el down
        Schema::table('users', function (Blueprint $table) {
            $table->dropForeign('users_role_id_foreign');
            $table->dropColumn('role_id');
        });
    }
  • Creamos o modificamos el RoleSeeder y el UserSeeder:

    public function run()
    {
        //con DB
        \DB::table('roles')->insert([
            'id' => '1',
            'name' => 'registrado'
        ]);

        \DB::table('roles')->insert([
            'id' => '2',
            'name' => 'usuario'
        ]);

        \DB::table('roles')->insert([
            'id' => '3',
            'name' => 'administrador'
        ]);

    }
    public function run()
    {
        //con DB
        \DB::table('users')->insert([
            'id' => '1',
            'name' => 'registrado',
            'email' => 'registrado@dws.es',
            'password' => bcrypt('secret')
        ]);

        \DB::table('users')->insert([
            'id' => '2',
            'name' => 'usuario',
            'email' => 'usuario@dws.es',
            'password' => bcrypt('secret'),
            'role_id' => 2
        ]);

        \DB::table('users')->insert([
            'id' => '3',
            'name' => 'admin',
            'email' => 'admin@dws.es',
            'password' => bcrypt('secret'),
            'role_id' => 3
        ]);
    }
  • No debemos olvidar actualizar el DatabaseSeeder:

    public function run()
    {
        $this->call(RoleSeeder::class);
        $this->call(UserSeeder::class);
        $this->call(StudiesSeeder::class);
        // \App\Models\User::factory(10)->create();
    }
  • Por fín los modelos: Vamos a tratar una relación 1:N

  • Un Role tiene muchos Users asociados.

  • La clase Role debe incluír el siguiente método:

class Role extends Model
{
    // resto del código
    //el método users nos devolverá una colección de usuarios
    //el nombre es significativo
    public function users()
    {
        return $this->hasMany(User::class);
    }
}
  • Un User pertenece a un Role.

  • La clase Role debe incluír el siguiente método:

    class User extends Authenticatable
    {
        // resto del código
        //el método role nos devolverá un objeto role
        // el nombre también es significativo
        public function role()
        {
            return $this->belongsTo(Role::class);
        }       
    }
  • Ahora podemos usarla en nuestras vistas.

  • Por ejemplo en "user/index.blade.php"

    @foreach($users as $user)
    <tr>
        <td>{{ $user->name }}</td>
        <td>{{ $user->role->name }}</td>
    </tr>
    @endforeach
  • O en "role/show.blade.php"

{{ $role->name }}

<h2>Lista de usuarios</h2>

@foreach ($role->users as $user)
  <li>{{$user->name}}</li>
@endforeach

Middleware

  • Los middleware son filtros que se ejecutan antes de que el control pase a una ruta o a un controlador.

  • Podemos asciar un middleware a una ruta:

    Route::get('profile', 'UserController@show')->middleware('auth');
  • O a un controlador, en su constructor:

    class UserController extends Controller
    {
      public function __construct()
      {
          $this->middleware('auth'); //aplicable a todos los métodos
          $this->middleware('log')->only('index');//solamente a ....
          $this->middleware('subscribed')->except('store');//todos excepto ...
      }
    }

kernel.php

  • En el kernel.php de la aplicación se definen varias cuestiones relativas a los middleware.

  • Un grupo de middleware que se aplican a todas las peticiones. Atributo $middleware.

  • Se asignan nombres a los middleware para ser usados en las rutas. Atributo $routeMiddleware.

  • Dos grupos de middleware:

    • web que se aplica a todas las rutas definidas en el fichero de rutas web.php

    • api que se aplica a las rutas del fichero api.php

Ejemplos de middleware

  • CheckForMaintenanceMode Se aplica a todas las rutas. Se activa su funcionalidad con los comandos:

    php artisan down
    php artisan up
    • Después de down genera un error 503

    • Comprueba la existencia del fichero /storage/framework/down

    • Down crea el fichero, up lo borra.

  • Vamos a ver los que se aplican a todas las peticiones web.

    • EncryptCookies. Encripta las cookies

    • AddQueuedCookiesToResponse. Añade las cookies creadas mediante el método Cookie::queue($micookie);

    • StartSession. Inicia sesión de forma automática.

    • ShareErrorsFromSession Hace visible el objeto errors tras la validación.

    • VerifyCsrfToken. Verifica los token CSRF.

    • SubstituteBindings ¿?

  • Además, dispondemos de los siguientes definidos en route:

    • auth. Pasa el filtro si estás autenticado.

    • guest. Pasa el filtro si no has hecho login

    • can. Filtro usado para autorización.

    • throttle. Limita el número de peticiones por minuto aceptadas desde un host determinado.

Construir un middleware:

  • Usamos artisasn:

    php artisan make:controller MiddlewareEsAdmin
  • Código.

    • La lógica está en la función handle.

    • Si se dan las condiciones adecuadas se pasa el filtro (return $next($request);)

    • Si no se dan las condiciones abortar o redirigir.

 <?php

namespace App\Http\Middleware;

use Closure;

class IsAdmin
{
    public function handle($request, Closure $next)
    {
        $user = $request->user();
        if ($user && $user->username == 'admin') {
            return $next($request);
        }
        abort(403, 'acceso denegado');
    }
}
  • Registrar el middleware en el kernel dentro de $routeMiddleware

  • Ya podemos usarlo como el resto:

Route::get('families/ajax', 'FamilyController@ajax')->middleware('admin');

Sesiones y Autenticación

Autenticación

  • Ya vimos como añadir el sistema de autenticación propio de Laravel

  • Disponde de un juego de migraciones, rutas, controladores y vistas que hacen posible la gesión de usuarios

  • Autenticarse es identificarse, que la apliación nos reconozca como un usuario concreto.

  • Para hacerlo debemos hacer uso de variables de sesión.

  • Laravel viene con un sistema base muy completo. Vamos a usar la versión usada en Laravel 7:

      composer require laravel/ui
      php artisan ui bootstrap --auth
  • Las vistas generadas están construidas con Twiter Bootstrap y se almacenan en el directorio: resources/views/auth.

  • Rutas:

    • Para login se usa /login

    • Tras el login se usa /home. Puede personalizarse en el LoginController.

    • Por defecto el nombre de usuario es su e-mail.

    • Para cerrar la sesión: logout

    • Para resetear la contraseña: password/reset

  • Para el reseteo de contraseñas se envia un correo con un token.

  • Para pasar los mails a log y no enviar realmente:

    • En el fichero .env cambiar el parámetro: `MAIL_DRIVER=log

      `

    • Completar en config/mail.php:

      'from' => [
        'address' => env('MAIL_FROM_ADDRESS', 'admin@example.com'),
        'name' => env('MAIL_FROM_NAME', 'Adminstrador'),
      ],
    • Los correos se guardan en storage/logs

    • Podemos ver el correo enviado para resetear contraseña

  • Si queremos acceder a la información del usuario actual debemos usar la clase fachada Auth.

//Mostrar la información de usuario
Route::get('usuario', function () {
    $user = Auth::user();  //namespace global: \Auth
    dd($user);
    //si sólo nos interesa el id podríamos hacer:
    //$id = Auth::id();
});

Sesiones

  • Los parámetros de configuración de sesiones se definen en config/session.php.

  • Importante es el almacenamiento y el tiempo de vida de la sesión.

  • Con Laravel no es preciso usar directamente la variable $_SESSION.

  • ni debemos usar las funciones session_start() o session_destroy()

  • Podemos acceder a un dato guardado en sesión de tres modos:

    • El request, esto requiere inyección de dependencias:

      public function showProfile(Request $request, $id)
      {
        $value = $request->session()->get('key');
      }
    • La fachada Session

      $value = Session::get('key');
    • La función helper session():

      $value = session('key');
  • Obtener todos los datos de una sesión

    $data = $request->session()->all();
    $data = Session::all(); //usando el Facade
  • Podemos guardar datos en sesión de dos modos:

    $request->session()->put('email', 'me@example.com');
    session(['email' => 'me@example.com']); //usando el helper
  • Podemos eliminar datos de sesión:

    $request->session()->forget('key'); //elemento key
    $request->session()->flush();   // todos
    Session::flush();               //todos con la fachada
  • Guardar datos sólo durante una petición (request) sólamente.

  • Esto se usa en validaciones de forma automática, transparente al usuario

    • Para los datos antiguos (old)

    • Para los mensajes de error

      //después de una petición se elimina. 
      $request->session()->flash('mensaje', '¡El usuario fue eliminado!');   
      //guardar los datos flasheados un request más.
      $request->session()->reflash();  
      //guardadr datos flasheados.
      $request->session()->keep(['mensaje', 'email']);

Query Builder. Paginación

  • Laravel nos permite realizar gran cantidad de consultas sin escribir sql.

  • Hasta el momento, sobre los modelos hemos usado algunos métodos: find(), all(), save(), first().

  • Las consultas pueden construirse a través de los modelos (User, Role, ...) o de la clase DB:

//todos los usuarios:
$user = User::first(); //mejor así para obtener objetos User
$user = DB::first(); //así obtenmos objetos "básicos"
  • Vamos a poder filtrar usando where en alguna de sus variantes.

  • El método all() no admite filtrado con where.

  • Debemos usar get()

      $users = User::where('id', 100)->get();
      $users = User::where('id', '>', 100)->get();
    
      $users = User::where('id', '<=', 100)
          ->where('email', 'admin@dws.es')
          ->get();
  • Existen otras variantes que podemos consultar en la documentación:

    • orWhere()

    • whereBetween()

    • whereIn()....

  • No vamos a ver su uso. Queda como investigación del alumno.

  • Para paginar basta con cambiar get() por paginate

      $users = $query->paginate(); //tamaño 15
      $users = $query->paginate($pageSize); //tamaño $pageSize
  • En la vista la generación del índice de páginas es automático.

      {{$users->links()}} //estándar: Tailwind CSS
      {{$users->links('pagination::bootstrap-4')}} //Bootstrap
  • En la rama del proyecto de clase "clase07" ha sido necesario depurar el código

  • Puedes ver las explicaciones en los vídeos de dicha rama.

Validación

  • La validación es una cuestión fundamental en cualquier aplicación, no sólo web. Cualquiera.

  • Validar consiste en verificar la validez de los datos antes de procesarlos.

  • ¿Validar en cliente o en servidor?.

    • En el cliente es más rápido, no necesitamos conectar con el servidor ni hacer uso de la red.

    • Pero nunca debemos confiar en la validación realizada en el cliente. Puede ser burlada intencionadamente o anulada de forma involuntaria.

    • Validar en el cliente es opcional pero en el servidor es obligatorio.

Cómo validar en Laravel

  • Existen varias maneras de hacerlo.

  • Nosotros usaremos el método validate del objeto $request.

Ejemplo:

public function store(Request $request)
{
    $request->validate([
    'title' => 'required|unique:posts|max:255',
    'body' => 'required',
    ]);
    //validado ->  a guardar
}

¿¿Qué pasa tras la validación ??

  • Si la validación falla se produce una excepción y se reenvía la petición a la URL previa.

  • Se crea un objeto $errors que permite mostrar los errores de validación.

  • Los datos del formulario se flashean a la sesión y estarán disponibles sólo en la próxima peticicón para ser volver a rellenar el formulario como estaba.

  • OJO. Si usamos Ajax se envía una respuesta al cliente con un código 422 y un JSON con los mensajes de error.

Rellenando con los datos viejos

  • La funcion helper old() nos permite acceder a los datos que no se han validado y están en la sesión.

    <label>Nombre:</label>
    <input type="text" name="name" value="{{ old('name')}}">

Mostrando los errores con blade

  • Todos los errores de la página

    @foreach ($errors->all() as $error)
    <li>{{ $error }}</li>
    @endforeach
  • Mostrar los errores de un campo:

    //sólo el primero
    {{ $errors->first('name') }}
    //o todos los del campo
    @foreach($errors->get('name') as $error)
    {{ $error }}
    @endforeach
  • También podemos usar la directiva @error de blade:

<label for="title">Post Title</label>

<input id="title" type="text" class="@error('title') is-invalid @enderror">

@error('title')
    <div class="alert alert-danger">{{ $message }}</div>
@enderror

Traduciendo mensajes

  • Podemos traducir todos los literales de Laravel. No sólo los de validación.

  • Debemos acceder a la siguiente ruta

  • Descargar la carpeta del idioma que nos interese.

  • Configurar el lenguaje en config.php

Reglas

  • Se pueden añadir múltiples reglas para cada campo. Entre comillas y separadas por la barra vertical.

  • La regla bail hace que no se sigan evaluando más reglas si esa falla.

  • Revisar la documentación oficial: Available Validation Rules.

Unique y update

  • Ojo cuando apliquemos la regla unique en actualizaciones (update).

  • Si aplicamos la regla sin más va a fallar porque ya existe un registro con ese dato:

    'code' => 'required|unique:studies|max:6',
  • Debemos indicar que no tenga en cuenta el propio registro para comprobar esta regla:

    'code' => 'required|max:6|unique:studies,code,' . $study->id,

Otros modos de validar

  • Hemos planteado la forma habitual pero existen otras:

    • Creando clases Request a la medida de una validación.

    • Creando clases Validator

  • Ambas soluciones son algo más sofisticadas y no las vamos a usar aunque podrían dar solución a situaciones más complejas.

Autorización: reglas y políticas

  • Laravel nos brinda dos sistemas para autorizar acciones: reglas y puertas.

  • Reglas

    • Las reglas se definen asociadas a la clase Gate.

    • Son una solución más simple.

  • Políticas

    • Son soluciones más elaboradas que engloban la autorización referida a un modelo concreto.

    • Se definen en clases dentro guardadas en app/Policies

  • Tienen una analogía a rutas y controladores, simpliciad vs complejidad y orden.

  • En un proyecteo usaremos una u otra (o ambas) de acuerdo a su complejidad y envergadura.

Definir reglas

  • Las reglas se definen en el AuthServiceProvider.php, dentro de su función boot.

  • Podemos definir una regla con un closure (función anónima) dentro de dicho método:

    public function boot()
    {
      $this->registerPolicies(); 
      //nuestra regla es esta:
      Gate::define('update-post', function ($user, $post) {
          return $user->id == $post->user_id;
      });
    }

Usar reglas

  • Para comprobar las reglas debemos usar la fachada Gate y alguno de sus métodos (allows, denies). Lo podemos hacer en la propia ruta o más correctamente en el controlador o en un middleware:

    if (Gate::denies('update-post', $post)) {
      abort(403);
      //o podríamos redireccionar:
      // return redirect('/home');
      // o mostrar una vista
      // return view('nombreVista');
    }
  • Observa que el $user no es pasado a la clase Gate, toma el autenticado. Si quisieramos pasarlo debemos hacerlo así:

    //preguntando si se le permite
    if (Gate::forUser($user)->allows('update-post', $post)) {
      // The user can update the post...
    }
    //o al revés ...
    if (Gate::forUser($user)->denies('update-post', $post)) {
      // The user can't update the post...
    }

Políticas

  • Si el proyecto es pequeño la solución anterior es válida pero para algo grande puede no ser una buena solución.

  • Las políticas separan código para no sobrecargar el código del AuthServiceProvider.

  • Las políticas son clases guardadas en app/Policies y que se crean así:

    //clase en blanco
    php artisan make:policy PostPolicy
    //clase con reglas CRUD predefinidas asociada a un modelo
    //recomendable:
    php artisan make:policy PostPolicy --model=Post
  • Tras crear las políticas, éstas deben ser regitradas en el AuthServiceProvider:

    class AuthServiceProvider extends ServiceProvider
    {
      //deben incluírsee en el siguiiente array:
      protected $policies = [
          Post::class => PostPolicy::class,
      ];

Escribiendo las políticas:

  • Podemos crear los métodos que necesitemos. Por ejemplo el método update lo podemos usar para actualizar un objeto del modelo afectado. Sus argumentos deben ser el user y un objeto del modelo protegido:

    public function update(User $user, Post $post)
    {
      return $user->id === $post->user_id;
    }
    //aunque si el método no usa ningún objeto del modelo lo podemos dejar así:
    public function create(User $user)
    {
      return true; //una expresión booleana...
    }
  • El método before permite tomar decisiones generales sin evaluar la política

public function before($user, $ability)
{
    if ($user->isSuperAdmin()) {
        return true;
    }
}
// si devuelve true -> autorizado. No comprueba nada más
// si devuelve false -> denegado. No comprueba nada más
// si devuelve null -> lo que diga la regla usada

TODO:

  • Test

  • Modelos y BBDD

  • Errores

  • Eloquent & Query Builder

  • Relaciones

  • Multi Idioma

  • Ajax

  • Paginación

  • Mail

  • Pdf

  • Trabajo

Last updated