-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Cache] Symfony PSR-6 implementation #17408
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
Conversation
a046b58
to
42e7c97
Compare
{ | ||
$now = time(); | ||
|
||
if (null === $expiration) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess you wanted to write $time, and not $expiration
c9d7fae
to
82324ea
Compare
Interesting, like the simplistic approach, and especially that there is only one backend lookup for getItem call. But missing use of multi get / set / (...) to reduce backend roundtrips further on getItems(array) and batch deferred updates. And calls to hasItem and getItem (and opposite) will result in two lookups here, but last point is maybe to be expected given the spec. I know @Nyholm, @aequasi and others have been working on this topic, and hope to be able to contribute from eZ as well. Could be an opportunity to join forces. Side: On design side would perhaps opt for following approach:
|
Thank you for the ping. I ran this implementation on our integration tests and found some issues. Like you do not protect for all the reserved characters and you have forgotten to clear the deferred items on The php-cache supports tagging, hierarchal keys and have doctrine bridges in both directions. We are currently outperforming other PSR-6 caches with a factor of 2 and we have ideas for even more performance improvements. We would definitely like to join forces somehow. We are open for a discussion about that. |
@@ -0,0 +1,8 @@ | |||
Symfony PSR-6 implementaton for caching |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
implementaton => implementation
b9d7ef8
to
c85b7dc
Compare
Comments addressed, thank you @Nyholm for the link to the integration test, this will definitely help. |
I added testing with cache/integration-tests, I'll look at the failures asap. I already fixed the missing |
If the goal is not to replace any existing cache libraries, why build another cache library? Wouldn't it be better if all Symfony components using cache relied on the PSR6 interface and then required the virtual package That way we use the benefit of PSRs and we can let the user decide what cache library to use. If they like to use feature X they install a library with feature X. If they want a strict implementation they would choose a strict implementation. |
@Nyholm ... and this component is a strict implementation that users can use if they want to, like any other ones. The goal of PSRs (if I get this right) is to allow several implementations with different philosophies, not to standardize on only one. |
You are correct. That is the goal of PSRs. But it was this sentence I reacted to:
If the goal is to provide a strict implementation of PSR-6 that will replace (or be an option to) any existing library, then I agree that we need this component. Otherwise I do not see the benefits at all. |
Understood, we definitely want to provide a strict implementation that can replace existing libraries :) |
Okey, awesome. Im happy to help. @nicolas-grekas do you want PR's with more adapters now or once this is merged? |
@Nyholm thank you that's much appreciated. Your test suite is already an amazing help. I think it would be easier to add more adapters after this one has been merge.
I didn't read anything about this in PSR-6, did I miss something? For the other ones, I'll check tomorrow hopefully:
|
*/ | ||
public function save(CacheItemInterface $item) | ||
{ | ||
if (!$item instanceof CacheItem) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need to do this?
It breaks the usage of CacheItemInterface
interface used as type hinting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@blanchonvincent the PSR specifies that implementation are not compatible. So the type hinting is specified by the PSR interface, but the $item MUST be an instance from the same implementation (i.e: I cannot make work my CacheItem from my custom Redis implementation to work with your custom Memcached implementation of CacheItemPool).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You breaking the whole idea of PSR-6 you can interchange multiple libraries together... That's why Interfaces exists
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GCDS did you read @xavierleune's comment just in top of yours?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the PSR itself:
Calling Libraries SHOULD NOT assume that an Item created by one Implementing Library is compatible with a Pool from another Implementing Library.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, sorry I thought differently...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, Interoperability between implementations is possible using something like the attached ProxyAdapter
We have given some thoughts in to this. The PSR doc block on Consider this: $item = $pool->getItem('key');
$pool->save($item); // Assume this is okey
$pool->hasItem('key'); // Should return true, yes? Or else, what is the point of saving?
/**
* Doc block for get():
* If isHit() returns false, this method MUST return null. Note that null
* is a legitimate cached value, so the isHit() method SHOULD be used to
* differentiate between "null value was found" and "no value was found."
*
* Doc block for isHit():
* Confirms if the cache item lookup resulted in a cache hit.
*/
$pool->getItem('key')->isHit(); // Must be false because "no value was found."
// So ...
$pool->hasItem('key') !== $pool->getItem('key')->isHit(); // This makes no sense. The pool should not have an item that never can be a hit. That would be a stale item and should be removed. |
@Nyholm I don't get your example, to me, assuming "key" is not already in the cache: $pool->hasItem('key'); // false
$item = $pool->getItem('key');
$item->get(); // null
$item->isHit(); // false
$pool->save($item); // true
$item = $pool->getItem('key');
$item->get(); // null
$item->isHit(); // true -> a value has been found, we saved it before! |
You have not saved any value. Since: "Note that null is a legitimate cached value, so the isHit() method SHOULD be used to differentiate between "null value was found" and "no value was found."" The last two rows in you example: $item->get(); // null
$item->isHit(); // true -> a value has been found, we saved it before! If The point is, |
My example above clearly allows to differentiate between a null a "found" vs a null as "not found". Nothing in the spec says that |
@nicolas-grekas Thank you for working on this - it is awesome! We should merge this. |
+1 to merge it now. It will allow to start migrating metadata systems and other places in Symfony using Doctrine Cache internally to PSR-6. |
"require-dev": { | ||
"cache/integration-tests": "^0.6", | ||
"doctrine/cache": "~1.6" | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it miss
provides: {
"psr/cache-implementation": "1.0"
},
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@joelwurtz indeed, fixed thanks
Thank you @nicolas-grekas. |
This PR was merged into the 3.1-dev branch. Discussion ---------- [Cache] Symfony PSR-6 implementation | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - From the provided README.md: This component provides a strict [PSR-6](http://www.php-fig.org/psr/psr-6/) implementation for adding cache to your applications. It is designed to have a low overhead so that caching is fastest. It ships with a few caching adapters for the most widespread and suited to caching backends. It also provides a `doctrine/cache` proxy adapter to cover more advanced caching needs. Todo: - [ ] add more tests - [ ] add a FilesystemAdapter Commits ------- 91e482a [Cache] Symfony PSR-6 implementation
IMO namespaces handling should be a separate decorator and not built into the adapter directly. See doctrine/cache#96 |
I also want to express my objections against the recent fast merges of new components. This was merged in less than two days which does not give enough time to review and discuss a full new component at all. Also it's against our own rules which says
I hope it's not going to be of the same quality as the LDAP component. |
True, it probably should be, but also a bit of a orange to apple comparison. In doctrine this is a namespace kept in cache and that needs to be checked on every cache lookup, that is not the case here (luckily, the extra cache lookup adds overhead, use case can be covered by tagging instead to avoid it). |
@Tobion sorry if you feel uncomfortable, we merged quickly that's true... I invite you to open a PR to move the namespace concept in it's own decorator if you think it's better (taking @andrerom into account). The component will see more enhancements until 3.1 is released, but at least we don't have to discuss about its interfaces, which makes the situation totally different from the Ldap component hopefully. |
if (!$item instanceof CacheItem) { | ||
return false; | ||
} | ||
static $prefix = "\0Symfony\Component\Cache\CacheItem\0"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not a const
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because a const is public...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@internal
? Or a private static property? This one looks a bit weird.
My suggestion for namespace would be to let Adaptor generate key with a namespace. For e.g. most Redis tools groups keys if they have |
@GCDS namespace (or prefix really) are already managed by adapters (they must deal with it in their constructor). Which means a Redis adapter could do whatever you'd need there. To me, the simple prefixing logic is rightfully in the abstract, because adding a prefix is really something common. Doing namespaces àla doctrine/cache is for a decorator, or at least not for the abstract, I agree. |
Ahh yeah, sounds correct to me :) |
{ | ||
$this->namespace = $namespace; | ||
$this->createCacheItem = \Closure::bind( | ||
function ($key, $value, $isHit) use ($defaultLifetime) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about adding a constructor to CacheItem
marked with @internal
? It will be easier to understand. (Currently even PHPStorm isn't able to interpret this snippet and marks it in red).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw it will remove the duplication of this factory closure in ProxyAdapter
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I preferred raw speed over phpstorm compliance: a constructor would slow down things for no valid reason (adding one function call in the chain). I doubt @internal
would be enough to prevent erroneous user side instantiation. The current no-constructor-nor-setter (for key and isHit) means we technically annoy users enough so that they are free to missuse but forced to think about it :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't a constructor call or a closure call in the chain similar in term of perfs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes of course, but making the constructor public has drawback IMHO (as detailed above). This drawback could be fixed by using a private constructor + a rebound closure, which is when an additional function call comes up. That's what I meant :)
From the provided README.md:
This component provides a strict PSR-6 implementation for adding cache to your applications. It is designed to have a low overhead so that caching is fastest. It ships with a few caching adapters for the most widespread and suited to caching backends. It also provides a
doctrine/cache
proxy adapter to cover more advanced caching needs.Todo: