diff --git a/README.md b/README.md index 97ce5e0..de355b4 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Or manually update `require` block of `composer.json` and run `composer update`. ```json { "require": { - "dragon-code/laravel-cache": "^3.7" + "dragon-code/laravel-cache": "^3.9" } } ``` @@ -84,6 +84,11 @@ Since the main problem of working with the cache's key compilation, this package By passing values to the `keys` method, we get a ready-made key at the output. +The hash is formed by the value `key=value`, which allows avoiding collisions when passing identical objects. + +In the case of passing nested arrays, the key is formed according to the principle `key1.key2=value`, where `key1` +and `key2` are the keys of each nested array. + For example: ```php @@ -91,7 +96,7 @@ use DragonCode\Cache\Services\Cache; Cache::make()->key('foo', 'bar', [null, 'baz', 'baq']); -// Key is `acbd18db4cc2f85cedef654fccc4a4d8:37b51d194a7513e45b56f6524f2d51f2:73feffa4b7f6bb68e44cf984c85f6e88:b47951d522316fdd8811b23fc9c2f583` +// Key is `d76f2bde023f5602ae837d01f4ec1876:660a13c00e04c0d3ffb4dbf02a84a07a:6fc3659bd986e86534c6587caf5f431a:bd62cbee62e027d0be4b1656781edcbf` ``` This means that when writing to the cache, the tree view will be used. @@ -101,14 +106,44 @@ For example: ```php use DragonCode\Cache\Services\Cache; -Cache::make()->key('foo', 'foo')->put('foo'); -Cache::make()->key('foo', 'bar')->put('bar'); -Cache::make()->key('baz')->put('baz'); +Cache::make()->key('foo', 'foo')->put('Foo'); +Cache::make()->key('foo', 'bar')->put('Bar'); +Cache::make()->key('baz')->put('Baz'); + +// d76f2bde023f5602ae837d01f4ec1876: +// 086f76c144511e1198c29a261e87ca50: Foo +// 660a13c00e04c0d3ffb4dbf02a84a07a: Bar +// 1b9829f3bd21835a15735f3a65cc75e9: Baz +``` + +#### Disable key hashing + +In some cases, you need to disable the use of the key hashing mechanism. +To do this, simply call the `hashKey(false)` method: + +```php +use DragonCode\Cache\Services\Cache; + +Cache::make()->key('foo', 'foo')->hashKey(false)->put('Foo'); +Cache::make()->key('foo', 'bar')->hashKey(false)->put('Bar'); +Cache::make()->key('baz')->hashKey(false)->put('Baz'); + +// 0=foo: +// 1=foo: Foo +// 1=bar: Bar +// 0=baz: Baz +``` + +```php +use DragonCode\Cache\Services\Cache; + +Cache::make()->key([ + ['foo' => 'Foo'], + ['bar' => 'Bar'], + [['Baz', 'Qwerty']], +])->hashKey(false)->put('Baz'); -// acbd18db4cc2f85cedef654fccc4a4d8: -// acbd18db4cc2f85cedef654fccc4a4d8: foo -// 37b51d194a7513e45b56f6524f2d51f2: bar -// 73feffa4b7f6bb68e44cf984c85f6e88: baz +// 0.foo=Foo:1.bar=Bar:2.0.0=Baz:2.0.1=Qwerty ``` ### With Authentication @@ -125,7 +160,8 @@ Cache::make()->withAuth()->key('foo', 'bar'); Cache::make()->key(get_class(Auth::user()), Auth::id(), 'foo', 'bar'); ``` -When processing requests with a call to the withAuth method, the binding will be carried out not only by identifier, but also by reference to the model class, since a project can +When processing requests with a call to the withAuth method, the binding will be carried out not only by identifier, but +also by reference to the model class, since a project can have several models with the possibility of authorization. For example, `App\Models\Employee`, `App\Models\User`. @@ -195,7 +231,8 @@ $cache->forget(); #### Method Call Chain -Sometimes in the process of working with a cache, it becomes necessary to call some code between certain actions, and in this case the `call` method will come to the rescue: +Sometimes in the process of working with a cache, it becomes necessary to call some code between certain actions, and in +this case the `call` method will come to the rescue: ```php use DragonCode\Cache\Services\Cache; @@ -306,12 +343,15 @@ Cache::make()->ttl('custom_key', false); Cache::make()->ttl((object) ['foo' => 'Foo'], false); ``` -If the value is not found, the [default value](config/cache.php) will be taken, which you can also override in the [configuration file](config/cache.php). +If the value is not found, the [default value](config/cache.php) will be taken, which you can also override in +the [configuration file](config/cache.php). ##### With Contract -Starting with version [`2.9.0`](https://github.com/TheDragonCode/laravel-cache/releases/tag/v2.9.0), we added the ability to dynamically specify TTLs in objects. To do this, you -need to implement the `DragonCode\Contracts\Cache\Ttl` contract into your object and add a method that returns one of the following types of variables: `DateTimeInterface` +Starting with version [`2.9.0`](https://github.com/TheDragonCode/laravel-cache/releases/tag/v2.9.0), we added the +ability to dynamically specify TTLs in objects. To do this, you +need to implement the `DragonCode\Contracts\Cache\Ttl` contract into your object and add a method that returns one of +the following types of variables: `DateTimeInterface` , `Carbon\Carbon`, `string` or `integer`. @@ -380,7 +420,8 @@ $cache->forget(); // Will remove the key from the cache. ``` -To retrieve a tagged cache item, pass the same ordered list of tags to the tags method and then call the get method with the key you wish to retrieve: +To retrieve a tagged cache item, pass the same ordered list of tags to the tags method and then call the get method with +the key you wish to retrieve: ```php use DragonCode\Cache\Services\Cache; diff --git a/composer.json b/composer.json index 76b0145..afcc87a 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ "require": { "php": "^8.0", "dragon-code/contracts": "^2.16", - "dragon-code/support": "^6.1", + "dragon-code/support": "^6.11.3", "illuminate/support": ">=6.0 <11.0" }, "require-dev": { diff --git a/src/Concerns/Arrayable.php b/src/Concerns/Arrayable.php index b8c1809..a0c89e9 100644 --- a/src/Concerns/Arrayable.php +++ b/src/Concerns/Arrayable.php @@ -22,7 +22,7 @@ trait Arrayable protected function arrayMap(array $values, callable $callback): array { return Arr::of($values) - ->map(function ($value) { + ->map(function (mixed $value) { if ($this->isArrayable($value)) { return $this->toArray($value); } @@ -40,6 +40,36 @@ protected function arrayMap(array $values, callable $callback): array ->toArray(); } + protected function arrayFlattenKeysMap(array $values, callable $callback): array + { + return Arr::of($values) + ->flattenKeys() + ->filter(static fn ($value) => ! empty($value) || is_numeric($value) || is_bool($value)) + ->map(fn (mixed $value, mixed $key) => $callback($key . '=' . $value)) + ->toArray(); + } + + protected function flattenKeys(mixed $array, string $delimiter = '.', ?string $prefix = null): array + { + $result = []; + + foreach ($array as $key => $value) { + $new_key = ! empty($prefix) ? $prefix . $delimiter . $key : $key; + + if (is_array($value)) { + $values = $this->flattenKeys($value, $delimiter, $new_key); + + $result = array_merge($result, $values); + + continue; + } + + $result[$new_key] = $value; + } + + return $result; + } + protected function toArray($value): array { return Arr::of(Arr::wrap($value)) diff --git a/src/Support/Key.php b/src/Support/Key.php index 8ec56e7..139c109 100644 --- a/src/Support/Key.php +++ b/src/Support/Key.php @@ -13,8 +13,11 @@ class Key { use Arrayable; - public function get(string $separator, array|ArrayObject|DragonArrayable|IlluminateArrayable $values, bool $hash = true): string - { + public function get( + string $separator, + array|ArrayObject|DragonArrayable|IlluminateArrayable $values, + bool $hash = true + ): string { $values = $this->toArray($values); $hashed = $this->hash($values, $hash); @@ -24,11 +27,7 @@ public function get(string $separator, array|ArrayObject|DragonArrayable|Illumin protected function hash(array $values, bool $hash = true): array { - return $this->arrayMap($values, static function (mixed $value) use ($hash) { - $value = is_bool($value) ? (int) $value : $value; - - return $hash ? md5((string) $value) : (string) $value; - }); + return $this->arrayFlattenKeysMap($values, fn (mixed $value) => $hash ? md5($value) : $value); } protected function compile(array $values, string $separator): string diff --git a/tests/Cache/When/Simple/RedisTest.php b/tests/Cache/When/Simple/RedisTest.php index 5d3b824..b784abb 100644 --- a/tests/Cache/When/Simple/RedisTest.php +++ b/tests/Cache/When/Simple/RedisTest.php @@ -101,15 +101,14 @@ public function testWithAuth() $this->assertTrue($this->cache()->doesntHave()); $this->assertTrue($this->cache()->withAuth()->doesntHave()); - $this->cache()->put($this->value); $this->cache()->withAuth()->put($this->value_second); - $this->assertFalse($this->cache()->doesntHave()); + $this->assertTrue($this->cache()->doesntHave()); $this->assertTrue($this->cache()->withAuth()->has()); $key = [User::class, $this->user_id, 'Foo', 'Bar', 'Baz']; $this->assertSame($this->value_second, $this->cache()->withAuth()->get()); - $this->assertSame($this->value_second, $this->cache([], $key)->get()); + $this->assertNull($this->cache([], $key)->get()); } } diff --git a/tests/Support/KeyTest.php b/tests/Support/KeyTest.php index a27112b..657383e 100644 --- a/tests/Support/KeyTest.php +++ b/tests/Support/KeyTest.php @@ -31,7 +31,8 @@ public function testString() { $key = Key::get(':', ['Foo', 'Bar', 'Baz']); - $expected = '1356c67d7ad1638d816bfb822dd2c25d:ddc35f88fa71b6ef142ae61f35364653:f8dce67f2c94388282ed3fa797968a7c'; + $expected = + '17358f5eb750c32289df798e7766e830:64db6856f253b7bf17202a3dd3254fc1:05797d9d2d667864e94e07ba8df60840'; $this->assertSame($expected, $key); } @@ -40,7 +41,8 @@ public function testNumeric() { $key = Key::get(':', [1, 2, 3]); - $expected = 'c4ca4238a0b923820dcc509a6f75849b:c81e728d9d4c2f636f067f89cc14862c:eccbc87e4b5ce2fe28308fd9f2a7baf3'; + $expected = + 'd944267ac25276f12cb03fc698810d94:7b2fb106352b24c6dd644a8cdf200295:d8526ab50063e2025ef690f730cd5542'; $this->assertSame($expected, $key); } @@ -53,7 +55,12 @@ public function testArray() [['Baz', 'Qwerty']], ]); - $expected = '1356c67d7ad1638d816bfb822dd2c25d:ddc35f88fa71b6ef142ae61f35364653:f8dce67f2c94388282ed3fa797968a7c:acbd9ab2f68bea3f5291f825416546a1'; + $expected = implode(':', [ + 'b58721335d52d66a9486072fe3383ccf', + 'f61f09aec2b68da240f6680a6fc88c6a', + 'a13960759cc35a02e91fafb356f491c6', + '70f48dde06f86de7fae03486c277f597', + ]); $this->assertSame($expected, $key); } @@ -62,7 +69,10 @@ public function testBoolean() { $key = Key::get(':', [true, false]); - $expected = 'c4ca4238a0b923820dcc509a6f75849b:cfcd208495d565ef66e7dff9f98764da'; + $expected = implode(':', [ + 'd944267ac25276f12cb03fc698810d94', + 'bcb8c4703eae71d5d05c0a6eec1f7daa', + ]); $this->assertSame($expected, $key); } @@ -71,7 +81,12 @@ public function testCombine() { $key = Key::get(':', [1, 'Foo', [['Bar', 'Baz']]]); - $expected = 'c4ca4238a0b923820dcc509a6f75849b:1356c67d7ad1638d816bfb822dd2c25d:ddc35f88fa71b6ef142ae61f35364653:f8dce67f2c94388282ed3fa797968a7c'; + $expected = implode(':', [ + 'd944267ac25276f12cb03fc698810d94', + '5bfe89f7c2ace87ef1c208c3d95fc1b6', + 'a6426f0db8f32e1156366c7ffe317a6c', + '6e30ad368454c1fdd71d181f47314222', + ]); $this->assertSame($expected, $key); } @@ -80,7 +95,10 @@ public function testArrayable() { $key = Key::get(':', $this->dto()); - $expected = '1356c67d7ad1638d816bfb822dd2c25d:ddc35f88fa71b6ef142ae61f35364653'; + $expected = implode(':', [ + 'b58721335d52d66a9486072fe3383ccf', + '8a5c22700ece9adc6c0265fa4af575f1', + ]); $this->assertSame($expected, $key); } @@ -89,7 +107,7 @@ public function testCustomObject() { $key = Key::get(':', [new CustomObject()]); - $expected = '1356c67d7ad1638d816bfb822dd2c25d'; + $expected = 'b58721335d52d66a9486072fe3383ccf'; $this->assertSame($expected, $key); } @@ -108,10 +126,14 @@ public function testMultiObjectArrays() // Before hashing, the keys look like this: // qwe:rty:Foo:Bar:WASD:Foo - $expected - = '76d80224611fc919a5d54f0ff9fba446:24113791d2218cb84c9f0462e91596ef:' - . '1356c67d7ad1638d816bfb822dd2c25d:ddc35f88fa71b6ef142ae61f35364653:' - . '91412421a30e87ce15a4f10ea39f6682:1356c67d7ad1638d816bfb822dd2c25d'; + $expected = implode(':', [ + '6ced27e919d8e040c44929d72fffb681', + 'b619de70b824374bf86438df3b059bca', + 'bc5ea0aa608c8e0ef8175083e334abff', + '89f5d16cceb669516a0d37c6f2d47df8', + '4d1c034a3e1f42f73339950f3a416c46', + 'eb499c88d41a72f17278348308b4bae8', + ]); $this->assertSame($expected, $key); } @@ -120,7 +142,10 @@ public function testEmpties() { $key = Key::get(':', [null, '', 0, [], false]); - $expected = 'cfcd208495d565ef66e7dff9f98764da:cfcd208495d565ef66e7dff9f98764da'; + $expected = implode(':', [ + '2d4bab7f33ac57126deb8cde12a0c2ae', + '3e3a3e1902376d96020b11c67bec7a08', + ]); $this->assertSame($expected, $key); } @@ -129,7 +154,8 @@ public function testModelKey() { $key = Key::get(':', [User::class, 'foo', 'bar']); - $expected = 'e07e8d069dbdfde3b73552938ec82f0a:acbd18db4cc2f85cedef654fccc4a4d8:37b51d194a7513e45b56f6524f2d51f2'; + $expected = + '87789eae95facc4a5bfdeb957b860942:086f76c144511e1198c29a261e87ca50:2b72000f7b07c51cbbe0e7f85a19597e'; $this->assertSame($expected, $key); } @@ -141,7 +167,7 @@ public function testCarbon() Carbon::parse('2022-10-07 15:00:00'), ]); - $expected = '1391beb7fd700594df5a1d09d0afe677:72d97f016fa1dda7e212de874853ae28'; + $expected = '67f1a84c86633483bea1d2080767711c:aeab04bbac549fe6268a7e12ef761165'; $this->assertSame($expected, $key); } @@ -157,7 +183,10 @@ public function testFormRequest() ]) ); - $expected = '1356c67d7ad1638d816bfb822dd2c25d:ddc35f88fa71b6ef142ae61f35364653'; + $expected = implode(':', [ + 'b58721335d52d66a9486072fe3383ccf', + '8a5c22700ece9adc6c0265fa4af575f1', + ]); $this->assertSame($expected, $key); } @@ -172,7 +201,10 @@ public function testEnum() $key = Key::get(':', [WithoutValueEnum::foo, WithValueEnum::bar]); - $expected = 'acbd18db4cc2f85cedef654fccc4a4d8:37b51d194a7513e45b56f6524f2d51f2'; + $expected = implode(':', [ + '33e35b61ea46b126d2a6bf81acda8724', + '660a13c00e04c0d3ffb4dbf02a84a07a', + ]); $this->assertSame($expected, $key); } @@ -187,7 +219,7 @@ public function testWithoutHash() $key = Key::get(':', $keys, false); - $expected = 'Foo:Bar:Baz:Qwerty'; + $expected = '0.foo=Foo:1.bar=Bar:2.0.0=Baz:2.0.1=Qwerty'; $this->assertSame($expected, $key); }