Build a Database with Eloquent, Faker and Flysystem

Aleksander Koko
Tweet
This entry is part 2 of 4 in the series REST App with Laravel and EmberJS

REST App with Laravel and EmberJS

In this part, we will create the structure of the database. We will create the tables using migrations and seed the database using seeders. Also, you will learn how to grab some random images from LoremPixel and put them on the filesystem using Flysystem. You’ll also be adding some randomly generated data using the Faker library.
Much like with part 1, you can download this part’s code from github.

Install the libraries

The installation is very straightforward with Composer.

Require these projects:

    "fzaninotto/faker": "1.5.*@dev",
    "league/flysystem": "dev-feature/eventable-filesystem",
    "dropbox/dropbox-sdk": "dev-master"

Execute composer update and everything will be installed and updated smoothly.

Build the structure of the database

Until now, we had just one migration file. We need two more for our application. One table that will keep the data for photos and another for categories. To find out more about Artisan and Migrations, see this post.

    php artisan migrate:make create_photos_table
    php artisan migrate:make create_categories_table

These two commands create two migration files. Let’s start filling them with some schema. First, we start with the photos migration file. Stop for a moment and think what we need for this one. Earlier, we installed the flysystem library.

The flysystem library can handle files on different locations: the local filesystem, Dropbox, AWS3, Rackspace Cloud Files and more. We need it for the local filesystem on our development environment and we have to use Dropbox for production. Why is that? Heroku is a Paas (Platform as a Service) so we don’t have full control of the filesystem. There are only two directories that can be writable: ./temp and ./logs. You can write files only using git for deployment, and not in any other folder. Why don`t we just use those two directories? Because they are temporary. If something goes wrong and the Dyno restarts, all the files in those directories are deleted. It’s not safe to work with those two directories.

If you want to know more about flysystem read this article on sitepoint by Lukas White or their documentation .

A Dropbox application

Before using flysystem and Dropbox for production, we have to create a Dropbox application which allows us to use their Api. Then, we need to do some configuring. If you don’t already have a Dropbox account, create one and then return for the rest of the article. If you use Rackspace, or AWS3 then you are free to use either of those. I’m using Dropbox here because it doesn’t require a credit card and it’s free to use. Follow the link and create a Dropbox App. Here is how to create one.

Create a DropBox App

Next come the configurations. We are going to use it only on production. Its a good idea to separate the configuration from App logic. Create dropbox.php inside /app/config or in /app/config/production.

    return array(
        'token'     =>  'token_here',
        'appName'   =>  'Your App Name'
    );

And for retrieving use:

    Config::get('dropbox.appName');
    Config::get('dropbox.token');

We need flysystem to create the images folder (the folder that will hold images/photos) and delete it. In the up method, we will create the folder and in the down method we will delete it. In __construct we create a Filesystem object by passing in a Local adapter (or a Dropbox, Rackspace…). Inside that local adapter we specify a path.

Note that creating a folder and deleting it inside a migration is an anti pattern. It’s nothing critical, but it breaks the single responsibility principle. Don’t worry, this code is just for demonstration purposes and to show you how the library works – just keep that in mind and try to use it inside the controllers next time.

The public_path() function is a Laravel helper function that returns the full path of the public folder.

To define the schema, just specify what type of fields you want to use and put the name of the field as a parameter. For more info on schema read the Laravel docs.

    /* /app/database/migrations/some_date_CreatePhotosTable.php */
    
    
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Database\Migrations\Migration;
    use Dropbox\Client;
    use League\Flysystem\Filesystem;
    use League\Flysystem\Adapter\Local as Adapter;
    use League\Flysystem\Adapter\Dropbox;
    
    
    class CreatePhotosTable extends Migration {
    
        private $filesystem;
    
        public function __construct(){
    
            if(App::environment() === "local"){     // If its on local use the local filesystem
            
                $this->filesystem = new Filesystem(new Adapter( public_path() ));
                
            }else{                                  // Use dropbox on other cases, 
                                                    // including testing here (not a good idea)
                                                    
                $client = new Client(Config::get('dropbox.token'), Config::get('dropbox.appName'));
                $this->filesystem = new Filesystem(new Dropbox($client));
    
            }
    
        }
    
    	public function up()
    	{
    
            $this->filesystem->createDir('images');
    
            Schema::create('photos', function($table)
            {
                $table->increments('id');
                $table->integer('user_id');
                $table->string('title');
                $table->string('url')->unique();
                $table->text('description');
                $table->integer("category");
                $table->timestamps();
            });
    	}
    
    	public function down()
    	{
    
            Schema::dropIfExists('photos');
    
            try{
                $this->filesystem->deleteDir('images');
            }catch (\Dropbox\Exception_BadResponse $e){}
    
    	}
    
    }

For the up method we created an id, user_id, title, url, a category, and a description for the photo. We also used something that doesn’t look like the others. $table->timestamps() is a special function that adds two more fields automatically: created_at and updated_at.

In the down method, drop the table if it exists and delete the folder that we created in the up method. We will be using seeders to set some images there, so it’s a good idea to delete all the photos and seed with new photos.

This seed file works in production, too. For development, we have used the local filesystem and for production a Dropbox App. Upload all the changes to Heroku and make a migration + seeding and you will see that in your Dropbox App a folder was created.

After creating the photos migration file, we need the categories migration file. This is easier. In the schema, we create an id that increments every time a new record is added into the table. We also need a name for the category. By having a name and an id for the category, we can make things more dynamic. In the down method, drop the table if it exists.

    /* /app/database/migrations/some_date_CreateCategoriesTable.php */
    
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Database\Migrations\Migration;
    
    class CreateCategoriesTable extends Migration {
    
    	public function up()
    	{
            Schema::create('categories', function($table)
            {
                $table->increments('id');
                $table->string('name');
                $table->timestamps();
            });
    	}
    
    	public function down()
    	{
            Schema::dropIfExists('categories');
    	}
    
    }

After creating those two files let’s apply the migration by executing:

    php artisan migrate:refresh
    php artisan db:seed

or

    php artisan migrate:refresh --seed

This will revert the old migration (if you had applied one) and build the new schema. Afterwards, the seeders will be executed.

If you hadn’t already made a migration then an error will be shown. Use php artisan migrate and everything afterwards will be working just fine.

Create the Models

Before going further with the seeders, it’s better if we create the models first. When creating the models, migrations, and the seeders, you have to know what you are doing. Think clearly about what you really want. The relations between models are important and save time down the line.

Laravel’s models extend Eloquent’s ORM class. A nice feature of Eloquent is the Relations. As we are dealing with a REST Api in this series, this feature helps us a lot on the endpoints to put the data that is related with a given resource. EmberJs on the front end side has a similar logic about relations and this automates a large part of the code. This parity makes the code more maintainable and scalable.

For the User model, you don’t have to worry. It’s created by Laravel and ready to use. Edit that Model and add some relation with other models that we are going to create. The models are located in /app/models.

    /* /app/models/User.php */

    use Illuminate\Auth\UserTrait;
    use Illuminate\Auth\UserInterface;
    
    class User extends Eloquent implements UserInterface{
    
    	use UserTrait;
    
    	protected $table = 'users';     // The table that this model uses
    
        public function photos()
        {
            return $this->hasMany('Photo');
        }
    
    }

The photos() method specifies a relation with the Photo model which doesn’t exist for now. Also, the protected variable $table is used for specifying the table name that this model is using. As an additional feature, this model implements the UserInterface for easier integration with authentication.

Let’s create the Photo model. Create a new file in /app/models and name it Photo.php. Remember that the class name should begin with uppercase letters and extend Eloquent.

    /* /app/models/Photo.php */
    
    class Photo extends Eloquent {

        protected $table = 'photos';
        
        public function author(){
            return $this->belongsTo('User', 'user_id');
        }
    
    }

The $table variable does not have to be specified in this case. If a $table variable does not exist, Laravel uses the plural of the name in lowercase as the table name. We have specified another relation here: the belongsTo relation. The first parameter is for the Model that has a relation with it. The second one is for the field that is being used on this model to specify which user owns this photo. It can by used like this:

    Photo::find($id)->author;

This returns a User object with an id that is the same as the user_id on the Photo model.

For the categories, create another file in /app/models and name it Category.php.

    class Category extends Eloquent {

        protected $table = 'categories';
    
    }

Seed the tables with some data

Now that we have a structure for the database, it is time to seed it with some data. This is where the Faker library comes in. For more info on Faker read the Faker docs or for more advanced features and use cases, read Simplifying Test Data Generation with Faker.

Currently, we have only a seeder file. Let’s edit it and use the Faker library to generate a fake username and password. Edit the UserTableSeeder.php file inside the /app/database/seeds folder.

We need to know one user for authentication. One user in the database should be created without Faker.

Init the Faker object:

    $faker = Faker\Factory::create();   // Faker uses Factory design pattern

How to create a username and a password:

    $faker->userName;                           // returns a username
    Hash::make($faker->name . $faker->year);    // Creates a password

In the end, the UserTableSeeder should look something like this:

    /* /app/database/seeds/UserTableSeeder.php */
    
    class UserTableSeeder extends Seeder {

        public function run()
        {
    
            Eloquent::unguard();
    
            DB::table('users')->delete();
    
            $faker = Faker\Factory::create();
    
    
            for($i = 0; $i < 2; $i++){
                User::create(array(
                    'username' => $faker->userName,
                    'password' => Hash::make($faker->name . $faker->year),
                    'name' => $faker->name,
                    'lastname' => $faker->lastName
                ));
            }
    
            User::create(array(
                'username' => 'foo',
                'password' => Hash::make('password'),
                'name' => $faker->name,
                'lastname' => $faker->lastName
            ));
    
        }
    
    }

By executing this seeder Class, we have created two fake users and a hardcoded one. Later, we will test this app by using basic authentication so we need to have a known user.

What’s left are the category and photo seed files. Create PhotoTableSeeder.php and CategoryTableSeeder.php inside /app/database/seeds.

    /* /app/database/seeds/CategoryTableSeeder.php */
    
    class CategoryTableSeeder extends Seeder {
    
        public function run()
        {
    
            Eloquent::unguard();
    
            DB::table('categories')->delete();
    
            for($i = 1; $i < 5; $i++){
                Category::create(array(
                    'name' => 'category' . $i
                ));
            }
            
        }
    
    }

For the categories seed file, we don’t even need Faker. Just categorynumber would do for the name of the category. The id will auto increment each time a new category is inserted.

The photo seed class is a bit trickier. In this migration file we need to get some images from LoremPixel and put them into the /public/images folder. Also, we need the flysystem library with a local adapter or Dropbox Adapter.

This is how we create a random string. The length of the argument is the length of the random string. This will be the name of the image.

    $faker->lexify($string = '???????????????????');

Getting the images from lorempixel is simple: width and height are URL fragments. Such a link then returns an image with the given size. By requesting this url several times, different images are returned so no duplication occurs. file_get_contents($url); gets the content of that url. In this case that url would be http://lorempixel.com/400/200/ , and it’s an image

    file_get_contents('http://lorempixel.com/640/400/');

The sentence method of Faker returns a sentence with the specified number of words as an argument

    $faker->sentence(40);

In the end, PhotoTableSeeder.php should look something like this.

    /* /app/database/seeds/PhotoTableSeeder.php */
    
    use Dropbox\Client;                     // DropBox Client
    use League\Flysystem\Filesystem;
    use League\Flysystem\Adapter\Local as Adapter;
    use League\Flysystem\Adapter\Dropbox;   // The DropBox Adapter
    
    
    class PhotoTableSeeder extends Seeder {
    
        private $filesystem;
        
        public function __construct(){
            if(App::environment() === "local"){
                $this->filesystem = new Filesystem(new Adapter( public_path() . '/images/'));
            }else{
                $client = new Client(Config::get('dropbox.token'), Config::get('dropbox.appName'));
                $this->filesystem = new Filesystem(new Dropbox($client, '/images'));
            }
    
        }
    
        public function run()
        {
    
            Eloquent::unguard();
    
            DB::table('photos')->delete();
    
            $faker = Faker\Factory::create();
    
            for($i = 0; $i < 10; $i++){
    
                $file = file_get_contents('http://lorempixel.com/640/400/');
    
                $url = $faker->lexify($string = '???????????????????');
                try{
                    $this->filesystem->write($url, $file);
                }catch (\Dropbox\Exception $e){
                    echo $e->getMessage();
                }
    
    
                Photo::create(array(
                    'url' => $url,
                    'user_id' => (int) rand(1, 4),
                    'title' => $faker->sentence(5),
                    'description' => $faker->sentence(40),
                    'category' => rand(1, 4)
                ));
            }
    
        }
    
    }

Don’t forget to call these migration files into DatabaseSeeder.php . Add these lines into /app/database/seeds/DatabaseSeeder.php:

    $this->call('PhotoTableSeeder');
    $this->call('CategoryTableSeeder');

This way, DatabaseSeeder will call the two new seeder files. Let’s apply the migration and seeding on Heroku. First push the project

    git add .
    git commit -m "a message here"
    git push heroku master

And then run the migration and the seed

    heroku run php artisan migrate:refresh --seed

After executing the migration and seeding on the server, this is the screenshoot on my dropbox apps folder.

Dropbox apps folder

Conclusion

In this part, we saw how to create the database using migrations and how to seed that database using seeders with some fake data. We used the Faker library and Flysystem to work with the filesystem. Eloquent is an easy to use ORM, yet very powerful – understanding how Eloquent works simplifies your workflow. Don’t forget that everything starts from the database architecture and defining a smart one in the beginning will spare you many headaches later on. Stay tuned for part 3!

REST App with Laravel and EmberJS

<< Build a New App with Laravel and EmberJS in VagrantBuild REST Resources with Laravel >>

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • aminemat

    Hello,
    Thanks for the article.
    I just wanted to point out that Faker ships with an image provider (that uses lorempixels BTW)
    $faker->image() to store it locally
    $faker->imageUrl() to get just the URL

    • Aleksander Koko

      That’s something I didn’t knew. Thanks for sharing with us :)

    • http://www.bitfalls.com/ Bruno Skvorc

      Thanks, that’s useful info!

  • Aldo Ziflaj

    Great work :) Keep it up mate

    • Aleksander Koko

      Thanks buddy :)

  • Aleksander Koko

    When dealing with many Images (or files) you have to generate a random string as a URL. That’s because you don’t want one image to override another. This is just a generator so for a few images with that great number of possibilities, chances are that wont override. In a real application you have to generate a random URL, check if this URL exists and then write it to the filesystem (or Dropbox in this case). I don’t know if I answered you question.

    • Hap

      I totally understand the need for random string, but my question is only about the `$string = ` in the function call:

      If I’m right, it’s like doing:

      $string = ‘???';
      $faker->lexify($string);

      But using $string = ‘???’ directly in the function call looks like a “named parameter” (which is not), and it’s rather confusing (and $string is never used after so it just looks like an undesirable side effect). I just wanted to highlight this detail. :)

      • Aleksander Koko

        The funny thing is that I was working with Faker today with a Rest Api and I asked myself: Why did I do that? It is the same situation. It’s like getting back to your code some days later and you don’t know why something is there. It is my mistake. Thanks for pointing it out.

        But you propably know how it feels coding for some hours at midnight and when you wake up, you don’t really understand why things are like that :)

  • Xandi Ostermeyr

    Awesome article. didn’t know lorempixels until now ;)

    • Aleksander Koko

      Thanks @xandiostermeyr:disqus . Actually is very useful for testing purposes. I readers have enough interest then I’m going to make a follow up series about this series. Adding a proper Ember with Yeoman, Grunt configuration, Pagination, Authentication using social sites (Facebook, Twitter) and maybe passing the project to another platform. That would be a surprise. What you would like to see on the follow up (if we decide to do it)?

    • Aleksander Koko

      I want to make the same question to you @ramirovjnr:disqus . Any ideas.. Please my comment to @xandiostermeyr:disqus .

  • http://about.me/ramirovjnr Ramiro Varandas Jr

    Great post!

    • Aleksander Koko

      Thanks @ramirovjnr:disqus :)