Seed Data Generation

This guide can be used to create databases with seed data. Note that seed data generation should ONLY be used on development or test environments as, without modifications to the scripts below, the databases are dropped and recreated.

Getting Started

Step 1: Docker Seeding Script

Inside the ~/Documents/elentra-developer/resources/scripts directory, create a file called 'install-elentra'.

Open that file and paste the following into it, replacing the database names with the ones used for testing:

#!/bin/bash
set -e 

echo "Dropping databases."
mysql -hmariadb -uroot -ppassword -e 'DROP DATABASE IF EXISTS <ELENTRA_ME>;'
mysql -hmariadb -uroot -ppassword -e 'DROP DATABASE IF EXISTS <ELENTRA_ME_CLERKSHIP>;'
mysql -hmariadb -uroot -ppassword -e 'DROP DATABASE IF EXISTS <ELENTRA_AUTH>;'

echo "Creating databases."
mysql -hmariadb -uroot -ppassword -e 'CREATE DATABASE <ELENTRA_ME>;'
mysql -hmariadb -uroot -ppassword -e 'CREATE DATABASE <ELENTRA_ME_CLERKSHIP>;'
mysql -hmariadb -uroot -ppassword -e 'CREATE DATABASE <ELENTRA_AUTH>;'

cd /var/www/vhosts/elentra-1x-me
rm www-root/core/config/config.inc.php

echo "Installing Elentra config script."

php www-root/setup/install.php \
      --entrada-url=http://elentra-1x-me.localhost \
      --entrada-absolute=$(pwd)/www-root \
      --entrada-storage=$(pwd)/www-root/core/storage \
      --database-adapter=mysqli \
      --database-host=mariadb \
      --database-username=root \
      --database-password=password \
      --entrada-database=<ELENTRA_ME> \
      --auth-database=<ELENTRA_AUTH> \
      --clerkship-database=<ELENTRA_ME_CLERKSHIP> \
      --admin-username=user1 \
      --admin-password=apple123 \
      --admin-firstname=System \
      --admin-lastname=Adminstrator \
      --admin-email=admin@example.com

echo "Running Migrations."
php elentra migrate --up --quiet


cd /var/www/vhosts/elentra-1x-me/www-root/core/library/vendor/elentrapackages/elentra-1x-api

#This could be used to test the seeding of a specific module
#php artisan db:seed --class=LotteriesSeeder

#This will seed all registered seed modules
php artisan db:seed --quiet

This file needs a chmod 777 on it for execution.

Important Note: This script will delete the config.inc.php file as well as drop the specified databases.

It will then recreate those 3 databases, and generate a new www-root/core/config/config.inc.php script to tell the system how to connect to the new databases.

Next, it runs all migration scripts inside the www-root/core/library/Migrate directory. This is to bring the databases up to date with whichever branch the developer is on.

Finally, it runs the seed files which are used to populate specified tables with test data using Laravel's database seeding functionality.

In your Elentra API directory, change the phpunit.xml file from:

 <testsuite name="Functional">
   <directory suffix="Test.php">./tests/Functional</directory>
 </testsuite>

to:

 <testsuite name="Functional">
      <directory suffix="Test.php">./tests/Functional</directory>
      <directory suffix="Test.php">./tests/Integration</directory>
    </testsuite>

This will tell PHPUnit to also run any tests that reside in the Integration directory

Edit the composer.json file and change:

"autoload": {
    "psr-4": {
        "Entrada\\": "app/"
    },
    "classmap": [
        "database"
    ]
},

to:

"autoload": {
    "psr-4": {
        "Entrada\\": "app/"
    },
    "classmap": [
        "database/seeds",
        "database/factories"
    ]
},

Run composer install to regenerate the classmap autoloader so that all files inside the database/seeds and database/factories directories are recognized by the system.

Step 2: Creating a JSON Seed File

Seed files are located within the module's Database/Seeds directory.

eg: elentra-1x-api/app/Modules/Locations/Database/Seeds/ClinicalLocationsSeedData.php

