troccoli's avatar

troccoli wrote a reply+100 XP

2mos ago

Maybe I'll do it. For now, I'm happy with what I've got. Thanks.

troccoli's avatar

troccoli wrote a reply+100 XP

2mos ago

Actually, PHPStan complains because according to the PHPDoc on the flexible method, both elements in the ttl array cannot be null.

However, I solved the problem by adding a stupid stale time

Cache::flexible(
    key: 'all-users',
    ttl: [now()->addHour(), now()->addCentury()],
    callback: function () {
        // ...
    },
);

I'm pretty confident the cache will be used before a century has passed, so the user will get the stale version.

troccoli's avatar

troccoli wrote a reply+100 XP

2mos ago

Thank you for your answer.

The reason I looked into a cache is because I need to store the state of a user between requests.

As far I understand it's up to you to tell which default store to use during testing. I agree it's usually array but it could be anything else. In any case that is true for the default store.

In my code I don't use the default cache store, but the persistent one directly, which uses the database driver, and therefore store the data in the persistent_cache table.

As I said in my message, when use the DB facade in the code and test directly then the test passes. So, in the end I decided to sort of implement the cache myself. I created a table user_contexts and its Model, and store, retrieve, and delete the data as any other model.

As you can see from my code I didn't really need a cache, as that data does not expire. I just thought it was a nice use of it because a) I may in future have the need to expire the data (but KISS right?) and b) I had access to various methods and artisan command to clear the cache.

Anyway, I resolved the whole issue by adding a table to the DB and use it for this sort of data.

troccoli's avatar

troccoli started a new conversation+100 XP

2mos ago

I have an application that displays statistics collected by another application. It gets the data via API. It also uses some filters, for example the users. So it get the list of all users via an API as well.

There are just a couple of hundreds users, but since the API response are paginated the app needs to go through all the pages and collect the users. This would be called every time the Livewire component to show the stats, and therefore the filters, is mounted.

To speed things up the list of users is cached, and I am currently using the Cache::flexible() method so that I will get the list of users quickly in any case, even though it may be incomplete. I'm ok with this as users don't change that often.

Cache::flexible(
    key: 'all-users',
    ttl: [now()->addHour(), now()->addHour()->addMinute()],
    callback: function () {
        /** @var Collection<int, array<string, mixed>> $users */
        $users = new Collection;

        $this->foreachPage(new Users, function (Response $response) use ($users): void {
            $response->collect('data')->each(fn (array $user) => $users->push($user));
        });

        return $users->map(fn (array $user): string => $user['email'])->all();
    },
);

As I understand, with this code if the requests come within one hour the value in the cache is returned. If it comes within one hour and and one minute, the cache is returned and it's also rebuilt in the background. If it comes after one hour and one minute, the cache is rebuilt.

The last part is what I am unhappy with. Since access to the stats page is not that frequent there is a highly chance that the request will come after one hour and one minute from the initial cache creation, and therefore rebuilding the whole cache.

I could increase the stale period, but still there is the chance that we hit the time when the cache is rebuilt. I would like to avoid that.

So my question is, if I set the stale parameter to null, would that mean that no matter when the request come, the cached value will always be returned, but the cache will be rebuild if it's after one hour? I actually looked a the Illuminate\Cache\Repository implementation and I think I am right, but it would be good if someone else could confirm it.

To be clear, the following code will always return the cache, and maybe rebuild it after one hour

Cache::flexible(
    key: 'all-users',
    ttl: [now()->addHour(), null],
    callback: function () {
        // ...
    },
);
troccoli's avatar

troccoli started a new conversation+100 XP

3mos ago

I need to store the state of a user, but instead of using fields in the users table I have decided to use a persistent cache. The reason is that I will have other use cases to store states not all related to the users, so I needed a more generic solution.

So, I created the migrations for persistent_cache and persistent_cache_lock as copies for the migrations for the cache provided by Laravel.

Then I configures my persistent cache as follows in config/cache.php:

'persistent' => [
    'driver' => 'database',
    'connection' => env('DB_CACHE_CONNECTION'),
    'table' => 'persistent_cache',
    'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
    'lock_table' => 'persistent_cache_locks',
],

