Generating Laravel IDE Helpers After Migrations

Last updated 5th September, 2023

What is IDE Helper Generation In Laravel

Laravel uses a lot of PHP magic under the hood to make it the most powerful framework on the web. This magic comes at a cost. Our IDEs (integrated developer environments) often can't decode the magic Laravel works without a bit of help.

A good example of this in the framework is models.

Models are classes that represent an object, this object has properties that are derived from a database. Because this data is stored elsewhere, it means that when you're accessing properties of a model in your IDE, it can't know what exists, and what doesn't.

You **could **define the properties on the model class itself, but to avoid bloating out model classes - Laravel doesn't do this.

So how do we tell our IDE what properties are available on our models?

In PHP you can annotate classes with comments. These comments can represent properties associated with that class, and your IDE can read them.

As a trivial example, below is a dummy Laravel Post model. It's very barebones, for the sake of this example. It has no annotations.

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $guarded = [];
}

// ...

$post = new Post();

$post->title;

Say that you have a few properties on this model; title, description, content, url. When you try to access these, your ide won't know that they exist in this class - and might complain at you.

In VsCode:

A screenshot of a VsCode window, unable to pick up types

And again in PhpStorm:

A screenshot of a PhpStorm window, unable to pick up types

Adding property annotations changes that. Here's the same class, properly annotated:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

/**
 * App\Models\BlogPPostost
 *
 * @property int $id
 * @property string|null $title
 * @property string|null $description
 * @property string|null $content
 * @property string|null $url
 */
class Post extends Model
{
    protected $guarded = [];
}

// ...

$post = new Post();

$post->title;

Now when we try to access these properties, our IDEs will look at those annotations and understand that they exist.

In VsCode:

A screenshot of a VsCode window, now able to determine model property types

And once more in PhpStorm:

A screenshot of a PhpStorm window, now able to determine model property types

These annotations don't affect the production code itself and exist purely for you and your editor's benefit.

Now, it would be pretty painful to try to maintain these comments yourself. Luckily, there's a package to automate this.

Automating IDE Helper Generation

IDE Helper Generator for Laravel is an awesome package. It produces IDE helpers to fix a ton of IDE / Laravel mismatches to make your development easier. Including:

I won't go through everything it does, but the GitHub page has great documentation on everything it offers.

There's also a great Laracasts Video on how to set up and use the package. It's a little old now but gets the point across.

Why We Should Generate Config After Migrating

Once you've installed the IDE Helper package, you'll be able to generate annotations for your models.

Now, when do we run them?

You'll need to run the generation each time your models change. So when you add/remove/change a column via a migration. There are two places we can run this:

  • In a scheduled action - Like a GitHub Action
  • As an event listener after migrations have finished

In this article, we'll focus on the latter. Generating them after migrating your database makes the most sense to me, as you usually need the annotations immediately!

Triggering Generation On The MigrationsEnded Event

As is common in Laravel, there is an event we can hook onto. We can listen to the MigrationsEnded event, and regenerate our model annotations once we know that something has changed.

There are a few ways of registering our event listener in Laravel, I'll go with the simplest to keep things brief.

namespace App\Providers;
use App\Listeners\MigrationsEndedListener;
use Illuminate\Database\Events\MigrationsEnded;
use Illuminate\Foundation\Support\Providers\EventServiceProvider;

class EventServiceProvider extends EventServiceProvider
{
    protected $listen = [
        MigrationsEnded::class => [
            MigrationsEndedListener::class,
        ],
    ];
}

The above code will trigger the listener any time migrations finish running (both up and down migrations!).

Now let's take a look at the listener:

namespace App\Modules\Core\Listeners;

use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Artisan;

class MigrationsEndedListener
{
    public function handle(): void
    {
        // We don't want to run this outside of a local environment,
        // as it may not have access to the database.
        // It might also cause some git problems in a production
        // environment
        if (! App::environment('local')) {
            return;
        }

		// Run the IDE generation command
        //
        // --reset refresh all annotations on generation
        // --write write the annotations directly to the model
        Artisan::command('ide-helper:models', [
            '--write', '--reset'
        ]);
    }
}

If you want to avoid running a standalone listener, you could also define a closure directly in the boot method of the EventServiceProvider.

namespace App\Providers;

use Illuminate\Database\Events\MigrationsEnded;
use Illuminate\Foundation\Support\Providers\EventServiceProvider;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Event;

class EventServiceProvider extends EventServiceProvider
{
    public function boot()
    {
        Event::listen(
            MigrationsEnded::class,
            function () {
                if (! App::environment('local')) {
            		return;
        		}
        
                Artisan::command('ide-helper:models', [
            		'--write', '--reset'
        		]);
            }
        );
    }
}

Final Thoughts

This article only scratches the surface of what the IDE helper package can do for your local development. I highly recommend checking out their documentation for an in-depth set-up guide.

See you next time 🤙

No comments yet…

DevInTheWild.

Login To Add Comments.

Want More Like This?

Subscribe to occasional updates.