Blog / Modify tables and records in real time with Laravel Population

Image used for article Modify tables and records in real time with Laravel Population

Modify tables and records in real time with Laravel Population




TL;DR: How to quickly and easily manipulate your database tables and records using the Laravel Population package.




A sample Laravel project can be found on this Github Repository.

Find out more on Capsules or X.




Laravel Population is a Laravel package dedicated to managing database migrations and records. It significantly simplifies the process of modifying tables, eliminating the need to create new migrations and seeders for these changes.




This package is experimental and in continuous development. It was created with the aim of simplifying database data management during structural modifications. Therefore, it is strongly discouraged to use it exclusively in a production environment.




This article provides an example of using the Laravel Population package by creating a Laravel project called 'template,' adding Users through a Seeder, and then splitting the name attribute into two separate attributes, first_name and last_name. The steps are as follows:



Create a Laravel project named template.



 composer create-project laravel/laravel template




Install the laravel-population package into the template project.



cd template

composer require --dev capsulescodes/laravel-population




Create the database for the template project and make the necessary changes to the environment variables.



mysql -u <username> -p <password> -e "CREATE DATABASE template"



.env


DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=template
DB_USERNAME=root
DB_PASSWORD=




Modify the DatabaseSeeder as well as the UserFactory.



database/seeders/DatabaseSeeder.php


<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use App\Models\User;

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        User::factory( 10 )->create();
    }
}



dabatase/factories/UserFactory.php


<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class UserFactory extends Factory
{
    public function definition(): array
    {
        return [
            'name' => fake()->firstName() . ' ' . fake()->lastName(),
            'email' => fake()->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
            'remember_token' => Str::random( 10 ),
        ];
    }

    public function unverified() : static
    {
        return $this->state( fn() => [ 'email_verified_at' => null ] );
    }
}




Run the migrations and seeds.



php artisan migrate --seed
# Output

INFO  Preparing database.

Creating migration table ............................................................................................................... 28ms DONE

 INFO  Running migrations.

2014_10_12_100000_create_password_reset_tokens_table ................................................................................... 90ms DONE
2019_08_19_000000_create_failed_jobs_table ............................................................................................. 83ms DONE
2019_12_14_000001_create_personal_access_tokens_table ................................................................................. 154ms DONE
2014_10_12_000000_create_users_table.php ............................................................................................... 92ms DONE

INFO  Seeding database.




The project preparation is complete. The Laravel project has been created, and its database now includes users with their first and last names stored in the name column.



By checking with the php artisan tinker command and calling User::all()->toJson(JSON_PRETTY_PRINT), we can examine the users records.



php artisan tinker

> User::all()->toJson( JSON_PRETTY_PRINT )
[
      {
          "id": 1,
          "name": "Alexandro Schinner",
          "email": "cummerata.juana@example.com",
          "email_verified_at": "2023-10-21T16:04:11.000000Z",
          "created_at": "2023-10-21T16:04:11.000000Z",
          "updated_at": "2023-10-21T16:04:11.000000Z"
      },
      {
          "id": 2,
          "name": "Silas Blick",
          "email": "bradtke.jarod@example.net",
          "email_verified_at": "2023-10-21T16:04:11.000000Z",
          "created_at": "2023-10-21T16:04:11.000000Z",
          "updated_at": "2023-10-21T16:04:11.000000Z"
      },
      ...
]




In order for the Laravel Population package to recognize the migrations it should observe, these migrations must include the public name property, typically injected directly as a parameter in the Schema::create() method. It is crucial to highlight this variable so that the package can identify the migration and its associated table.



database/migrations/2014_10_12_000000_create_users_table.php


<?php

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

return new class extends Migration
{
    public $name = 'users';

    public function up() : void
    {
        Schema::create( $this->name, 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();
        });
    }

    public function down() : void
    {
        Schema::dropIfExists( $this->name );
    }
};




It is now time to remove the name column and add two new columns, first_name and last_name.



database/migrations/2014_10_12_000000_create_users_table.php


<?php

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

return new class extends Migration
{
    public $name = 'users';

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

    public function down() : void
    {
        Schema::dropIfExists( $this->name );
    }
};




Modifying the User model is also necessary by removing name and adding first_name and last_name to the $fillable array.



app/Models/User.php


<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    protected $fillable = [ 'first_name', 'last_name', 'email', 'password' ];

    protected $hidden = [ 'password', 'remember_token' ];

    protected $casts = [ 'email_verified_at' => 'datetime', 'password' => 'hashed' ];
}




The populate command can be executed :



php artisan populate
 INFO  Migration changes :

