Skip to content

Commit 92ab6d6

Browse files
authored
Merge pull request #448 from jackalope/cache-psr16-compatible
Fix PSR16 cache support: sanitize all non-guaranteed characters in key
2 parents 4e26403 + 6b999d5 commit 92ab6d6

File tree

6 files changed

+62
-20
lines changed

6 files changed

+62
-20
lines changed

.github/workflows/test-application.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ jobs:
7575
extensions: "pdo, pdo_sqlite, pdo_mysql, mysql, pdo_pgsql"
7676
tools: 'composer:v2'
7777

78+
- name: Require dependencies
79+
if: ${{ matrix.php-version == '8.0' }}
80+
run: composer require "psr/simple-cache:2.*" --no-update --dev # This can be removed if min version of symfony/cache is ^6.0
81+
7882
- name: Install dependencies with Composer
7983
uses: ramsey/composer-install@v1
8084
with:

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ Changelog
44
1.x
55
===
66

7+
1.13.0
8+
------
9+
10+
* Fixed cache key sanitize for PSR-16 cache.
11+
* Fixed not found detection for PSR-16 cache.
12+
713
1.12.0
814
------
915

composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
},
3131
"require-dev": {
3232
"psr/log": "^1 || ^2 || ^3",
33+
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0",
3334
"phpcr/phpcr-api-tests": "2.1.22",
3435
"phpunit/phpunit": "8.5.21",
3536
"symfony/cache": "^5.4 || ^6.2"

src/Jackalope/Transport/DoctrineDBAL/CachedClient.php

+17-7
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ public function __construct(FactoryInterface $factory, Connection $conn, array $
5151

5252
$this->caches = $caches;
5353
$this->keySanitizer = static function ($cacheKey) {
54-
return str_replace(' ', '_', $cacheKey);
54+
return str_replace(
55+
['%', '.'],
56+
['_', '|'],
57+
\urlencode($cacheKey)
58+
);
5559
};
5660
}
5761

@@ -259,7 +263,7 @@ public function getNode($path)
259263
$cacheKey = "nodes: $path, ".$this->workspaceName;
260264
$cacheKey = $this->sanitizeKey($cacheKey);
261265

262-
if (false !== ($result = $this->get('nodes', $cacheKey))) {
266+
if (null !== ($result = $this->get('nodes', $cacheKey))) {
263267
if ('ItemNotFoundException' === $result) {
264268
throw new ItemNotFoundException("Item '$path' not found in workspace '$this->workspaceName'");
265269
}
@@ -319,7 +323,7 @@ protected function getSystemIdForNodeUuid($uuid, $workspaceName = null)
319323
$cacheKey = "id: $uuid, ".$workspaceName;
320324
$cacheKey = $this->sanitizeKey($cacheKey);
321325

322-
if (false !== ($result = $this->get('nodes', $cacheKey))) {
326+
if (null !== ($result = $this->get('nodes', $cacheKey))) {
323327
if ('false' === $result) {
324328
return false;
325329
}
@@ -495,7 +499,7 @@ public function getNodePathForIdentifier($uuid, $workspace = null)
495499
$cacheKey = "nodes by uuid: $uuid, $this->workspaceName";
496500
$cacheKey = $this->sanitizeKey($cacheKey);
497501

498-
if (false !== ($result = $this->get('nodes', $cacheKey))) {
502+
if (null !== ($result = $this->get('nodes', $cacheKey))) {
499503
if ('ItemNotFoundException' === $result) {
500504
throw new ItemNotFoundException("no item found with uuid $uuid");
501505
}
@@ -560,7 +564,7 @@ public function getReferences($path, $name = null)
560564
$cacheKey = "nodes references: $path, $name, " . $this->workspaceName;
561565
$cacheKey = $this->sanitizeKey($cacheKey);
562566

563-
if (false !== ($result = $this->get('nodes', $cacheKey))) {
567+
if (null !== ($result = $this->get('nodes', $cacheKey))) {
564568
return $result;
565569
}
566570

@@ -608,7 +612,7 @@ public function query(Query $query)
608612
$cacheKey = "query: {$query->getStatement()}, {$query->getLimit()}, {$query->getOffset()}, {$query->getLanguage()}, ".$this->workspaceName;
609613
$cacheKey = $this->sanitizeKey($cacheKey);
610614

611-
if (false !== ($result = $this->get('query', $cacheKey))) {
615+
if (null !== ($result = $this->get('query', $cacheKey))) {
612616
return $result;
613617
}
614618

@@ -667,6 +671,12 @@ private function get(string $name, string $key)
667671
return $this->caches[$name]->get($key);
668672
}
669673

670-
return $this->caches[$name]->fetch($key);
674+
$result = $this->caches[$name]->fetch($key);
675+
676+
if ($result === false) {
677+
return null;
678+
}
679+
680+
return $result;
671681
}
672682
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Jackalope\Transport\DoctrineDBAL;
4+
5+
use Doctrine\DBAL\Connection;
6+
use Jackalope\Factory;
7+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
8+
use Symfony\Component\Cache\Psr16Cache;
9+
10+
class CachedClientFunctionalTest extends ClientTest
11+
{
12+
protected function getClient(Connection $conn)
13+
{
14+
$nodeCacheAdapter = new ArrayAdapter();
15+
$nodeCache = new Psr16Cache($nodeCacheAdapter);
16+
17+
$metaCacheAdapter = new ArrayAdapter();
18+
$metaCache = new Psr16Cache($metaCacheAdapter);
19+
20+
return new CachedClient(new Factory(), $conn, [
21+
'nodes' => $nodeCache,
22+
'meta' => $metaCache,
23+
]);
24+
}
25+
}

tests/Jackalope/Transport/DoctrineDBAL/CachedClientTest.php

+9-13
Original file line numberDiff line numberDiff line change
@@ -31,29 +31,25 @@ public function testArrayObjectIsConvertedToArray()
3131
public function testCacheHit()
3232
{
3333
$cache = new \stdClass();
34-
$this->cacheMock->method('fetch')->with('nodes:_/test,_tests')->willReturn($cache);
34+
$this->cacheMock->method('fetch')->with('nodes_3A+_2Ftest_2C+tests')->willReturn($cache);
3535

3636
$this->assertSame($cache, $this->transport->getNode('/test'));
3737
}
3838

3939
/**
40-
* The default key sanitizer replaces spaces with underscores
40+
* The default key sanitizer keeps the cache key compatible with PSR16
4141
*/
4242
public function testDefaultKeySanitizer()
4343
{
44-
$first = true;
45-
$this->cacheMock
46-
->method('fetch')
47-
->with(self::callback(function ($arg) use (&$first) {
48-
self::assertEquals($first ? 'nodetypes:_a:0:{}' : 'node_types', $arg);
49-
$first = false;
44+
$client = $this->getClient($this->getConnection());
45+
$reflection = new \ReflectionClass($client);
46+
$keySanitizerProperty = $reflection->getProperty('keySanitizer');
47+
$keySanitizerProperty->setAccessible(true);
48+
$defaultKeySanitizer = $keySanitizerProperty->getValue($client);
5049

51-
return true;
52-
}));
50+
$result = $defaultKeySanitizer(' :{}().@/"\\'); // not allowed PSR16 keys
5351

54-
/** @var CachedClient $cachedClient */
55-
$cachedClient = $this->transport;
56-
$cachedClient->getNodeTypes();
52+
$this->assertEquals('+_3A_7B_7D_28_29|_40_2F_22_5C', $result);
5753
}
5854

5955
public function testCustomkeySanitizer()

0 commit comments

Comments
 (0)