Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Add a simple string cache #1097

Merged
merged 2 commits into from
Aug 5, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
## x.y.z

### Added
- Add a dedicated class for caching (#1097)
- Allow installation together with Symfony 6 (#1065)
- Support more file types in the `.editorconfig` (#1035)
- Set `align` attribute of `<th>` elements with `CssToAttributeConverter` (#1008)
Expand Down
2 changes: 2 additions & 0 deletions phpcs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
<rule ref="Squiz.Commenting.DocCommentAlignment"/>
<rule ref="Squiz.Commenting.EmptyCatchComment"/>
<rule ref="Squiz.Commenting.FunctionComment">
<exclude-pattern>*/tests/*</exclude-pattern>

<!-- Allow no comment for self-describing parameter and exception class names. -->
<exclude name="Squiz.Commenting.FunctionComment.MissingParamComment"/>
<exclude name="Squiz.Commenting.FunctionComment.EmptyThrows"/>
Expand Down
87 changes: 87 additions & 0 deletions src/Caching/SimpleStringCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

declare(strict_types=1);

namespace Pelago\Emogrifier\Caching;

/**
* This cache caches string values with string keys. It is not PSR-6-compliant.
*
* Usage:
*
* ```php
* $cache = new SimpleStringCache();
* $cache->set($key, $value);
* …
* if ($cache->has($key) {
* $cachedValue = $cache->get($value);
* }
* ```
*
* @internal
*/
class SimpleStringCache
{
/**
* @var array<string, string>
*/
private $values = [];

/**
* Checks whether there is an entry stored for the given key.
*
* @param string $key the key to check; must not be empty
*
* @throws \InvalidArgumentException
*/
public function has(string $key): bool
{
$this->assertNotEmptyKey($key);

return isset($this->values[$key]);
}

/**
* Returns the entry stored for the given key, and throws an exception if the value does not exist
* (which helps keep the return type simple).
*
* @param string $key the key to of the item to retrieve; must not be empty
*
* @return string the retrieved value; may be empty
*
* @throws \BadMethodCallException
*/
public function get(string $key): string
{
if (!$this->has($key)) {
throw new \BadMethodCallException('You can only call `get` with a key for an existing value.', 1625996246);
}

return $this->values[$key];
}

/**
* Sets or overwrites an entry.
*
* @param string $key the key to of the item to set; must not be empty
* @param string $value the value to set; can be empty
*
* @throws \BadMethodCallException
*/
public function set(string $key, string $value): void
{
$this->assertNotEmptyKey($key);

$this->values[$key] = $value;
}

/**
* @throws \InvalidArgumentException
*/
private function assertNotEmptyKey(string $key): void
{
if ($key === '') {
throw new \InvalidArgumentException('Please provide a non-empty key.', 1625995840);
}
}
}
152 changes: 152 additions & 0 deletions tests/Unit/Caching/SimpleStringCacheTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<?php

declare(strict_types=1);

namespace Pelago\Emogrifier\Tests\Unit\Caching;

use Pelago\Emogrifier\Caching\SimpleStringCache;
use PHPUnit\Framework\TestCase;

/**
* @covers \Pelago\Emogrifier\Caching\SimpleStringCache
*/
final class SimpleStringCacheTest extends TestCase
{
/**
* @var SimpleStringCache
*/
private $subject;

protected function setUp(): void
{
$this->subject = new SimpleStringCache();
}

/**
* @test
*/
public function hasForEmptyKeyThrowsException(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(1625995840);
$this->expectExceptionMessage('Please provide a non-empty key.');

$this->subject->has('');
}

/**
* @test
*/
public function hasInitiallyReturnsFalseForAnyKey(): void
{
self::assertFalse($this->subject->has('hello'));
}

/**
* @test
*/
public function hasForKeyThatHasNotBeenSetReturnsFalse(): void
{
$this->subject->set('hello', 'world');

self::assertFalse($this->subject->has('what'));
}

/**
* @test
*/
public function hasForKeyThatHasBeenSetReturnsTrue(): void
{
$key = 'hello';
$this->subject->set($key, 'world');

self::assertTrue($this->subject->has($key));
}

/**
* @test
*/
public function hasForKeyThatHasBeenSetAndOverwrittenReturnsTrue(): void
{
$key = 'hello';
$this->subject->set($key, 'world');
$this->subject->set($key, 'PHP');

self::assertTrue($this->subject->has($key));
}

/**
* @test
*/
public function getForEmptyKeyThrowsException(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(1625995840);
$this->expectExceptionMessage('Please provide a non-empty key.');

$this->subject->get('');
}

/**
* @test
*/
public function setForEmptyKeyThrowsException(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(1625995840);
$this->expectExceptionMessage('Please provide a non-empty key.');

$this->subject->set('', 'some value');
}

/**
* @test
*/
public function getForKeyThatHasNotBeenSetThrowsException(): void
{
$this->expectException(\BadMethodCallException::class);
$this->expectExceptionMessage('You can only call `get` with a key for an existing value.');
$this->expectExceptionCode(1625996246);

$this->subject->get('hello');
}

/**
* @return array<string, array<int, string>>
*/
public function provideValues(): array
{
return [
'empty string' => [''],
'non-empty string' => ['hello'],
];
}

/**
* @test
*
* @dataProvider provideValues
*/
public function getForKeyThatHasBeenSetReturnsSetValue(string $value): void
{
$key = 'hello';

$this->subject->set($key, $value);

self::assertSame($value, $this->subject->get($key));
}

/**
* @test
*/
public function getForKeyThatHasBeenSetTwiceReturnsValueSetLast(): void
{
$key = 'hello';
$value = 'world';

$this->subject->set($key, 'coffee');
$this->subject->set($key, $value);

self::assertSame($value, $this->subject->get($key));
}
}