Laravel: URL amigables (parte 18)

Archivos:

  • database/migrations
  • database/factories

Crear el campo slug

Genramos un nuevo campo dentro de la migración de la tabla cursos, llamado «slug»

    public function up()
    {
        Schema::create('cursos', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('slug');
            $table->text('description');
            $table->text('categoria');
            $table->timestamps();
        });
    }

Editamos el factory de esta tabla:

    public function definition()
    {
        return [
            //
            'name'=>$this->faker->sentence(),
            'slug'=>Str::slug($this->faker->sentence(),'-'),
            'categoria'=>$this->faker->randomElement(['Desarrollo web','Diseño web']),
            'description'=>$this->faker->paragraph()
        ];

Hemos llamado a la clase Str y a su método slug. Cambiará todas las mayúsculas por minúsculas y sustituirá los espacios por guiones (porque hemos establecido un guión como segundo parámetro).

En este caso tendremos un slug distinto al título, y queremos que sea el mismo, con lo que lo podemos poner así:

public function definition()
    {
        $name =$this->faker->sentence();

        return [
            //
            'name'=>$name,
            'slug'=>Str::slug($name,'-'),
            'categoria'=>$this->faker->randomElement(['Desarrollo web','Diseño web']),
            'description'=>$this->faker->paragraph()
        ];
    }

No podemos olvidar llamar a la clase Str al principio del archivo, si no lo ha autorrellenado Intelephense:

use Illuminate\Support\Str;

Luego migramos para ver los cambios en la base de datos:

php artisan migrate:fresh --seed

Uso del campo slug

Abrimos la vista index de los cursos, comprobemos la url actual:

@foreach ($cursos as $curso)

        <li>
         <a href="{{route('cursos.show', $curso->id)}}">{{$curso->name}}</a> 
         </li>
          {{route('cursos.show', $curso->id))}}

        @endforeach

Recordamos que en Laravel podemos llamar a la id directamente de la siguiente manera, ya que el recurso de llamar a la id es muy utilizado y se puede hacer directamente así:

@foreach ($cursos as $curso)

        <li>
         <a href="{{route('cursos.show', $curso)}}">{{$curso->name}}</a> 
         </li>
          {{route('cursos.show', $curso))}}

        @endforeach

LA id se la pasamos al controlador mediante la variable curso. Necesitaríamos que cuando utilicemos el método route y le pasemos el objeto curso, en vez de la id, recupere el slug del objeto. Devolverá el registro cuyo slug coincida con lo que le pasemos por la url.

Vamos al modelo Curso, que vemos que se extiende de la clase Model.

Vemos que uno de los muchos métodos que tiene es getroutekeyname:

     public function getRouteKeyName()
    {
        return $this->getKeyName();
    }

Podríamos modificar este mnétodo tal que así, para lograr lo que queremos:

     public function getRouteKeyName()
    {
       // return $this->getKeyName();
 return 'slug';
    }

Pero lo mejor es hacerlo en el modelo Curso:

Recursos:

Enlace de Youtube: https://www.youtube.com/watch?v=VYuthGIhrS0&list=PLZ2ovOgdI-kWWS9aq8mfUDkJRfYib-SvF&index=25

Laravel: Route resource (parte 18)

Archivos:

  • routes/web.php

Uso de route resource

Desde la consola de Git Bash, navegamos hasta el directorio de nuestro proyecto y ejecutamos el siguiente comando de artisan: php artisan «route list»

php artisan r:l

Comentamos todas las rutas y vamos a definir la rutas del CRUD de la siguiente forma:

 Route::resource('cursos', CursoController::class);

Si volvemos a ejecutar el comando route list, veremos todas las 7 rutas del CRUD de nuevo en la consola.

El resultado es exactamente el mismo porque anteriormente creamos las rutas una a una siguiendo las convenciones de nombres.

Cambio de rutas creadas con route resource

En la documentación de Laravel se nos indica cómo hacerlo: https://www.oulub.com/docs/laravel/controllers#restful-localizing-resource-uris

Según se nos indica allí, vamos al archivo App/providers/appserviceprovider.php y poner en la parte superior del archivo:

use Illuminate\Support\Facades\Route;

Y dentro del método boot:

    public function boot()
    {
        Route::resourceVerbs([
            'create' => 'crear',
            'edit' => 'editar',
        ]);
    }

Y podemos hacer esto porque una cosa es la url, y otra el nombre de la ruta. Lo que estamos cambiando es la url, mientras que los formularios y lois enlaces siguen apuntando a las rutas por su nombre.

