Contents

  1. Introduction
  2. Custom Artisan Command
  3. Database Seeders
  4. Migrations
  5. Links and References

Introduction

With very simple applications such as a todo list or a blogging app, we could go and design a system that purely acts as a middleman between raw input (HTML forms, JSON requests, etc.) and a persistence layer (Relational databases, NoSQL backends, flat file etc.). So you start with nothing but a schema for your persistence layer and a way to handle raw input, and your application works with the persisted data through its lifecycle. But in reality, any domain that is complex enough depends on some initial data in order to facilitate its business logic. A Real Estate system should know the states and cities where it offers its services. A Veterinary management app might need a list of dog breeds and it’s characteristics. A system for a Job Board needs a list of business categories etc.

In this article, we’ll take a look at two common patterns that I encounter in Laravel applications. I’ll also give a suggestion, a way how I personally handle the problem at hand. We’ll take a fictional Real Estate app and try to load the list of US states below into the system.

// database/states.json
{
    'states': [
        {'name': 'Alaska', 'abbreviation': 'AK'},
        {'name': 'Alabama', 'abbreviation': 'AL'},
        {'name': 'Arkansas', 'abbreviation': 'AK'},
        // ...
    ]
}

Custom Artisan Command

Artisan Commands allows us to interact with a Laravel application through the command line. It could be useful for one-off tasks such as cleaning up old database entries, sending promotional emails, interactively creating admin acounts and so on. In this case, we will write an Artisan command that opens up the states.json file and save its content to the database.

First off, we need to generate an Artisan Command:

$ php artisan make:command LoadStates

This will generate a LoadStates command. The implementation might look something like this:

// app/Console/Commands/LoadStates.php
<?php

namespace App\Console\Commands;

use App\State;
use Illuminate\Console\Command;

class LoadStates extends Command
{
    protected $signature = 'init:state';

    protected $description = 'Load data into the states table.';

    public function __construct()
    {
        parent::__construct();
    }

    public function handle()
    {
        $json = file_get_contents(database_path() . '/states.json');
        $states = json_decode($json, true)['states'];

        foreach ($states as $state) {
            State::firstOrCreate($state);
        }
    }
}

This command will be run after the database migrations. An initialization script for continuous integration might look something like this:

# install.sh
composer install
php artisan key:generate
php artisan migrate:fresh
php artisan init:state
# ...other setup commands

So far so good!

Database Seeders

Another way of tackling this issue is through Database Seeders. Database seeding is the process of seeding data into the database, which is particularly useful for testing when used with Eloquent Factories. In this case, we will use seeders to write actual relevant data into our database.

As usual, we need to generate a database seeder:

$ php artisan make:seeder StateSeeder

The implementation should be already familiar:

// database/seeds/StateSeeder.php
<?php

use App\State;
use Illuminate\Database\Seeder;

class StateSeeder extends Seeder
{
    public function run()
    {
        $json = file_get_contents(database_path() . '/states.json');
        $states = json_decode($json, true)['states'];

        foreach ($states as $state) {
            State::firstOrCreate($state);
        }
    }
}

To run the database seeders, we need to use the artisan command db:seed. This assumes that a state table already exists, so be sure to run the migrations first:

# install.sh
composer install
php artisan key:generate
php artisan migrate:fresh
php artisan db:seed
# ...other setup commands

Migrations

This is how I’d personally do it. What’s wrong with the previous methods? Nothing, really. They solve the problem at hand and they don’t really give any downside at all. But what I see is context of use. Artisan Commands are usually used for logic that doesn’t really fit anywhere else on your Laravel app. Database Seeds, on the other hand, are meant for pre-populating your database with test data. Migrations, on the other hand, are meant for interacting with your database schema. Defining SQL statements such as CREATE, UPDATE, and DELETE are already being used under the hood when you write a migration file. So it makes sense that initial data (INSERT) belongs in migration files as well.

We already have a migration file for states. We can use that (or generate a separate migration file) to load the states data:

// database/migrations/2014_10_12_000000_create_states_table.php
<?php

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

class CreateStatesTable extends Migration
{
    public function up()
    {
        Schema::create('states', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('abbreviation');
            $table->timestamps();
        });

        $json = file_get_contents(database_path() . '/states.json');
        $states = json_decode($json, true)['states'];

        foreach ($states as $state) {
            State::firstOrCreate($state);
        }
    }

    public function down()
    {
        Schema::drop('states');
    }
}

Now your initialization script doesn’t involve an extra step, your initial data is not coupled with test data through seeds, and it is version controlled together with the rest of your database access code. Sounds good to me!


Got any feedback or suggestions? Feel free to send me an email or a tweet.
Ciao!