Blog / Modify tables and records in real time with Laravel Population
6 min - 02 Nov 2023
TL;DR: How to quickly and easily manipulate your database tables and records using the Laravel Population package.
You will find the source code via this Github Repository. Find out more on Capsules, X or Bluesky.
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 : string
⇂ create column : 'first_name' => type : string
⇂ create column : 'last_name' => type : string
┌ Do you want to proceed on populating the 'users' table? ─────┐
│ ○ Yes / ● No │
└──────────────────────────────────────────────────────────────┘
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.