Modify tables and records in real time with Laravel Population
Tags: php, laravel, package, tutorial
Published: 2023-11-02 09:44:34

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

&nbsp;
&nbsp;

A sample Laravel project can be found on this [Github Repository](https://github.com/capsulescodes/articles).

&nbsp;
&nbsp;

[Laravel Population](https://github.com/capsulescodes/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.

&nbsp;
&nbsp;

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.

&nbsp;
&nbsp;

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:

&nbsp;
&nbsp;

Create a Laravel project named `template`.

&nbsp;

```
 composer create-project laravel/laravel template
```

&nbsp;
&nbsp;

Install the `laravel-population` package into the `template` project.

&nbsp;

```
cd template

composer require --dev capsulescodes/laravel-population
```

&nbsp;
&nbsp;

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

&nbsp;

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

&nbsp;
&nbsp;


`.env`

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

&nbsp;
&nbsp;

Modify the `DatabaseSeeder` as well as the `UserFactory`.

&nbsp;

`database/seeders/DatabaseSeeder.php`

```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();
    }
}
```

&nbsp;
&nbsp;

`dabatase/factories/UserFactory.php`


```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 ] );
    }
}
```

&nbsp;
&nbsp;

Run the migrations and seeds.

&nbsp;

```
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.
```

&nbsp;
&nbsp;

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.

&nbsp;
&nbsp;

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

&nbsp;

```
php artisan tinker

> User::all()->toJson( JSON_PRETTY_PRINT )
```
```json
[
      {
          "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"
      },
      ...
]
```

&nbsp;
&nbsp;

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.

&nbsp;

`database/migrations/2014_10_12_000000_create_users_table.php`

```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 );
    }
};
```

&nbsp;
&nbsp;

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

&nbsp;

`database/migrations/2014_10_12_000000_create_users_table.php`

```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 );
    }
};
```

&nbsp;
&nbsp;

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

&nbsp;

`app/Models/User.php`

```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' ];
}
```

&nbsp;
&nbsp;

The `populate` command can be executed :

&nbsp;

```json
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                                                 │
 └──────────────────────────────────────────────────────────────┘
```
Laravel Population will list the modified migrations, along with specific details of the changes made to each migration.

&nbsp;
&nbsp;

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

&nbsp;

```json
 ┌ 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                                                                                │
 └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
```

&nbsp;
&nbsp;

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.

```json
{
    "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"
}
```

&nbsp;
&nbsp;

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

&nbsp;

```php
$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 ];
```

&nbsp;
&nbsp;

Let's integrate these simplified formulas into the assistant.

&nbsp;

```json
 ┌ 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.
```

&nbsp;
&nbsp;

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

&nbsp;
&nbsp;

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

&nbsp;

```json
[
      {
          "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"
      },
      ...
]
```

&nbsp;
&nbsp;

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

&nbsp;
&nbsp;

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

&nbsp;
&nbsp;

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

&nbsp;

```json
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.
```

&nbsp;
&nbsp;

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

&nbsp;

```json
[
      {
          "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"
      },
      ...
]
```

&nbsp;
&nbsp;

The old records have been restored. Quickly and easily.

&nbsp;
&nbsp;

Glad this helped.

&nbsp;
&nbsp;

Find out more on [Capsules](https://capsules.codes) or [X](https://x.com/capsulescodes)