Selling digital products using Laravel part 10: Miscellaneous interesting tidbits + outro
We've already covered a lot of ground in this series. Let's finish by highlighting some miscellaneous interesting tidbits.
- Part 10: Miscellaneous interesting tidbits + outro (you are here)
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...');
collect(config('services.rss'))
->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!
What are your thoughts on "Selling digital products using Laravel part 10: Miscellaneous interesting tidbits + outro"?