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

Laravel: Consultas con Eloquent (parte 11)

Métodos para las consultas que vamos a utilizar:

  • all
  • get
  • where
  • orderBy
  • select
  • take
  • first
  • find

Vamos a practicar con Tinker haciendo consultas. El primer paso es ejecutar Tinker y hacer uso del modelo que necesitaremos para la tabla en cuestión.

php artisan tinker
use Model\All\Curso;

Para recuperar todos los registros de la tabla

Curso::all(); 

Para devolver sólo la categoría «Diseño web», necesitamos una restricción.
Guardaremos el resultado de la consulta en una variable.

$cursos = Curso::where('categoria', 'Diseño web')

Para que los devuelva, usamos el método get:

$cursos = Curso::where('categoria', 'Diseño web')->get();

Para invertir el orden, llamamos al mismo método pero antes del get pasamos el método order by indicando el campo por el que queremos que se ordene:

$cursos = Curso::where('categoria', 'Diseño web')->orderBy('id','desc')->get();

También podemos ordenarlo por el nombre

$cursos = Curso::where('categoria','Diseño web')->orderBy('name','asc')->get();

Obtener el primer registro: cambiamos el método get por el método first

$cursos = Curso::where('categoria','Diseño web')->orderBy('name','asc')->first();

Para que devuelva un campo en concreto: esta vez usamos el método select

$cursos = Curso::select('name','description')->get();

Haciendo la consulta más completa, ordenando los campos

$cursos = Curso::select('name','description')->orderBy('name', 'asc')->get();

o filtrando por una categoría en concreto:

$cursos = Curso::select('name','description','categoria')->orderBy('name', 'desc')->where('categoria','Diseño web')->get();

para llamar al nombre como ´título (ponerle ese alias) debemos agregar al select el comando AS

$cursos = Curso::select('name as title','description','categoria')->orderBy('name', 'desc')->where('categoria','Diseño web')->get();

podemos especificar la cantidad de regitros que queremos que devuelva la consulta, esto antes del método get

$cursos = Curso::select('name as title','description','categoria')->where('description','Diseño web')->take(5)->get();

Vamos a buscar un registro en particular;

$curso = Curso::where('name' , 'Et illum aperiam blanditiis sequi ea sit ut nam.')->get();

Esto devuelve un array, así que no podemos acceder al nombre usando

$curso->name;

porque obtendremos un error. Podemos hacer la consulta de la siguiente manera:

$curso = Curso::where('name' , 'Et illum aperiam blanditiis sequi ea sit ut nam.')->first();

Y ahora sí tendremos el nombre usando $curso->name;

Lo mismo, pero buscando por id:

$curso = Curso::where('id',5)->first();

Otra forma de acceder a este tipo de datos es no usar el método where sino el método find del que nos provee eloquent, ya que la búsqueda por id es muy utilizada:

$curso = Curso::find(5);

Solicitamos todos los registros cuya id sea mayor que 45, ahora a where le pasamos 3 parámetros

$cursos = Curso::where('id','>',45)->get();

Se puede hacer con mayor que, menor que distinto a, mayor o igual. También podemos usar like , para que devuelva lo que contenga en alguna parte del campo los caracteres del tercer parámetro…

$cursos = Curso::where('name','like','%voluptate%')->get();

Recursos:

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

Laravel: Factories (parte 10)

Archivos que vamos a tratar

  • /database/factories
  • /database/factories/UserFactory.php
  • /seeders/DatabaseSeeder.php
  • /seeders/CursoSeeder.php

¿Y si necesitamos 50 registros de prueba? Usaremos Factories para esto.

Creamos un Factory con el siguiente comando para la tabla cursos:

php artisan make:factory CursoFactory

Creará un documento llamado CursoFactory.php en la carpeta factories. En el documento creado, tendremos una clase que se extiende de la clase Factory.

En el curso se nos explica que podemos hacerlo con el siguiente comando, pero hemos visto en la práctica que no es necesario:

 php artisan make:factory CursoFactory --model=Curso

Lo que sí necesitamos es añadir al principio del archivo, después del namespace:

use App\Models\Curso;

Y al parecer esta línea, antes de la función definition no es necesaria, con lo que la dejamos comentada:

// protected $model = Curso::class;

Ahora definiremos todos los campos de nuestra tabla, dentro de la función definition() de la clase:

    public function definition()
    {
        return [
            //
            'name'=>$this->faker->sentence(),
            'category'=>$this->faker->paragraph(),
            'description'=>$this->faker->randomElement(['Desarrollo web','Diseño web'])
        ];
    }
  • Para hacer uso de uno de los métodos de esta clase, escribimos $this
  • Utilizamos el método faker
  • EL campo name se rellenará con una oración, por lo que usamos sentence()
  • El campo description se llenará con un párrafo
  • Para la categoría, se escogerá entre dos elementos de un array, por lo que pasamos la función randomElement con un array de dos elementos
  • Hay muchos más de tipos de datos que podemos rellenar con faker.

Editamos CursoSeeder eliminando todos los registros que pusimos antes:

 public function run()
    {

        $curso = new Curso();
        $curso->name='Laravel';
        $curso->description='El mejor Framework';
        $curso->categoria='Desarrollo web';

        $curso->save();
        
        $curso2 = new Curso();
        $curso2->name='Laravel';
        $curso2->description='El mejor Framework';
        $curso2->categoria='Desarrollo web';

        $curso2->save();
        
        $curso3 = new Curso();
        $curso3->name='Laravel';
        $curso3->description='El mejor Framework';
        $curso3->categoria='Desarrollo web';

        $curso3->save();
    }

Y rellenaremos la tabla con 50 elementos:

    public function run()
    {
        Curso::factory(50)->create();
    }

Podemos ejecutar el comando fresh para que se borren y se creen todas las tablas de nuevo, y ejecutar los seeders. En la lección anterior vimos que podíamos hacerlo en una sola línea:

php artisan migrate:fresh --seed

Este es el momento en el que vamos a borrar nuestro seeder. No necesitamos el archivo CursoSeeder si creamos el factory y añadimos la línea correspondiente a CursoSeeder dentro de DatabaseSeeder. Estos es una recomendación de uso de la versión de Laravel que estamos trabajando. Así que, llegados a este punto:

  • Copiamos la línea Curso::factory(50)->create(); de CursoSeeder,
  • La pegamos en DatabaseSeeder,
  • Añadimos el modelo curso: use App\Models\Curso; al comienzo del arechivo DatabaseSeeder
  • Borramos el archivo CursoSeeder (ya no es necesario)

Si ejecutamos de nuevo:

php artisan migrate:fresh --seed

Todo funciona como anteriormente, sólo que nuestro código es más limpio y acorde a los estándares de uso.

Llenemos la tabla users con el factory del que ya nos provee Laravel. De momento no vamos a comentar el archivo UserFactory.php, pero podemos comprobar que es bastante parecido al CursoFactory.php que habíamos creado anteriormente. Nuestro archivo DatabaseSeeder quedará así (he eliminado los comentarios y añadido los míos para hacer hincapié en lo importante):

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Curso; // lo añadimos anteriormente para usar el factory Curso
use App\Models\User; // así podremos usar el factory User


class DatabaseSeeder extends Seeder
{
    public function run()
    {
        Curso::factory(50)->create(); //crea 50 registros
        User::factory(10)->create(); // esta línea viene escrita originalmente así: \App\Models\User::factory(10)->create();
    }
}

Terminamos con el comando de artisan que estábamos usando anteriormente, y luego comprobamos que las tablas se han rellenado con 50 y 10 registros aleatorios:

php artisan migrate:fresh --seed

Recursos:

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