To create a seed data file, right click on the appropriate module's Database/Seeds directory and select 'New Class'. All seed data files are suffixed with 'SeedData'. eg:

  • ClinicalLocationsSeedData.php

  • LotteriesSeedData.php

  • UserAccessSeedData.php

All seed data files must implement the SeedDataInterface. The SeedDataInterface` has no namespace which means the Class definition would look like:

class ClinicalLocationsSeedData implements \SeedDataInterface

SeedDataInterface provides 2 methods:

public function getData(): array;

public function getType();

For a basic seed data file implementing the SeedDataInterface would look like this:

namespace Entrada\Modules\Courses\Database\Seeds;

use Entrada\Modules\Lotteries\Models\Base\CurriculumPeriod;

class CurriculumPeriodsSeedData implements \SeedDataInterface
{

    public static function getCurriculumPeriods()
    {
        return
            [
                [
                    "cperiod_id" => 1,
                    "curriculum_type_id" => 1,
                    "curriculum_period_title" => "Test Period 1",
                    "start_date" => 1565064000,
                    "finish_date" => 1596772799,
                    "active" => 1
                ]
            ];
    }

    public function getData(): array
    {
        return self::getCurriculumPeriods();
    }

    public function getType()
    {
        return CurriculumPeriod::class;
    }
}

The getData() method returns the predefined JSON array of values to be inserted into the database. The getType() method returns the Model class of the database table to be seeding with the JSON values.

Creating a Seed Data File With No Model Class

Not all tables have a corresponding Model class to associate to. An example is the user_auth.user_access table. All queries to the user_auth.user_access table are performed with joins and no Model class (eg: UserAccess.php) is required.

In this case we also implement the RawSeederInterface. The RawSeederInterface provides 2 methods:

public function getTablename() : string;

public function getTimestamps() : bool;

An example of a class definition would be:

class ScheduleFilesSeedData implements \SeedDataInterface, \RawSeederInterface

An example of a class would be:

namespace Entrada\Modules\Lotteries\Database\Seeds;

use Entrada\Modules\Clinical\Models\Entrada\RotationScheduleFile;

class ScheduleFilesSeedData implements \SeedDataInterface, \RawSeederInterface
{

    public static function getScheduleFiles()
    {
        return
            [
                [
                    "schedule_file_id" => 1,
                    "schedule_id" => 1,
                    "type" => "",
                    "size" => 0,
                    "name" => "Test Schedule File",
                    "label" => "",
                    "view" => "view",
                    "created_date" => 0,
                    "created_by" => 0,
                    "updated_date" => null,
                    "updated_by" => null,
                    "deleted_date" => null,
                    "deleted_by" => null
                ]
            ];
    }

    public function getData(): array
    {
        return self::getScheduleFiles();
    }

    public function getType()
    {
        return RotationScheduleFile::class;
    }

    public function getTablename(): string
    {
        return 'cbl_schedule_files';
    }

    public function getTimestamps(): bool
    {
        return true;
    }
}

The getTablename() method is returning the name of the table to insert the JSON data into. The getTimestamps() method is returning a true/false value to determine if timestamp columns are required. When a table contains the following columns:

  • created_at

  • updated_at

The getTimestamps method should return true. If those columns do not exist the method should return false. This is because Laravel will automatically append those columns to an insert query unless we explicitly tell it not to.

Step 3: Registering the Seed File

Once a seed file is created it needs to be registered. Inside the /database/seeds directory at the root of the elentra-1x-api repository, create a new Seeder class. The file naming convention is as follows: Seeder.php eg: LotteriesSeeder.php

A seeder class must extend ElentraBaseSeeder. example:

class LocationsSeeder extends ElentraBaseSeeder
{

    public function __construct()
    {
        parent::__construct(array(
            new ClinicalLocationsSeedData(),
            new ClinicalPreceptorsSeedData()
        ));
    }
}

The Seeder classes have no methods except the constructor, which is where we register the SeedData files we created in the previous section. These are defined in the constructor and passed into the parent ElentraBaseSeeder which has the run() method to iterate any passed in SeedData classes.

Tips for Making Json from Database

  • If you are on Windows, a useful IDE for MySQL/MariaDB is SqlYog. It allows you to select tables for export and select JSON as a format.

  • If you are on Linux you can install "W.ine I.s N.ot an E.mulator" (wine) and run SqlYog quite seamlessly.

  • If you are on Mac and your IDE does not support exporting as JSON, you can export as SQL and use https://codebeautify.org/sql-to-json-converter. It'll take a couple of edits to the generated output from your IDE but works quite nicely for making JSON from SQL.

4: Adding the Seed Data Registry to Laravel

In the older repository it is still performed this way:

The final step is registering the Seeder class which is a registry of SeedData files with the Laravel provided DatabaseSeeder. This is located in the /database/seeds directory at the root of the API repository.

The DatabaseSeeder class has 1 Laravel-provided method:

/**
* Run the database seeds.
*
* @return void
*/
public function run(): void
{
    $this->call([
        CoursesTableSeeder::class,
        LotteriesSeeder::class,
        LocationsSeeder::class,
        UsersSeeder::class
    ]);
}

In the newer repository it is performed this way:

The final step is registering the Seeder class which is a registry of SeedData files with the elentra.php file. This is located in the /config directory at the root of the API repository.

 /* around line 67
    | Database
    |--------------------------------------------------------------------------
    |
    | Configuration for the database
    |
    */
    'database' => [
        'seeders' => [
            //UsersTableSeeder::class, /* Creates random users, not suitable for our tests that depend on specific values. */

             // Application seeders
            AssessmentsSeeder::class,
            BookmarksSeeder::class,
            CommunitiesSeeder::class,
            CoursesTableSeeder::class,
            DisclaimersTableSeeder::class,
            EventsSeeder::class,
            ExamsSeeder::class,
            GradebooksSeeder::class,
            LocationsSeeder::class,
            LotteriesSeeder::class,
            UsersSeeder::class,
        ],
    ],

This is where the Seeder classes are registered with the Laravel system.

A visual representation:

                   Lotteries Seeder is registered to the DatabaseSeeder class
                       |     Lottery Seed Data is registered to a LotteriesSeeder class
                       |                       |
                       |                       V
                       |             <--- LotteriesSeedData
                       V             <--- LotteryStagesSeedData
DatabaseSeeder: <-- LotteriesSeeder: <--- LotteryStagePhasesSeedData
                <-- CoursesSeeder
                <-- UsersSeeder

Once any new Seeder class is registered in the DatabaseSeeder class we need to flush the composer autoloader to force recognizing the new file:

composer dump-autoload

Step 4: Running the Seed Scripts

From the ~/Documents/elentra-developer directory, open a new developer shell:

./developer shell

inside the developer shell:

install-elentra

This will call the script from anywhere within the shell and will populate all seed data.

Troubleshooting:

Whenever a new Seed script is registered, the Laravel system must be refreshed. From the developer shell:

composer dump-autoload

This will reset the system and recognize any new changes.

If you need to see the output of the seeder scripts for debugging, edit the install-elentra script so that the line:

php artisan db:seed --quiet

is changed to:

php artisan db:seed

Turning off 'quiet' mode will provide verbose logging and display any possible SQL exceptions that occur.

Class DatabaseSeeder not found

Check the symlink directory to ensure the changes in composer.json are present. If they aren't you'll need to redo the symlink:

cd /var/www/vhosts/elentra-1x-me/www-root/core/library/vendor/elentrapackages;

rm -Rf elentra-1x-api;

ln -s /var/www/vhosts/elentra-1x-api

TLDR;

  1. Create the install-elentra script inside of ~/Documents/elentra-developer/resources/scripts

  2. Create a SeedData class inside a Module's database/seeds directory

  3. Create a Seeder class inside the elentra-1x-api/database/seeds directory and register the SeedData class inside that Seeder's constructor

  4. Edit the DatabaseSeeder class to call the Seeder class in its array

  5. Inside the developer shell run install-elentra

Last updated