skip to content

In Laravel, how does a real-time facade imported as use Facades\App\Crm\LeadScorer; work, and what does it cost you?

level: middleimportance: nice to knowfreq 24%

answer

  1. a namespace prefix, not a new class
  2. AliasLoader prepended to the autoloader
  3. stub written to storage/framework/cache
  4. accessor = class name after the prefix
  5. hidden dependency, only the use line

basics

~20 s

Prefixing an import with Facades\ makes Laravel's AliasLoader generate a facade class whose getFacadeAccessor() returns the rest of the name, so static calls resolve that class from the container. It saves injection but hides the dependency.

solid answer

~40 s

A real-time facade turns any class into a facade without writing one. `RegisterFacades` registers `AliasLoader` at the front of the autoloader stack. When PHP needs a class whose name starts with `Facades\`, the loader builds one from its `facade.stub`: namespace `Facades\App\Crm`, class `LeadScorer extends Facade`, and a `getFacadeAccessor()` returning `'App\Crm\LeadScorer'`. It writes that file atomically to `storage/framework/cache/facade-<sha1>.php` and requires it. `LeadScorer::score($lead)` then works like any facade: the container resolves `App\Crm\LeadScorer`, auto-wiring its constructor or using an interface binding, and the resolved object is reused. The costs: the dependency appears only in a `use` line, the storage directory must be writable, and an unbound interface fails with a `BindingResolutionException`. `php artisan cache:clear` deletes the generated files.

code

php · 15 lines
php
<?php

namespace App\Models;

use Facades\App\Crm\LeadScorer;
use Illuminate\Database\Eloquent\Model;

class Lead extends Model
{
    public function rescore(): void
    {
        // Resolves App\Crm\LeadScorer from the container on first use.
        $this->update(['score' => LeadScorer::score($this)]);
    }
}

go deeper

for a junior

Recognise the Facades\ import prefix and know it lets you call a normal class's methods statically through the container.

for a middle

Explain AliasLoader, the generated stub in storage/framework/cache and that the accessor is the class name after the prefix.

for a senior

Weigh the convenience against hidden dependencies and deployment details such as a writable cache directory and cache:clear deleting the files.

for a principal

Decide whether real-time facades are allowed at all in shared code, since they make dependencies the hardest of all styles to spot in review.

## The idea Laravel's documentation describes **real-time facades** as a way to "treat any class in your application as if it was a facade". Instead of creating a facade class, you prefix the import with the `Facades` namespace: ```php use Facades\App\Crm\LeadScorer; LeadScorer::score($lead); ``` The class after the prefix, `App\Crm\LeadScorer`, is an ordinary class (or interface) with instance methods. The facade is generated for you. ## How the class comes into existence 1. During bootstrap, the `RegisterFacades` bootstrapper creates `Illuminate\Foundation\AliasLoader` and calls `register()`, which prepends its `load()` method to PHP's autoloader stack with `spl_autoload_register(..., true, true)`. 2. When PHP first meets `Facades\App\Crm\LeadScorer`, it asks the autoloaders. `AliasLoader::load()` sees the name begins with its `$facadeNamespace`, `'Facades\\'`, and calls `loadFacade()`. 3. `ensureFacadeExists()` looks for `storage/framework/cache/facade-` plus the SHA-1 of the alias plus `.php`. If the file is missing, it fills `Foundation/stubs/facade.stub` with the namespace (`Facades\App\Crm`), the class name (`LeadScorer`) and the target (`App\Crm\LeadScorer`), writes it to a temp file and renames it into place, an atomic write that avoids two processes reading a half-written file. 4. The loader `require`s the file. The generated class extends `Illuminate\Support\Facades\Facade`, carries a `@mixin \App\Crm\LeadScorer` docblock for editors, and returns `'App\Crm\LeadScorer'` from `getFacadeAccessor()`. From there it is a normal facade: `__callStatic()` resolves the accessor through the container and stores the result in the resolved-instance map. ## What gets resolved - A **concrete class** is auto-wired: the container reflects on its constructor and injects each type-hinted dependency. - An **interface** needs a binding. Without one, the container throws `Illuminate\Contracts\Container\BindingResolutionException` with "Target [...] is not instantiable." - A binding registered with `bind()` is still resolved **once** and reused by the facade, because facades cache their root by default. ## What it costs | Concern | With a real-time facade | With constructor injection | |---|---|---| | Where the dependency is declared | only the `use` statement | the constructor signature | | Class size feedback | none; any method can add calls | a growing constructor is visible | | Runtime files | generated PHP in `storage/framework/cache` | none | | Works outside a booted app | no; needs the facade application set | yes, pass collaborators by hand | | Editor support | via the generated `@mixin` docblock | native types | Operationally: - The generated files live in the **cache directory**, so `php artisan cache:clear` removes them (its `flushFacades()` step deletes every `facade-*.php` there) and they are regenerated on next use. - The web and worker users need **write access** to `storage/framework/cache`, as they already do for other framework caches. - The generated file is keyed by the SHA-1 of the alias, so renaming or moving the target class simply produces a new file; the stale one is harmless until the next `cache:clear` removes it. - A reader who only scans the class body sees `LeadScorer::score()` and cannot tell whether it is a facade, a static method or a real-time facade without checking the imports. ## A real-time facade versus a hand-written one | Aspect | Hand-written facade | Real-time facade | |---|---|---| | Source file | a class you commit, e.g. `App\Support\Facades\Scorer` | generated from `facade.stub` at runtime | | Accessor | whatever `getFacadeAccessor()` returns: alias, class or interface | always the full name after `Facades\` | | Custom static helpers or `$cached = false` | possible, you own the class | not possible, the stub is fixed | | Discoverability | a named facade class a reader can open | only the import prefix | The fixed stub is the main technical limit: a real-time facade can only proxy a container key equal to a class or interface name, and it always uses the base class's default caching. If you need a short alias key, a custom static method or per-call resolution, write the facade by hand. ## When it earns its place The documentation's example is a model method that needs a collaborator: injecting it would force every caller of `publish()` to pass a service. A real-time facade keeps the call site simple while the collaborator stays swappable through the container. It suits occasional glue in models, Blade-adjacent code or quick scripts. For services with real business logic, explicit injection keeps dependencies visible and the class usable without a booted application.

  • What happens if the class after the Facades\ prefix is an interface with no container binding?
    The facade class is still generated, but the first static call asks the container to build the interface, which fails with `Illuminate\Contracts\Container\BindingResolutionException`: "Target [...] is not instantiable." Bind the interface to an implementation in a service provider, or point the facade at a concrete class.
  • Why does a real-time facade need a writable storage directory in production?
    The first time a `Facades\...` class is autoloaded, `AliasLoader` writes the generated class to `storage/framework/cache/facade-<sha1>.php` and then requires it. If that directory is read-only and the file does not already exist, moving the generated file into place fails and the class cannot load.

saying these in an interview costs you the question

  • Real-time facades convert the target class's methods into static methods
  • You must register a real-time facade in the config/app.php aliases array
  • A real-time facade builds a new object with new on every call
  • Real-time facades are generated in memory and never touch the disk
  • Only interfaces, not concrete classes, can be used as real-time facades