Crear módulo Prestashop

logotipo prestashop

Prestashop pone a disposición de los desarrolladores un formulario que genera los archivos necesarios para la creación de un módulo, incluyendo opciones como para compatibilidad de versiones, hooks a los que se anclará el módulo, uso de posible tabla propia en la base de datos…

La siguiente dirección nos permitirá generar los archivos necesarios para comenzar con la creación de cualquier módulo:

https://validator.prestashop.com/generator

A partir de las instrucciones de la documentación de Prestashop, hemos seguido un «tutorial» para crea un módulo muy básico, disponible ya en mi repositorio a través de esta dirección:

https://github.com/JorgeGolo/first-upload-module

Este es el enlace a la documentación oficial:

Voy a listar unas notas que me han parecido bastante esclarecedoras sobre la forma que tiene Prestashop de estructurar sus módulos, y de cómo seguir sus estándares. Algunas ya las conocía de otras versiones:

  • Todo módulo generado con el formulario que hemos comentado, llamado «Nombredelmodulo», tiene un archivo principal llamado nombredelmodulo.php, con una estructura de carpetas similar a todos los demás módulos
  • Si tiene una sección de «Configuración» en el BackOffice tiene una función llamada getContent() en el archivo nombredelmodulo.php
  • Tools::isSubmit() es un método específico de Prestashop que comprueba si un formulario ha sido ejecutado.
  • Tools::getValue() es otro método específico que lee el parámetro desde POST o GET. En el ejemplo, Tools::getValue(‘MYMODULE_CONFIG’), donde MYMODULE_CONFIG es el name que le hemos dado al input del formulario.
  • updateValue(‘MYMODULE_CONFIG’, $nombrevariable) actualizará el texto del campo del formulario.
  • El objeto Validate contiene muchos métodos de validación, uno de ellos es isGenericName(), que ayuda a validar una string con «formato Prestashop válido», es decir, sin caracteres especiales.
  • displayError() muestra un mensaje de error
  • Hay algunos otros métodos explicados en el ejemplo que son fáciles de seguir.

En el tutorial también se habla de HelperForm. En el contexto de programación web y sistemas de gestión de contenido como PrestaShop, HelperForm es una función o clase utilizada para simplificar la creación de formularios en el back office de PrestaShop. Estos formularios suelen ser parte de módulos o extensiones que se integran en el panel de administración de PrestaShop. Aquí tienes una lista de ventajas del uso de los estándares con HelperForm:

  1. Facilita el Desarrollo:
    • Un helperform podría proporcionar métodos y funciones listas para usar que simplifican la creación de elementos comunes de formularios, como campos de entrada, botones, selectores, etc.
    • La estandarización facilita el desarrollo, ya que los desarrolladores pueden utilizar la misma interfaz y estructura para crear formularios en diferentes partes del back office.
  2. Consistencia Visual:
    • Al utilizar una clase o función común para la creación de formularios, se promueve la consistencia visual en la interfaz de administración de PrestaShop.
    • Los elementos del formulario generados por el helperform seguirían un estilo y diseño coherentes, lo que mejora la experiencia del usuario y la apariencia general del back office.
  3. Reutilización de Código:
    • La función o clase «helperform» podría contener métodos que encapsulen la lógica comúnmente utilizada en la manipulación y validación de datos de formularios.
    • Esto permite a los desarrolladores reutilizar código, evitando la duplicación y reduciendo la probabilidad de errores.
  4. Adaptabilidad a Futuras Actualizaciones:
    • Al seguir una interfaz estandarizada proporcionada por el helperform, los módulos y extensiones desarrollados con esta clase pueden ser más adaptables a futuras actualizaciones de PrestaShop.
    • Cambios en la implementación interna de PrestaShop podrían ser manejados dentro de la clase helperform, minimizando el impacto en los módulos existentes.
  5. Documentación Clara:
    • Una función o clase dedicada simplifica la documentación para los desarrolladores. Pueden entender rápidamente cómo crear formularios y qué métodos están disponibles, facilitando la adopción y el mantenimiento.
  6. Aumento de la Productividad:
    • Al reducir la complejidad y la cantidad de código necesario para crear formularios, los desarrolladores pueden ser más productivos y centrarse en la lógica específica de sus módulos en lugar de la implementación de formularios.
  • Para generar un formulario, usamos un array multinivel y luego se lo pasamos como parámetro a la clase HelperForm
  • Se crea una instancia de la clase y se le pasan parámetros a sus métodos.
  • Luego se usa el método generateForm() pasándole como variable el array que hemos creado anteriormente.
  • Todo esto dentro de una función displayForm() que tendrá este return:        