De hecho, podríamos modificar la palabra «cursos» del route resource por cualquier otra, por ejemplo, «asignaturas»; y todo seguiría funcionando como hasta ahora, pero tendríamos que usar un par de métodos más para que no falle todo:

Route::resource('asignaturas', CursoController::class)->parameters(['asignatura' => 'curso'])->names('cursos');

El método names permitirá que las rutas sigan funcionando como hasta ahora (pues están en todas las vistas, y si no, tendríamos que modificar cada vista) y el método parameters incluye un array para los nombres de las variables que usamos para generar las rutas (que, de nuevo, necesitarían ser cambiadas por «asignatura»).

Recursos:

Enlace de Youtube: https://www.youtube.com/watch?v=PT6BoDQdkXk&list=PLZ2ovOgdI-kWWS9aq8mfUDkJRfYib-SvF&index=24

Laravel: Eliminar registros (parte 17)

Archivos:

  • routes/web.php
  • CursoController.php

Crearemos una ruta de tipo delete, que apunte a una url llamada curso.destroy.

Route::delete('cursos/{curso}',[CursoController::class, 'destroy'])->name('cursos.destroy');

Crearemos un método destroy() en nuestro controlador. Como se está mandando información por la url, lo rescatamos en el controlador.

    public function destroy(Curso $curso) {
        $curso->delete();
    }    

Añadiremos un botón eliminar en la vista show dentro de un formulario. El formulario tendrá la directiva method con el método del mismo (que no es post, sino delete) y el token de seguridad, además de que en el action pasaremos la nueva ruta creada con el parámetro curso. En la vista show:

<form action="{{route('cursos.destroy',$curso)}}" method="POST">
        @csrf
        @method('delete')
        <button type="submit">Eliminar</button>
    </form>

Tenemos que pedirle al método destroy que nos redirija para que el formulario no muestre una página en blanco,

    public function destroy(Curso $curso) {
        $curso->delete();
        return redirect()->route('cursos.index');
    }    

Recursos:

Enlace de Youtube: https://www.youtube.com/watch?v=3uUW1IviXns&t=3s

Laravel: Asignación masiva formularios (parte 16)

Archivos:

  • resources/views/cursos/create.blade.php

Actualizando método store

Si un formulario tuviese muchísimos campos, resultaría tedioso asignar cada campo a un valor dentro del método del controlador. Para ello podemos asignar todos los campos con una única línea de código, aunque para ello tendremos que tomar ciertas medidas de seguridad.

En el método store, creábamos un objeto Curso y asignábamos los valores provenientes del formulario con las siguientes líneas, y lo salvábamos en la base de datos:

$curso = new Curso();

$curso ->name= $request->name;
$curso ->description= $request->description;
$curso ->categoria= $request->categoria;
$curso ->save();

Ahora usaremos un método llamado create() al que pasaremos como parámetro un array:

        $curso = Curso::create([
            'name' => $request->name,
            'description' => $request->description,
            'categoria' => $request->categoria
        ]);

Lo cual se puede escribir mucho más sencillo así:

        $curso = Curso::create($request->all());

Pero si intentamos ahora usar el formulario obtendremos el siguiente error:

Add [_token] to fillable property to allow mass assignment on [App\Models\Curso].

Así que esto nos dice que, por seguridad, tendremos que especificar qué campos se pueden rellenar y qué no en nuestra base de datos – ya que estamos mandando todos los campos al mismo tiempo, sin importar cuántos ni cuáles. Esto lo podemos hacer de dos formas: usando la propiedad fillable o la propiedad guarded en el módulo Curso.

Propiedad fillable

Así especificamos qué campos queremos que se puedan rellenar:

class Curso extends Model
{
    use HasFactory;
    protected $fillable = ['name','description','categoria'];
}

(Ponemos campos permitidos e ignoramos lo campos protegidos)

Propiedad guarded

Así especificamos qué campos no queremos que se puedan rellenar, lo cual es bastante útil ahora mismo para ahorrar código:

class Curso extends Model
{
    use HasFactory;
    protected $guarded = ['status'];
}

Esto es útil si, por ejemplo, tuviéramos un campo llamado status cuya función sería que el registro se mostrara o no, y estuviera pendiente de ser aprobado o no mientras este campo tuviera cierto valor o se modificara. En nuestro caso, aún no tenemos establecido nada de esto y podemos dejarlo así:

