Skip to content

Commit e7df0e3

Browse files
authored
add relative time filters to facilitate better cacheability (#37)
1 parent 84cf843 commit e7df0e3

10 files changed

+307
-1
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
},
2323
"require-dev": {
2424
"phpunit/phpunit": "^5.7",
25-
"mockery/mockery": "~0.9.9"
25+
"mockery/mockery": "~0.9.9",
26+
"symfony/phpunit-bridge": "^2|^3"
2627
},
2728
"conflict": {
2829
"hhvm": "*"

phpunit.xml.dist

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,9 @@
1313
<directory>./src</directory>
1414
</whitelist>
1515
</filter>
16+
17+
<listeners>
18+
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
19+
</listeners>
20+
1621
</phpunit>

src/Contentful.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Markup\Contentful\Exception\LinkUnresolvableException;
1515
use Markup\Contentful\Exception\ResourceUnavailableException;
1616
use Markup\Contentful\Filter\ContentTypeFilterProvider;
17+
use Markup\Contentful\Filter\DecidesCacheKeyInterface;
1718
use Markup\Contentful\Log\LoggerInterface;
1819
use Markup\Contentful\Log\LogInterface;
1920
use Markup\Contentful\Log\NullLogger;
@@ -932,6 +933,10 @@ private function generateCacheKey($spaceKey, $queryType, $isPreview, $disambigua
932933
};
933934
usort($parameters, $paramSort);
934935
$key .= '-' . implode(',', array_map(function (ParameterInterface $param) {
936+
if ($param instanceof DecidesCacheKeyInterface) {
937+
return $param->getCacheKey();
938+
}
939+
935940
return sprintf('|%s|%s↦%s', $param->getName(), $param->getKey(), $param->getValue());
936941
}, $parameters));
937942
}

src/Filter/AfterFilter.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Markup\Contentful\Filter;
4+
5+
class AfterFilter extends RelativeTimeFilter
6+
{
7+
/**
8+
* The key in a query string on an API request.
9+
*
10+
* @return string
11+
*/
12+
public function getKey()
13+
{
14+
return $this->createGreaterThanFilter()->getKey();
15+
}
16+
17+
/**
18+
* The value in a query string on an API request.
19+
*
20+
* @return string
21+
*/
22+
public function getValue()
23+
{
24+
return $this->createGreaterThanFilter()->getValue();
25+
}
26+
27+
private function createGreaterThanFilter()
28+
{
29+
return new GreaterThanFilter(
30+
$this->getProperty(),
31+
new \DateTime($this->getRelativeTime())
32+
);
33+
}
34+
}

