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

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

Laravel: Modelos, Eloquent y uso de Tinker (parte 8)

Definiciones

  • ORM: Modelo de programación que permite mapear las estructuras de una base de datos relacional (en nuestro ejercicio, MySQL), sobre una estructura lógica de entidades para simplificar y acelerar el desarrollo. Las estructuras de la base de datos quedan vinculadas con las entidades lógicas o base de datos virtual definida en el ORM, de tal modo que las acciones CRUD (CreateReadUpdateDelete) se realizan de forma indirecta por medio del ORM. Así, realizar consultas a la base de datos se puede hacer sin escribir código SQL, sino utilizando objetos y métodos.
  • Modelos: En Laravel crearemos un modelo por cada una de las tablas que queramos administrar. Existe una convención para el nombre de las tablas y los modelos, ya que éstos tendrán el nombre en singular para un nombre de tabla en plural (en inglés). Siempre podemos crear una variable de tipo protected dentro de la clase del modelo para especificar otro nombre de tabla, si fuera necesario no seguir la convención.
  • Eloquent: El ORM que incluye Laravel.
  • Tinker: es una herramienta para ejecutar código php en la consola de Laravel, con la que podemos hacer pruebas de funcionamiento, y también podemos usar la directivas de Eloquent.

Vamos a ejercitarnos con el uso de Tinker, una herramienta que nos permite ejecutar el consola acciones sobre la base de datos mediante el ORM, instanciando objetos y llamado a sus métodos. Para ejecutar Tinker, en consola:

php artisan tinker

Para salir de Tinker, simplemente ejecutamos exit.

Antes de continuar, vamos a crear un modelo para la tabla «cursos», por lo que lo llamamos Curso siguiendo la convención:

php artisan make:model Curso

Ya tenemos nuestro archivo dentro de la capeta Models.

Insertar registros en la base de datos

  • Llamamos al modelo con el comando use y su namespace
  • Crear una instancia del objeto con el comando new
  • Agregar propiedades a los métodos
  • Salvar con save; si queremos guardar los registros en la base de datos.

En Tinker:

use App\Models\Curso
$curso = new Curso; // aquí estamos instanciando el objeto 
// dentro de una variable llamada curso
$curso->name ="nombre del curso"; // asignamos un valor 
// a la propiedad name
// que es un campo de la tabla
$curso->description="descripción del curso"; // más propiedades
$curso; // esta línea sólo devuelve info del objeto
$curso->save(); // guarda la info del objeto en la tabla

Si después de salvar, volvemos a ejecutar:

$curso;

Tendremos la información de los campos created_at y updated_at leídos directamente desde la base de datos. Si modificáramos el objeto, se actualizaría el campo updated_at.

Para actualizar un campo, como el objeto ya está creado, basta con escribir:

$curso->description="descripción actualizada";
$curso->save();

Podemos así añadir más campos:

$curso2 = new Curso;
$curso2->name ="otro curso"; 
$curso2->description="otra descripción del curso";
$curso2->save();

Recursos

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

Comentario sobre git: https://es.stackoverflow.com/questions/368437/como-crear-versiones-de-un-proyecto-con-git

Guía breve para git: https://rogerdudler.github.io/git-guide/index.es.html

Laravel: Modificar bases de datos (parte 7)

Archivos/carpetas
  • database/migrations

Diferencia entre migrate:fresh y migrate:refresh

El primero ejecuta uno a uno el método down de cada migración, y luego ejecuta uno a uno el método up.

El segundo borra todas las tablas, y ejecuta uno a uno el método up.

Son métodos destructivos, que eliminarían los registros de la base de datos si los hubiera. Por ello vamos a aprender a modificar las tablas in tener que ejecutar estos métodos

Agregar campos a una tabla

Vamos a añadir un campo a nuestra tabla users.

Para practicar esto, añadimos manualmente un registro, con phpmyadmin, a la tabla users, con nombre, contraseña y email.

Creamos una nueva migración, escrita con una convención para facilitarnos las cosas.

php artisan make:migration add_avatar_to_users_table

add_avatar_to_users_table indica que estamos haciendo una migración de añadir una campo lamado avatar a la tabla users. Esto creará un archivo de migración que contiene las siguientes funciones:

public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            //
        });
    }

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

La función up llama a la clase Schema con el método table, que incluye la función con el parámetro del tipo Blueprint con nombre table, para que podamos añadir campos a la tabla. En la función down escribiremos lo necesario para revertir los campos.

    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('avatar')->nullable();            
        });
    }

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

Hemos usado el método nullable para el campo avatar, indicando que éste puede ser null. Ahora podemos ejecutar la migración con php artisan migrate para ver los cambios.

    php artisan migrate

Y vamos a volver a migrar modificando antes el método up de nuestra migración, donde añadimos el método after para colocar el campo donde queremos:

    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('avatar')->nullable()->after('email');            
        });
    }

El campo está al final de la tabla, donde ya hay registros, y tiene el valor null. Revertimos los cambios con un rollback:

    php artisan migrate:rollback

Modificar propiedades de un campo de una tabla

Observamos que el campo name acepta 255 caracteres. Vamos a hacer que sólo acepte 150.

Para este tipo de cambios necesitamos instalar una dependencia, ejecutando este comando en la terminal:

composer require doctrine/dbal

Debemos crear una migración como la que sigue, usando también el nombre por convención:

php artisan make:migration cambiar_propiedades_to_users_table

Ya tenemos nuestro nuevo archivo de migración que, como en el ejercicio anterior, está nombrado por convención para facilitar la escrituradel código.

El siguiente comando ejecuta el método up de cada una de las migraciones, así crearemos las tablas. Desde la consola, migramos. recordamos que para migrar tenemos que ejecutar este comando:

php artisan migrate

El método down eliminará la tabla cuando ejecutemos ciertos comandos de artisan. Modificamos las funciones up y down como sigue, creo que el código se explica solo: estamos usando el método change para cambiar el número de caracteres del campo name de la tabla users, y poniéndolo como estaba en la función down usando el mismo método:

    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('name', 150)->change();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('name', 255)->change();
        });
    }

Podemos ejecutar la migración y comprobar los cambios con phpmyadmin. En ningún momento estamos perdiendo datos de la tabla.

Seguimos practicando: añadamos un poco más de código, para hacer que name acepte campos nulos:

    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('name', 150)->nullable()->change();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('name', 255)->nullable('false')->change();
        });
    }

Hemos añadido la opción de que el campo name pueda contener valores nulos. para hacer efectivo este nuevo cambio, debemos hacer un rollback y migrar de nuevo.

Recursos

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

Laravel: bases de datos (parte 6)

Carpetas de esta lección
  • config/database.php
  • .env
  • database/migrations

Creamos base de datos con phpmyadmin

Creamos una base de datos desde phpmyadmin y la llamamos blog, el mismo nombre que nuestro proyecto.

Abrimos el archivo config/database.php.

Ahí está definida el tipo de base de datos con la que nos conectamos, así como otras variables por defecto. El tipo de base de datos está en esta línea:

    'default' => env('DB_CONNECTION', 'mysql'),

Más abajo debemos indicar el host, la base de datos, el usuario, la contraseña. Podemos hacerlo ahí directamente, pero es mejor no hacerlo porque al subirlo a Github quedaría expuesto. Por eso es recomendado hacerlo en el archivo .env, como indica el mismo archivo. Nos podemos quedar con los valores por defecto para este ejercicio, que son los valores que adjudica XAMPP.

Creación de tablas, migraciones

Podemos crear las tablas desde phpmyadmin, mas lo vamos a hacer usando las herramientas de Laravel.

Las migraciones son el control de versiones de la base de datos, así como su estructura. Echemos un vistazo a la migración de database/migrations llamado create_users. Vemos dos funciones públicas, up y down, y nos detendremos en la función up que nos va a crear nuestra tabla con sus columnas:

public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

La función up llamará a la clase Schema con el método create, con dos parámetros: el nombre de la tabla y una función que recibe como parámetro el objeto de tipo Blueprint llamado $table, y ese objeto llama´ra sucesivamente a varios métodos. Cada uno de ellos tiene unas propiedades muy concretas que las asignará a cada tabla. por ejemplo, el solo hecho de llamar al método id() hará que se cree un campo id que tendrá la propiedad autoincrement, del tipo entero y sin signo.

No nos detendremos de momento a analizar cada uno de los métodos que nos proporciona Laravel para los campos de la tabla, con muchas propiedades distintas y con su utilidad. En la documentación de Laravel tendremos todas las propiedades de las columnas y los métodos.

El siguiente comando ejecuta el método up de cada una de las migraciones, así crearemos las tablas. Desde la consola:

php artisan migrate

El método down eliminará la tabla cuando ejecutemos ciertos comandos de artisan.

Más sobre migraciones