class Curso extends Model
{
    use HasFactory;
    protected $guarded = [];
}

(Ponemos campos protegidos e ignoramos lo campos permitidos)

Actualizando método update

Vamos al método update, para editar registros, y lo modificamos de la siguiente manera: comentamos todas las líneas de creación de objeto y asignación, y le pasamos el método update al objeto $curso que hemos creado, pasándole como parámetro el array de request:

public function update(Request $request, Curso $curso) {
        // return $request->all();

        $request->validate([
            'name' => 'required',
            'description' => 'required',
            'categoria' => 'required',
        ]);

        // $curso->name = $request->name;
        // $curso->description = $request->description;
        // $curso->categoria = $request->categoria;

        // $curso->save();

        $curso->update($request->all());

        return redirect()->route('cursos.show',$curso->id);

    }

Recursos:

Enlace de Youtube: https://www.youtube.com/watch?v=n_C_HFqS8XY&list=PLZ2ovOgdI-kWWS9aq8mfUDkJRfYib-SvF&index=22

Laravel: Validar formularios (parte 15)

Archivos:

  • resources/views/cursos/create.blade.php
  • appHttpControllersCursoController.php
  • lang/
  • config/app.php
  • app/Http/Requests

Validar campos requeridos

Campos no vacíos

Cuando un campo de la base de datos no tenga la propiedad nullable, no deberíamos intentar guardar campos vacíos sin mostrar un error. Para eso tenemos que validar los campos del formulario.

En la función store, usaremos un método del objeto request llamado validate. Dentro del método tendremos un array con las reglas de validación:

        $request->validate([

        ]);

Así especificamos que queremos que los campos sean requeridos:

        $request->validate([
            'name' => 'required',
            'description' => 'required',
            'categoria' => 'required',
        ]);

Mostrar errores

Debajo de cada campo, mostraremos un mensaje de error, usando una directiva de blade:

        @error('record')
            *{{$message}}
        @enderror

El error lo maquetaremos de la siguiente manera:

(...)
<label>
 Nombre:<br>
 <input type="text" name="name">
        </label>      
         @error('name')
            <br>
                <small>*{{$message}}</small>
            <br>
        @enderror
<br>

Hacemos lo mismo con todos los campos:

<br>    <form action="{{route('cursos.store')}}" method="POST">

        @csrf

        <label>
            Nombre:<br>
            <input type="text" name="name">
        </label>

        @error('name')
            <br>
                <small>*{{$message}}</small>
            <br>
        @enderror
        <br>

        <label>
            Descripción:<br>
            <textarea name="description" rows="5"></textarea>
        </label>
        @error('description')
            <br>
                <small>*{{$message}}</small>
            <br>
        @enderror
        <br>

        <label>
            Categoría:<br>
            <input type="text" name="categoria">
        </label>
           @error('categoria')
            <br>
                <small>*{{$message}}</small>
            <br>
        @enderror 
        <br>
        <button type="submit">Enviar formulario</button>
    
    </form>

Traducción de los mensajes

Vamos a la carpeta lang, donde podemos ver todos los mensajes en inglés de la paginación, la valicación…. Duplicamos la carpeta y le cambiamos el nombre a es.

Los mensajes ya están traducidos por la comunidad de Laravel, los podemos descargar de aquí: https://github.com/Laraveles/spanish. Reemplazamos el contenido de nuestros archivos con el contenido de los de spanish/resources/lang/es/.

Nos dirigimos al archivo config/app.php y editamos el valor «locale»:

    'locale' => 'es',

Más personalización

Dentro del archivo de traducción español validation.php encontraremos este array:

    'attributes' => [],

El campo nombre de nuestro formulario se llama name, con lo que podemos hacer lo siguiente:

        'attributes' => [
        'name' => 'nombre'
    ],

Asi nuestro mensaje de validación no será:

*El campo name es obligatorio.

Sino así:

*El campo nombre es obligatorio.

Dejaremos el array de la siguiente forma:

    'attributes' => [
        'name' => 'nombre del curso',
        'description' => 'descripción'
    ],

Mantener los valores de los campos aunque haya un error

Si el usuario olvida un campo pero rellena otros, el valor de los campos válidos se mantendrá cuando se recargue el formulario.

Usamos el método old en tags value de los input del formulario, y dentro del textarea:

