Memoisation in Laravel Using the "once" Helper

Ashley Allen ยท 19th January 2026

Introduction

When building Laravel applications, there may be times when you need to use a value multiple times within the same request lifecycle, but calculating it is expensive or time-consuming. A possible solution optimise performance in these scenarios is "memoisation" (or "memoization").

Memoisation is the technique of temporarily storing the result of an expensive function call so that subsequent calls to that function return the cached result rather than recalculating it. If you've used caching before, memoisation is pretty similar, except that it generally lasts only for the duration of a single request, at least in the context of Laravel. Depending on the use case, it can help to reduce calls to databases, external APIs, cache stores, or methods that take a long time to run.

In this article, we're going to take a look at how to use Laravel's built-in once helper function to implement memoisation in your applications. We'll also explore the differences between using once inside functions and within object methods, including static methods. We'll then explore how Laravel Octane integrates with the once helper to ensure that memoised values are reset between requests. Finally, we'll briefly discuss other approaches to memoisation in Laravel.

Using the "once" Helper in Laravel

The once helper function in Laravel is really simple to use. It accepts a closure as an argument; the first time it's called, it returns the closure's result and stores it for future calls within the same request. On subsequent calls, it returns the cached result instead of executing the closure again. Unless the value is cleared, the cached result will persist for the duration of the request. After the request completes, the cached value is discarded. This means a value memoised with once in a request cannot be accessed in any other requests.

Let's take a look at a simple example. So we can focus on the memoisation aspect rather than any specific business logic, we'll just imagine our calculateExpensiveValue function performs an expensive calculation. We'll return the result of random_int to highlight that the value is only calculated once.

function calculateExpensiveValue(): int
{
    // Imagine this function performs an expensive calculation. We'll
    // use the "random_int" function to simulate this.

return random_int(min: 1, max: 100);
}

echo calculateExpensiveValue(); // e.g., outputs: 42
echo calculateExpensiveValue(); // e.g., outputs: 57
echo calculateExpensiveValue(); // e.g., outputs: 13

As we can see, the output changes each time we call the function because it recalculates the value every time.

Let's take a look at what happens when we wrap the calculation in the once helper:

return once( static fn (): int => random_int(min: 1, max: 100) ); }

echo calculateExpensiveValue(); // e.g., outputs: 42 echo calculateExpensiveValue(); // outputs: 42 echo calculateExpensiveValue(); // outputs: 42


As we can see, the output remains the same for each call to `calculateExpensiveValue` because the result of the first call is cached and returned on subsequent calls.

## Using "once" Inside Objects

There are a few "gotchas" to be aware of when using the `once` helper inside objects, particularly when it comes to instance methods versus static methods. So I wanted to quickly cover them here so you don't make the same mistakes as I've made in the past.

### Using "once" in Methods

Let's stick with our previous example, but this time we'll put the `calculateExpensiveValue` function inside a class as a method:

