Define, fire and listen for custom Laravel model events within a trait

It took me some time to figure out the right way to define, fire and listen for custom events for a Laravel model using a trait, so I’m writing down how I did it in case it’s helpful to others.

Let’s say you have a model Post that you set up as having a “draft” status by default, but eventually will have a status of “publish”. Let’s also say you want to make the act of publishing a post a custom model event that can be listened for in addition to the standard model events like “created” or “updated”. And let’s say you want to do all of this using a trait so that you can apply the same logic to another model in the future, such as comments on the post, without repeating yourself.

Here’s what my Post model might look like:

<?php

namespace App\Models;

class Post extends Model
{
    //
}

Let’s create a Publishable trait that can be applied to this model:

<?php

namespace App\Models\Traits;

trait Publishable
{
    // Add to the list of observable events on any publishable model
    public function initializePublishable()
    {
        $this->addObservableEvents([
            'publishing',
            'published',
        ]);
    }

    // Create a publish method that we'll use to transition the 
    // status of any publishable model, and fire off the before/after events
    public function publish()
    {
        if (false === $this->fireModelEvent('publishing')) {
            return false;
        }
        $this->forceFill(['status' => 'publish'])->save();
        $this->fireModelEvent('published');
    }

    // Register the existence of the publishing model event
    public static function publishing($callback)
    {
        static::registerModelEvent('publishing', $callback);
    }

    // Register the existence of the published model event
    public static function published($callback)
    {
        static::registerModelEvent('published', $callback);
    }
}

This new trait can now be applied to the Post model:

Continue reading Define, fire and listen for custom Laravel model events within a trait

My standard Laravel development tools

Now that I’ve been actively developing applications with the Laravel framework for a few years, I thought I’d write down the tools and services I tend to use on a regular basis in that work.

I’ve spent a fair amount of time researching and experimenting with these tools and their alternatives in order to make a choice, so maybe it will help someone else who hasn’t gone through that yet. I’m always glad when others share details about their development environments so that the rest of us who are just getting going can build on that foundation.

Hardware and Development Environment

Launching a New Project

composer global update laravel/installer
laravel new example-app --git --branch="main"
cd example-app
valet link
valet secure example-app

Then I create a database, add the DB info in .env, run artisan migrate, and I’m ready to develop. Sometimes I have to make sure PhpStorm has the right coding standards and PHP Code Sniffer config in place.

Sometimes I add a .psysh.php file to my project repo with these contents:

<?php
DB::listen(function ($query) {
    dump("[{$query->time}ms] {$query->sql}");
    if ($query->bindings) {
        dump($query->bindings);
    }
});

When using artisan tinker, this prints out any SQL queries that were run by a given command within the tinker session, for faster debugging.

For WordPress projects, I use a customized version of this “valetpress” bash script to initialize new projects.

Continue reading My standard Laravel development tools

Creating a personalized, private RSS feed for users in Laravel

In building WP Lookout I had the need to create user-specific, private RSS feeds as a way for users to get updates and information specific to their account. Here’s how I did it.

I started with the laravel-feed package from Spatie, which allows you to easily generate RSS feeds, either from a model’s records or from some other method that you define. I found that someone has proposed a feature and submitted a PR to add support for signed routes, which is Laravel’s way of adding a hash key to a given URL so that it can’t be accessed if it’s been modified, perfect for something like a user-specific RSS feed. But the Spatie folks decided not to include the feature, so I had to figure out how to do it on my own.

First, I added the package to my project and published the related config file:

$ composer require spatie/laravel-feed
$ php artisan vendor:publish \
    --provider="Spatie\Feed\FeedServiceProvider" \
    --tag="config"

Then, I added a user-specific feed identifier to the User model:

$ php artisan make:migration add_feed_id_to_users_table --table=users

The migration looked like this in part:

public function up()
{
    Schema::table('users', function (Blueprint $table) {
        //
        $table->string('feed_id');
    });

    DB::statement('UPDATE users SET feed_id = ...');
}

