Have you ever wondered what happens behind the scenes in the Model when we call User::all()
? This is, of course, only one example. In reality, you should understand the difference between such static calls and various other ways to create a new object. Let's explore the details.
As I mentioned, such a Class::method()
call is a static
way. Under the hood, it's this:
Illuminate/Database/Eloquent/Model.php
public static function all($columns = ['*']){ return static::query()->get( is_array($columns) ? $columns : func_get_args() );}
As you can see, under the hood, it calls another static method query(),
which returns a Query Builder object.
This is a chained one-liner. Actually, under the hood, two things are happening:
$query = static::query(); // Building a query objectreturn $query->get();
In other words, static ::all()
method is an "abstraction" wrapper shortcut to make our job easier: we can just type Model::all()
instead of building a query with Model::query()->get()
every time. After all, this is the framework's purpose: to help us write code quicker, right?
But from the OOP perspective, there's a deeper meaning behind static
. It actually means "stateless", so we don't care about the property values of that class properties at any time. We call the method applicable for any User, not for a particular User object.
Compare this:
// We work with a SPECIFIC user here$user = new User();$user->name = 'John';$user->save();
Versus this:
// We don't care about a specific user here$users = User::all();
So generally, in Laravel and other PHP/Laravel packages, you will often find static methods like "helpers" that are just shortcuts for something deeper happening under the hood.
Let's get outside of the framework code into our own custom application code. Developers often create so-called Service
classes to offload some heavy logic from the Controllers.
The question is: how should that Service be initiated to call a method from it?
For example, you want to create a User and create a UserService
:
app/Services/UserService.php:
namespace App\Services; use App\Models\User; class UserService { public static function store(array $userData): User { $user = User::create($userData); // ... // More logic with that user: // - attach more data // - fire some events // - send notifications // - etc. return $user; } }
As you can see, this store()
method is static
. This means that from Controller, we don't need to create a new UserService()
, we may just call it like this:
app/Http/Controllers/UserController.php:
use App\Services\UserService;use App\Http\Requests\StoreUserRequest; class UserController extends Controller{ public function store(StoreUserRequest $request) { UserService::store($request->validated()); // ... more logic and return }}
And this is totally fine. In this case, the store()
method doesn't care about a specific User object - it just gets the array of data and saves it into the database. So, it's "stateless".
Now, what if we need to pass a parameter to such a service?
Let's imagine that we need a UserService
with a specific condition/parameter, like the company name of that user, to be used inside of one/multiple methods of that Service class.
So, we need to pass that parameter to the constructor method of that whole class:
app/Services/UserService.php:
class UserService { public function __construct(public string $companyName) {} public static function store(array $userData): User { // $this->companyName would be used in multiple methods $userData['company'] = $this->companyName; return User::create($userData); } }
Notice: to auto-assign this parameter to $this->companyName
, we're using a PHP 8 syntax of Constructor property promotion.
Our method store()
is not static
anymore, because it uses the $this->companyName
property, which means it cares about a specific object of the UserService
. In other words, some other Controller method may call that UserService
with another $companyName
value and get totally different results.
Now, the question is, how to call it from the Controller?
The UserService::store($data)
wouldn't work anymore because we don't have the $companyName
as a parameter here.
Then, we have to "manually" create the Service class like this:
app/Http/Controllers/UserController.php:
use App\Services\UserService;use App\Http\Requests\StoreUserRequest; class UserController extends Controller{ public function store(StoreUserRequest $request) { (new UserService('Microsoft'))->store($request->validated()); // Or, if you don't like long one-liners: $userService = new UserService('Microsoft'); $userService->store($request->validated()); // ... more logic and return }}
As you can see, we're creating an object of that Service class with a specific company, "Microsoft", and then calling the method store()
.
Now, have you noticed a specific syntax of the StoreUserRequest $request
parameter? Let's talk about that one cause it's another powerful feature of the Laravel framework.
If you type-hint the class name in your parameter of the Controller, Laravel will automatically create a class object for you.
In other words, in general PHP, we would do something like this:
public function store(){ $request = new StoreUserRequest(); (new UserService('Microsoft'))->store($request->validated());}
So, Laravel helps us with so-called "auto-resolving" the class instance so that we can use the $request
variable in multiple statements of our method.
This will work when that class doesn't require any additional parameters. Otherwise, PHP would throw an error asking for those parameters.
So, this is the OOP theory with a few practical examples so you would understand what's happening under the hood and which object creation way you should use.
You may also read a more in-depth tutorial: