Skip to content

Commit 2139c80

Browse files
committed
Builtin cache invalidation system aka make API Platform fast as hell
1 parent c86a607 commit 2139c80

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1304
-123
lines changed

behat.yml.dist

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ default:
55
- 'FeatureContext': { doctrine: '@doctrine' }
66
- 'HydraContext'
77
- 'SwaggerContext'
8+
- 'HttpCacheContext'
89
- 'Behat\MinkExtension\Context\MinkContext'
910
- 'Behatch\Context\RestContext'
1011
- 'Behatch\Context\JsonContext'
@@ -25,11 +26,11 @@ default:
2526
coverage:
2627
suites:
2728
default:
28-
contexts:
2929
contexts:
3030
- 'FeatureContext': { doctrine: '@doctrine' }
3131
- 'HydraContext'
3232
- 'SwaggerContext'
33+
- 'HttpCacheContext'
3334
- 'CoverageContext'
3435
- 'Behat\MinkExtension\Context\MinkContext'
3536
- 'Behatch\Context\RestContext'

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"doctrine/orm": "^2.5",
3737
"doctrine/annotations": "^1.2",
3838
"friendsofsymfony/user-bundle": "^2.0",
39+
"guzzlehttp/guzzle": "^6.0",
3940
"nelmio/api-doc-bundle": "^2.11.2",
4041
"php-mock/php-mock-phpunit": "^1.1",
4142
"phpdocumentor/reflection-docblock": "^3.0",
@@ -63,6 +64,7 @@
6364
},
6465
"suggest": {
6566
"friendsofsymfony/user-bundle": "To use the FOSUserBundle bridge.",
67+
"guzzlehttp/guzzle": "To use the HTTP cache invalidation system.",
6668
"phpdocumentor/reflection-docblock": "To support extracting metadata from PHPDoc.",
6769
"psr/cache-implementation": "To use metadata caching.",
6870
"symfony/cache": "To have metadata caching when using Symfony integration.",
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
use Behat\Symfony2Extension\Context\KernelAwareContext;
15+
use Symfony\Component\HttpKernel\KernelInterface;
16+
17+
/**
18+
* @author Kévin Dunglas <[email protected]>
19+
*/
20+
class HttpCacheContext implements KernelAwareContext
21+
{
22+
/**
23+
* @var KernelInterface
24+
*/
25+
private $kernel;
26+
27+
public function setKernel(KernelInterface $kernel)
28+
{
29+
$this->kernel = $kernel;
30+
}
31+
32+
/**
33+
* @Then ":iris" IRIs should be purged
34+
*/
35+
public function irisShouldBePurged(string $iris)
36+
{
37+
$purger = $this->kernel->getContainer()->get('api_platform.http_cache.purger');
38+
39+
$purgedIris = implode(',', $purger->getIris());
40+
$purger->clear();
41+
42+
if ($iris !== $purgedIris) {
43+
throw new \PHPUnit_Framework_ExpectationFailedException(
44+
sprintf('IRIs "%s" does not match expected "%s".', $purgedIris, $iris)
45+
);
46+
}
47+
}
48+
}

features/http_cache/headers.feature

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Feature: Default values of HTTP cache headers
2+
In order to make API responses caheable
3+
As an API software developer
4+
I need to be able to set default cache headers values
5+
6+
@createSchema
7+
@dropSchema
8+
Scenario: Cache headers default value
9+
When I send a "GET" request to "/relation_embedders"
10+
Then the response status code should be 200
11+
And the header "Etag" should be equal to '"21248afbca1f242fd3009ac7cdf13293"'
12+
And the header "Cache-Control" should be equal to "max-age=60, public, s-maxage=3600"
13+
And the header "Vary" should be equal to "Content-Type, Cookie"

features/http_cache/tags.feature

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
Feature: Cache invalidation through HTTP Cache tags
2+
In order to have a fast API
3+
As an API software developer
4+
I need to store API responses in a cache
5+
6+
@createSchema
7+
Scenario: Create some embedded resources
8+
When I add "Content-Type" header equal to "application/ld+json"
9+
And I send a "POST" request to "/relation_embedders" with body:
10+
"""
11+
{
12+
"anotherRelated": {
13+
"name": "Related",
14+
"thirdLevel": {}
15+
}
16+
}
17+
"""
18+
Then the response status code should be 201
19+
And the header "Cache-Tags" should not exist
20+
And "/relation_embedders,/related_dummies,/third_levels" IRIs should be purged
21+
22+
Scenario: Tags must be set for items
23+
When I send a "GET" request to "/relation_embedders/1"
24+
Then the response status code should be 200
25+
And the header "Cache-Tags" should be equal to "/relation_embedders/1,/related_dummies/1,/third_levels/1"
26+
27+
Scenario: Create some more resources
28+
When I add "Content-Type" header equal to "application/ld+json"
29+
And I send a "POST" request to "/relation_embedders" with body:
30+
"""
31+
{
32+
"anotherRelated": {
33+
"name": "Another Related",
34+
"thirdLevel": {}
35+
}
36+
}
37+
"""
38+
Then the response status code should be 201
39+
And the header "Cache-Tags" should not exist
40+
41+
Scenario: Tags must be set for collections
42+
When I send a "GET" request to "/relation_embedders"
43+
Then the response status code should be 200
44+
And the header "Cache-Tags" should be equal to "/relation_embedders/1,/related_dummies/1,/third_levels/1,/relation_embedders/2,/related_dummies/2,/third_levels/2,/relation_embedders"
45+
46+
Scenario: Purge item on update
47+
When I add "Content-Type" header equal to "application/ld+json"
48+
And I send a "PUT" request to "/relation_embedders/1" with body:
49+
"""
50+
{
51+
"paris": "France"
52+
}
53+
"""
54+
Then the response status code should be 200
55+
And the header "Cache-Tags" should not exist
56+
And "/relation_embedders,/relation_embedders/1,/related_dummies/1" IRIs should be purged
57+
58+
Scenario: Purge item and the related collection on update
59+
When I add "Content-Type" header equal to "application/ld+json"
60+
And I send a "DELETE" request to "/relation_embedders/1"
61+
Then the response status code should be 204
62+
And the header "Cache-Tags" should not exist
63+
And "/relation_embedders,/relation_embedders/1,/related_dummies/1" IRIs should be purged
64+
65+
Scenario: Create two Relation2
66+
When I add "Content-Type" header equal to "application/ld+json"
67+
And I send a "POST" request to "/relation2s" with body:
68+
"""
69+
{
70+
}
71+
"""
72+
And I send a "POST" request to "/relation2s" with body:
73+
"""
74+
{
75+
}
76+
"""
77+
Then the response status code should be 201
78+
79+
Scenario: Embedded collection must be listed in cache tags
80+
When I send a "GET" request to "/relation2s/1"
81+
Then the header "Cache-Tags" should be equal to "/relation2s/1"
82+
83+
Scenario: Create a Relation1
84+
When I add "Content-Type" header equal to "application/ld+json"
85+
And I send a "POST" request to "/relation1s" with body:
86+
"""
87+
{
88+
"relation2": "/relation2s/1"
89+
}
90+
"""
91+
Then the response status code should be 201
92+
And "/relation1s,/relation2s/1" IRIs should be purged
93+
94+
@dropSchema
95+
Scenario: Update a Relation1
96+
When I add "Content-Type" header equal to "application/ld+json"
97+
And I send a "PUT" request to "/relation1s/1" with body:
98+
"""
99+
{
100+
"relation2": "/relation2s/2"
101+
}
102+
"""
103+
Then the response status code should be 200
104+
And "/relation1s,/relation1s/1,/relation2s/2,/relation2s/1" IRIs should be purged

features/main/subresource.feature

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ Feature: Subresource support
1212
And the JSON should be equal to:
1313
"""
1414
{
15-
"@context": "/contexts/Answer",
16-
"@id": "/answers/1",
17-
"@type": "Answer",
18-
"id": 1,
19-
"content": "42",
20-
"question": "/questions/1"
15+
"@context": "\/contexts\/Answer",
16+
"@id": "\/answers\/1",
17+
"@type": "Answer",
18+
"id": 1,
19+
"content": "42",
20+
"question": "\/questions\/1"
2121
}
2222
"""
2323

phpstan.neon

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ parameters:
44
excludes_analyse:
55
- tests/Fixtures/app/cache
66
ignoreErrors:
7-
- '#Call to an undefined method Symfony\\Component\\Routing\\Exception\\ExceptionInterface::getCode()#'
7+
- '#Call to an undefined method Symfony\\Component\\Routing\\Exception\\ExceptionInterface::getCode\(\)#'
88
- '#Call to an undefined method Prophecy\\Prophecy\\ObjectProphecy::[a-zA-Z0-9_]+\(\)#'
99
- '#Access to an undefined property Prophecy\\Prophecy\\ObjectProphecy::\$[a-zA-Z0-9_]+#'
1010
- '#Call to an undefined method PHPUnit_Framework_MockObject_MockObject::[a-zA-Z0-9_]+\(\)#'
11+
- '#Call to an undefined method Doctrine\\Common\\Persistence\\Mapping\\ClassMetadata::getAssociationMappings\(\)#'
1112

1213
# False positives
1314
- '#Parameter \#2 \$dqlPart of method Doctrine\\ORM\\QueryBuilder::add\(\) expects Doctrine\\ORM\\Query\\Expr\\Base, Doctrine\\ORM\\Query\\Expr\\Join\[\] given#' # Fixed in Doctrine's master

src/Api/IdentifiersExtractorInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
namespace ApiPlatform\Core\Api;
1515

16+
use ApiPlatform\Core\Exception\RuntimeException;
17+
1618
/**
1719
* Extracts identifiers for a given Resource according to the retrieved Metadata.
1820
*

0 commit comments

Comments
 (0)