# 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](/content/blog/an-introduction-to-caching-in-laravel/index.html) 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](https://consuming-apis-in-laravel.com/), 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](/content/blog/how-to-create-your-own-helper-functions-in-laravel/index.html) to implement memoisation in your applications. We'll also explore the differences between using `once` inside functions and within object methods, including [static methods](/content/blog/the-difference-between-self-static-and-parent-in-php/index.html). 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.

```php
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:

```php
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](/content/blog/the-difference-between-self-static-and-parent-in-php/index.html). 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:

```php
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:

```php
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.

```php
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:

```php
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:

```php
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.

```php
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.

```php
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.

```php
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.
