Simple Database Seeder for WordPress

I’ve been doing some WordPress development at my day job and have come to the conclusion that the development life-cycle of WordPress is kinda bad, actually. However, there are systems & processes we can implement to improve the developer and development experience. We can make it more enjoyable to work on WordPress!

One of those systems that I sorely miss is a database seeder that can populate your site with test and dummy content. The go-to for WordPress seems to be to download a database from a live or test site. I am not a fan of this pattern at all and would rather not touch production data. If the database is large this can be a very time-consuming process. If you need media and attachments this can become a huge burden on your workstation as you download gigabytes of assets.

The default method for seeding data on WordPress is to first set up a WordPress site somewhere, then create a bunch of test content, then export the content into an XML file. You can import that XML file into your local install of WordPress. However, a downside of this is that if there are attachments, they need to be publicly available either at the test site or somehow locally available. You need to have already created the content. Making updates is also tedious. I think the workflow is poor and we can do better.

What I would prefer is to be more intentional about creating that content.

My end goal is to be able to run a command to seed content from classes I create that very intentionally define the type of content I want available. Also, it would be nice to be able to reset the data and reseed. The command interface I want:

$ wp-cli dbseeder seed
$ wp-cli dbseeder truncate
$ wp-cli dbseeder replant

I leaned into wp-cli to help me here. I created a custom wp-cli command to wrap the business logic of seeding, truncating, and replanting the content.

<?php
namespace RoycomCommands;

use Exception;
use HaydenPierce\ClassFinder\ClassFinder;
use WP_CLI;
use WP_CLI_Command;
use RoycomTheme\DbTruncate;

if (class_exists('WP_CLI_Command')) {
    /**
     * Class DbSeederCommand
     */
    class DbSeederCommand extends WP_CLI_Command
    {
        /**
         * @return void
         * @throws Exception
         */
        public function seed(): void
        {
            $classes = $this->getSeedClasses();
            foreach ($classes as $class) {
                WP_CLI::log(sprintf("Seeding %s...", $class));

                $seed = new $class;
                $result = $seed->run();

                if ($result["success"] === false) {
                    WP_CLI::error($result["message"]);
                } else {
                    WP_CLI::success($result["message"]);
                }
            }

            WP_CLI::success("Command executed");
        }

        /**
         * @return void
         */
        public function truncate(): void
        {
            WP_CLI::warning("Truncating database...");
            $DbTruncate = new DbTruncate();
            $DbTruncate->run();
        }

        /**
         * @return void
         * @throws Exception
         */
        public function replant(): void
        {
            $this->truncate();
            $this->seed();
        }

        /**
         * @return array
         * @throws Exception
         */
        private function getSeedClasses(): array
        {
            $class_finder = new ClassFinder();
            $class_finder::setAppRoot(ABSPATH);
            return $class_finder::getClassesInNamespace('RoycomTheme\\DbSeeds', ClassFinder::RECURSIVE_MODE);
        }
    }
}

I registered the command on my WordPress site.

<?php

use WP_CLI;

# ... the rest of functions.php...

WP_CLI::add_command('dbseeder', 'RoycomCommands\\DbSeederCommand');

I created my first seed. The class AbstractSeed is pretty bare bones here, it just defines an interface method called `run1 that we have to implement in our seed class.

<?php

namespace RoycomTheme\DbSeeds;

use RoycomTheme\AbstractDbSeed;

class ExampleSeed extends AbstractDbSeed
{
    public function run(): array
    {
        $post_id = wp_insert_post([
            'post_title' => 'Example Post',
            'post_content' => 'This is an example post.',
            'post_status' => 'publish',
            'post_author' => 1,
            'post_type' => 'post',
        ]);

        return [
            "success" => true,
            "message" => sprintf('Created Post with ID #%d', $post_id),
            "post_id" => $post_id
        ];
    }
}

My composer.json file defines where to find the seeds, and where to find commands. This is a very trimmed down version of composer.json.

{
  "require": {
    "haydenpierce/class-finder": "^0.5.3"
  },
  "autoload": {
    "psr-4": {
      "Roycom\\": "src/",
      "RoycomCommands\\": "wp-cli/commands/",
      "RoycomTheme\\": "wp-content/themes/roylart/includes/classes"
    }
  }
}

The gist of what is happening is I discover seed files in the directory DbSeeds in my theme directory using ClassFinder and then for each of them I execute a run method. The run method simply runs some WordPress functions to create posts, create taxonomies, add placeholder images. Really, it can do whatever I need it to do.

For example, I created a PagesSeed class that creates some generic pages, makes one of them the front page and one the blog page, and then also create a menu.

Another seed I created is called ArtworksSeed for a custom post type called artworks that will create example posts, download images from loremflickr, and also add data to custom MetaBox fields.