<form action="{{route('cursos.store')}}" method="POST">

        @csrf

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

        @error('name')
            <br>
                <small>*{{$message}}</small>
            <br>
        @enderror
        <br>

        <label>
            Descripción:<br>
            <textarea name="description" rows="5">{{old('description')}}</textarea>
        </label>
        @error('description')
            <br>
                <small>*{{$message}}</small>
            <br>
        @enderror
        <br>

        <label>
            Categoría:<br>
            <input type="text" name="categoria" value="{{old('categoria')}}">
        </label>
           @error('categoria')
            <br>
                <small>*{{$message}}</small>
            <br>
        @enderror 
        <br>
        <button type="submit">Enviar formulario</button>
    
    </form>

En el proyecto tenemos otro formulario, el de edición. Antes de continuar con el siguiente paso de la validación, haremos los cambios convenientes al formulario de edición: añadimos la regla de validación al método update y los mensajes de error a cada uno de los labels.

Un caso específico de los formularios de edición

Comentemos un caso concreto: Si en el formulario de editar curso hago ciertos cambios en un campo, pero dejo otro vacío, al recargar el formulario mostrará el mensaje de error, pero no hará caso de los recientes cambios, sino que mostrará el valor anterior ya almacenado en la base de datos.

Para ello, el método old que antes utilizamos podemos volver a usarlo con un segundo parámetro, el valor por defecto del campo:

(...)
    <h1>Edición de curso</h1>
    <form action="{{route('cursos.update',$curso)}}" method="POST">

        @csrf

        @method('put')

        <label>
            Nombre:<br>
            <input type="text" name="name" value="{{old('name',$curso->name)}}">
        </label>

        @error('name')
        <br>
            <small>*{{$message}}</small>
        <br>
    @enderror 
        <br>

        <label>
            Descripción:<br>
            <textarea name="description" rows="5">{{old('description',$curso->description)}}</textarea>
        </label>

        @error('description')
        <br>
            <small>*{{$message}}</small>
        <br>
    @enderror 
        <br>

        <label>
            Categoría:<br>
            <input type="text" name="categoria" value="{{old('categoria',$curso->categoria)}}">
        </label>
        @error('categoria')
            <br>
                <small>*{{$message}}</small>
            <br>
        @enderror 

        <br>
        <button type="submit">Actualizar curso</button>
    
    </form>

Es decir, el campo por defecto seguirá siendo el mismo, pero rescatará el valor más reciente antes de actualizar el formulario gracias al método old.

Más de una regla de validación

Si quisiéramos que el campo nombre, además de que no estuviera vacío, tuviera un máximo de 10 caracteres y la descripción un mínimo de 5, usaremos una segunda regla de validación con el símbolo |. En la función store:

        $request->validate([
            'name' => 'required|max:10',
            'description' => 'required|min:5',
            'categoria' => 'required',
        ]);

Hemos hecho la validación en el controlador. En ocasiones, las reglas de validación son muchas, y en este caso es recomendable hacerlo en un archivo a parte. De hecho, es lo recomendado en cualquier caso.


Archivo de validación

Enlace a la documentación de Laravel: https://laravel.com/docs/9.x/validation#form-request-validation

Usaremos un comando de artisan para crear un archivo de validación del formulario para guardar registros:

php artisan make:request StoreCurso

Se ha creado un archivo php en la ruta:Http/Requests/ . En el mismo hay dos métodos: auth() sirve para establecer las reglas de los permisos de usuario, el cual debemos hacer que de momento devuelva un valor true. En el método rules() allí ponemos nuestras reglas de validación, que antes estaban en el método store:

    public function authorize()
    {
        return true;
    }
    
(...)

public function rules()
    {
        return [
            'name' => 'required|max:10',
            'description' => 'required|min:5',
            'categoria' => 'required',
        ];
    }

En la parte superior de controlador añadimos la línea:

   use App\Http\Requests\StoreCurso;