In my mind, with these settings I could just use the Cache facade as normal, but just specifying this store: for example Cache::store('persistent')->put($key, $value).

My Livewire component's method needs to fire an event when the user has filled in their profile. The method is

public function completeProfile(): void
{
    // Save user's profile
    // ...

    $cacheKey = sprintf(SignUpAttempt::CACHE_KEY_TEMPLATE, $user->getKey());
    $signUpAttempt = Cache::store('persistent')->get(key: $cacheKey);

    /** @var ?SignUpAttempt $signUpAttempt */
    if (filled($signUpAttempt)) {
        Cache::store('persistent')->forget(key: $cacheKey);
        event(new UserSignedUp($user, $signUpAttempt));
    }

    $this->redirectIntended(default: route('home'), navigate: true);
}

Now, I want to write a test, and I thought I could store a value in the cache, complete the profile, and then assert the event was fired with the correct details. So here's my test:

it('fires the UserSignedUp', function () {
    Event::fake();
    $user = User::factory()->withoutProfile()->create();

    $cacheKey = sprintf(SignUpAttempt::CACHE_KEY_TEMPLATE, $user->getKey());
    Cache::store('persistent')->put(key: $cacheKey, value: new SignUpAttempt(...));

    Livewire::actingAs($user)
        ->test(CompleteProfile::class)
        ->set('form.firstName', $firstName = fake()->firstName())
        ->set('form.lastName', $lastName = fake()->lastName())
        ->set('form.company', $company = fake()->company())
        ->set('form.jobTitle', $jobTitle = fake()->jobTitle())
        ->call('completeProfile')
        ->assertHasNoErrors();

    Event::assertDispatched(UserSignedUp::class);
});

The test fails as the event is never dispatched.

Now, the thing is, if I change the component and the test to use the DB facade and write into the persistent_cache table directly, then the test passes. But I really do not understand why. So does anybody have any ideas why the test fails if I use the Cache facade?

By the way, I'm using a real, as in not in memory, SQLite database, and the RefreshDatabase trait.

troccoli's avatar

troccoli was awarded Best Answer+1000 XP

3mos ago

And it's now possible to do it how I envisage it, thank the Notification being macroable in Laravel 12.47.0

https://laravel-news.com/laravel-12-47-0#content-macroable-notifications

troccoli's avatar

troccoli wrote a reply+100 XP

3mos ago

And it's now possible to do it how I envisage it, thank the Notification being macroable in Laravel 12.47.0

https://laravel-news.com/laravel-12-47-0#content-macroable-notifications

troccoli's avatar

troccoli wrote a reply+100 XP

4mos ago

No, that did not work.

troccoli's avatar

troccoli started a new conversation+100 XP

4mos ago

I have an API written using Resources in Laravel. The controller return a paginated response

class SignInLogsController extends Controller
{
    /**
     * @apiResourceCollection App\Http\Resources\SignInLogResource
     * @apiResourceModel App\Models\SignInLog paginate=15
     */   public function __invoke(Request $request): ResourceCollection
    {
        return SignInLog::paginate()->toResourceCollection();
    }
}

The response has a links section which uses absolute URLs

  +"links": {#4322
    +"first": "https://hub.test/api/v1/sign-ins?page=1"
    +"last": "https://hub.test/api/v1/sign-ins?page=2"
    +"prev": null
    +"next": "https://hub.test/api/v1/sign-ins?page=2"
  }

But when I generate the documentation with Scribe, the links section has relative paths

    "links": {
        "first": "/?page=1",
        "last": "/?page=1",
        "prev": null,
        "next": null
    },

How can I make Scribe use absolute URL?

I haven't change anything in the Scribe config file, and APP_URL is correctly set in my .env file.

troccoli's avatar

troccoli wrote a reply+100 XP

4mos ago

I have to say that for once Lary was helpful.

I had the strangest error were Laravel thought the URL was invalid, when in fact it wasn't. I had checked the key used, the payload, and the signature when creating the signed URL and when Laravel checks it, and everything matched.

This is about a user email verification, so no custom code. It worked locally, but not on AWS.

I came to Laracast to ask for help and I stumble upon this thread. When I read about the load balancer I got a lightbulb moment.

I was already enforcing the https schema, but I did not set HTTPS to true in the request. And that saved me!!