src/Filter/BeforeFilter.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Markup\Contentful\Filter;
4+
5+
class BeforeFilter extends RelativeTimeFilter
6+
{
7+
/**
8+
* The key in a query string on an API request.
9+
*
10+
* @return string
11+
*/
12+
public function getKey()
13+
{
14+
return $this->createLessThanFilter()->getKey();
15+
}
16+
17+
/**
18+
* The value in a query string on an API request.
19+
*
20+
* @return string
21+
*/
22+
public function getValue()
23+
{
24+
return $this->createLessThanFilter()->getValue();
25+
}
26+
27+
private function createLessThanFilter()
28+
{
29+
return new LessThanFilter(
30+
$this->getProperty(),
31+
new \DateTime($this->getRelativeTime())
32+
);
33+
}
34+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace Markup\Contentful\Filter;
4+
5+
/**
6+
* Interface to be implemented by a filter when that filter has a custom means of deciding how it is represented
7+
* in a query cache key.
8+
*/
9+
interface DecidesCacheKeyInterface
10+
{
11+
/**
12+
* @return string
13+
*/
14+
public function getCacheKey();
15+
}

src/Filter/RelativeTimeFilter.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace Markup\Contentful\Filter;
4+
5+
use Markup\Contentful\PropertyInterface;
6+
7+
abstract class RelativeTimeFilter extends PropertyFilter implements DecidesCacheKeyInterface
8+
{
9+
use ClassBasedNameTrait;
10+
11+
/**
12+
* @var string
13+
*/
14+
private $relativeTime;
15+
16+
/**
17+
* @param PropertyInterface $property
18+
* @param string $relativeTime
19+
*/
20+
public function __construct(PropertyInterface $property, $relativeTime)
21+
{
22+
parent::__construct($property);
23+
$this->relativeTime = $relativeTime;
24+
}
25+
26+
/**
27+
* @return string
28+
*/
29+
public function getCacheKey()
30+
{
31+
return sprintf(
32+
'|%s|%s↦%s',
33+
$this->getName(),
34+
$this->getProperty()->getKey(),
35+
str_replace(' ', '_', $this->relativeTime)
36+
);
37+
}
38+
39+
protected function getRelativeTime()
40+
{
41+
return $this->relativeTime;
42+
}
43+
}

tests/ContentfulTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Markup\Contentful\Decorator\AssetDecoratorInterface;
1212
use Markup\Contentful\EntryInterface;
1313
use Markup\Contentful\Exception\ResourceUnavailableException;
14+
use Markup\Contentful\Filter\BeforeFilter;
1415
use Markup\Contentful\Filter\EqualFilter;
1516
use Markup\Contentful\Filter\LessThanFilter;
1617
use Markup\Contentful\Link;
@@ -303,6 +304,26 @@ public function testCacheMissDoesFetch()
303304
$contentful->getEntries($filters);
304305
}
305306

