Back to Course |
PHP for Laravel Developers

Static, New Object and Laravel Auto-Resolving

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.


Static Methods: Quick Shortcuts

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 object
return $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.


Service Classes: Static Methods

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?


Service Classes: Constructor with Parameters

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().


Dependency Injection and Auto-Resolving

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: