Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/Contracts/Cacheable.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

namespace Saloon\CachePlugin\Contracts;

use DateTimeImmutable;
use Saloon\Http\Response;

interface Cacheable
{
/**
Expand All @@ -12,7 +15,7 @@ interface Cacheable
public function resolveCacheDriver(): Driver;

/**
* Define the cache expiry in seconds
* Resolve the cache expiry in seconds or as an DateTimeImmutable
*/
public function cacheExpiryInSeconds(): int;
public function resolveCacheExpiry(Response $response): DateTimeImmutable|int;
Comment thread
kevinmade marked this conversation as resolved.
}
3 changes: 0 additions & 3 deletions src/Contracts/Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Saloon\CachePlugin\Contracts;

use Saloon\Data\RecordedResponse;
use Saloon\CachePlugin\Data\CachedResponse;

interface Driver
Expand All @@ -16,8 +15,6 @@ public function set(string $key, CachedResponse $cachedResponse): void;

/**
* Get the cached response from the driver.
*
* @return RecordedResponse|null
*/
public function get(string $cacheKey): ?CachedResponse;

Expand Down
10 changes: 9 additions & 1 deletion src/Data/CachedResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Saloon\CachePlugin\Data;

use DateInterval;
use DateTimeImmutable;
use Saloon\Data\RecordedResponse;
use Saloon\Http\Faking\FakeResponse;
Expand All @@ -17,7 +18,6 @@ class CachedResponse
public function __construct(
readonly public RecordedResponse $recordedResponse,
readonly public DateTimeImmutable $expiresAt,
readonly public int $ttl,
) {
//
}
Expand All @@ -38,6 +38,14 @@ public function hasNotExpired(): bool
return ! $this->hasExpired();
}

/**
* Get the cache TTL as an interval based on the expiry date.
*/
public function getTtl(): DateInterval
{
return (new DateTimeImmutable())->diff($this->expiresAt);
}

