DEV Community

Cover image for Laravel’s ForwardsCalls trait
Donato Riccio
Donato Riccio

Posted on

Laravel’s ForwardsCalls trait

In the previous article, Giuliano showed us how to leverage PHP’s Magic Methods and Late Biding to create a powerful architecture.

This, however, is neither a secret nor a new cutting-edge OOP pattern.

This same system has been used in Laravel since version 4.0* albeit in the more PHP plain way, using call_user_func_array (Laravel Model Class).

The ForwardsCalls trait has been present since version 5.7 and if you read the first part of this series you are already familiar with the concept of using this method to simplify your development. Pretty cool, right?

The devs working on the Laravel Framework thought the exact same thing (I suppose), and created the ForwardsCalls trait.

NOTE
In this article we will reference Laravel 9.x, but the principles should be valid for all the versions that implement the ForwardsCalls trait.

Table of contents

Everyone's using it!

One of the first instances where every developer using Laravel takes advantage of this approach is when they query their database through a Model.

Look at this line of code extracted from a class called ProjectController:

$projects = Project::orderByDesc('id')->get();
Enter fullscreen mode Exit fullscreen mode

Pretty ordinary, right?

If you would dive deeper in the orderByDesc method, where would you go looking?
Probably inside the Project class.

Now, if you have some experience with Laravel, you already know this Project class is almost empty because it inherits all of its properties and methods from the Model abstract class.

Inside the Model class we go!

The Model Class

Once here we can see that this abstract class implements a lot of interfaces and uses a good number of traits, but we are looking for a specific method: orderByDesc.

So we open the find window, type our string and find… nothing.

The method is not defined in this class. We can search inside every trait but without results. Seems like the orderByDesc method is non-existent.

Every good developer should now think: how is this possible?

The answer lies in the ForwardsCalls trait, PHP Magic Methods (__call and __callStatic), and in the Late Binding!
In the first part of this series, Giuliano did a great job explaining how to re-create this pattern with a couple of classes in plain PHP, so I will not repeat what he already said.

Long story short: the Model class makes use of the ForwardsCalls trait (you can see that just by opening the class).
If you start to explore this class you will find both the __call and __callStatic methods and see the __callStatic method do a Late Binding to invoke the requested method as non-static:

return (new static)->$method(...$parameters);
Enter fullscreen mode Exit fullscreen mode

This way every static method not defined inside the Model class will be redirected to a non-static method (still not defined in this class) and will trigger the __call magic method.

Inside the __call magic method we can see a couple of if-blocks, but what we are looking for is the last line:

return $this->forwardCallTo($this->newQuery(), $method, $parameters);
Enter fullscreen mode Exit fullscreen mode

What’s going on here?

The ForwardsCalls trait

The previous code is calling the forwardCallTo method, defined inside the ForwardsCalls trait (what a surprise!), passing along 3 parameters:

  • the first is a Query Builder instance (where the orderByDesc method is defined!)
  • the second is the name of the method we invoked (again, orderByDesc)
  • the third is the parameters we used ('id' in the previous example)

Now is the right moment to ask: why didn’t the developers keep using the more straightforward call_user_func_array method?

There are a good number of reasons understandable by looking at the ForwardsCalls trait:

  • the framework has better exception management (look at how the exceptions are handled inside this trait)
  • the framework has more flexibility (look at both forwardCallTo and forwardDecoratedCallTo methods)
  • the developers avoid code repetition inside every class (they just need to use the trait and write a couple of lines for the magic methods)
  • and more...

This is a great example of a developer-friendly pattern 🙂

Digging deeper

How could we take advantage of this pattern?
It's time to put our hands on some code!

Just imagine this scenario: you are developing an e-commerce and now you have the need to export your data in various formats (i.e. XML and CSV).
Because you don't need a UI to get these exports, you will create an Artisan Command to run your code right from the terminal.

Pre-requisite: ExportData - part one

Don't be scared if you are not familiar with the creation of Artisan Commands: the code is super simple and you don't need to understand it to follow this tutorial.
To keep things even more simple our export classes will not really export data.

NOTE
If you want to learn about Artisan Commands check out the corresponding section inside the Laravel documentation

To create a new command we just need to run:

php artisan make:command ExportData
Enter fullscreen mode Exit fullscreen mode

This command will create the ExportData class inside app\Console\Commands.
Looking at the code we can clearly see two properties and the handle method. Maybe another time I will better explain how Artisan Commands works, but it's not the point of this article.

Quoting directly from the documentation:

After generating your command, you should define appropriate values for the signature and description properties of the class. These properties will be used when displaying your command on the list screen. The signature property also allows you to define your command's input expectations. The handle method will be called when your command is executed. You may place your command logic in this method.

So let's copy this code for the $signature property:

protected $signature = 'data:export
                        {type : The export format: XML, CSV}
                        {class : The class to export: Product, User}';
Enter fullscreen mode Exit fullscreen mode

And this is for the $description property:

protected $description = 'Export selected data in the given format';
Enter fullscreen mode Exit fullscreen mode

In the handle method we will echo the sentence "it works" and test everything running the command:

php artisan data:export xml product
Enter fullscreen mode Exit fullscreen mode

If you see it works in the terminal, great! You just successfully ran your custom command!
Now it's time to create the export logic and make use of the ForwardsCalls trait.

The Exporter Logic

Our exporter will be structured this way:

  • The main ExportManager class, taking care of the general logic
  • The IExporter interface, defining how the single exporter is implemented
  • A couple of classes for the exporter logic: CsvExporter and XmlExporter implementing IExporter

All these classes are placed inside app\Support (the full project is available on GitHub).
How do they work?

ExportManager

