Using Generics in Laravel Eloquent
Generics in programming are a feature that allows you to define functions, classes, and data structures with placeholder types. This enables you to write more flexible and reusable code.
Generics are commonly used in statically typed languages like Java, C#, and C++. Dynamic languages like Python also support it.
Why generics?
Using generics can have multiple benefits:
- Type Safety: Generics ensure that the relationships return the correct type of models, reducing the risk of runtime errors due to incorrect model types.
- Better IDE Support: IDEs can provide better autocompletion and type checking when generics are used, making development easier and reducing the likelihood of errors.
- Improved Readability: The use of generics makes the code more self-documenting. It is clear from the method signatures what type of relationships and models are involved.
Here’s what a typical generic function in Java looks like.
public <T> T getFirstElement(List<T> list) {
return list.get(0);
}
In this example, the getFirstElement
function takes a List
of type T
and returns an element of type T
. The T
is a placeholder type that can be replaced with any other type when the function is called.
Using Generics in Laravel
When it comes to PHP, generics still seems to be a far-fetched dream but that doesn’t stop us from using them thanks to tools like PHPStan or PSalm which lets you add static type checking to your PHP code.
We can define generics as comments or annotations to provide type hints to the IDE and tools like PHPStan can use them to perform static analysis.
In the recent Laravel release, a community member has added the support to use generics in Eloquent models. The role of generics here is to add the ability to specify the types that a class or method will work with.
Generics provide a mechanism for type hinting and better code readability, especially when dealing with relationships in Eloquent models.
Take the following for example.
class User extends Model
{
/** @return HasOne<Address, $this> */
public function address(): HasOne
{
return $this->hasOne(Address::class);
}
/** @return HasMany<Post, $this> */
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
}
Explanation
As you can tell, we can now specify the return types of the relationships but on top of that, we can also specify the type of the model that the relationship is related to.
-
HasOne<Address, $this>
: This indicates that the address method returns aHasOne
relationship where the related model isAddress
and the parent model is the currentUser
instance. -
HasMany<Post, $this>
: This indicates that the posts method returns aHasMany
relationship where the related model isPost
and the parent model is the currentUser
instance.
The same goes for other methods like belongsTo
, hasManyThrough
, belongsToMany
, etc.
Now, if there are inconsistencies in the defined types, Larastan (a PHPStan wrapper for Laravel), for instance, will throw a detailed error message that points out the issue.
Take the following for example.
/** @return HasMany<Comment, $this> */
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
In this example, the posts method specifies a generic type HasMany<Comment, $this>
, but it returns a relationship with Post::class
.
Running Larastan (using the command ./vendor/bin/phpstan analyse
) will produce the following error message.
Line app/Models/User.php
------ ----------------------------------------------------------------------------
10 Method App\Models\User::posts() should return
App\Models\Relations\HasMany<App\Models\Comment, App\Models\User>
but returns App\Models\Relations\HasMany<App\Models\Post, App\Models\User>.
------ ----------------------------------------------------------------------------
[ERROR] Found 1 error
That’s how generics can help us write more robust and maintainable code in Laravel and save us from ugly runtime errors.
Like this article?
Buy me a coffee👋 Hi there! I'm Amit. I write articles about all things web development. You can become a sponsor on my blog to help me continue my writing journey and get your brand in front of thousands of eyes.