/**
* Create a fake response
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Drivers/LaravelCacheDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function __construct(
*/
public function set(string $key, CachedResponse $cachedResponse): void
{
$this->store->set($key, serialize($cachedResponse), $cachedResponse->ttl);
$this->store->set($key, serialize($cachedResponse), $cachedResponse->getTtl());
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Drivers/PsrCacheDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function __construct(
*/
public function set(string $key, CachedResponse $cachedResponse): void
{
$this->store->set($key, serialize($cachedResponse), $cachedResponse->ttl);
$this->store->set($key, serialize($cachedResponse), $cachedResponse->getTtl());
}

/**
Expand Down
3 changes: 1 addition & 2 deletions src/Http/Middleware/CacheMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ class CacheMiddleware implements RequestMiddleware
*/
public function __construct(
protected Driver $driver,
protected int $ttl,
protected ?string $cacheKey,
protected bool $invalidate = false,
) {
Expand Down Expand Up @@ -63,7 +62,7 @@ public function __invoke(PendingRequest $pendingRequest): ?FakeResponse
// the prepend option, so it runs first.

$pendingRequest->middleware()->onResponse(
callable: new CacheRecorderMiddleware($driver, $this->ttl, $cacheKey),
callable: new CacheRecorderMiddleware($driver, $cacheKey),
order: PipeOrder::FIRST
);

Expand Down
20 changes: 17 additions & 3 deletions src/Http/Middleware/CacheRecorderMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
use Saloon\Data\RecordedResponse;
use Saloon\CachePlugin\Contracts\Driver;
use Saloon\Contracts\ResponseMiddleware;
use Saloon\CachePlugin\Contracts\Cacheable;
use Saloon\CachePlugin\Data\CachedResponse;
use Saloon\CachePlugin\Exceptions\HasCachingException;

class CacheRecorderMiddleware implements ResponseMiddleware
{
Expand All @@ -18,7 +20,6 @@ class CacheRecorderMiddleware implements ResponseMiddleware
*/
public function __construct(
protected Driver $driver,
protected int $ttl,
protected string $cacheKey,
) {
//
Expand All @@ -35,11 +36,24 @@ public function __invoke(Response $response): void
return;
}

$expiresAt = new DateTimeImmutable('+' . $this->ttl .' seconds');
$request = $response->getRequest();
$connector = $response->getConnector();

if (! $request instanceof Cacheable && ! $connector instanceof Cacheable) {
throw new HasCachingException(sprintf('Your connector or request must implement %s to use the HasCaching plugin', Cacheable::class));
}

$expiresAt = $request instanceof Cacheable
? $request->resolveCacheExpiry($response)
: $connector->resolveCacheExpiry($response);

if (is_int($expiresAt)) {
$expiresAt = new DateTimeImmutable('+' . $expiresAt .' seconds');
}

$this->driver->set(
key: $this->cacheKey,
cachedResponse: new CachedResponse(RecordedResponse::fromResponse($response), $expiresAt, $this->ttl)
cachedResponse: new CachedResponse(RecordedResponse::fromResponse($response), $expiresAt)
);
}
}
8 changes: 2 additions & 6 deletions src/Traits/HasCaching.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,16 @@ public function bootHasCaching(PendingRequest $pendingRequest): void
? $request->resolveCacheDriver()
: $connector->resolveCacheDriver();

$cacheExpiryInSeconds = $request instanceof Cacheable
? $request->cacheExpiryInSeconds()
: $connector->cacheExpiryInSeconds();

// Register a request middleware which wil handle the caching
// and recording of real responses for caching.

$pendingRequest->middleware()->onRequest(function (PendingRequest $middlewarePendingRequest) use ($cacheDriver, $cacheExpiryInSeconds) {
$pendingRequest->middleware()->onRequest(function (PendingRequest $middlewarePendingRequest) use ($cacheDriver) {
// We'll call the cache middleware invokable class with the $middlewarePendingRequest
// because this $pendingRequest has everything loaded, unlike the instance that
// the plugin is provided. This allows us to have access to body and merged
// properties.

return call_user_func(new CacheMiddleware($cacheDriver, $cacheExpiryInSeconds, $this->cacheKey($middlewarePendingRequest), $this->invalidateCache), $middlewarePendingRequest);
return call_user_func(new CacheMiddleware($cacheDriver, $this->cacheKey($middlewarePendingRequest), $this->invalidateCache), $middlewarePendingRequest);
}, order: PipeOrder::FIRST);
}

Expand Down
17 changes: 17 additions & 0 deletions tests/Feature/CacheTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Saloon\CachePlugin\Tests\Fixtures\Requests\CachedConnectorRequest;
use Saloon\CachePlugin\Tests\Fixtures\Requests\AllowedCachedPostRequest;
use Saloon\CachePlugin\Tests\Fixtures\Requests\CustomKeyCachedUserRequest;
use Saloon\CachePlugin\Tests\Fixtures\Requests\ResponseBasedExpiryRequest;
use Saloon\CachePlugin\Tests\Fixtures\Requests\ShortLivedCachedUserRequest;
use Saloon\CachePlugin\Tests\Fixtures\Requests\CachedUserRequestWithoutCacheable;
use Saloon\CachePlugin\Tests\Fixtures\Requests\CachedUserRequestOnCachedConnector;
Expand Down Expand Up @@ -249,6 +250,22 @@
expect($responseC->json())->toEqual(['name' => 'Michael']);
});

test('you can define a cache expiry based on a response', function () {
$expectedExpiry = 90;
$mockClient = new MockClient([
MockResponse::make(['expiry' => $expectedExpiry]),
]);

$connector = new TestConnector;

$request = new ResponseBasedExpiryRequest();
$response = $connector->send($request, $mockClient);

$expiry = $request->resolveCacheExpiry($response);

expect($expiry)->toEqual($expectedExpiry);
});

test('you can define a cache on the connector and it returns a cached response', function () {
$mockClient = new MockClient([
MockResponse::make(['name' => 'Sam']),
Expand Down
3 changes: 2 additions & 1 deletion tests/Fixtures/Connectors/CachedConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Saloon\CachePlugin\Tests\Fixtures\Connectors;

use Saloon\Http\Response;
use Saloon\Http\Connector;
use League\Flysystem\Filesystem;
use Saloon\CachePlugin\Contracts\Driver;
Expand All @@ -26,7 +27,7 @@ public function resolveCacheDriver(): Driver
return new FlysystemDriver(new Filesystem(new LocalFilesystemAdapter(cachePath())));
}

public function cacheExpiryInSeconds(): int
public function resolveCacheExpiry(Response $response): int
{
return 60;
}
Expand Down
3 changes: 2 additions & 1 deletion tests/Fixtures/Requests/AllowedCachedPostRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use League\Flysystem\Filesystem;
use Saloon\Contracts\Body\HasBody;
use Saloon\Traits\Body\HasJsonBody;
Expand Down Expand Up @@ -39,7 +40,7 @@ public function resolveCacheDriver(): Driver
return new FlysystemDriver(new Filesystem(new LocalFilesystemAdapter(cachePath())));
}

public function cacheExpiryInSeconds(): int
public function resolveCacheExpiry(Response $response): int
{
return 60;
}
Expand Down
3 changes: 2 additions & 1 deletion tests/Fixtures/Requests/BodyCacheKeyRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Http\PendingRequest;
use League\Flysystem\Filesystem;
use Saloon\Contracts\Body\HasBody;
Expand Down Expand Up @@ -36,7 +37,7 @@ public function resolveCacheDriver(): Driver
return new FlysystemDriver(new Filesystem(new LocalFilesystemAdapter(cachePath())));
}

public function cacheExpiryInSeconds(): int
public function resolveCacheExpiry(Response $response): int
{
return 60;
}
Expand Down
3 changes: 2 additions & 1 deletion tests/Fixtures/Requests/CachedPostRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use League\Flysystem\Filesystem;
use Saloon\Contracts\Body\HasBody;
use Saloon\Traits\Body\HasJsonBody;
Expand Down Expand Up @@ -39,7 +40,7 @@ public function resolveCacheDriver(): Driver
return new FlysystemDriver(new Filesystem(new LocalFilesystemAdapter(cachePath())));
}

public function cacheExpiryInSeconds(): int
public function resolveCacheExpiry(Response $response): int
{
return 60;
}
Expand Down
3 changes: 2 additions & 1 deletion tests/Fixtures/Requests/CachedUserRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use League\Flysystem\Filesystem;
use Saloon\CachePlugin\Contracts\Driver;
use Saloon\CachePlugin\Traits\HasCaching;
Expand Down Expand Up @@ -41,7 +42,7 @@ public function resolveCacheDriver(): Driver
/**
* Define the cache expiry in seconds
*/
public function cacheExpiryInSeconds(): int
public function resolveCacheExpiry(Response $response): int
{
return 60;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use League\Flysystem\Filesystem;
use Saloon\CachePlugin\Contracts\Driver;
use Saloon\CachePlugin\Traits\HasCaching;
Expand All @@ -29,7 +30,7 @@ public function resolveCacheDriver(): Driver
return new FlysystemDriver(new Filesystem(new LocalFilesystemAdapter(cachePath() . '/custom')));
}

public function cacheExpiryInSeconds(): int
public function resolveCacheExpiry(Response $response): int
{
return 30;
}
Expand Down
3 changes: 2 additions & 1 deletion tests/Fixtures/Requests/CustomKeyCachedUserRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Http\PendingRequest;
use League\Flysystem\Filesystem;
use Saloon\CachePlugin\Contracts\Driver;
Expand Down Expand Up @@ -33,7 +34,7 @@ public function resolveCacheDriver(): Driver
return new FlysystemDriver(new Filesystem(new LocalFilesystemAdapter(cachePath())));
}

public function cacheExpiryInSeconds(): int
public function resolveCacheExpiry(Response $response): int
{
return 60;
}
Expand Down
3 changes: 2 additions & 1 deletion tests/Fixtures/Requests/LaravelCachedUserRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Illuminate\Support\Facades\Cache;
use Saloon\CachePlugin\Contracts\Driver;
use Saloon\CachePlugin\Traits\HasCaching;
Expand All @@ -28,7 +29,7 @@ public function resolveCacheDriver(): Driver
return new LaravelCacheDriver(Cache::store('file'));
}

public function cacheExpiryInSeconds(): int
public function resolveCacheExpiry(Response $response): int
{
return 60;
}
Expand Down
3 changes: 2 additions & 1 deletion tests/Fixtures/Requests/PsrCachedUserRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\CachePlugin\Contracts\Driver;
use Saloon\CachePlugin\Traits\HasCaching;
use Saloon\CachePlugin\Contracts\Cacheable;
Expand Down Expand Up @@ -33,7 +34,7 @@ public function resolveCacheDriver(): Driver
return new PsrCacheDriver($this->cache);
}

public function cacheExpiryInSeconds(): int
public function resolveCacheExpiry(Response $response): int
{
return 60;
}
Expand Down
37 changes: 37 additions & 0 deletions tests/Fixtures/Requests/ResponseBasedExpiryRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace Saloon\CachePlugin\Tests\Fixtures\Requests;

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use League\Flysystem\Filesystem;
use Saloon\CachePlugin\Contracts\Driver;
use Saloon\CachePlugin\Traits\HasCaching;
use Saloon\CachePlugin\Contracts\Cacheable;
use Saloon\CachePlugin\Drivers\FlysystemDriver;
use League\Flysystem\Local\LocalFilesystemAdapter;

class ResponseBasedExpiryRequest extends Request implements Cacheable
{
use HasCaching;

protected Method $method = Method::GET;

public function resolveEndpoint(): string
{
return '/user';
}

public function resolveCacheDriver(): Driver
{
return new FlysystemDriver(new Filesystem(new LocalFilesystemAdapter(cachePath())));
}

public function resolveCacheExpiry(Response $response): int
{
return $response->json()['expiry'];
}
}
Loading