return $helper->generateForm([$form]);
  • La función displayForm la pasamos a su vez al return de getContent(), junto con otras variables (si queremos):      
return $output . $this->displayForm();

Prestashop pone a disposición de los desarrolladores otros helpers como HelperList. En esta entrada se hace uso de HelperList:

https://www.prestashop.com/forums/topic/334228-solved-helperlist-not-generating-the-list/

Laravel: Seeders (parte 9)

Archivos que vamos a tratar

  • /database/seeders/DatabaseSeeder.php
  • /migrations

Rellenaremos la base de datos con datos de prueba para hacer consultas con Eloquent.

Antes de crear Seeders y Factories, ejecutamos este comando:

php artisan migrate:reset

Esto eliminará todas las tablas de la base de datos. Lo hacemos porque vamos a eliminar las migraciones que creamos en el capítulo anterior, y vamos a agregar una columna a la tabla cursos manualmente, añadimos una columna llamada «categoría»:

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

Luego abrimos el archivo DatabaseSeeder.php. Tenemos que escribir al principio de este archivo que estamos usando el modelo Curso:

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Curso; // añadimos esta línea

Y dentro de la función run() escribimos lo que practicábamos con Tinker en la lección anterior, para crear un registro:

    public function run()
    {
        $curso = new Curso();
        $curso->name('Laravel');
        $curso->description('El mejor Framework');
        $curso->categoria('Desarrollo web');

        $curso->save();
    }

Podemos ejecutar el comando fresh para que se borren y se creen todas las tablas de nuevo:

php artisan migrate:fresh

Y luego ejecutamos los seeders:

php artisan db:seed

Ya se ha añadido el primer registro. Añadamos tres a la vez:

    public function run()
    {
        $curso = new Curso();
        $curso->name='Laravel';
        $curso->description='El mejor Framework';
        $curso->categoria='Desarrollo web';

        $curso->save();
        
        $curso2 = new Curso();
        $curso2->name='Laravel';
        $curso2->description='El mejor Framework';
        $curso2->categoria='Desarrollo web';

        $curso2->save();
        
        $curso3 = new Curso();
        $curso3->name='Laravel';
        $curso3->description='El mejor Framework';
        $curso3->categoria='Desarrollo web';

        $curso3->save();
    }

Ejecutamos de nuevo los comandos para tener más registros en la base de datos:

php artisan migrate:fresh
php artisan db:seed

Pero lo podemos hacer mejor, creando un seeder para los cursos:

php artisan make:seeder CursoSeeder

Dentro del archivo CursoSeeder, pegamos los registros que creamos en DatabaseSeeder. Además, llamamos al modelo Curso como lo hicimos allí también. Y en DatabaseSeeder, en vez de la creación de registros de la función pública run (que ahora está en CursoSeeder):

$this->Call(CursoSeeder::class);

Ahora ejecutamos los dos comandos de artisan fresh y seed, pero esta vez lo haremos en una sola línea:

php artisan migrate:fresh --seed

Recursos

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

Seeders con tablas relacionadas: https://www.php-dev-zone.com/blog/seeding-table-with-relationships-in-laravel-8