Borramos donde antes teníamos las reglas de validación (el método store) y añadimos la nueva clase como parámetro de la función:

    public function store(StoreCurso $request){
(...)

Los campos ya deberían validarse como anteriormente.

Función attributes

Bajo el método rules() tenemos la posibilidad de usar este otro método para personalizar el nombre de los atributos del formulario en el mensaje de error, como ya hicimos en el archivo de traducción:

   public function attributes()
    {
        return [
        'name' => 'del nombre del curso'
        ];
    }

Función message

   public function attributes()
    {
        return [
        'name' => 'del nombre del curso'
        ];
    }

Para personalizar el mensaje de error completo del campo description, para la regla de validación required:

    public function messages()
    {
        return [
            'description.require' => 'Es obligatoria una descripción.'
        ];
    }

Vemos que como el campo tiene dos reglas de validación, necesitamos especificar para qué regla vamos a mostrar este mensaje personalizado.

Recursos:

Enlace de Youtube: https://www.youtube.com/watch?v=KbpbqZshUus&list=PLZ2ovOgdI-kWWS9aq8mfUDkJRfYib-SvF&index=20, https://www.youtube.com/watch?v=Ze-Sg2BT3mc&list=PLZ2ovOgdI-kWWS9aq8mfUDkJRfYib-SvF&index=21

Laravel: Registros y formularios (parte 14)

Archivos:

  • routes/web.php
  • resources/views/cursos/show.blade.php
  • \app\Http\Controllers\CursoController.php
  • resources/views/cursos/edit.blade.php

Crear el formulario

Formulario HTML

Este es el código HTML que nos mostrará el formulario, lo escribiremos en la plantilla:

  <form action="{{route('cursos.store')}}" method="POST">

        <label>
            Nombre:<br>
            <input type="text" name="name">
        </label>
        <br>

        <label>
            Descripción:<br>
            <textarea name="description" rows="5"></textarea>
        </label>
        <br>

        <label>
            Categoría:<br>
            <input type="text" name="categoria">
        </label>
        <br>
        <button type="submit">Enviar formulario</button>
    
    </form>

Ruta para el formulario

Creamos una ruta tipo post para procesar los datos, hacia la url «cursos.store», usando el método store (que en seguida crearemos) del controlador CursoController:

Route::post('cursos', [CursoController::class, 'store'])->name('cursos.store');

En al formulario asignaremos la ruta en el parámetro action:

<form action="{{route('cursos.store')}}" method="POST">

Token de seguridad

Tenemos que añadir el token de seguridad del formulario que nos brinda Laravel:

 @csrf

Recuperar datos del formulario

Definimos un objeto de tipo Request en el método. Dentro del mismo podemos recuperar su valor.:

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

Ya podemos ver los valores enviados usando el formulario. Ahora hacemos uso del objeto Curso, y ya podemos guardar un nuevo curso en la base de datos:

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

        $curso = new Curso();

        $curso ->name= $request->name;
        $curso ->description= $request->description;
        $curso ->categoria= $request->categoria;

        $curso->save();
    }

Redirección del formulario

Veamos el registro una vez agregado.

Vamos un segundo a nuestro método index para que se ordenen los cursos de manera descendente (útil ahora mismo para pruebas):

 $cursos = Curso::orderBy('id','desc')->paginate();

Añadimos una redirección al método store que nos muestre los cursos:

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

        $curso = new Curso();

        $curso ->name= $request->name;
        $curso ->description= $request->description;
        $curso ->categoria= $request->categoria;

        $curso->save();

         //return redirect()->route('cursos.show',$curso->id);

         return redirect()->route('cursos.show',$curso);
    }

Aunque en esta ocasión no le hemos pasado la id del curso, sino el curso completo, Laravel lo hace de manera inteligente.

Formulario para editar registro

Ruta para editar el registro

Crearemos una nueva ruta en web.php, de tipo get, pasando un parámetro id por la url, con un nuevo método que enseguida crearemos, llamado edit:

    Route::get('cursos/{id}/edit', [CursoController::class, 'edit'])->name('cursos.edit');

Nos dirigimos a la vista show y agregamos un nuevo enlace donde rezará «editar curso», y le pasamos la nueva ruta que hemos creado, con el objeto curso:

    <a href="{{route('cursos.edit', $curso)}}">Editar curso</a>

Aquí el método edit que rescatará el objeto curso, para poder editarlo:

    public function edit($id) {
        $curso = Curso::find($id);
        return $curso;        
    }

Modificación en las rutas

Vamos a proceder a cambiar la forma que tenemos de recuperar la información. Primero cambiamos el método, usando como parámetro un objeto tipo Curso:

    public function edit(Curso $id) {

        return $id;        

    }

Renombramos las variables para evitar confusión:

    public function edit(Curso $curso) {
        return $curso;        
    }

Como hemos renombrado la variable tenemos que cambiar la ruta:

Route::get('cursos/{curso}/edit', [CursoController::class, 'edit'])->name('cursos.edit');