307+
public function testBeforeFilterUsesOwnCacheKey()
308+
{
309+
$handlerOption = $this->getSuccessHandlerOption($this->getEntriesData(), '235345lj34h53j4h');
310+
$expectedCacheKey = 'jskdfjhsdfk-entries-|before|fields.published↦now';
311+
$cachePool = $this->getMockCachePool();
312+
$cacheItem = $this->getMockCacheItem();
313+
$cachePool
314+
->shouldReceive('getItem')
315+
->with($expectedCacheKey)
316+
->andReturn($cacheItem);
317+
$cacheItem
318+
->shouldReceive('isHit')
319+
->once()
320+
->andReturn(false);
321+
$spaces = array_merge_recursive($this->spaces, ['test' => ['cache' => $cachePool]]);
322+
$contentful = $this->getContentful($spaces, array_merge($this->options, $handlerOption));
323+
$filters = [new BeforeFilter(new FieldProperty('published'), 'now')];
324+
$contentful->getEntries($filters);
325+
}
326+
306327
public function testCacheHitUsesCacheAndDoesNotFetch()
307328
{
308329
$handlerOption = $this->getExplodyHandlerOption();

tests/Filter/AfterFilterTest.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
namespace Markup\Contentful\Tests\Filter;
4+
5+
use Markup\Contentful\Filter\AfterFilter;
6+
use Markup\Contentful\Filter\DecidesCacheKeyInterface;
7+
use Markup\Contentful\Filter\PropertyFilter;
8+
use Markup\Contentful\FilterInterface;
9+
use Markup\Contentful\PropertyInterface;
10+
use Mockery as m;
11+
use Mockery\Adapter\Phpunit\MockeryTestCase;
12+
13+
class AfterFilterTest extends MockeryTestCase
14+
{
15+
/**
16+
* @var PropertyInterface|m\MockInterface
17+
*/
18+
private $property;
19+
20+
/**
21+
* @var string
22+
*/
23+
private $relativeTime;
24+
25+
/**
26+
* @var AfterFilter
27+
*/
28+
private $filter;
29+
30+
protected function setUp()
31+
{
32+
$this->property = m::mock(PropertyInterface::class);
33+
$this->relativeTime = 'now';
34+
$this->filter = new AfterFilter($this->property, $this->relativeTime);
35+
}
36+
37+
public function testIsFilter()
38+
{
39+
$this->assertInstanceOf(FilterInterface::class, $this->filter);
40+
}
41+
42+
public function testIsPropertyFilter()
43+
{
44+
$this->assertInstanceOf(PropertyFilter::class, $this->filter);
45+
}
46+
47+
public function testGetKeyUsesLessThan()
48+
{
49+
$propertyKey = 'created';
50+
$this->property
51+
->shouldReceive('getKey')
52+
->andReturn($propertyKey);
53+
$this->assertEquals('created[gt]', $this->filter->getKey());
54+
}
55+
56+
/**
57+
* @group time-sensitive
58+
*/
59+
public function testGetDateTimeValue()
60+
{
61+
$nowValue = \DateTime::createFromFormat('U', time())->format('Y-m-d\TH:i:s\Z');
62+
$this->assertEquals($nowValue, $this->filter->getValue());
63+
}
64+
65+
public function testDecidesCacheKey()
66+
{
67+
$this->assertInstanceOf(DecidesCacheKeyInterface::class, $this->filter);
68+
$propertyName = 'published';
69+
$this->property
70+
->shouldReceive('getKey')
71+
->andReturn($propertyName);
72+
$this->assertEquals('|after|published↦now', $this->filter->getCacheKey());
73+
}
74+
}

tests/Filter/BeforeFilterTest.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
namespace Markup\Contentful\Tests\Filter;
4+
5+
use Markup\Contentful\Filter\DecidesCacheKeyInterface;
6+
use Markup\Contentful\Filter\BeforeFilter;
7+
use Markup\Contentful\Filter\PropertyFilter;
8+
use Markup\Contentful\FilterInterface;
9+
use Markup\Contentful\PropertyInterface;
10+
use Mockery as m;
11+
use Mockery\Adapter\Phpunit\MockeryTestCase;
12+
13+
class BeforeFilterTest extends MockeryTestCase
14+
{
15+
/**
16+
* @var PropertyInterface|m\MockInterface
17+
*/
18+
private $property;
19+
20+
/**
21+
* @var string
22+
*/
23+
private $relativeTime;
24+
25+
/**
26+
* @var BeforeFilter
27+
*/
28+
private $filter;
29+
30+
protected function setUp()
31+
{
32+
$this->property = m::mock(PropertyInterface::class);
33+
$this->relativeTime = 'now';
34+
$this->filter = new BeforeFilter($this->property, $this->relativeTime);
35+
}
36+
37+
public function testIsFilter()
38+
{
39+
$this->assertInstanceOf(FilterInterface::class, $this->filter);
40+
}
41+
42+
public function testIsPropertyFilter()
43+
{
44+
$this->assertInstanceOf(PropertyFilter::class, $this->filter);
45+
}
46+
47+
public function testGetKeyUsesLessThan()
48+
{
49+
$propertyKey = 'created';
50+
$this->property
51+
->shouldReceive('getKey')
52+
->andReturn($propertyKey);
53+
$this->assertEquals('created[lt]', $this->filter->getKey());
54+
}
55+
56+
/**
57+
* @group time-sensitive
58+
*/
59+
public function testGetDateTimeValue()
60+
{
61+
$nowValue = \DateTime::createFromFormat('U', time())->format('Y-m-d\TH:i:s\Z');
62+
$this->assertEquals($nowValue, $this->filter->getValue());
63+
}
64+
65+
public function testDecidesCacheKey()
66+
{
67+
$this->assertInstanceOf(DecidesCacheKeyInterface::class, $this->filter);
68+
$propertyName = 'published';
69+
$this->property
70+
->shouldReceive('getKey')
71+
->andReturn($propertyName);
72+
$this->assertEquals('|before|published↦now', $this->filter->getCacheKey());
73+
}
74+
}

0 commit comments

Comments
 (0)