En nuestras base de datos se ha creado una tabla adicional, donde se registran cada una de las migraciones, la tabla migrations. Cada vez que creemos y hagamos nuevas migraciones, se grabará un registro en esta tabla.

Para crear nuestras migraciones, tenemos el siguiente comando:

php artisan make:migration cursos

Así crearemos una nueva migración, con el nombre «cursos». Este comando creará un archivo php con toda la estructura de una migración, incluyendo los métodos up y down (por ahora vacíos) para nuestra migración. En ella podemos crear una tabla o modificar alguna tabla existente.

En el método up, llamamos a la clase Schema, con el método create, que recibe dos parámetros, el nombre de la tabla y una función anónima, que a su vez recibirá un parámetro que es un objeto de tipo Blueprint, instanciado con la variable $table:

public function up()
    {
        Schema::create('cursos', function(Blueprint $table){
            //métodos
        })
    }

Ahora tenemos que llamar a varios de sus métodos, para crear los campos de la tabla. Aquí llamaremos al método id() y al método timestamps() (para crear las tablas created_at y update_at):

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

Creamos dos columnas más, una para el nombre del curso y otra para la descripción. El campo del nombre puede ser del tipo varchar, así que usamos el método string(), para un campo de máximo 255 caracteres. Necesitamos que el campo de la descripción almacene más de 255 caracteres, por lo que usamos el método text(). A estos dos métodos le pasamos también el nombre de la tabla como parámetro.

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

No podemos olvidar el método down, donde borraremos la tabla en la migración. Para ello usamos de nuevo la clase Schema con el método dropIfExist:

    public function down()
    {
        Schema::dropIfExists('cursos');
    }

Si ejecutamos de nuevo el comando de artisan php artisan migrate podremos crear la nueva tabla. En consola obtendremos esta respuesta:

Migrating: 2022_07_03_102823_cursos
Migrated:  2022_07_03_102823_cursos (47.42ms)

Ojo con la tabla migrations, pues se ha añadido un registro con el campo batch (lote) con el valor 2, el segundo lote de nuestras migraciones. Para eliminar este registro, tenemos que «ir un paso atrás».

Para revertir la última migración, usamos el siguiente comando, rollback. El registro de migrations al que hemos hecho mención se eliminará, y también se ejecutará el método down de la última migración, es decir, se eliminará la nueva tabla.:

php artisan migrate:rollback

De hecho, si volvemos a ejecutar el comando se eliminarán por completo todas las tablas, pues están creadas en el lote 1. Basta con volver a ejecutar php artisan migrate para crear, en esta ocasión, todas a la vez.

En este punto, nos interesa dejar la base de datos vacía, luego eliminar manualmente el archivo de la migración de cursos, y luego migrar de nuevo, pues vamos a crear la tabla cursos de otra forma:

php artisan make:migration create_cursos_table

Si seguimos esta convención, crearemos una migración como anteriormente, con la tabla cursos… pero en esta ocasión Laravel ya incluye los campos id, created at y updated at, y ya rellena el método down:

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

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

Ahora sólo nos quedaría añadir manualmente los campos nombre y descripción para nuestra tabla y volver a hacer la migración.

Explicaremos el siguiente comando:

   php artisan migrate:fresh

Ejecuta el método down de cada una de las migraciones, seguido del método up. Es decir, elimina todas la tablas y las vuelve a crear. No es recomendable cuando el proyecto está en producción, porque al eliminar todas las tablas eliminaría también los registros de las mismas. Por eso, si quisiéramos añadir o quitar campos de una tabla o cambiar propiedades de los campos, tenemos otras posibilidades.

Recursos

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

Laravel: vistas y plantillas (parte 5)

Carpetas de esta lección
  • resources/views
  • Http/Controllers
  • resources/views/layout

Primera parte: uso de vistas

Para mostrar código HTML, debemos crear unos documentos llamados vistas. Hasta ahora estamos devolviendo sólo frases.

Vamos a resources/views y creamos un archivo llamado home.php. También crearemos una carpeta dentro llamada «cursos» donde estarán las vistas de cada una de las URL de los cursos, que son 3. Por convención, al archivo de cada vista lo nombraremos igual que a cada método invocado en las rutas.

Atajo de teclado: dentro de cada uno de estos archivos php escribimos en Visual Studio Code un signo de exclamación y pulsamos enter, para que se autocomplete con todo el código HTML básico para un archivo con Doctype HTML y los tags mínimos imprescindibles para un archivo de este tipo (html, head title y body).