Usemos la misma técnica para el método show. Como la ruta ya usa una variable llamada curso, sólo cambiaremos el método. Véase que nos ahorramos una línea de código, que hemos comentado:

    public function show(Curso $curso){
        // $curso = Curso::find($id);
        return view("cursos.show", compact("curso"));
    }

Vista para editar el registro

Modificamos el método edit para mostrar una vista, a la que pasaremos el curso a editar.

    public function show(Curso $curso){
        // $curso = Curso::find($id);
        return view("cursos.show", compact("curso"));
    }

Creamos la vista edit.blade.php, duplicando la vista create (que ya tiene un formulario) en nuestra carpeta de vistas. Editamos el archivo creado añadiendo los tags values y sus valores (que serán los parámetros del curso elegido), y modificando un poco el texto para más claridad:

    <form action="{{route('cursos.store')}}" method="POST">

        @csrf

        <label>
            Nombre:<br>
            <input type="text" name="name" value="{{$curso->name}}">
        </label>
        <br>

        <label>
            Descripción:<br>
            <textarea name="description" rows="5" value="">
{{$curso->description}}</textarea>
        </label>
        <br>

        <label>
            Categoría:<br>
            <input type="text" name="categoria" value="{{$curso->categoria}}">
        </label>
        <br>
        <button type="submit">Actualizar curso</button>
    
    </form>

Ruta para guardar el registro

Crearíamos una ruta tipo post que se encargará de actualizar el registro y un método que llamaremos update para actualizarlo. Pero es recomendado que se utilice el método put para actualizar formularios.

La ruta la escribiremos así:

Route::put('cursos/{curso}', [CursoController::class, 'update'])->name('cursos.update');

Tenemos que cambiar el action del formulario par apuntar a esta ruta, y pasarle como parámetro el curso a editar, además de indicar que el método que usaremos será un método put:

(...)
    <form action="{{route('cursos.update', $curso)}}" method="POST">

        @csrf

        @method('put')
(...)

En el controlador crearemos el método put. Como parámetros, necesitamos tanto el contenido del formulario como el objeto curso (siguiendo las convenciones como anteriormente):

    public function update(Curso $curso) {
        return $curso;
    }

Si probamos el formulario veremos la información del curso. Pero tenemos que pasarle el objeto request para que capture los valores del formulario, y así lo podemos probar. Veremos que ahora pasa los parámetros method y el token:

       public function update(Request $request, Curso $curso) {
        return $request->all();
    }

De esta manera podemos editar el registro, y además redirigir a otra vista:

    public function update(Request $request, Curso $curso) {
        // return $request->all();

        $curso->name = $request->name;
        $curso->description = $request->description;
        $curso->categoria = $request->categoria;

        $curso->save();

        return redirect()->route('cursos.show',$curso->id);

    }

Recursos:

Enlace de Youtube: https://www.youtube.com/watch?v=oBxfBlV_2sU&list=PLZ2ovOgdI-kWWS9aq8mfUDkJRfYib-SvF&index=18

Laravel: Listar y leer registros (parte 13)

Archivos:

  • app/Http/controllers/CursoController.php
  • resources/views/cursos/index.blade.php
  • resources/views/layouts/plantilla.blade.php
  • routes/web.php
  • resources/views/cursos/show.blade.php

Listar registros

Según hemos aprendido en lecciones anteriores, podemos mostrar la colección completa de cursos si añadimos estas líneas al método index de CursoController:

$cursos = Curso::all();
return $cursos;

Para poder darle formato a estos registros, tenemos que pasarle la colección a la vista. Por lo tanto, eliminamos la segunda línea (return $cursos) y se la pasamos a la vista de la siguiente manera:

$cursos = Curso::all();
return view("cursos.index", compact('cursos'));

Ahora podemos utilizar la variable cursos en la vista. Abrimos index.blade.php, y usamos este bucle foreach escrito en «formato blade», en el que imprimiremos una lista ul de los cursos:

@section('content')
    <h1>Bienvenido a la página de cursos</h1>
    <ul>
        @foreach ($cursos as $curso)
            <li>{{$curso}}</li>
        @endforeach
    </ul>
@endsection

Listar parte de los registros

Vamos a mostrar sólo el nombre de cada curso, para ello en vez de {{$curso}} accedemos a la propiedad name.

@section('content')
    <h1>Bienvenido a la página de cursos</h1>
    <ul>
        @foreach ($cursos as $curso)
            <li>{{$curso->name}}</li>
        @endforeach
    </ul>
