Oh Dear is the all-in-one monitoring tool for your entire website. We monitor uptime, SSL certificates, broken links, scheduled tasks and more. You'll get a notifications for us when something's wrong. All that paired with a developer friendly API and kick-ass documentation. O, and you'll also be able to create a public status page under a minute. Start monitoring using our free trial now.

Selling digital products using Laravel part 10: Miscellaneous interesting tidbits + outro

Original – by Freek Van der Herten – 6 minute read

We've already covered a lot of ground in this series. Let's finish by highlighting some miscellaneous interesting tidbits.

Importing blogpost using RSS

Let's take a look at our homepage. Several of my colleagues and I write regularly on our blogs. Using the RSS feeds on our blogs, we import the last written post and display it right on the homepage of spatie.be.


Those RSS feeds are imported by a Laminas Framework package: laminas-feed. All feed entries are imported in the local database in the insights table.

Here's the code of the ImportInsightsCommand command that does the import. It's pretty straightforward.

namespace App\Console\Commands;

use App\Models\Insight;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Laminas\Feed\Exception\ExceptionInterface;
use Laminas\Feed\Reader\Entry\AbstractEntry;
use Laminas\Feed\Reader\Reader;
use Laminas\Http\Client\Adapter\Exception\TimeoutException;

class ImportInsightsCommand extends Command
    protected $signature = 'import:insights';

    protected $description = 'Import the blog posts of team members.';

    public function handle()
        $this->info('Syncing insights from RSS feeds...');

            ->each(function (string $feedUrl): void {
                try {
                    $feed = Reader::import($feedUrl);

                    foreach ($feed as $entry) {
                        $insight = Insight::updateOrCreate([
                            'url' => $entry->getLink(),
                        ], [
                            'title' => $this->sanitizeTitle($entry->getTitle()),
                            'created_at' => new Carbon($entry->getDateModified()->format(DATE_ATOM)),
                            'url' => $entry->getLink(),
                            'website' => $this->getWebsite($entry),

                        $this->info("Imported `{$insight->title}`");
                } catch (ExceptionInterface | TimeoutException $exception) {
                    $this->error("Could not load {$feedUrl}");

    protected function sanitizeTitle(string $title): string
        $title = ltrim($title, '★ ');

        $title = htmlspecialchars_decode($title, ENT_QUOTES);

        return $title;

    protected function getWebsite(AbstractEntry $entry): string
        $host = parse_url($entry->getLink(), PHP_URL_HOST);

        $host = ltrim($host, 'www.');

        return $host;

Running tests via GitHub actions

I'll be honest with you. We should have more tests for spatie.be. Currently, we only test the action classes and some of the pages. I feel that we now mainly test the happy paths of our code. We will add unhappy path tests soon.

We do run the current suite of tests on each commit via GitHub Actions. You'll find the workflow here. To know more about our testing workflow on GitHub actions, read this blog post I wrote earlier this year.

Automatically fixing code formatting issues

To format our code, we mostly follow PSR-12. In the spatie.be repo there's a GitHub workflow that will be executed each time a commit is pushed.

The workflow will run FriendsOfPHP/PHP-CS-Fixer on GitHub actions using the config file included in the repo. The GitHub workflow will commit all code style fixes in this step. To know more about the workflow, read this excellent post by Stefan Zweifel.

Deploying spatie.be to a server

Our site is hosted on a Forge provisioned Digital Ocean server. To deploy our site to the server, we use the vastly underrated Envoy.

Using Envoy, small tasks can be defined that should be performed on a local or remote server. You can find the Envoy script we use in this Blade file.

In short, the deploy task will perform a near-zero-downtime deploy. It creates a new release folder on the server and builds the new release by cloning the repo, running Composer and Yarn, and a few things more. After the application is fully prepared, we'll symlink that directory as the current one.

The only downside of the deploy task is that it can be quite slow. We've also added a deploy-code task that just pulls the GitHub repo in the current directory and clears the cache. This task completes much faster and is quite handy if you want only to change a small piece of PHP code.

We've been using the deploy and deploy-code tasks for a few years now on all Spatie projects. They are very robust. In this old blog post, I highlight a few interesting tidbits happening under the hood.

Monitoring spatie.be

To monitor our site, we use two services that I've helped building: Oh Dear and Flare.

Oh Dear is an uptime and performance monitoring service. It will notify us via Slack whenever our site is down. It can also measure performance. As you can see in this screenshot, we are more or less always seeing a response time of around 200ms.


Additionally, Oh Dear will also crawl our site and notify us when there is a broken link or mixed content. This is quite nice because we know for sure that our users will not see any 404s when navigating the site.


A final thing that Oh Dear does is keeping an eye on the scheduled tasks. We get a notification whenever a scheduled task did not complete successfully at the expected time or didn't run at all.

To sync the schedule of the spatie.be Laravel app to Oh Dear we use our homegrown spatie/laravel-schedule-monitor package.

You can see that package in action in this video:

To monitor exceptions occurring in our production environment, we use Flare. This homegrown service is specifically built for Laravel apps. Like most of our other products, we ensured that the UI is very straightforward to use and polished.


In closing

Congratulations! You've made it to the end. There sure is a lot going on at spatie.be. If you like what you've read in this post, consider sponsoring on GitHub, or purchasing one of our paid products. When you do that, you'll see all the things explained above in action.

I'd also like to emphasize that I did not create this website alone. During the past summer, my colleagues Rias and Alex did a lot of the development. I think they did a fantastic job. Like always, my colleague Willem made everything look beautiful.

Thanks for reading!

Stay up to date with all things Laravel, PHP, and JavaScript.

You can follow me on these platforms:

On all these platforms, regularly share programming tips, and what I myself have learned in ongoing projects.

Every month I send out a newsletter containing lots of interesting stuff for the modern PHP developer.

Expect quick tips & tricks, interesting tutorials, opinions and packages. Because I work with Laravel every day there is an emphasis on that framework.

Rest assured that I will only use your email address to send you the newsletter and will not use it for any other purposes.


What are your thoughts on "Selling digital products using Laravel part 10: Miscellaneous interesting tidbits + outro"?

Comments powered by Laravel Comments
Want to join the conversation? Log in or create an account to post a comment.