The UPDATE statement there is used to set the initial value of the feed identifier for existing users. An alternative would have been to make the field nullable and then set it elsewhere. For new users, I set the feed identifier as users are created, via the boot() method in the User model:

Continue reading Creating a personalized, private RSS feed for users in Laravel

Intercepting mail with MailHog in Laravel Valet

I’m trying out using Laravel Valet to manage the development environments for both my WordPress and Laravel related work. (This is not the result of any dissatisfaction with VVV, which I’ve happily been using for the WP work, and Laravel Homestead, which I’ve happily been using for Laravel work, but comes as a necessity now that I am using a Mac with Apple’s M1 ARM chip, which doesn’t support Intel Vagrant virtual machines.)

The Valet installation and setup was easy and fast using their instructions, and I’ve been able to successfully move projects over to it without a lot of hassle. The one place where I was really bit was taking for granted how those previous virtual environments where handling the intercepting of email generated by my applications, so that they didn’t actually go out onto the Internet for actual delivery. I got used to spinning up environments with copies of production data and not worrying about real users getting email messages.

Well, you can guess what happened when I did that in Valet, which by default delivers email just like any other email generated from a process running on my Mac. Spinning up a WordPress dev site with a slightly outdated database dump and with functionality that is all about notifying users via email of things that are about to happen or have happened meant…lots of emails going out.

Ugh. Apologies, installing Stop Emails and other cleanup ensued.

Fortunately, the long term fix is pretty easy. Yes, there are various plugins one can install on individual WordPress sites to stop email from going out, or to change default SMTP behavior. But I didn’t want to have to worry about that each time I clone a site. So instead I changed the default SMTP behavior for the php-fpm processes that Valet runs to deliver all mail to MailHog.

First, I installed MailHog:

brew install mailhog
brew services start mailhog

Then, I added a smtp-mailhog.ini file in the PHP conf.d directory for my setup. In my case that was each version in /opt/homebrew/etc/php/X.X/conf.d. The contents of that file are:

; Use Mailhog for mail delivery
sendmail_path=mailhog sendmail

Then I ran valet restart and tested by triggering a password reset email from a WordPress site, and confirmed that it had been intercepted by visiting the MailHog interface at http://127.0.0.1:8025.

Of course this setup may not fit every use case, so adjust your config accordingly. (I submitted a PR to the Laravel docs with this info, but understandably it was probably too specific to my setup.) Even if you don’t change the PHP-level settings, with MailHog installed you could set up individual sites/applications to send to it as needed.

What are AWS hosting costs using Laravel Vapor?

When I was researching tools and services for launching a SaaS app, I was pretty clear that I wanted to use Laravel Vapor to manage the Amazon Web Services deployment. The main mystery about that decision was what it would actually cost to have a Vapor-managed deployment on AWS for the size of my application and my expected usage levels.

I found a few articles and blog posts about that topic (the most helpful was Cost & Performance optimization in Laravel Vapor) but, as is the case with AWS hosting in general, there was no clear formula that would lead me to a precise monthly hosting cost for a brand new web application.

In hopes that it helps someone else in a similar situation, I’d like to add one more data point to the mix. Here are some details about what it’s costing me (so far) to host my Laravel-powered application on AWS as managed by Vapor.

Vapor itself is $39/month. This cost does not change if you use Vapor across multiple projects, so your per-project cost can go down over time if you plan to launch more than one project. Some people have raised eyebrows at this baseline cost but as anyone who has ever had to manage their own hosting server infrastructure and worry about upgrades, security issues, configuration management, etc. knows, it feels like a great deal. I wrote about that more in my other post on launching a SaaS business, but this sentiment remains true:

It felt like the magical world of cloud hosting that was always promised but never quite delivered had finally become reality. Even a month later I’m still constantly amazed by it. Huge kudos to the Vapor team.

Now, on to AWS itself. I’m currently paying approximately $1.00 per day for AWS services, and the monthly bill ends up being about $33.00.

Continue reading What are AWS hosting costs using Laravel Vapor?