@endsection

Así podríamos acceder a cualquier propiedad de los cursos (id, descripción…).

Paginar registros

Hagamos el código más eficiente mostrando los resultados paginados. Para ello recuperaremos los cursos en el controlador con otro método distinto a all():

$cursos = Curso::paginate();
return view("cursos.index", compact('cursos'));

Esto mostrará sólo la primera página, y debería acceder a la url http://localhost/blog/public/cursos?page=2 para la siguiente.

Agregaremos unos controles para poder navegar entre las páginas. Añadimos lo siguiente al final de la vista:

    <ul>
        @foreach ($cursos as $curso)
            <li>{{$curso->name}}</li>
        @endforeach
    </ul>
    {{$cursos->links()}}

Primer uso de Tailwind

Tanto los botones de Previus y Next como los número de página tienen estilos prestablecidos que utilizan las clases de tailwind. Instalemos la librería tailwind para ver los estilos: accediento a tailwindcss.com podemos copiar este cdn y pegarlo en la plantilla que estamos usando, que anteriormente hemos llamado plantilla.blade.php:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@yield('title')</title>
    <!-- favicon -->
    <!-- estilos -->
    <script src="https://cdn.tailwindcss.com"></script>
</head>

(...)

Con la simple adición de este fragmento de código en la plantilla, todas nuestras vistas se beneficiarán del css de tailwind, y veremos un resultado de la paginación más agradable. De momento no usamos tailwind, así que comentamos la línea.

Ajuste en las rutas

Vamos a realizar un pequeño ajuste en las rutas de nuestro proyecto.

Para comenzar, en el archivo routes/web.php, le daremos la propiedad name a la ruta. Concretamente, a la ruta «cursos/create» le daremos el nombre de «cursos.create». A las otras rutas les daremos su parámetro también, para usarlas más adelante. Esto permite cambiar la ruta con mucha facilidad. Podemos hacerlo con la ruta agrupada o con la ruta normal, a continuación pongo el código necesario para hacerlo en ambos casos:

// ruta agrupada

Route::controller(CursoController::class)->group(function(){
    Route::get('cursos','index')->name('cursos.index');
    Route::get('cursos/create','create')->name('cursos.create');
    Route::get('cursos/{curso}','show')->name('cursos.show');
});

// ruta normal

/*
Route::get('cursos', [CursoController::class, 'index']);
Route::get('cursos/create', [CursoController::class, 'create'])->name('cursos.create');
Route::get('cursos/{curso}', [CursoController::class, 'show']);
*/

Al comienzo de la vista index.blade.php, donde estábamos haciendo la paginación, podemos hacer referencia a esta ruta de la siguiente manera:

@section('content')
    <h1>Bienvenido a la página de cursos</h1>    
     {{-- <a href="cursos/create">Crear curso</a> --}}
    <a href="{{route('cursos.create')}}">Crear curso</a>

Si quisiéramos, podríamos cambiar el nombre de la ruta sin preocuparnos de que el enlace siga apuntando a la misma ruta:

// podríamos tener la ruta que queramos, todo seguirá funcionando:
(...)
    Route::get('blablabla','create')->name('cursos.create');
(...)

Una página para cada curso

Usaremos una ruta que ya teníamos para que cada elemento de la lista sea un enlace a cada página del curso. Para ello, convertiremos cada elemento de la lista en un enlace a una de nuestras rutas, a al que hemos añladido el atributo name (como hemos hecho en este mismo artículo, más arriba):

(...)
    Route::get('cursos/{curso}','show')->name('cursos.show');
(...)

Dentro de la vista:

<li>
<a href="{{route('cursos.show')}}">{{$curso->name}}</a> 
</li>

Pero esto nos devolverá un error, porque show necesita un parámetro. Así, tenemos que pasarle algo del registro, como puede ser el id. Veamos cómo se mostraría así:

<li>
{{route('cursos.show',$curso->id)}} 
</li>

Esas son las rutas que queremos. Dejemos el código así:

 @foreach ($cursos as $curso)

      {{-- <li>{{$curso->name}}</li> --}}
      {{-- <li>{{route('cursos.show')}}</li>  --}}
      {{-- <li>{{route('cursos.show', $curso->id)}}</li>  --}}

      <li>
      <a href="{{route('cursos.show', $curso->id)}}">{{$curso->name}}</a> 
       </li>

@endforeach

Afinando el código

Haremos una serie de cambios en nuestro código para mejorarlo:

