Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
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
4 changes: 2 additions & 2 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
compiler: jit

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Install libmemcached
run: sudo apt-get install -y libmemcached-dev
Expand All @@ -48,7 +48,7 @@ jobs:

- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
Expand Down
36 changes: 29 additions & 7 deletions library/iFixit/Matryoshka/Scope.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,20 @@ public function getPrefix() {
return $this->scopePrefix ?: $this->getScopePrefix();
}

public function getScopePrefix(bool $reset = false) {
public function getScopePrefix(bool $reset = false, bool $generateOnMiss = true) {
if ($this->scopePrefix === null || $reset) {
$scopeValue = $this->backend->getAndSet($this->getScopeKey(),
function() {
return substr(md5(microtime() . $this->scopeName), 0, 16);
}, 0, $reset);

$this->scopePrefix = "{$scopeValue}-";
$scopeValue = $reset ? self::MISS : $this->backend->get($this->getScopeKey());
if ($scopeValue === self::MISS) {
if ($generateOnMiss) {
$scopeValue = substr(md5(microtime() . $this->scopeName), 0, 16);
$this->scopePrefix = "{$scopeValue}-";
$this->backend->set($this->getScopeKey(), $scopeValue);
} else {
return self::MISS;
}
} else {
$this->scopePrefix = "{$scopeValue}-";
}
Comment thread
masonmcelvain marked this conversation as resolved.
}

return $this->scopePrefix;
Expand All @@ -52,4 +58,20 @@ public function deleteScope(): bool {
private function getScopeKey() {
return "scope-{$this->scopeName}";
}

public function get($key) {
// If the scope prefix doesn't exist, all keys in this scope are a miss.
if (!$this->getScopePrefix(generateOnMiss: false)) {
Comment thread
masonmcelvain marked this conversation as resolved.
return self::MISS;
}
return parent::get($key);
}

public function getMultiple(array $keys) {
// If the scope prefix doesn't exist, all keys in this scope are a miss.
if (!$this->getScopePrefix(generateOnMiss: false)) {
Comment thread
masonmcelvain marked this conversation as resolved.
return [array_fill_keys(array_keys($keys), self::MISS), $keys];
}
return parent::getMultiple($keys);
}
}
51 changes: 49 additions & 2 deletions tests/ScopeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ protected function getBackend() {
public function testScope() {
$memoryCache = new Matryoshka\Ephemeral();
$scope = 'scope';
$scopedCache = new Matryoshka\Scope($memoryCache,
$scope);
$scopedCache = new Matryoshka\Scope($memoryCache, $scope);
list($key1, $value1) = $this->getRandomKeyValue();
list($key2, $value2) = $this->getRandomKeyValue();

Expand Down Expand Up @@ -41,6 +40,54 @@ public function testScope() {
$this->assertTrue($scopedCache->set($key1, $value1));

$this->assertSame($value1, $scopedCache->get($key1));

$scopedCache = new Matryoshka\Scope($memoryCache, $scope);
$this->assertSame($value1, $scopedCache->get($key1));
}

public function testScopeShortCircuitGet() {
$scope = 'scope';
$mockBackend = $this->getMockBuilder(Matryoshka\Ephemeral::class)
->setMethods(['get'])
->getMock();

// Assert get() is called only once because a missing scope value means
// that all underlying values are also missing.
$mockBackend->expects($this->once())
->method('get')
->with($this->stringContains($scope))
->willReturn(Matryoshka\Backend::MISS);
$scopedCache = new Matryoshka\Scope($mockBackend, $scope);

$key = (string)rand(1, 100000);
$this->assertNull($scopedCache->get($key));
}

public function testScopeShortCircuitGetMultiple() {
$scope = 'scope';
$mockBackend = $this->getMockBuilder(Matryoshka\Ephemeral::class)
->setMethods(['get', 'getMultiple'])
->getMock();

// Assert get() is called only once because a missing scope value means
// that all underlying values are also missing.
$mockBackend->expects($this->once())
->method('get')
->with($this->stringContains($scope))
->willReturn(Matryoshka\Backend::MISS);
// Assert getMultiple() is never called because a missi
// we don't need to check individual keys.
$mockBackend->expects($this->never())
->method('getMultiple');

$scopedCache = new Matryoshka\Scope($mockBackend, $scope);

$key = (string)rand(1, 100000);
$keys = [$key => 'no matter'];
$this->assertSame(
[[$key => Matryoshka\Backend::MISS], $keys],
$scopedCache->getMultiple($keys)
);
}
Comment thread
danielbeardsley marked this conversation as resolved.

public function testAbsoluteKey() {
Expand Down