Skip to content

Commit 91af444

Browse files
authored
Merge pull request #7 from DirectoryTree/array-repository
Add ability to set array repository for testing
2 parents 55f6201 + 9670d62 commit 91af444

File tree

5 files changed

+388
-2
lines changed

5 files changed

+388
-2
lines changed

src/Model.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use DirectoryTree\ActiveRedis\Exceptions\DuplicateKeyException;
1515
use DirectoryTree\ActiveRedis\Exceptions\InvalidKeyException;
1616
use DirectoryTree\ActiveRedis\Exceptions\JsonEncodingException;
17+
use DirectoryTree\ActiveRedis\Repositories\ArrayRepository;
1718
use DirectoryTree\ActiveRedis\Repositories\RedisRepository;
1819
use DirectoryTree\ActiveRedis\Repositories\Repository;
1920
use Illuminate\Contracts\Redis\Connection;
@@ -26,6 +27,7 @@
2627
use Illuminate\Support\Facades\Date;
2728
use Illuminate\Support\Str;
2829
use Illuminate\Support\Traits\ForwardsCalls;
30+
use InvalidArgumentException;
2931
use JsonException;
3032
use Stringable;
3133

@@ -84,6 +86,11 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, Stringable, Ur
8486
*/
8587
protected ?string $connection = null;
8688

89+
/**
90+
* The repository for the model.
91+
*/
92+
protected static string $repository = 'redis';
93+
8794
/**
8895
* Handle dynamic method calls into the model.
8996
*/
@@ -411,12 +418,24 @@ public function newBuilder(): Query
411418
return new Query($this, $this->newRepository());
412419
}
413420

421+
/**
422+
* Set the repository for the model.
423+
*/
424+
public static function setRepository(string $repository): void
425+
{
426+
static::$repository = $repository;
427+
}
428+
414429
/**
415430
* Create a new repository instance.
416431
*/
417432
protected function newRepository(): Repository
418433
{
419-
return new RedisRepository($this->getConnection());
434+
return match ($repository = static::$repository) {
435+
'array' => new ArrayRepository,
436+
'redis' => new RedisRepository($this->getConnection()),
437+
default => throw new InvalidArgumentException("Repository [{$repository}] is not supported."),
438+
};
420439
}
421440

422441
/**

src/Repositories/ArrayRepository.php

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<?php
2+
3+
namespace DirectoryTree\ActiveRedis\Repositories;
4+
5+
use Closure;
6+
use Generator;
7+
use Illuminate\Support\Str;
8+
9+
class ArrayRepository implements Repository
10+
{
11+
/**
12+
* Constructor.
13+
*/
14+
public function __construct(
15+
protected array $data = [],
16+
protected array $expiry = [],
17+
) {}
18+
19+
/**
20+
* Determine if the given hash exists.
21+
*/
22+
public function exists(string $hash): bool
23+
{
24+
return isset($this->data[$hash]);
25+
}
26+
27+
/**
28+
* Chunk through the hashes matching the given pattern.
29+
*/
30+
public function chunk(string $pattern, int $count): Generator
31+
{
32+
$matches = [];
33+
34+
foreach (array_keys($this->data) as $key) {
35+
if (Str::is($pattern, $key)) {
36+
$matches[] = $key;
37+
}
38+
}
39+
40+
foreach (array_chunk($matches, $count) as $chunk) {
41+
yield $chunk;
42+
}
43+
}
44+
45+
/**
46+
* Set the hash field's value.
47+
*/
48+
public function setAttribute(string $hash, string $attribute, string $value): void
49+
{
50+
$this->data[$hash][$attribute] = $value;
51+
}
52+
53+
/**
54+
* Set the hash field's value.
55+
*/
56+
public function setAttributes(string $hash, array $attributes): void
57+
{
58+
foreach ($attributes as $attribute => $value) {
59+
$this->setAttribute($hash, $attribute, $value);
60+
}
61+
}
62+
63+
/**
64+
* Get the hash field's value.
65+
*/
66+
public function getAttribute(string $hash, string $field): mixed
67+
{
68+
if ($this->isExpired($hash)) {
69+
$this->delete($hash);
70+
71+
return null;
72+
}
73+
74+
return $this->data[$hash][$field] ?? null;
75+
}
76+
77+
/**
78+
* Get all the attributes in the hash.
79+
*/
80+
public function getAttributes(string $hash): array
81+
{
82+
if ($this->isExpired($hash)) {
83+
$this->delete($hash);
84+
85+
return [];
86+
}
87+
88+
return $this->data[$hash] ?? [];
89+
}
90+
91+
/**
92+
* Set a time-to-live on a hash key.
93+
*
94+
* Not supported in ArrayRepository.
95+
*/
96+
public function setExpiry(string $hash, int $seconds): void
97+
{
98+
$this->expiry[$hash] = time() + $seconds;
99+
}
100+
101+
/**
102+
* Get the time-to-live of a hash key.
103+
*/
104+
public function getExpiry(string $hash): ?int
105+
{
106+
if (! isset($this->expiry[$hash])) {
107+
return null;
108+
}
109+
110+
$remaining = $this->expiry[$hash] - time();
111+
112+
return $remaining > 0 ? $remaining : null;
113+
}
114+
115+
/**
116+
* Delete the attributes from the hash.
117+
*/
118+
public function deleteAttributes(string $hash, array|string $attributes): void
119+
{
120+
foreach ((array) $attributes as $attribute) {
121+
unset($this->data[$hash][$attribute]);
122+
}
123+
}
124+
125+
/**
126+
* Delete the given hash.
127+
*/
128+
public function delete(string $hash): void
129+
{
130+
unset($this->data[$hash]);
131+
}
132+
133+
/**
134+
* Perform a transaction.
135+
*/
136+
public function transaction(Closure $operation): void
137+
{
138+
$operation($this);
139+
}
140+
141+
/**
142+
* Determine if the given hash has expired.
143+
*/
144+
protected function isExpired(string $hash): bool
145+
{
146+
return isset($this->expiry[$hash]) && $this->expiry[$hash] <= time();
147+
}
148+
}

src/Repositories/RedisRepository.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,9 @@ public function getExpiry(string $hash): ?int
127127
{
128128
// The number of seconds until the key will expire, or
129129
// null if the key does not exist or has no timeout.
130-
return $this->redis->ttl($hash);
130+
$ttl = $this->redis->ttl($hash);
131+
132+
return $ttl > 0 ? $ttl : null;
131133
}
132134

133135
/**
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
use DirectoryTree\ActiveRedis\Repositories\ArrayRepository;
4+
5+
it('can check if a hash exists', function () {
6+
$repository = new ArrayRepository(['foo' => ['bar' => 'baz']]);
7+
8+
expect($repository->exists('foo'))->toBeTrue();
9+
expect($repository->exists('bar'))->toBeFalse();
10+
});
11+
12+
it('can chunk through hashes matching a pattern', function () {
13+
$repository = new ArrayRepository([
14+
'user:1' => [],
15+
'user:2' => [],
16+
'post:1' => [],
17+
'user:3' => [],
18+
]);
19+
20+
$chunks = iterator_to_array($repository->chunk('user:*', 2));
21+
22+
expect($chunks)->toHaveCount(2);
23+
expect($chunks[0])->toEqual(['user:1', 'user:2']);
24+
expect($chunks[1])->toEqual(['user:3']);
25+
});
26+
27+
it('can set and get a single attribute', function () {
28+
$repository = new ArrayRepository;
29+
30+
$repository->setAttribute('foo', 'bar', 'baz');
31+
32+
expect($repository->getAttribute('foo', 'bar'))->toBe('baz');
33+
});
34+
35+
it('can set and get multiple attributes', function () {
36+
$repository = new ArrayRepository;
37+
38+
$repository->setAttributes('foo', ['bar' => 'baz', 'qux' => 'quux']);
39+
40+
expect($repository->getAttributes('foo'))->toEqual(['bar' => 'baz', 'qux' => 'quux']);
41+
});
42+
43+
it('can delete attributes', function () {
44+
$repository = new ArrayRepository(['foo' => ['bar' => 'baz', 'qux' => 'quux']]);
45+
46+
$repository->deleteAttributes('foo', 'bar');
47+
48+
expect($repository->getAttributes('foo'))->toEqual(['qux' => 'quux']);
49+
50+
$repository->deleteAttributes('foo', ['qux']);
51+
52+
expect($repository->getAttributes('foo'))->toEqual([]);
53+
});
54+
55+
it('can delete a hash', function () {
56+
$repository = new ArrayRepository(['foo' => ['bar' => 'baz']]);
57+
58+
$repository->delete('foo');
59+
60+
expect($repository->exists('foo'))->toBeFalse();
61+
});
62+
63+
it('can set and get expiry', function () {
64+
$repository = new ArrayRepository;
65+
66+
$repository->setExpiry('foo', 10);
67+
68+
expect($repository->getExpiry('foo'))->toBeGreaterThanOrEqual(9);
69+
});
70+
71+
it('returns null for expiry of non-existent key', function () {
72+
$repository = new ArrayRepository;
73+
74+
expect($repository->getExpiry('foo'))->toBeNull();
75+
});
76+
77+
it('deletes expired hashes', function () {
78+
$repository = new ArrayRepository;
79+
80+
$repository->setAttributes('foo', ['bar' => 'baz']);
81+
82+
$repository->setExpiry('foo', 1);
83+
84+
sleep(2);
85+
86+
expect($repository->getAttributes('foo'))->toEqual([]);
87+
});
88+
89+
it('handles getting attributes from expired hash', function () {
90+
$repository = new ArrayRepository;
91+
92+
$repository->setAttributes('foo', ['bar' => 'baz']);
93+
94+
$repository->setExpiry('foo', 1);
95+
96+
sleep(2);
97+
98+
expect($repository->getAttribute('foo', 'bar'))->toBeNull();
99+
});

0 commit comments

Comments
 (0)