2014_10_12_000000_create_users_table.php .......................................................................................................................... DONE

 INFO  Table 'users' has changes.

  ⇂ delete column : 'name' => type : stringcreate column : 'first_name' => type : stringcreate column : 'last_name' => type : string

 ┌ Do you want to proceed on populating the 'users' table? ─────┐
 │ ○ Yes / ● No                                                 │
 └──────────────────────────────────────────────────────────────┘
  • Laravel Population will list the modified migrations, along with specific details of the changes made to each migration.




A confirmation will be requested to decide whether the table modifications should be applied.



 ┌ Do you want to proceed on populating the 'users' table? ─────┐
 │ Yes                                                          │
 └──────────────────────────────────────────────────────────────┘
 
 ┌ How would you like to convert the records for the column 'first_name' of type 'string'?  'fn( $attribute, $model ) => $attribute' ┐
 │ fn( $attribute, $model ) => $attribute                                                                                │
 └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘




You will be asked to provide a data population formula for each new column. The uniqueness of this formula is that it allows you to access both the attribute and the model. Let's take the first User as an example.



{
    "id": 1,
    "name": "Alexandro Schinner",
    "email": "cummerata.juana@example.com",
    "email_verified_at": "2023-10-21T16:04:11.000000Z",
    "created_at": "2023-10-21T16:04:11.000000Z",
    "updated_at": "2023-10-21T16:04:11.000000Z"
}




We want to separate Alexandro from Schinner. This involves splitting the string into an array of strings using the PHP explode() method :



$firstName = explode( ' ', $user->name )[ 0 ]; // Alexandro

#formule pour `first_name`

fn( $attribute, $model ) => explode( ' ', $model->name )[ 0 ];

$lastName = explode( ' ', $user->name )[ 0 ]; // Schinner

#formule pour `last_name`

fn( $attribute, $model ) => explode( ' ', $model->name )[ 1 ];




Let's integrate these simplified formulas into the assistant.



 ┌ Do you want to proceed on populating the 'users' table? ─────┐
 │ Yes                                                          │
 └──────────────────────────────────────────────────────────────┘
 
 ┌ How would you like to convert the records for the column 'first_name' of type 'string'?  'fn( $attribute, $model ) => $attribute' ┐
 │ fn( $a, $b ) => explode( ' ', $b->name )[ 0 ];                                                                                    │
 └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

 ┌ How would you like to convert the records for the column 'last_name' of type 'string'?  'fn( $attribute, $model ) => $attribute' ┐
 │ fn( $a, $b ) => explode( ' ', $b->name )[ 1 ];                                                                                   │
 └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

  INFO  Population succeeded.




Laravel helpers are also available. Therefore, adding Faker data is possible, and the expressions fn() => fake()->firstName() and fn() => fake()->lastName() will work perfectly.



By checking with the php artisan tinker command and calling User::all()->toJson(JSON_PRETTY_PRINT), we can examine the users records.



[
      {
          "id": 1,
          "first_name": "Alexandro",
          "last_name": "Schinner",
          "email": "cummerata.juana@example.com",
          "email_verified_at": "2023-10-21T16:04:11.000000Z",
          "created_at": "2023-10-21T16:24:03.000000Z",
          "updated_at": "2023-10-21T16:24:03.000000Z"
      },
      {
          "id": 2,
          "first_name": "Silas",
          "last_name": "Blick",
          "email": "bradtke.jarod@example.net",
          "email_verified_at": "2023-10-21T16:04:11.000000Z",
          "created_at": "2023-10-21T16:24:03.000000Z",
          "updated_at": "2023-10-21T16:24:03.000000Z"
      },
      ...
]




We observe that the users now have an first_name attribute and a last_name attribute containing the former information from the name attribute.



A copy of the old database is now available in the databases folder within the storage/framework/databases directory.



If the previous manipulations are unsatisfactory, a command to restore the previous data is available.



php artisan populate:rollback
WARN  The rollback command will only set back the latest copy of your database. You'll have to modify your migrations and models manually.

INFO  Database copy successfully reloaded.




By checking with the php artisan tinker command and calling User::all()->toJson(JSON_PRETTY_PRINT), we can examine the users records.



[
      {
          "id": 1,
          "name": "Alexandro Schinner",
          "email": "cummerata.juana@example.com",
          "email_verified_at": "2023-10-21T16:04:11.000000Z",
          "created_at": "2023-10-21T16:04:11.000000Z",
          "updated_at": "2023-10-21T16:04:11.000000Z"
      },
      {
          "id": 2,
          "name": "Silas Blick",
          "email": "bradtke.jarod@example.net"
          "email_verified_at": "2023-10-21T16:04:11.000000Z",
          "created_at": "2023-10-21T16:04:11.000000Z",
          "updated_at": "2023-10-21T16:04:11.000000Z"
      },
      ...
]




The old records have been restored. Quickly and easily.




Glad this helped.

v1.2.1

X IconGithub Icon