mostafaznv / nova-laracache

Nova LaraCache integrates the LaraCache package with Laravel Nova, providing a convenient tool to moderate and manage cache entities within the Laravel Nova.
MIT License
9 stars 1 forks source link

Trouble getting this package to work #15

Open mathieufrh opened 1 month ago

mathieufrh commented 1 month ago

Hello,

First I'd like to thank you for creating and sharing this package, along with the laracache package.

However I've encounter multiple bugs when using this package.

  1. public const LARACACHE_MODELS_LIST = 'laracache.models'; is hardcoded and does not correspond with the default laracache config 'laracache-list' => 'laracache.list',.
  2. The $expiration = Carbon::createFromTimestamp($cache->expiration); throw an exception when not specifying expiration date for the cache entry.
  3. There are many exceptions during the jobs execution, like ErrorException: Array to string conversion in /var/www/html/vendor/mostafaznv/laracache/src/Jobs/UpdateLaraCacheModelsList.php:38. This one can be fixed in the same way as the next issue :

    // in class Mostafaznv\LaraCache\Jobs\UpdateLaraCacheModelsList
    public function handle(): void
    {
        $list = Cache::driver($this->driver)->get($this->key, []);
        $list[] = $this->model;
    
        Cache::driver($this->driver)->forever(
            key: 'laracache.models',
            value: array_unique(array_keys($list))
        );
    }
  4. In the ApiController I've add to change this line :

    protected function models(): array
    {
        $models = [];
        $key = UpdateLaraCacheModelsList::LARACACHE_MODELS_LIST;
    
        $list = Cache::driver($this->driver)->get($key);
        $list = is_array($list) ? $list : [];
        $list = Arr::sort($list);
    
        // here, it seems that $item is an associative array
        // so using `$list as $item` is not working
        foreach ($list as $item => $values) {
            $model = $this->model($item);
    
            if (method_exists($model, 'cacheEntities')) {
                $models[] = $model;
            }
        }
    
        return $models;
    }

can you confirm those are bugs or am I using this package wrong ?

Overall, I think that those package would benefit of an enhanced documentation with some examples. I have no doubt those packages are working well but it's frustrating knowing it and not be able to make them work.

Well, now it is working and I can see the components in the ache tool.

Still, I have some questions:

  1. What is the "CREATED" status displaying in green in the right end of the cards ? What does it correspond to ?
  2. Click the trash icon print a success message but does not delete anything.
  3. If you have implemented this package you must have use Nova. Can you explain how to use laracache with nova in an easy and effective way ? Should I just add the trait to each of my models and that's all ?
mostafaznv commented 1 month ago

Hi @mathieufrh,

  1. The LARACACHE_MODELS_LIST variable is used in the lower layers of the nova-laracache package. it doesn't have anything to do with laracache's laracache.list. You don't need to use or change it.

  2. I couldn't reproduce the bug. if you don't set any type of expiration (validForRestOfWeek, validForRestOfDay, plain ttl, etc), the default TTL will be set to 0, meaning the cached entity remains valid forever.

  3. I believe this error might have occurred because you manipulated the content of LARACACHE_MODELS_LIST yourself. This list should be an empty array or an array containing strings representing the names of all cached models. If you changed something and it contains non-string values, you will get an error with the array_unique function.

  4. That list is not an associative array, so it's not valid.


Overall, as mentioned in the documentation, to use nova-laracache, you should first read the documentation of laracache itself.


Your questions:

  1. it simply indicates that the content of your cache entity is created and functional. Since some cache entities are heavy tasks to calculate and may need to be executed in the background, creating them might take a few seconds, during which time you should see the not-created status. Moreover, if you've disabled cache generation on created, updated, etc, cache items will only be generated when someone hits them. So, seeing not-created is normal based on your usage.

  2. All actions happen in background jobs. if you are using a queue driver other than sync to perform all actions (delete, refresh, etc.), you must run php artisan queue:work through your terminal.

  3. As explained here, just add it to your nova tools in NovaServiceProvider But remember that this package works based on laracache, and to learn to work with laracache itself, you must read its documentation first.

mathieufrh commented 1 month ago

Thank you very much for your answer.

I have installed Laracache and it is working:

I ran php artisan laracache:update -m Client.

Then

