RSS

January 30, 2019
3 min read

Publish posts with the scheduler

For publishing posts we need a simple Post model. For the sake of simplicity it only has the following properties:

Schema::create('posts', function(Blueprint $table) {
    $table->increments('id');
    $table->string('title');
    $table->text('body');
    $table->boolean('is_published')->default(*false*);
    $table->datetime('publish_date');
    $table->timestamps();
});

We also need a PostFactory to generate posts in our test. Also if you don’t write tests (I really don’t know why anybody should do this), the factories are a super convenient way to seed up your project with dummy data.

<?php

use Faker\Generator as Faker;

$factory->define(App\Post::class, function(Faker $faker) {
    return [
        'title' => $faker->words(rand(3, 10), true),
        'body' => $faker->paragraphs(rand(4, 10), true),
        'is_published' => true,
        'publish_date' => now()->subDays(rand(4, 100)),
    ];
});

Let’s start with a test

As we wanted to add this feature with Test Driven Development, lets write our Test:

<?php

namespace Tests\Unit;

use App\Post;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class PublishPostsTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    public function it_publishes_all_unpublished_posts_with_publish_date_in_the_past()
    {
        $post = factory(Post::class)->create([
            'is_published'=> false,
            'publish_date' => now()->subDays(3)
        ]);

        $this->artisan('publish:posts');

        $this->assertTrue($post->fresh()->is_published);

    }
}

The test creates first a post with the factory we created above. The array which is passed to the create() method of the factory is to override data for the model. This is how we achieve to create a post which has a publish_date in the past and is_published is false.
This was the preparation part of the test. The next part will be the execution part. In this part we execute the code which gets tested by the test. In our case we execute an Artisan command called publish:posts. The last part of the test is the assertion. Here we assert that $post->fresh()->is_published is true after we executed the artisan command.

If we execute the test, it will fail and say that it can’t find a command called publish:posts. Thats totally normal since we did not created it until now.

Creating the command

There is an artisan make command to create commands. This will create the boilerplate command class which we need : php artisan make:command PublishPosts.

Next we can update the command to our needs. I came up with the following:

<?php

namespace App\Console\Commands;

use App\Post;
use Illuminate\Console\Command;

class PublishPosts extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'publish:posts';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'This command publishes all posts where the publish_date is in the past';

    /**
     * Execute the console command.
     *
     * @return void
     */
    public function handle()
    {
        Post::where('is_published', false)
            ->whereDate('publish_date', '<=', now())
            ->get()
            ->each(function (Post $post) {
                $post->update(['is_published' => true]);
            });
    }
}

The handle() method does the whole magic. It looks for every Post which publish_date is older than now() ans is_published is false. The result of the get() method is an Eloquent collection, why we can directly chain the each() method to simply update the model.

As a result the test should now be green.

PHPUnit 7.5.2 by Sebastian Bergmann and contributors.

.                                               1 / 1 (100%)

Time: 545 ms, Memory: 20.00MB

OK (1 test, 1 assertion)

Add the command to the scheduler

Now we need to add the command to the scheduler. This is not more than putting the following line inside the schedule() method of the App\Console\Kernel.

    /**
     * Define the application's command schedule.
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
         $schedule->command('publish:posts')
                  ->hourly();
    }

Add a cronjob to the system which executes the scheduler

To execute the scheduler every minute we need to add a cronjob to the system. Normally this work with execution crontab -e on the shell and add the following line at the end of the file:

* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

And that’s everything we need to do. I am always impressed how easy Laravel makes such tasks.

Legal Notice  |  Privacy  |  RSS  |  © 2019 christlieb.eu