Vamos al archivo HomeController, y tomamos la frase que devolvemos dentro del método para pegarla dentro de un tag h1 dentro del body de home.php. Luego, escribimos el método view dentro del invoke de HomeController. No es necesario especificar que el archivo que estamos lamando es un archivo php. Queda la clase así:

class HomeController extends Controller
{
    public function __invoke(){
        return view("home");
    }
}

Y el archivo home.php así:

<!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>Document</title>
</head>
<body>
    <h1>Bienvenido a la página principal</h1>
</body>
</html>

Hacemos esto mismo para CursoController, modificando también cada una de las vistas (añadiendo las frases dentro de h1 como en el caso de home.php), y dejamos su código así:

class CursoController extends Controller
{
    public function index(){
        return view("curso.index");
    }

    public function create(){
        return view("curso.index");
    }

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

Lo primero, notamos que ahora estamos especificando la ruta de cada vista, mediante un punto: curso.index, curso.create y curso.show.

Las dos primeras vistas están perfectas, pero debemos hacer algo con la variable $curso del método show(). No basta con escribir la variable dentro de la vista show como sigue:

(...)
<body>
    <h1>Url con la variable <?php echo $curso?></h1>
</body>
(...)

Porque Laravel nos arrojaría un error diciendo que la variable no está definida dentro del método. Definimos la variable dentro de un array (entre corchetes:

 public function show($curso){
        return view("cursos.show", ['curso' => $curso]);
    }

Si le pusiéramos otro nombre a la variable, como por ejemplo «alfa», la rescataríamos dentro de la vista con ese nombre:

 public function show($curso){
        return view("cursos.show", ['alfa' => $curso]);
    }

Cuando tenemos intención de pasarle una variable a la vista que coincide con el nombre que estoy recibiendo podemos usar compact() así:

    public function show($curso){
        // return view("cursos.show", ['curso' => $curso]);
        return view("cursos.show", compact("curso"));
    }

Plantillas

Vamos a reciclar todo el código HTML que se repite dentro de cada vista usando plantillas con el motor blade. Primero, vamos a una de las vistas y añadimos algunos comentarios que se reemplazarían por código HTML que también se usaría en otras vistas:

<!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>Home</title>
    <!-- favicon -->
    <!-- estilos -->
</head>
<body>
    <!-- header -->
    <!-- nav -->
    <h1>Bienvenido a la página principal</h1>
    <!-- footer -->
    <!-- script -->
</body>
</html>

Atajo de teclado de Visual Studio Code: si escribimos una línea, la seleccionamos y pulsamo [CTRL] + K, después pulsamos [CTRL] + C para comentar y [CTRL] + U para descomentar.

Creamos la carpeta layout dentro de views, y dentro el archivo plantilla.blade.php. La extensión blade.php es necesaria. Para el contenido que queremos que varíe dentro de cada platilla, usaremos @yield(«nombre identificativo del contenido»). La plantilla queda así:

<!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 -->
</head>
<body>
    <!-- header -->
    <!-- nav -->
    @yield('content')
    <!-- footer -->
    <!-- script -->
</body>
</html>

Vamos con la vista home.php. En primer lugar, renombramos el archivo como home.blade.php. Luego añadimos un @extends al comienzo de la vista para hacer referencia a la plantilla. Además, usaremos @section para especificar el contenido que cambia dentro de cada vista. Usaremos @section de dos formas, una con una sola línea de código y otra que permite varias líneas de código. La vista home.blade.php queda de la siguiente manera:

@extends('layouts.plantilla')

@section('title', 'Home')

@section('content')
    <h1>Bienvenido a la página principal</h1>
@endsection

Inspeccionando el HTML de esta página vemos que el title es «Home» yq ue el contenido está dentro del body, a su vez dentro de un h1, como especificamos en el @section.

Antes de continuar con todas las vistas, podemos instalar la extensión Laravel Blade Snippets para colorear el có´digo de blade y además autocompletarlo.

Hacemos lo propio con cada una de las vistas, pasando el título y el contenido de cada una con @section, luego de extender la plantilla. También renombramos cada archivo con la extensión blade.php. Ojo con la vista que contiene una variable, show.blade.php, cuya sección content quedaría mejor así, con la doble llave para pasar la variable (nótese también la doble llave para comentar código):

@extends('layouts.plantilla')
@extends('layouts.plantilla')

@section('title', 'Página con variable')

@section('content')
    {{-- <h1>Url con la variable <?php echo $curso?></h1> --}}
    <h1>Url con la variable {{$curso}}</h1>
@endsection

Recursos

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