> Client::cacheEntities()
= [
    Mostafaznv\LaraCache\CacheEntity {#6206
      +name: "list.forever",
      +driver: "redis",
      +forever: true,
      +isQueueable: false,
      +queueName: "default",
      +queueConnection: "redis",
      +validForRestOfDay: false,
      +validForRestOfWeek: false,
      +ttl: 0,
      +refreshAfterCreate: true,
      +refreshAfterUpdate: true,
      +refreshAfterDelete: true,
      +refreshAfterRestore: true,
      +default: null,
      +cacheClosure: Closure() {#6209 …3},
    },
    Mostafaznv\LaraCache\CacheEntity {#6208
      +name: "latest",
      +driver: "redis",
      +forever: false,
      +isQueueable: false,
      +queueName: "default",
      +queueConnection: "redis",
      +validForRestOfDay: true,
      +validForRestOfWeek: false,
      +ttl: 0,
      +refreshAfterCreate: true,
      +refreshAfterUpdate: true,
      +refreshAfterDelete: true,
      +refreshAfterRestore: true,
      +default: null,
      +cacheClosure: Closure() {#6207 …3},
    },
  ]

Then with Client::cache()->get('list.forever') I get all the client records.

Now I have installed fresh version of nova-laracache using composer, I add it to the tools method of the NovaServiceProvider class. But when I go in the frontend page here is what I have : Screenshot 2024-08-01 at 14 49 50

I add a debug message in the models method of the ApiController class :

    protected function models(): array
    {
        $models = [];
        $key = UpdateLaraCacheModelsList::LARACACHE_MODELS_LIST;

        $list = Cache::driver($this->driver)->get($key);
        $list = is_array($list) ? $list : [];
        $list = Arr::sort($list);

        \Debugbar::info($list);

        foreach ($list as $item) {
            $model = $this->model($item);

            if (method_exists($model, 'cacheEntities')) {
                $models[] = $model;
            }
        }

        return $models;
    }

And this is what I get :

Screenshot 2024-08-01 at 14 54 51

I don't know where those integers come from. So I clear the cache, then the debug message shows an empty array, great. I reload the cache frontend page and now it is better, except that no it fails trying to parse the ttl :

Screenshot 2024-08-01 at 14 57 49

Which is expected since the line

$expiration = Carbon::createFromTimestamp($cache->expiration);

of the entityToArray method, receives null.

However, you're right, the ttl is 0 :

    Mostafaznv\LaraCache\CacheEntity {#6167
      +name: "list.forever",
      +driver: "redis",
      +forever: true,
      +isQueueable: false,
      +queueName: "default",
      +queueConnection: "redis",
      +validForRestOfDay: false,
      +validForRestOfWeek: false,
      +ttl: 0,
      +refreshAfterCreate: true,
      +refreshAfterUpdate: true,
      +refreshAfterDelete: true,
      +refreshAfterRestore: true,
      +default: null,
      +cacheClosure: Closure() {#6164 …3},
    },

Still it fails. Changing the line with $expiration = Carbon::createFromTimestamp($cache->expiration ?? 0); make everything work.

But maybe it is because of my code. The static aspect of the cacheEntities method is not convenient for many some cases. So I decided to test and build the CacheEntity using database records. I know it's not a good practice to store code in the database, and I'm not very happy with this, but I am simply tinkering with the solution ow. So here is the code :

<?php

namespace App\Models\Concerns;

use App\Models\CacheEntity as CacheEntityModel;
use Mostafaznv\LaraCache\CacheEntity;
use Mostafaznv\LaraCache\Traits\LaraCache;

trait Cacheable
{
    use LaraCache;

    public const int VALID_FOR_REST_OF_WEEK = -1;
    public const int VALID_FOR_REST_OF_DAY = -2;

    /**
     * Get all cache entities, including dynamic ones.
     *
     * @return array<CacheEntity>
     */
    public static function cacheEntities(): array
    {
        return array_merge(self::getStaticEntities(), self::getDynamicEntities());
    }

    /**
     * Get static cache entities.
     *
     * @return array<CacheEntity>
     */
    private static function getStaticEntities(): array
    {
        return [
            CacheEntity::make('list.week')->validForRestOfWeek()->cache(fn() => static::query()->latest()->get()),
            CacheEntity::make('latest')->validForRestOfDay()->cache(fn() => static::query()->latest()->first()),
        ];
    }

    /**
     * Get dynamic cache entities.
     *
     * @return array<CacheEntity>
     */
    private static function getDynamicEntities(): array
    {
        $dynamicEntities = cache()->remember(
            'dynamic_entities_' . static::class,
            5,
            fn() => CacheEntityModel::where('model', static::class)->get()
        );

        return $dynamicEntities->map(function ($dynamicEntity) {
            $entity = CacheEntity::make($dynamicEntity->name)
                ->cache(fn() => ($dynamicEntity->callback)());

            return self::applyDuration($entity, $dynamicEntity->duration);
        })->toArray();
    }

    /**
     * Apply duration to the cache entity.
     *
     * @param CacheEntity $entity
     * @param int|null $duration
     * @return CacheEntity
     */
    private static function applyDuration(CacheEntity $entity, ?int $duration): CacheEntity
    {
        if ($duration === self::VALID_FOR_REST_OF_WEEK) {
            return $entity->validForRestOfWeek();
        }

        if ($duration === self::VALID_FOR_REST_OF_DAY) {
            return $entity->validForRestOfDay();
        }

        if ($duration) {
            return $entity->ttl($duration);
        }

        return $entity->ttl(0);
    }
}

Do you think there is something wrong in it ?

mostafaznv commented 1 month ago

Thank you for your detailed report and investigations. However, I believe if you reinstall both laracache and nova-laracache, and also clear your project's cache using php artisan cache:clear or Cache::flush();, your project will work properly.

In case the above instructions don't work, it would be helpful if you could provide me with a reproduction GitHub repo, so I can check it better and in detail.