The ExportManager use the ForwardsCalls to redirect most of the calls to the dedicated exporter class.
Inside this class, you can see both __call and __callStatic magic methods, a couple of static methods (list and sendExport), and the private method getExporter.
If you read the first part of this article's series you are already familiar with how these magic methods work.

The __call method gets the type and model from the $parameters array and capitalizes them (i.e. xml -> Xml, PRODUCT -> Product).
After that it calls the forwardCallTo method, passing an instance of the selected Exporter, the $method called by our code, and the $parameters array (just in case we should need it).

To get the correct Exporter instance (i.e. CsvExporter, XmlExporter, etc...) the ExportManager use the getExporter method.
This method returns an instance created dynamically from both $type and $model parameters.

/**
 * Create an exporter instance
 * 
 * @param string $type the Exporter type (i.e. Csv)
 * @param string $model the model to use in the export (i.e. Product)
 * 
 * @return mixed an instance of the exporter (i.e. CsvExporter, XmlExporter, etc...)
 */
private function getExporter($type, $model)
{
    try {

        // $type = Csv -> App\Support\Exporters\CsvExoporter
        $exporter = "App\\Support\\Exporters\\{$type}Exporter";

        // $model = Product -> App\Models\Product
        $data = "App\\Models\\{$model}";

        // new App\Support\Exporters\CsvExoporter(App\Models\Product)
        return new $exporter($data);
    } catch (Error | Exception $e) {
        throw $e;
    }
}
Enter fullscreen mode Exit fullscreen mode

This way we get the instance of the export, initialized with the model it will use to get the data from the database.
The ExportManager class act both as a manager for the exports (it can list them or send a particular report to the administrator) and as a switch to call the class needed for the selected type of export (using the Dynamic Class Instantiation).

IExporter, CsvExporter, XmlExporter

The IExporter interface allows us to create code that specifies which methods the exporters must implement, without having to define how these methods are implemented:

interface IExporter
{
    /**
     * Get the data from the model and prepare the export
     */
    public function prepareExport();

    /**
     * Create the export
     */
    public function export();

    /**
     * Save the export inside the database
     */
    public function save();
}
Enter fullscreen mode Exit fullscreen mode

Using an interface we define the basic standard for all the exporters we will create, allowing us to create objects of different classes that may be used interchangeably because they implement the same interface.
This is what we want for all our current and future exporters.

Now let's take a look at one of the exporters (for the sake of simplicity both the exporters do almost the same things, but of course, they can do different operations).

class CsvExporter implements IExporter
{
    public static $filetype = "CSV";
    private $items;
    private $model;
    public $lastExportId;

    public function __construct($_model)
    {
        $this->model = $_model;
    }

    public function prepareExport()
    {
        $this->items = $this->model::all();

        echo "Preparing export of " . get_class(new $this->model);
        echo "\n";
    }

    public function export()
    {
        $this->prepareExport();

        echo "{$this->items->count()} records exported in " . static::$filetype . " format";
        echo "\n";

        return $this;
    }

    public function save()
    {
        $this->lastExportId = rand(1, 1305);

        echo "Export n. {$this->lastExportId} saved inside database";
        echo "\n";

        return $this;
    }
}
Enter fullscreen mode Exit fullscreen mode

Feel free to download the example project and add your custom exporter! Get your hands dirty it's a great way to wrap your head around this kind of pattern.

Remember to create a DB and run all the migrations and seeder before running the data:export command.

Basically, every exporter is instantiated with a model, use this model to get all the records and simulate a DB writing.
The export and save methods return an instance of the export class so that we can chain the methods calls and also get the instance in our DataExport class.

NOTE
We get the exporter instance (CsvExporter, XmlExporter, etc...) because we are using the forwardCallTo method inside __call in our ExportManager class.

If we had used the forwardDecoratedCallTo method, we would have got an instance of ExportManager. Try it!

With all this logic out of the way we have only one more thing to do: add the correct code in the DataExport handle method.

ExportData - part two

Remember our handle method that does nothing more than echo "it works"? It's time to make it work for real!
We need to import the ExportManager class by writing use App\Support\ExportManager; before the class definition.

In the handle method we can now call the export method on the ExportManager (even if it's defined on the specific exporter) and get the correct instance.
With this instance we can then call sendReport, pass it the ID of the export we just created, and have it sent to the administrator inbox.

public function handle()
{
    $export = ExportManager::export($this->argument('type'), $this->argument('class'))->save();
    ExportManager::sendExport($export->lastExportId);
}
Enter fullscreen mode Exit fullscreen mode
Run the command

Finally, you can test if everything it's working by running this command:

php artisan export:data csv product
Enter fullscreen mode Exit fullscreen mode

Our data is exported in the format we want! Yay!
The logic we implemented allowed us to create a very flexible exporter: we can export all the Models defined in our Laravel application and should we need a different kind of exporter (i.e. PdfExporter, ExcelExporter, etc...) we just need to create a new class caring only about the exporting logic and nothing more!

In conclusion

It was a wild ride, wasn't it?
I hope this helped you better understand one of the gems hidden inside the Laravel Framework!

The ForwardsCalls trait gives us a powerful pattern to leverage when developing inside a Laravel application. It gives us a lot of flexibility and the possibility to keep our classes clean, well defined but still able to communicate.

Giuliano also showed us how to bring this pattern outside Laravel and use it virtually in every PHP project we want.

I enjoyed writing this article's series with Giuliano and I really hope this article inspired you to dig a little bit inside the code of the libraries and frameworks you use.

Here you can find the first half made by Giuliano. ;)

If this article was helpful or want to start a conversation, feel free to reach out in the comments or here @gosty93 and @donato-riccio-wda
We'll be happy to receive any feedback or ideas for future articles and tutorials.
Happy Coding | _ 0

  • This is the earliest version you could find on their public repository

Top comments (0)