One limitation of the PHP language is the inability to create multiple constructors. In Java, for example, you can create different constructors as long as they have unique method signatures. This allows the programmer to create distinct ways to create the same object depending on the situation.
Because the PHP language lacks this feature, we solve this problem using different software design patterns. Most of the time, we just create a factory. The factory then deals with these different use-cases for creating an object.
One lesser known type of factory pattern is the static factory method pattern. This pattern allows us to address the multiple constructor problem. But it allows us to do so without needing to create a factory class which we might not need otherwise.
Now, I’ve written about how to use the static factory method pattern with WordPress before. But, as I’ve used Laravel more with client projects and building Ymir, I feel it’s worth revisiting it in that context as well. Specifically, I think it’s worth looking at how you can use it with Eloquent models.
Static factory methods are already in Eloquent
Eloquent has several ways of initializing new model objects. It has a constructor which you can use. But there are also the Model::create
and Model::make
methods that map to Eloquent Builder
class. (There are also more use case specific methods such as Model::firstOrCreate
or Model::firstOrNew
.)
These methods are static factory methods themselves. The issue is that they’re very generic. They all take attributes
as their parameter.
That’s because these methods will mass assign these attributes to the model when you create it. (Well, that’s unless the model has the attributes guarded.) If you don’t want to use mass assignment, you need to assign each attribute one at a time.
An excellent starting point
The great thing about these methods being generic is that we can use them as a starting point for our own methods. What we’ll do is create our own methods, which will then call either the create
or make
static methods. Let’s look at an example.
namespace App; use Illuminate\Database\Eloquent\Model; class BlogPost extends Model { /** * Make a new BlogPost object from the given User object and save it to the database. */ public static function createFromUser(User $author, string $title): self { return self::create([ 'user_id' => $author->id, 'title' => $title, ]); } // ... }
Above is a model called BlogPost
. The class has a static factory method called createFromUser
. The method has two parameters: author
and title
which we type hinted.
The ability to have type hints is another reason why creating custom static factory methods can be beneficial. It allows you to have increased type safety. This is something that I always value when writing code.
The rest of the static factory method just calls the generic Model::create
method. We pass it an array with the attributes we want it to assign to the new BlogPost
object. The array contains the ID of the user who authored the post and the title.
What about the make method?
For a lot of Laravel developers, the createFromUser
method will be enough. But I tend to prefer to use the make
method and delay saving the model until I’m done modifying it. So what I’d need is a makeFromUser
static factory method.
Having both a createFromUser
and makeFromUser
method is actually quite easy to do. We can just do the same thing that Laravel does with create
and make
. If you look at the create
and make
methods in the Builder
class, they look like this:
namespace Illuminate\Database\Eloquent; class Builder { // ... public function create(array $attributes = []) { return tap($this->newModelInstance($attributes), function ($instance) { $instance->save(); }); } // ... public function make(array $attributes = []) { return $this->newModelInstance($attributes); } // ... }
Basically, the only difference between both methods is the use of the tap
helper function to save the model we made. That’s it. So we can just do the same thing when building our createFromUser
and makeFromUser
methods.
namespace App; use Illuminate\Database\Eloquent\Model; class BlogPost extends Model { /** * Make a new BlogPost object from the given User object and save it to the database. */ public static function createFromUser(User $author, string $title): self { return tap(self::makeFromUser($author, $title))->save() } /** * Make a new BlogPost object from the given User object. */ public static function makeFromUser(User $author, string $title): self { return self::make([ 'user_id' => $author->id, 'title' => $title, ]); } // ... }
Normally, the save
method returns a boolean value. So we’d have to break everything into multiple lines of code. By using tap
helper function, we can use the save
method, but still return the new BlogPost
post object.
Another example: Form Requests
Static factory methods are great with other Eloquent models. But they also synergize well with other Laravel features. One of those is form requests.
Form requests are a cool feature that let you create custom Request
classes. These custom Request
classes are convenient for storing all your validation code. But you can also use them to offer helper methods for interacting with an HTTP request.
<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class CreateBlogPostRequest extends FormRequest { /** * Determine if the user is authorized to make this request. */ public function authorize(): bool { return true; } /** * Determine if the user is authorized to make this request. */ public function getTitle(): string { return $this->get('title'); } /** * Get the validation rules that apply to the request. */ public function rules(): array { return [ 'title' => 'required|string', ]; } }
So above is the CreateBlogPostRequest
class which we can use to create our BlogPost
objects. It has three methods: authorize
, getTitle
and rules
. The authorize
method determines if the current user can process the request. We just return true
which means any user can process this request.
Meanwhile, the getTitle
method just returns the title
value from the request. This is why the rules
method, which returns the validation rules to use with the request, has validation rules for title
. We need to make sure it’s always present and that it’s a string.
Creating the static factory method for the form request
Now, we can just update our BlogPost
class. We’ll add a static factory method that takes a CreateBlogPostRequest
object as an argument. You can see the modified class below.
namespace App; use App\Http\Requests\CreateBlogPostRequest; use Illuminate\Database\Eloquent\Model; class BlogPost extends Model { /** * Make a new BlogPost object from the given form request object and save it to the database. */ public static function createFromRequest(CreateBlogPostRequest $request): self { return tap(self::makeFromRequest($request))->save() } /** * Make a new BlogPost object from the given form request object. */ public static function makeFromRequest(CreateBlogPostRequest $request): self { return self::makeFromUser($request->user(), $request->getTitle()); } // ... }
We added two new static factory methods: makeFromRequest
and createFromRequest
. These two methods behave the same as our previous two static factory methods. The create method calls the make method and saves the model using the tap
function.
The interesting thing is what we do in the makeFromRequest
method. We use the returned values from the CreateBlogPostRequest
methods and pass them to the makeFromUser
method. This lets us leverage our code from the other static factory method while offering an alternative way to create a BlogPost
object.
When should you use a traditional factory?
As you can see, static factory methods are often more than sufficient for your model creation needs. That said, there are cases where I’ve had to use a traditional factory class combined with static factory methods. A common one is if your model has a one-to-many relationship and you need to create both the parent and child models at the same time.
namespace App; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; class Database extends Model { // ... public function users(): HasMany { return $this->hasMany(DatabaseUser::class, 'database_id'); } // ... }
To demonstrate this, let’s take the Database
model shown above. It has a one-to-many relationship with the DatabaseUser
model. When creating a Database
object, you’ll want to at least add one DatabaseUser
for the admin user.
Now, you could create a static factory method in the Database
class that also creates the DatabaseUser
object. But I feel like it defeats the purpose of the static factory method which is to be a custom constructor. This is really a situation where you’d want a factory class like this one.
namespace App\Factories; use App\Database; use App\DatabaseUser; class DatabaseFactory { public function create(string $name, string $username, string $password): Database { $database = Database::create([ 'name' => $name, ]); $database->users()->create([ 'username' => $username, 'password' => $password, ]); return $database; } }
So the DatabaseFactory
class above has a single method called create
. You pass it three arguments: name
, username
and password
. We then use these three arguments to create our Database
and DatabaseUser
objects in the database.
The method starts by creating the Database
object. We then call the users
method which returns the HasMany
relationship object. We then call its create
method which will create the DatabaseUser
and associate it with the Database
object.
We finish by returning the created Database
object. And like that, our create
method created two objects in the database at once. This would have made less sense as a static factory method in the Database
class.
Combining with static factory methods
That said, this doesn’t mean that you can’t use static factory methods with a factory class. Both factory patterns serve different purposes, so there’s nothing wrong with using them both at the same time.
namespace App\Factories; use App\Database; use App\DatabaseUser; class DatabaseFactory { public function create(string $name, string $username, string $password): Database { $database = Database::createFromName($name); DatabaseUser::createFromDatabase($database, $username, $password); return $database; } }
Here’s the DatabaseFactory
class updated to use static factory methods. We create the Database
object using the createFromName
method. We then pass it to the createFromDatabase
static factory method to create the DatabaseUser
object.
Bringing a bit more structure to your Eloquent models
So that wraps up our look at static factory methods. Like all software patterns, you don’t want to use them for every problem you encounter. But, with Eloquent, they’ve been an invaluable tool to bring some structure to creating model objects.