Laravel: PHPDocs for Models everyone should have

Your favorite IDE will pick them up and make your life easier

Italo Baeza Cabrera
5 min readSep 20, 2021
Photo by Rémi Walle on Unsplash

When using the Eloquent ORM, you will note there is a lot of magic going around. With that bunch of logic behind the scenes, it’s normal for an IDE to just not understand what’s going on.

Most of the problems with writing PHP comes from documenting that type of magic. When the IDE is not “intelligent enough” to understand what happens behind methods like __call() and __get(), we end up with no code assistance. Considering the huge codebase behind the Eloquent ORM and the importance of database models, it can be problematic.

I don’t like too much going around installing packages if not necessary, so one of the things I’m accustomed to do is to copy-paste a bunch of PHPDocs on top of the model class declaration. This declarations allows PHPStorm (my favorite) and other IDE to understand that magic, at least at an acceptable state.

The PHPDocs I’m sharing now it’s divided in six sections: builder helpers, properties, timestamps, accessors, relationships and scopes.

Builder Helpers

First and foremost, since all methods not in the model are passed down to a new Eloquent Builder instance, we need to use the @mixin statement and point to the Builder class. This tells our IDE that we can call the mixed class methods from this class.

Is usually good idea to deal with the query() static method, which is a call to newQuery() and mostly used internally by Laravel. Opening with query() will allow your IDE to also mix in your local scopes since we also set it to return the model.

The rest of declarations are needed to tell the IDE we’re returning the same model instance instead of a generic one. The list is quite large but useful nonetheless.

After doing that, we’re free for the next section.

Properties

One of the magics of the Eloquent ORM are dynamic properties, wich return null if these don’t exist.

First, I usually add an exclusive line for the primary key of the model since these are mostly read-only (generated by the app or the database). My User class is using a integer that’s auto-incremented by the database, and matches the $keyType value of the model itself.

From there, the list of properties mirrors the columns of the table. You can just start writing the @property statement for each with the type of the property’s value and its name.

Obviously you can point to a Collection or Carbon instance if you are casting or mutating their values, and add null if the column in the database table is nullable. You can skip any property not useful to you, like the Password and the Remember Token columns.

Timestamps

If your model is using timestamps, it’s nice to also add them in the PHPDoc, specially if you work with them constantly. They’re safe to skip if you don’t deal with timestamps, but I do deal with them in some projects, like when you need to calculate the expiration from the creation time.

Note that these use Laravel’s Carbon class, not the native Carbon instance, but the differences are minimal.

Accessors

The next section is separate from the normal properties since the list is not part of the table columns, but rather properties created dynamically for the model. Let’s use a simple example: the full name of the user.

Since this will be accessed as full_name, and cannot be set (since it has no mutator), we can state it as a read-only property.

I do this for every property that is accessible, and I remove the -read part of the statement if I can set it with a mutator like setFullNameAttribute().

Relations

Relations in Eloquent ORM can be accessed dynamically using the method name that declares it, like $user->posts for posts(), but you can’t set them like it were properties — for that you will need to use setRelation().

There are are two types of relations: single and multiple.

First are single relations, like Has-one, Morph-one or Belongs-to. We can add the model, and add null if we expect a null value and we’re not using withDefault().

For multiple relationships, we need to point to the Eloquent Collection “or” an array of the relationship type. For example, we can do that for the posts relation.

You could use some generics in the future, but for now this will suffice.

Local Scopes

Finally, you may need to setup some local scopes to avoid building cryptic queries instead of purposeful methods. Local scopes can be accessed from the outside without the scope prefix, so we can safely state a new method that returns a Builder instance or the model (in case we need to chain up another scope).

We need to state the Model using “or” because the IDE won’t know that we can chain another scope. Is a minimal trade-off, nonetheless.

Obviously you can/should add the parameters of the scope, apart from the builder instance.

Wrap up

This is how the model looks like in the end. You’re free to edit the PHPDocs or copy-paste them into your project. I added these to Larawiz long time ago, so when I make small projects I don’t need to copy-paste them in every model.

--

--

Italo Baeza Cabrera

Graphic Designer graduate. Full Stack Web Developer. Retired Tech & Gaming Editor. https://italobc.com