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

Implement caching types #26

Merged
merged 1 commit into from
Feb 27, 2017
Merged

Implement caching types #26

merged 1 commit into from
Feb 27, 2017

Conversation

acrobat
Copy link
Contributor

@acrobat acrobat commented Feb 22, 2017

Q A
Bug fix? yes and no
New feature? yes
BC breaks? no
Deprecations? no
Related tickets fixes #3
Documentation php-http/documentation#178
License MIT

This is a first simple version to get the discussion going and see what changes are needed here.

What's in this PR?

As discussed in #3 this PR introduces 2 factory methods and an option to specify which cache control directives should be checked

Checklist

Copy link
Contributor

@dbu dbu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i find the name excluded_directives confusing. we seem to not exclude those directives, but use them.

i would call the option respect_response_cache_directives and deprecate respect_cache_headers.

public static function clientCache(CacheItemPoolInterface $pool, StreamFactory $streamFactory, array $config = [])
{
// Allow caching of private requests
$config['excluded_directives'] = ['no-cache'];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should check if the option already exists and add it to the existing list

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe just allow respect_cache_headers to be either boolean or array. If boolean true then assume it is all directives. If false then assume it is empty array.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the things like no-cache are not headers, but directives. and some can be both on request and/or response, and we should treat that separately to allow the user full control. maybe they want no-cache on request to be respected, but on the response they still want to cache...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also more in favor of separate options. Because I would avoid mixing option types bool/array

@acrobat
Copy link
Contributor Author

acrobat commented Feb 22, 2017

@dbu thanks for the feedback! I've updated the code!

@acrobat
Copy link
Contributor Author

acrobat commented Feb 23, 2017

I've tested my patch on a project using the github api (which sends the private cache directive with each request). And I can confirm it works when you construct the cache plugin with the clientCache method.

The phpspec tests fail after the patch, but I will look into this fail later tonight!

Copy link
Contributor

@dbu dbu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, i think we move in the right direction with this. this will end up being quite flexible, while still being reasonably understandable.

@@ -242,7 +268,7 @@ private function createCacheKey(RequestInterface $request)
*/
private function getMaxAge(ResponseInterface $response)
{
if (!$this->config['respect_cache_headers']) {
if (!$this->config['respect_cache_headers'] || !in_array('max-age', $this->config['respect_response_cache_directives'], true)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would do the cleanup (create an empty array in respect_response_cache_directives) of this in the constructor and issue a deprecation warning if respect_cache_headers is defined. if both are defined in the constructor, throw an exception as its wrong to do that.

return false;

foreach ($this->config['respect_response_cache_directives'] as $cacheDirective) {
if ($this->getCacheControlDirective($response, $cacheDirective)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this means that if max-age is set to be respected and defined, we do not cache. i think you need a constant for NO_CACHE_FLAGS and array intersect that with respect_response_cache_directives

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you are right, I will look into a way to do this. We can't use a constant with an array value as the cacheplugin supports php >=5.4

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, right. then private property is fine too

@@ -58,6 +59,28 @@ public function __construct(CacheItemPoolInterface $pool, StreamFactory $streamF
$this->config = $optionsResolver->resolve($config);
}

public static function clientCache(CacheItemPoolInterface $pool, StreamFactory $streamFactory, array $config = [])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets add a phpdoc here that says this sets up the cache plugin in a client mode. it will cache even with cache-control private or no-store.

'respect_cache_headers' => true,
'hash_algo' => 'sha1',
'methods' => ['GET', 'HEAD'],
'respect_response_cache_directives' => ['no-cache', 'private', 'max-age'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to stay BC, this should include no-store

@acrobat
Copy link
Contributor Author

acrobat commented Feb 23, 2017

@dbu I've incorporated the feedback and I've added a phpspec test for this new option/behavior

Copy link
Member

@Nyholm Nyholm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this PR. I really like the idea of these factory methods.

I had some comments on some things I did not understand.

@@ -34,31 +35,83 @@
private $config;

/**
* Cache directives indicating if a response can be cached.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"can be cached"? "noCacheFlags"?

You mean cannot be cached?

* }
*/
public function __construct(CacheItemPoolInterface $pool, StreamFactory $streamFactory, array $config = [])
{
$this->pool = $pool;
$this->streamFactory = $streamFactory;

if (isset($config['respect_cache_headers']) && $config['respect_response_cache_directives']) {
throw new \InvalidArgumentException('You can\'t provide config option "respect_cache_headers" and "respect_response_cache_directives". Use "respect_response_cache_directives" instead.');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally. Make lines shorter than 100 chars. (PSR-2)

$optionsResolver = new OptionsResolver();
$this->configureOptions($optionsResolver);
$this->config = $optionsResolver->resolve($config);
}

/**
* This method will setup the cachePlugin in client cache mode. When using the client cache mode the plugin will cache responses with `private` cache directive.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.. Long lines.

*
* @param CacheItemPoolInterface $pool
* @param StreamFactory $streamFactory
* @param array $config
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"See config for constructor" or something similar.

}

/**
* This method will setup the cachePlugin in server cache mode. This is the default caching behavior (refuses to cache responses with the `private`or `no-cache` directives.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space

// Allow caching of private requests
if (isset($config['respect_response_cache_directives'])) {
if (!in_array('no-cache', $config['respect_response_cache_directives'], true)) {
$config['respect_response_cache_directives'][] = ['no-cache'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should you add an array here? ['no-cache'] vs 'no-cache'.

*
* @param CacheItemPoolInterface $pool
* @param StreamFactory $streamFactory
* @param array $config
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, document what parameters or refer to constructor

@@ -183,11 +236,12 @@ protected function isCacheable(ResponseInterface $response)
if (!in_array($response->getStatusCode(), [200, 203, 300, 301, 302, 404, 410])) {
return false;
}
if (!$this->config['respect_cache_headers']) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cannot be removed. That will break BC.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On line 361 I've added some BC logic. If this option is false we add an empty array so all the checks are skipped and original behavior is kept

if (!$this->config['respect_cache_headers']) {
return true;
}
if ($this->getCacheControlDirective($response, 'no-store') || $this->getCacheControlDirective($response, 'private')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing this will also break BC, right?

Can we do something smart in the constructor? If respect_cache_headers is true then populate respect_response_cache_directives with "no-store", "private".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this breaks BC as the default options include the headers

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree with acrobat, this is handled below with the directives

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good

if ($this->getCacheControlDirective($response, 'no-store') || $this->getCacheControlDirective($response, 'private')) {
return false;

$cacheableDirectives = $this->extractDirectives($this->config['respect_response_cache_directives'], $this->noCacheFlags);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$cacheableDirectives can only be one of these four (no matter what). Is this really the intention?

 ['no-cache', 'private']
 ['private']
 ['no-cache']
[]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The directive list contains other values like max-age, no-store and possibly other values in the future. So I've added this method to easily extract cache directives if they are provided in the config

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i agree with the approach. but i would directly use array_intersect here. as you assign it to a variable, its clear enough without the method. i would call the variable $nocacheDirectives.

@acrobat
Copy link
Contributor Author

acrobat commented Feb 23, 2017

Thanks @Nyholm for the review! I've fixed your comments

if (isset($config['respect_response_cache_directives'])) {
$config['respect_response_cache_directives'][] = 'no-cache';
$config['respect_response_cache_directives'][] = 'max-age';
array_unique($config['respect_response_cache_directives']);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

afaik array_unique does not modify the input array. you need to re-assign the value to the field.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes you're right! Fixed!

if ($this->getCacheControlDirective($response, 'no-store') || $this->getCacheControlDirective($response, 'private')) {
return false;

$cacheableDirectives = $this->extractDirectives($this->config['respect_response_cache_directives'], $this->noCacheFlags);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i agree with the approach. but i would directly use array_intersect here. as you assign it to a variable, its clear enough without the method. i would call the variable $nocacheDirectives.

if (!$this->config['respect_cache_headers']) {
return true;
}
if ($this->getCacheControlDirective($response, 'no-store') || $this->getCacheControlDirective($response, 'private')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree with acrobat, this is handled below with the directives

@acrobat
Copy link
Contributor Author

acrobat commented Feb 24, 2017

@dbu Fixed your new comments!

Copy link
Contributor

@dbu dbu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are almost there i think. no-cache was not handled previously, but i consider that a bugfix rather than a BC break.

* we have to store the cache for a longer time than the server originally says it is valid for.
* We store a cache item for $cache_lifetime + max age of the response.
* @var array $methods list of request methods which can be cached
* @var array $respect_response_cache_directives list of cache directives this plugin will respect while caching responses.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was indented on purpose, because its the list of fields in the $config array.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think this was fix for styleCi but indeed I've reverted the indent changes!

* We store a cache item for $cache_lifetime + max age of the response.
* @var array $methods list of request methods which can be cached
* @var array $respect_response_cache_directives list of cache directives this plugin will respect while caching responses.
* }.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and this was left becaues it ends the list of options

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above

*
* @var array
*/
private $noCacheFlags = ['no-cache', 'private'];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this list should also have no-store

Copy link
Contributor

@tuupola tuupola Feb 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK you can actually cache the result if response has no-cache directive. It just means you must always revalidate before actually using the cached result. If origin server returns 304 Not Modified you can use the cached result.

That said the easy implementation is just not to cache. Only difference is some bandwith might be wasted, but implementation is much easier.

https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.1

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right. lets open an issue to implement the full no-cache behaviour in the future. but rather than completely ignoring, completely not caching is at least correct according to the protocol.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I've added the no-store directive and left the no-cache for now

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

created #27 for the full no-cache support

'respect_cache_headers' => true,
'hash_algo' => 'sha1',
'methods' => ['GET', 'HEAD'],
'respect_response_cache_directives' => ['no-cache', 'private', 'max-age', 'no-store'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like adding no-cache is a change over current behaviour. i think we can consider that a bugfix. but please add a note to the changelog so people have a chance of seeing the change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a line about this change in the changelog!

@acrobat
Copy link
Contributor Author

acrobat commented Feb 26, 2017

I've did some changes according to the feedback and updated the changelog. @dbu & @Nyholm do you guys have time for a last review as I think we are almost ready here?

Copy link
Contributor

@dbu dbu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! almost there. i noticed one glitch in the constructor sanity check.

* }
*/
public function __construct(CacheItemPoolInterface $pool, StreamFactory $streamFactory, array $config = [])
{
$this->pool = $pool;
$this->streamFactory = $streamFactory;

if (isset($config['respect_cache_headers']) && $config['respect_response_cache_directives']) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the second check also needs to have an isset. as is, this will be a fatal error if respect_response_cache_directives is not set. (and throw no exception if respect_cache_headers is set and the other is [])

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch! Fixed!

@acrobat
Copy link
Contributor Author

acrobat commented Feb 27, 2017

Fixed comment from @dbu and squashed/rebase the PR

Copy link
Contributor

@dbu dbu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me now. what do the other maintainers think?

Copy link
Member

@Nyholm Nyholm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. I really like this PR now =)

Thank you @acrobat

@acrobat acrobat changed the title [WIP] Implement caching types Implement caching types Feb 27, 2017
@dbu
Copy link
Contributor

dbu commented Feb 27, 2017

styleci is a bit weird. i guess we should remove that dot (i would be ok with it, but styleci should be green and i guess we can't easily make it allow just that one place)

@acrobat
Copy link
Contributor Author

acrobat commented Feb 27, 2017

@dbu Yeah it was indeed a bit weird that's why I left it there. But removed the dot and now is styleci passing again!

@dbu
Copy link
Contributor

dbu commented Feb 27, 2017

thanks a lot! created php-http/documentation#178 about the documentation. needs not a lot, but would be great to have it mentioned in the doc

@acrobat acrobat deleted the caching-types branch February 27, 2017 09:26
@acrobat
Copy link
Contributor Author

acrobat commented Feb 27, 2017

Yes indeed, that was still a todo for after the PR was merged. I will try to do that tonight!

@acrobat
Copy link
Contributor Author

acrobat commented Feb 27, 2017

Is there an ETA for the 1.3.0 release? As I would like to implement this feature in KnpLabs/php-github-api

@Nyholm
Copy link
Member

Nyholm commented Feb 27, 2017

There is no ETA yet. But I guess it will be sooner rather than later.

Feel free to start a PR on KnpLabs/php-github-api and I'll be happy to review that one. I'll make sure both libraries release a new tag with this feature.

@acrobat
Copy link
Contributor Author

acrobat commented Feb 27, 2017

Thanks for the info @Nyholm! I will start a PR to change the cachePlugin usage

@acrobat
Copy link
Contributor Author

acrobat commented Mar 3, 2017

@sagikazarmark Do you maybe have some time to create a new release? Would be awesome to use this feature and the method caching feature from #24! Thanks!

@tuupola
Copy link
Contributor

tuupola commented Mar 8, 2017

Wait a bit with a release. I just noticed that the regexp in #24 allowes / character which should not be there. I will make a bugfix PR tomorrow.

@acrobat
Copy link
Contributor Author

acrobat commented Mar 19, 2017

ping @tuupola any time to fix this so we can have a release anytime soon?

@sagikazarmark
Copy link
Member

Do you maybe have some time to create a new release?

Sorry totally missed this one. My mailbox is quite full lately.

@tuupola ping me if you are done or need anything from me.

@tuupola
Copy link
Contributor

tuupola commented Mar 20, 2017

Sorry for the delay. I will do it today.

@Nyholm Nyholm mentioned this pull request Mar 23, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Cache-Control header handling
5 participants