Skip to content

Commit

Permalink
Merge pull request #5 from vormkracht10/feature/cached-value-class
Browse files Browse the repository at this point in the history
Feature/cached value class
  • Loading branch information
markvaneijk authored Jan 3, 2024
2 parents 801877c + eb43b39 commit 6ab42d5
Show file tree
Hide file tree
Showing 15 changed files with 308 additions and 260 deletions.
3 changes: 0 additions & 3 deletions CHANGELOG.md

This file was deleted.

129 changes: 109 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/vormkracht10/laravel-permanent-cache)
[![Latest Version on Packagist](https://img.shields.io/packagist/v/vormkracht10/laravel-permanent-cache.svg?style=flat-square)](https://packagist.org/packages/vormkracht10/laravel-permanent-cache)

This package aims to provide functionality of using permanent cache for heavy Eloquent models, database queries or long duration tasks in Laravel. The permanent cache updates itself in the background using a scheduled task, so no visitors are harmed waiting long on a given request.
This package aims to provide functionality of using permanent cache for heavy Eloquent models,
database queries or long duration tasks in Laravel. The permanent cache updates itself
in the background using a scheduled task or by reacting to an event
so no visitors are harmed waiting long on a given request.

## Installation

Expand All @@ -17,49 +20,135 @@ You can install the package via composer:
composer require vormkracht10/laravel-permanent-cache
```

You can publish the config file with:
# Usage

```bash
php artisan vendor:publish --tag="laravel-permanent-cache-config"
This package provides a handy `Cached` class. This allows you
to easily cache data based on a schedule or an event.

## "Reactive" caches

To get started with this Cached class, make a `HelloCache` class like so.
This cache will respond to a `TestEvent` by caching whatever will
be returned from the `run` method.

```php
use Vormkracht10\PermanentCache\Cached;

// ...

class HelloCache extends Cached
{
protected $store = 'redis:hello';

public function run(TestEvent $event): string
{
return "Hallo, {$event->name}!";
}
}
```

This is the contents of the published config file:
To know *where* to cache the returned value, we have the `$store` property.
This is formatted like `driver:identifier`, but you can also omit the `driver:`
like so `protected $store = 'hello';` and we will use the config's `cache.default` value instead.

##### if you don't want to type hint the `TestEvent` class in the `run` method, you can also explicitly specify the type like so `protected $event = TestEvent::class;`

## "Static" caches

Static caches are a little different to the Reactive caches, these do not respond to events
and must be called manually or scheduled. Here is an example.

By default, a cache will not do anything if it doesn't listen for any events.
Thus, we need to schedule it.

### Scheduling with cron expressions

You can use cron expressions to schedule your cache, a very basic example is shown below.
This will "run" the cache every minute.

```php
return [
];
use Vormkracht10\PermanentCache\Scheduled;

// ...

class MinutesCache extends Cached implements Scheduled
{
protected $store = 'redis:minutes';

protected $expression = '* * * * *';

public function run(): mixed
{
return CounterCache::get() + 1;
}
}
```

## Usage
### Static caches with Laravel magic

Now you can run `php artisan schedule:work` and every minute, the `minutes` count will be incremented!
But, if you're anything like me, you don't really like writing raw cron expressions
and much rather use Laravel's cool `Schedule` class. Well, you can.

Let's take our previous snippet, and edit it a little to use Laravel's `Schedule` instead.

```php
$permanentCache = new Vormkracht10\PermanentCache();
echo $permanentCache->echoPhrase('Hello, Vormkracht10!');
class MinutesCache extends Cached implements Scheduled
{
protected $store = 'redis:minutes';

public function run(): mixed
{
return CounterCache::get() + 1;
}

public static function schedule($callback)
{
$callback->everyMinute();
}
}
```

## Testing
### Manually updating static caches.

```bash
composer test
Manually updating static caches is very simple. Just use the static `update` method.
This will automatically queue your job, if it should be.

```php
MinutesCache::update();
```

## Changelog
## Queued caches

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
You can also queue caches, for both the static and reactive caches.
You can do this by simply implementing Laravel's `ShouldQueue` interface!

## Contributing
```php
class HelloCache extends Cached implements ShouldQueue
{
protected $connection = 'redis';

protected $store = 'redis:hello';

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
public function run(TestEvent $event): string
{
return "Hallo, {$event->name}!";
}
}
```

## Security Vulnerabilities
You can specify a bunch of things, like the queue connection using the `$connection` property.
You can basically configure you cache as a Laravel job. This works because the `Cached` class from which
we are inheriting is structured like a Laravel job!

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.
##### [Read more on Jobs & Queues](https://laravel.com/docs/queues)

## Credits

- [Mark van Eijk](https://github.com/vormkracht10)
- [David den Haan](https://github.com/daviddenhaan)
- [All Contributors](../../contributors)

## License

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
The MIT License (MIT). Please see the [License File](LICENSE.md) for more information.
5 changes: 0 additions & 5 deletions config/permanent-cache.php

This file was deleted.

2 changes: 0 additions & 2 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ parameters:
level: 4
paths:
- src
- config
- database
tmpDir: build/phpstan
checkOctaneCompatibility: true
checkModelProperties: true
Expand Down
143 changes: 143 additions & 0 deletions src/Cached.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php

namespace Vormkracht10\PermanentCache;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Cache;
use ReflectionClass;

/**
* @method mixed run()
*
* @template E
*/
abstract class Cached
{
use Queueable;

/**
* The driver and identifier that will be used to cache this value.
* This value should be in the format 'driver:identifier'.
*
* @var string|null
*/
protected $store = null;

/**
* The event that this cacher will listen for, this is optional
* as it can also be inferred by type hinting an argument
* in the run method.
*
* @var class-string<E>|null
*/
protected $event = null;

/**
* The cron expression that will be used if the `schedule`
* method has not been overridden.
*
* @var string|null
*/
protected $expression = null;

/**
* Update the cached value, this method expects an event if
* the cacher is not static.
*
* @param E $event
*/
final public function handle($event = null): void
{
[$driver, $ident] = self::parseCacheString($this->store
?? throw new \Exception('The $store property in ['.static::class.'] must be overridden'),
);
Cache::driver($driver)->forever($ident,
/** @phpstan-ignore-next-line */
$this->run($event),
);
}

/**
* Manually force a static cache to update.
*/
final public static function update(): void
{
$instance = app()->make(static::class);

if (! is_a(static::class, ShouldQueue::class, true)) {
$instance->handle();

return;
}

dispatch($instance);
}

/**
* Get the cached value this cacher provides.
*/
final public static function get(): mixed
{
$store = (new ReflectionClass(static::class))
->getProperty('store')
->getDefaultValue();

[$driver, $ident] = self::parseCacheString($store
?? throw new \Exception('The $store property in ['.static::class.'] must be overridden'),
);

return Cache::driver($driver)->get($ident);
}

// Default implementation for the \Scheduled::schedule method.
public static function schedule($callback)
{
if (! is_a(static::class, Scheduled::class, true)) {
throw new \Exception('Can not schedule a cacher that does not implement the ['.Scheduled::class.'] interface');
}

$reflection = new ReflectionClass(static::class);

$concrete = $reflection->getProperty('expression')->getDefaultValue();

if (is_null($concrete)) {
throw new \Exception('Either the Cached::$expression property or the ['.__METHOD__.'] method must be overridden by the user.');
}

$callback->cron($concrete);
}

/**
* Get the event (if any) this cacher listens for.
*
* @return class-string<E>|null
*/
final public static function getListenerEvent(): ?string
{
$reflection = new ReflectionClass(static::class);

$concrete = $reflection->getProperty('event')->getDefaultValue();

/** @phpstan-ignore-next-line */
return $concrete ?? ($reflection
->getMethod('run')
->getParameters()[0] ?? null)
?->getType()
?->getName();
}

/**
* @return array{string, string}
*/
private static function parseCacheString(string $store): array
{
[$driver, $ident] = explode(':', $store) + [1 => null];

if (is_null($ident)) {
[$driver, $ident] = [config('cache.default'), $driver];
}

return [$driver, $ident];
}
}
57 changes: 0 additions & 57 deletions src/Commands/UpdatePermanentCacheCommand.php

This file was deleted.

11 changes: 0 additions & 11 deletions src/Enums/Status.php

This file was deleted.

Loading

0 comments on commit 6ab42d5

Please sign in to comment.