Home Backend Development PHP Tutorial Laravel Under The Hood - Extending the framework

Laravel Under The Hood - Extending the framework

Dec 10, 2024 pm 06:05 PM

Laravel Under The Hood - Extending the framework

Hello ?

A few days ago, I was fixing a flaky test, and it turned out I needed some unique and valid values within my factory. Laravel wraps FakerPHP, which we usually access through the fake() helper. FakerPHP comes with modifiers like valid() and unique(), but you can use only one at a time, so you can't do fake()->unique()->valid(), which is exactly what I needed. This made me think, what if we want to create our own modifier? For example, uniqueAndValid(), or any other modifier. How can we extend the framework?

Thinking out loud

I will be dumping my train of thought.

Before jumping into any over-engineered solution, I always want to check if there is a simpler option and understand what I'm dealing with. So, let's take a look at the fake() helper:

function fake($locale = null)
{
    if (app()->bound('config')) {
        $locale ??= app('config')->get('app.faker_locale');
    }

    $locale ??= 'en_US';

    $abstract = \Faker\Generator::class.':'.$locale;

    if (! app()->bound($abstract)) {
        app()->singleton($abstract, fn () => \Faker\Factory::create($locale));
    }

    return app()->make($abstract);
}
Copy after login
Copy after login

Reading the code, we can see that Laravel binds a singleton to the container. However, if we inspect the abstract, it's a regular class that does not implement any interface, and the object is created via a factory. This complicates things. Why?

  1. Because if it were an interface, we could just create a new class that extends the base FakerGenerator class, add some new features, and rebind it to the container. But we don't have this luxury.
  2. There is a factory involved. This means it's not a simple instantiation, there is some logic being run. In this case, the factory adds some providers (PhoneNumber, Text, UserAgent, etc..). So, even if we try to rebind, we will have to use the factory, which will return the original FakerGenerator.

Solutions ?? One might think, "What's stopping us from creating our own factory that returns the new generator as outlined in point 1?" Well, nothing, we can do that, but we won't! We use a framework for several reasons, one of them being updates. What will happen if FakerPHP adds a new provider or has a major upgrade? Laravel will adjust the code, and people who haven't made any changes won't notice anything. However, we would be left out, and our code might even break (most likely). So, yes, we don't want to go that far.

So, what do we do?

Now that we've explored the basic options, we can start thinking of more advanced ones, like design patterns. We don't need an exact implementation, just something familiar to our problem. This is why I always say it's good to know them. In this case, we can "decorate" the Generator class by adding new features while maintaining the old ones. Sounds good? Let's see how!

First, let's create a new class, FakerGenerator:

function fake($locale = null)
{
    if (app()->bound('config')) {
        $locale ??= app('config')->get('app.faker_locale');
    }

    $locale ??= 'en_US';

    $abstract = \Faker\Generator::class.':'.$locale;

    if (! app()->bound($abstract)) {
        app()->singleton($abstract, fn () => \Faker\Factory::create($locale));
    }

    return app()->make($abstract);
}
Copy after login
Copy after login

This will be our "decorator" (kinda). It is a simple class that expects the base Generator as a dependency and introduces a new modifier, uniqueAndValid(). It also uses the ForwardsCalls trait from Laravel, which allows it to proxy calls to the base object.

This trait has two methods: forwardCallTo and forwardDecoratedCallTo. Use the latter when you want to chain methods on the decorated object. In our case, we will always have a single call.

We also need to implement the UniqueAndValidGenerator, which is the custom modifier, but this is not the point of the article. If you are interested in the implementation, this class is basically a mixture of the ValidGenerator and UniqueGenerator that ship with FakerPHP, you can find it here.

Now, let's extend the framework, in the AppServiceProvider:

<?php

namespace App\Support;

use Closure;
use Faker\Generator;
use Illuminate\Support\Traits\ForwardsCalls;

class FakerGenerator
{
    use ForwardsCalls;

    public function __construct(private readonly Generator $generator)
    {
    }

    public function uniqueAndValid(Closure $validator = null): UniqueAndValidGenerator
    {
        return new UniqueAndValidGenerator($this->generator, $validator);
    }

    public function __call($method, $parameters): mixed
    {
        return $this->forwardCallTo($this->generator, $method, $parameters);
    }
}
Copy after login

The extend() method checks if an abstract matching the given name has been bound to the container. If so, it overrides its value with the result of the closure, take a look:

<?php

namespace App\Providers;

use Closure;
use Faker\Generator;
use App\Support\FakerGenerator;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->extend(
            $this->fakerAbstractName(), 
            fn (Generator $base) => new FakerGenerator($base)
        );
    }

    private function fakerAbstractName(): string
    {
        // This is important, it matches the name bound by the fake() helper
        return Generator::class . ':' . app('config')->get('app.faker_locale');
    }
}
Copy after login