Cambiamos la función show de nuestro controlador. Estaba de la siguiente manera:

     public function show($curso){
        return view("cursos.show", compact("curso"));
    }

Ahora le pasaremos la id como variable, y dentro recuperaremos el curso con esa id:

     public function show($id){
        $curso = Curso::find($id);
        return view("cursos.show", compact("curso"));
    }

Si visitamos alguno de los enlaces de la lista, ahora se mostrará el curso completo, pero con un mal formato. Vamos a la vista cursos.blade.php y mostraremos el nombre, el nombre en el título y la categoría, además de un enlace para volver a la lista de cursos:

@extends('layouts.plantilla')

@section('title', $curso->name)

@section('content')
    {{-- <h1>Url con la variable <?php echo $curso?></h1> --}}
    <h1>Bienvenido al curso {{$curso->name}}</h1>
    <a href="{{route('cursos.index')}}">Volver a cursos</a>
    <p><strong>Categoría: </strong>{{$curso->categoria}}</p>
    <p><strong>Descripción: </strong>{{$curso->description}}</p>
@endsection

Recursos:

Enlace de Youtube: https://www.youtube.com/watch?v=bisZbFOB_Io&list=PLZ2ovOgdI-kWWS9aq8mfUDkJRfYib-SvF&index=17

Laravel: Mutadores y Accesores (parte 12)

Archivos:

  • Models/Curso.php

Definiciones

Los mutadores y accesores nos dan cierto control sobre la forma en que almacenamos o leemos los registros en la base de datos.

El código para crear un mutador y un accesor

Al principio del modelo tenemos que importat la definición del atributo al que luego vamos a llamar:

use Illuminate\Database\Eloquent\Casts\Attribute;

Luego, dentro de la clase user, incluiremos un método (al final de la clase) con el siguiente código:

(...)
protected function name(): Attribute { // 1

    protected function name(): Attribute { // 1

        return new Attribute( 

            get: function($value) { // 4
                return ucwords($value); // 6
            }, // 5

            set: function($value) { // 2
                return strtolower($value); // 3
            }
        );
    } 
(...)

Explicación del código

  1. Debemos crear una función de tipo protected con el mismo nombre del atributo que queremos modificar, que nos retorne una nueva instancia de la clase Attribute (por eso los dos puntos y la palabra Attribute)
  2. Dentro de la función capturaremos lo que esté mandando dentro del atributo name, en una variable llamada value, con una función precedida por la palabra set.
  3. El campo se transformará en minúsculas, por lo que usamos una función de php llamada strlower. Cada vez que intentemos agregar un nuevo registro, antes de almacenarlo, lo transformará en minúsculas y lo almacenará en la base de datos
  4. El accesor lo escribimos antes que el mutador, con la palabra get y estableciendo también una función con el parámetro $value
  5. El mutador y el accesor van separados por una coma
  6. Usamos el me´todo de php que transformará el valor con cada palabra en mayúsculas, el método ucwords.

Funcionando desde Tinker

Así podemos probar que nuestro mutador funciona como es debido.

php artisan tinker // abrir tinker
use App\Models\User; // usar el modelo
$user = new User(); // creamos una instancia del modelo
$user-> name='Jorge GOMeZ' // le asignamos el atributo name
// nótese que hay mayúsculas y minúsculas indiscriminadamente
$user-> email='Jorge@email.com' // le asignamos un mail
$user-> password=bcrypt(12345678) // le asignamos una contraseña, encriptada
$user-> save()// almacenamos los valores, que se almacenarán "mutados"

Para probar el accesor, también desde Tinker:

php artisan tinker // abrir tinker
use App\Models\User; // usar el modelo
$user = new User::first(); // leemos el primer registro de nuestra base de datos

Usando las funciones flecha de PHP 8

Esta forma de escribir las funciones nos las provee la versión 8 de PHP y sucesivas, para un código más breve:

protected function name(): Attribute { 

        return new Attribute( 

            get: fn($value) => ucwords($value),
            set: fn($value) => strtolower($value)
        );
}  

Los mutadores y accesores en versiones anteriores

public function getNameAttribute($value) {
     return ucwords($value);
}

public function getNameAttribute($value) {

     $this->attributes['name']=strtolower($value);
}

Recursos:

Enlace de Youtube: https://www.youtube.com/watch?v=0st6AA_7fBA&list=PLZ2ovOgdI-kWWS9aq8mfUDkJRfYib-SvF&index=16