====== Trabajando con bases de datos en Laravel ====== [[informatica:programacion:php:frameworks:laravel|Laravel]] A partir de la versión 11, cada proyecto nuevo de Laravel está configurado para trabajar con una base de datos SQLite. ===== Conexiones soportadas ===== Laravel soporta por defecto los siguientes motores de bases de datos: * MariaDB * MySQL * PostgreSQL * SQLite * SQL Server En el fichero ''config/database.php'' podemos ver los diferentes tipos de conexiones con bases de datos. ===== Conexión con bases de datos ===== ==== SQLite ==== Fichero ''.env'': // ... DB_CONNECTION=sqlite #DB_HOST= #DB_PORT= #DB_DATABASE= #DB_USERNAME= #DB_PASSWORD= Laravel, por defecto, asumirá que usaremos un fichero llamado ''database.sqlite'' que se almacenará en ''database/''. En ese directorio crearemos el archivo. ==== MySQL ==== Una vez creada la base de datos MySQL, vamos a ''config/database.php''. En cierto punto veremos que la conexión por defecto es a SQLite: 'default' => env('DB_CONNECTION', 'sqlite'), La variable que vamos a cambiar, ''DB_CONNECTION'' está en el archivo ''.env'', en la raíz del proyecto. Hacemos los siguientes cambios: DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=blog DB_USERNAME=root DB_PASSWORD= Probaremos si la conexión es exitosa ejecutando unas migraciones: php artisan migrate Revisamos que se hayan creado nuevas tablas en nuestra base de datos de MySQL. ===== Migraciones ===== Son clases a partir de las cuales podemos crear o modificar tablas en la base de datos. Si vamos a la carpeta ''database/migrations'' veremos 3 ficheros de migraciones: * ''database/migrations/xxxx_create_users_table.php'' * ''database/migrations/xxxx_create_cache_table.php'' * ''database/migrations/xxxx_create_jobs_table.php'' Esas migraciones son las responsables de crear tablas. Por ejemplo, el fichero ''xxxx_create_users_table.php'' crearía las siguientes tablas: * ''users'' * ''password_reset_tokens'' * ''sesions'' ==== Ejecutar migraciones ==== php artisan migrate Si ejecutamos migraciones de nuevo (sin haberlas borrado), Laravel no hará nada porque sabe que se ejecutó anteriormente porque mantiene una tabla llamada **migrations** con esa información. ==== Deshacer migraciones ==== php artisan migrate:rollback Ese comando ejecutará el método ''down()'' de cada uno de los ficheros de migraciones para eliminar todas las tablas. ==== Crear migración ==== Para crear una migración que nos permita crear una tabla de productos: php artisan make:migration create_posts_table Esto habrá creado una migración, una clase anónima con los métodos ''up'' y ''down'': // ... public function up(): void { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->timestamps(); }); } public function down(): void { Schema::dropIfExists('posts'); } La completamos a nuestro gusto: // ... public function up(): void { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->longText('content'); $table->timestamps(); }); } ''$table->timestamps()'' creará los campos ''created_at'' y ''updated_at''. Para ejecutar las migraciones: php artisan migrate:fresh El modificador ''fresh'' **elimina todo de la base de datos**. Es útil mientras desarrollamos, pero no deberíamos hacer eso nunca en producción. Si usamos el modificador ''rollback'', solo borrará las migraciones del último lote (campo ''batch'' de la tabla **migrations**). Existe el modificador ''refresh'' que no eliminaría las tablas creadas sin usar ''php artisan'' Si tuviéramos un método para insertar datos: php artisan migrate:fresh --seed ==== Modificar tablas ==== Si hacemos cambios en las tablas gestionadas por las migraciones, lo ideal es crear una nueva migración: php artisan make:migration add_avatar_to_users_table Esto creará un nuevo fichero de migración: // ... public function up(): void { Schema::table('users', function (Blueprint $table) { }); } public function down(): void { Schema::table('users', function (Blueprint $table) { }); } Y ahora lo modificaremos para añadir y quitar el campo: // ... /** * Run the migrations */ public function up(): void { Schema::table('users', function (Blueprint $table) { $table->string('avatar')->nullable()->after('name'); // Para que se añada el campo después del campo 'name', usamos el método 'after' }); } /** * Reverse the migrations */ public function up(): void { Schema::table('users', function (Blueprint $table) { $table->dropColumn('avatar'); }); } Ya podríamos usar el comando: php artisan migrate ===== Eloquent ===== Eloquent es el ORM de Laravel. Un ORM (//Object-Relational Mapping//) se encarga de "mapear" / asociar la base de datos con estructuras de modelos que tengamos en nuestro sistema. El propósito es poder interactura con la base de datos como si fueran objetos dentro de la Programación Orientada a Objetos. ===== Modelos ===== La carpeta ''app/Models'' se incluyó por primera vez en la versión 8 En Laravel, los modelos y las tablas están estrechamente relacionados. Los modelos son el mecanismo de conexión entre nuestro código y la estructura de tablas y base de datos. Un modelo es una clase y contiene la funcionalidad de los campos de la tabla a la que representa. Hay varios tipos de atributos: * ''$fillable'': atributos que se pueden asignar de manera masiva. * ''$hidden'': atributos que se ocultarán al recuperar información desde la base de datos. * ''$casts'': atributos que deben ser transformados a cierto tipo de dato. Por defecto, Laravel toma como nombre de la tabla el nombre del modelo, pero en plural, es decir, si tenemos un modelo **User**, Laravel asumirá que la tabla será **users**. Si no queremos seguir esa convención, añadiríamos al modelo el atributo ''$table'' asignándole como valor el nombre de la tabla. La clase ''Model.php'', que es de la que heredan los modelos que creamos, está en ''vendor/laravel/framework/src/Illuminate/Database/Eloquent/''. ==== Creación de un modelo ==== Nos ayudamos de Artisan: php artisan make:model Post Esto habrá creado el modelo ''Post.php'' en ''app/Models/'': namespace App\Models; use Illuminate\Database\Eloquent\Model; class Post extends Model { } Debemos indicarle qué tabla va a administrar: // ... class Post extends Model { protected $table = 'posts'; } Si la tabla se llama igual que la clase, pero en plural, no sería necesario crear la variable ''$table'' con el nombre de la tabla. Hay que tener en cuenta que Eloquent usa el inglés, hay que tenerlo en cuenta para que funcionen estas convenciones. Por ejemplo, si tenemos una tabla llamada "voces" y el modelo ''Voz'', Eloquent esperará encontrarse una tabla llamada ''vozs''. ==== Añadir registros ==== Si queremos añadir un registro: $post = new Post; $post->title = "Título de prueba"; $post->content = "Contenido de prueba"; $post->save(); ==== Editar registro ==== Si queremos editar un registro: $post = Post::find(1); // Recuperamos el registro con ID 1 $post->title = "Nuevo título de prueba"; $post->save(); Podemos listar todos los posts atendiendo a cierto criterio: $post = Post::orderBy('id', 'asc') ->select('id', 'title) ->take(2) // Limítalo a 2 registros ->get(); return $post; ==== Eliminar registro ==== Para eliminar un registro: $post = Post::find(1); // Recuperamos el registro con ID 1 $post->delete(); ==== Mutadores y accesores ==== Imaginemos que queremos que antes de meterse un valor en un campo, queremos tratarlo. Y también queremos tratarlo a la hora de recuperarlo. Para eso están los "mutadores" y "accesores". En el modelo que gestiona la tabla que tiene el campo que nos interesa, crearemos una función con el nombre del campo en cuestión: // ... Use Illuminate\Database\Eloquent\Casts\Attribute; class Post extends Model { protected function title(): Attribute { return Attribute::make( set: function($value) { return strtolower($value); }, get: function($value) { return ucfirst($value); } ); } } Con el código anterior, cuando se vaya a introducir un valor al campo ''title'', se transformará antes a minúscula. Cuando vayamos a coger el valor del campo, se mostrará la primera letra en mayúscula. ==== Casting ==== Atributos de fecha con [[https://github.com/briannesbitt/Carbon|Carbon]] para facilitar operaciones como la diferencia de fechas, etc: namespace App\Models; Use Illuminate\Database\Eloquent\Model; class Post extends Model { // Ahora indicamos qué campo debe ser transformado a una instancia de Carbon protected function casts(): array { return [ 'published_at' => 'datetime' ]; } } Ahora podríamos usar el nuevo campo con las funcionalidades de Carbon: Route::get('prueba', function() { $post = Post::find(1); return $post->published_at->format('d/m/Y'); // return $post->published_at->diffForHumans(); // Tiempo transcurrido (relativo) }); Podemos ver todos los tipos que podríamos transformar (//casting//) desde la [[https://laravel.com/docs/12.x/eloquent-mutators#attribute-casting|documentación oficial]]. ===== Insertar información ===== ==== Seeders ==== Podemos definir qué registros estén presente siempre que creemos una tabla. Para esto existen los //seeders// en Laravel. En la carpeta ''database/seeders/'' existe un fichero llamado ''DatabaseSeeder.php'' con un método ''run()''. Imaginemos que cada vez que se cree una tabla de usuarios, se añada un registro de un usuario con ciertos campos: namespace Database\Seeders; use App\Models\User; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database */ public function run(): void { $user = new User(); $user->name = 'Pepito Grillo'; $user->email = 'pepito@paisdelasmaravillas.com'; $user->password = bcrypt('contraseña'); $user->save(); } } Si ahora ejecutamos: php artisan migrate:fresh Se habrán borrado todas las tablas, pero no se añade el registro que queremos. Para ello, debemos usar otro comando: php artisan db:seed Esto poblará las tablas de las bases de datos con lo que hemos programado. Si queremos ejecutar solo un determinado //seeder//: php artisan db:seed --class=NombreSeeder Podemos combinar los dos comandos anteriores de la siguiente manera: php arisan migrate:fresh --seed Si queremos añadir varios registros, no es muy cómodo hacerlo todo en el fichero ''DatabaseSeeder.php''. Lo mejor es crear archivos con //seeders// por separado: php artisan make:seeder UserSeeder Esto habrá creado el fichero ''UserSeeder.php'' en ''database/seeders''. Y ahí iremos creando el código para añadir usuarios. Lo mismo para ''PostSeeder.php''. Después tendremos que modificar el método ''run()'' del fichero ''DatabaseSeeder.php'' para indicar qué //seeders// queremos que se ejecuten: class DatabaseSeeder extends Seeder { /** * Seed the application's database */ public function run(): void { $this->call([ PostSeeder::class, UserSeeder::class ]); } } Para ejecutar los //seeders//: php artisan migrate:fresh --seed ==== Factories ==== Los //factories// son clases con el nombre de un modelo y luego una definición donde indicamos cómo queremos rellenar los datos del modelo. Funcionan como una serie de fábricas donde indicamos qué queremos que se cree en cada campo. Al crear un proyecto en Laravel, incluye el factory **User** en ''database/factories/UserFactory.php''. // ... public function definition(): array { return [ 'name' => fake()->name(), 'email' => fake()->unique()->safeEmail(), 'email_verified_at' => now(), 'password' => static::$password ??= Hash::make('password'), 'remember_token' => Str::random(10), ]; } Para generar estos datos, Laravel se apoya en la biblioteca [[https://github.com/FakerPHP/Faker|Faker]] que permite generar información aleatoria. Para llamar a un //factory// existente, primero hay que modificar el método ''run()'' de ''DatabaseSeeder.php'': class DatabaseSeeder extends Seeder { /** * Seed the application's database */ public function run(): void { // ... User::factory(10)->create(); } } En el código anterior indicamos que queremos crear 10 registros de usuarios utilizando el //factory// ''UserFactory.php''. Para ejecutar el //factory//: php artisan migrate:fresh --seed También podíamos haber colocado el código de //factory// en ''UserSeeder.php'': // ... public function run(): void { $user = new User(); $user->name = 'Pepito Grillo'; $user->email = 'pepito@paisdelasmaravillas.com'; $user->password = bcrypt('alicia'); $user->save(); User::factory(10)->create(); } Para crear nuestro propio //factory//, usaremos Artisan: php artisan make:factory PostFactory Hemos indicado que el factory que queremos crear es para el modelo **Post** siguiendo la convención del nombre. Tendremos que rellenar la definición de la //factory// recién creada: public function definition() { return [ 'title' => $this->faker->sentence(), 'content' => $this->faker->text(1000), 'category' => $this->faker->word(), 'published_at' => $this->faker->dateTime(), ]; } Es importante indicar en nuestro modelo (''app/Models/Post.php'') que tiene una //factory//: use Illuminate\Database\Eloquent\Factories\HasFactory; class Post extends Model { use HasFactory; } Ahora vamos a ''PostSeeder.php'' para usarlo: // ... public function run(): void { Post::factory(100)->create(); } Ya podremos usarlo y se generarán 100 registros en la tabla ''posts'' con información aleatoria: php artisan migrate:fresh --seed De todos modos, si nuestro ''PostSeeder.php'' queda solo con esa instrucción, mejor sería moverla a ''DatabaseSeeder.php'' haciendo que el fichero ''PostSeeder.php'' ya no sea necesario: // ... public function run(): void { Post::factory(100)->create(); $this->call([ UserSeeder::class ]); } ==== Database seeder ==== Podemos indicar por código cómo queremos crear instancias de nuestros modelos sin usar tinker. Vamos a ''database/seeders/DatabaseSeeder.php'': public function run() { $products = Product::factory(5)->create(); } Para ejecutar este seeder: php artisan migrate:fresh --seed Se crea de nuevo la base de datos y sus tablas y finalmente el seeder que ejecutará 5 instancias de Producto y las meterá en base de datos. Si solo queremos ejecutar el seeder: php artisan db:seed ===== Construyendo consultas a base de datos ===== ==== Usando el Query Builder ==== Laravel dispone de un [[https://laravel.com/docs/10.x/queries|Query Builder]] que permite construir las consultas SQL para interactuar directamente con la base de datos. Si queremos obtener toda la información de cierta tabla usando Query Builder: use Illuminate\Support\Facades\DB; class ProductController extends Controller { public function index() { $products = DB::table("products")->get(); dd($products); } } Cogiendo solo un elemento: // code public function show($product) { $product = DB::table("products")->where("id", $product)->first(); dd($product); } Podríamos abreviar la consulta usando el método ''find'' ya que buscará el primer elemento que coincida con el ID que indicamos: // code $product = DB::table("products")->find($product); ==== Usando los modelos ==== Cualquier cosa que se pueda hacer con el Query Builder, también se puede hacer a través del modelo, y es lo recomendable (usando Eloquent). La mayor utilidad de usar el modelo es su mantenimiento y escalabilidad del proyecto. Por ejemplo, si en algún momento se cambia el nombre de la tabla, usando el Query Builder, tendríamos que buscar en nuestro código todas las sentencias donde usamos el Query Builder con dicha tabla. Si usamos modelos, basta con hacer el cambio en el atributo ''$table'' del modelo. Si queremos obtener una lista completa de productos usando el modelo y Eloquent (el ORM de Laravel): use Illuminate\Support\Facades\DB; class ProductController extends Controller { public function index() { $products = Product::all(); dd($products); } } Cogiendo solo un elemento: // code public function show($product) { $product = Product::find($product); dd($product); } Tenemos un método interesante para cuando no se encuentra lo que buscamos: // code public function show($product) { $product = Product::findOrFail($product); // code } Lo que hará ''findOrFail'' es lanzar una excepción que Laravel tratará como un error 404. Si no lo usásemos y no existiera el producto, ''find'' devolvería ''NULL''.