That's why we defined the fakerAbstractName() method, which generates the same name the fake() helper binds in the container.

Recheck the code above if you missed it, I left a comment.

Now, every time we call fake(), an instance of FakerGenerator will be returned, and we will have access to the custom modifier we introduced. Every time we invoke a call that does not exist on the FakerGenerator class, __call() will be triggered, and it will proxy it to the base Generator using the forwardCallTo() method.

That's it! I can finally do fake()->uniqueAndValid()->randomElement(), and it works like a charm!

Before we conclude, I want to point out that this is not a pure decorator pattern. However, patterns are not sacred texts; tweak them to fit your needs and solve the problem.

Conclusion

Frameworks are incredibly helpful, and Laravel comes with a lot of built-in features. However, they can't cover all the edge cases in your projects, and sometimes you might hit a dead end. When that happens, you can always extend the framework. We've seen how simple it is, and I hope you understood the main idea, which applies beyond just this Faker example.

Always start simple and look for the simplest solution to the problem. Complexity will come when it's necessary, so if basic inheritance does the trick, there's no need to implement a decorator or anything else. When you do extend the framework, ensure you don't go too far, where the loss outweighs the gain. You don't want to end up maintaining a part of the framework on your own.

The above is the detailed content of Laravel Under The Hood - Extending the framework. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Explain JSON Web Tokens (JWT) and their use case in PHP APIs. Explain JSON Web Tokens (JWT) and their use case in PHP APIs. Apr 05, 2025 am 12:04 AM

JWT is an open standard based on JSON, used to securely transmit information between parties, mainly for identity authentication and information exchange. 1. JWT consists of three parts: Header, Payload and Signature. 2. The working principle of JWT includes three steps: generating JWT, verifying JWT and parsing Payload. 3. When using JWT for authentication in PHP, JWT can be generated and verified, and user role and permission information can be included in advanced usage. 4. Common errors include signature verification failure, token expiration, and payload oversized. Debugging skills include using debugging tools and logging. 5. Performance optimization and best practices include using appropriate signature algorithms, setting validity periods reasonably,

How does session hijacking work and how can you mitigate it in PHP? How does session hijacking work and how can you mitigate it in PHP? Apr 06, 2025 am 12:02 AM

Session hijacking can be achieved through the following steps: 1. Obtain the session ID, 2. Use the session ID, 3. Keep the session active. The methods to prevent session hijacking in PHP include: 1. Use the session_regenerate_id() function to regenerate the session ID, 2. Store session data through the database, 3. Ensure that all session data is transmitted through HTTPS.

What are Enumerations (Enums) in PHP 8.1? What are Enumerations (Enums) in PHP 8.1? Apr 03, 2025 am 12:05 AM

The enumeration function in PHP8.1 enhances the clarity and type safety of the code by defining named constants. 1) Enumerations can be integers, strings or objects, improving code readability and type safety. 2) Enumeration is based on class and supports object-oriented features such as traversal and reflection. 3) Enumeration can be used for comparison and assignment to ensure type safety. 4) Enumeration supports adding methods to implement complex logic. 5) Strict type checking and error handling can avoid common errors. 6) Enumeration reduces magic value and improves maintainability, but pay attention to performance optimization.

Describe the SOLID principles and how they apply to PHP development. Describe the SOLID principles and how they apply to PHP development. Apr 03, 2025 am 12:04 AM

The application of SOLID principle in PHP development includes: 1. Single responsibility principle (SRP): Each class is responsible for only one function. 2. Open and close principle (OCP): Changes are achieved through extension rather than modification. 3. Lisch's Substitution Principle (LSP): Subclasses can replace base classes without affecting program accuracy. 4. Interface isolation principle (ISP): Use fine-grained interfaces to avoid dependencies and unused methods. 5. Dependency inversion principle (DIP): High and low-level modules rely on abstraction and are implemented through dependency injection.

How to debug CLI mode in PHPStorm? How to debug CLI mode in PHPStorm? Apr 01, 2025 pm 02:57 PM

How to debug CLI mode in PHPStorm? When developing with PHPStorm, sometimes we need to debug PHP in command line interface (CLI) mode...

How to send a POST request containing JSON data using PHP's cURL library? How to send a POST request containing JSON data using PHP's cURL library? Apr 01, 2025 pm 03:12 PM

Sending JSON data using PHP's cURL library In PHP development, it is often necessary to interact with external APIs. One of the common ways is to use cURL library to send POST�...

Explain late static binding in PHP (static::). Explain late static binding in PHP (static::). Apr 03, 2025 am 12:04 AM

Static binding (static::) implements late static binding (LSB) in PHP, allowing calling classes to be referenced in static contexts rather than defining classes. 1) The parsing process is performed at runtime, 2) Look up the call class in the inheritance relationship, 3) It may bring performance overhead.

See all articles