```php
declare(strict_types=1);

namespace App\Services;

final readonly class NumberService
{
    public function calculateExpensiveValue(): int
    {
        return once(
            static fn (): int => random_int(min: 1, max: 100)
        );
    }
}

Now let's call this method a few times:

use App\Services\NumberService;

$numberService = new NumberService();

dump($numberService->calculateExpensiveValue()); // e.g., outputs: 42
dump($numberService->calculateExpensiveValue()); // outputs: 42
dump($numberService->calculateExpensiveValue()); // outputs: 42

$anotherNumberService = new NumberService();

dump($anotherNumberService->calculateExpensiveValue()); // e.g., outputs: 57
dump($anotherNumberService->calculateExpensiveValue()); // outputs: 57
dump($anotherNumberService->calculateExpensiveValue()); // outputs: 57

As we can see, the output is consistent for each instance of the App\Services\NumberService class, but different between instances. This is because the once helper caches the result per object instance.

This has caught me out in the past because I assumed the cached value would be shared across all instances of the class, in a similar way to static properties. But as we can see, that's not the case.

Using "once" in Static Methods

However, when the method is static, it is not tied to a specific instance of the class. Instead, it's tied to the class itself, so the cached value is shared across all calls to the static method.

Let's look at what I mean by this. We'll update our calculateExpensiveValue method to be static:

declare(strict_types=1);

namespace App\Services;

final readonly class NumberService
{
    public static function calculateExpensiveValue(): int
    {
        return once(
            static fn (): int => random_int(min: 1, max: 100)
        );
    }
}

And we'll call the static method a few times:

use App\Services\NumberService;

$numberService = new NumberService();

dump($numberService::calculateExpensiveValue()); // e.g., outputs: 42
dump($numberService::calculateExpensiveValue()); // outputs: 42
dump(NumberService::calculateExpensiveValue()); // outputs: 42
dump(NumberService::calculateExpensiveValue()); // outputs: 42

As we can see, the output remains consistent across all calls to the static method.

Using "once" with Laravel Octane

As I mentioned at the beginning of the article, memoised values created with the once helper are stored in memory and last only for the duration of a single request. This means requests can't access the memoised values of other requests, and when a request completes, the cached values are discarded.

However, you might be wondering how this works when using Laravel Octane, which keeps your application in memory between requests to improve performance. In this case, if the memoised values were not cleared between requests, they would persist across requests, which is not the intended behavior.

Laravel Octane handles this in a really cool way by providing an event listener that flushes the memoised values at the end of each request lifecycle. This ensures that each request starts with a clean slate, and any memoised values are recalculated as needed. Let's take a look at how this is implemented.

Octane's config file (located at config/octane.php) includes a listeners array where various event listeners are registered. The Laravel\Octane\Listeners\FlushOnce listener is registered to listen for the Laravel\Octane\Contracts\OperationTerminated event.

use Laravel\Octane\Contracts\OperationTerminated;
use Laravel\Octane\Listeners\FlushOnce;

return [
    'listeners' => [
        OperationTerminated::class => [
            FlushOnce::class,
        ],
    ],
];

So when the Laravel\Octane\Contracts\OperationTerminated event is fired at the end of each request, the Laravel\Octane\Listeners\FlushOnce listener is executed. This listener calls the Illuminate\Support\Once::flush method to clear all memoised values:

namespace Laravel\Octane\Listeners;

use Illuminate\Support\Once;

class FlushOnce
{
    public function handle($event): void
    {
        if (class_exists(Once::class)) {
            Once::flush();
        }
    }
}

The flush method achieves this by setting the static $instance property of the Illuminate\Support\Once class to null, effectively discarding all cached values:

namespace Illuminate\Support;

class Once
{
    protected static ?self $instance = null;
    public static function flush()
    {
        static::$instance = null;
    }
}

Although this is all handled automatically by Laravel Octane, it's good to understand how it works under the hood so you can be confident that your memoised values are being managed correctly.

Other Approaches to Memoisation in Laravel

Although the once helper is a great built-in way to handle memoisation in Laravel, there are other approaches you may want to take into consideration depending on your specific use case.

The "Cache::memo" Method

Laravel ships with a handy memo method which can be used when interacting with the cache. It holds the value retrieved from the cache in memory so that subsequent calls to retrieve the same cache key within the same request do not hit the cache store again.

use Illuminate\Support\Facades\Cache;

$value = Cache::memo()->get('key'); // First call hits the cache store
$value = Cache::memo()->get('key'); // Subsequent calls return the value from memory

The "array" Cache Store

Another approach you can use, which I don't see used too often, is the array cache store. This driver stores cached values in an in-memory array for the duration of the request, allowing them to be reused multiple times within the same request without hitting an external cache store.

use Illuminate\Support\Facades\Cache;

Cache::store('array')->put('key', 'value', 60); // Store value in array cache
$value = Cache::store('array')->get('key');

Properties and Static Properties

Another approach to memoisation is to simply use class properties or static properties to store values that you want to reuse within the same request. This is a straightforward approach that can work well in many scenarios.

declare(strict_types=1);

namespace App\Services;

final class NumberService
{
    private ?int $expensiveValue = null;

public function calculateExpensiveValue(): int
    {
        return $this->expensiveValue ??= random_int(min: 1, max: 100);
    }
}

Conclusion

Hopefully, this article has given you a good understanding of how to use the once helper in Laravel for memoisation, as well as the nuances of using it within object methods. We've also explored how Laravel Octane integrates with the once helper to ensure that memoised values are reset between requests.