Hello,
I am now putting PSR-6 forward for review.
I’m exhausted but also really happy to be able be able to put this up for review as we’re at a point where we feel this PSR is complete + viable enough to pave the way for a solid level of caching consistency in the php world.
If we decide to move forward with an acceptance vote, we can do so as early as Tuesday, 27th October 2015.
FROM BYLAW:
The goal is also not necessarily to have every PHP-FIG member agree with the approach chosen by the proposal. The goal however is to have all PHP-FIG members agree on the completeness and objectivity of the meta document.
For the current version see:
* https://github.com/php-fig/fig-standards/blob/master/proposed/cache.md
* https://github.com/php-fig/fig-standards/blob/master/proposed/cache-meta.md
Many thanks,
Paul
It's not just commit(), clear() has the same issue.
--
Just realised I was a little vague about why I don't like isHit() so I'm going to expand on it a bit and a bit more on expiry in general.
In it's current form this PSR allows for two broad models of the CacheItem. In one model the CacheItem is just a packaged result and the value for the key has actually already been retrieved (Or at least an attempt made), in the other the CacheItem is a lazy object and will attempt retrieval of the value at the first get/isHit.
Now the issue being that this is an interface, the end-user should not care about this implementation difference which means the semantics of both model are slightly constrained. What this means is that the internal miss/hit state *must* be determined on the first get/isHit - otherwise there would be a significant difference in behaviour if the CacheItem is created but not accessed for a length of time (Say five minutes). In the former model this could succeed and in the latter fail (Or with concurrency concerns, the reverse). Therefore there is additional logic which must go into the first model, an isHit could look like this:
private $fake_hitness;
private $actual_hitness
public function isHit() {
if($this->actual_hitness) {
if($this->fake_hitness === null) {
$this->fake_hitness = time() <= $this->expiry;
}
return $this->fake_hitness;
}
return false;
}Suddenly the CacheItem has gone from being a fairly lightweight wrapper to a value to having an internal state. It would be more sensible if the CacheItem always checked the expiry and never gave up the value if the expiry was surpassed (In which case we must resolve the race condition, in my opinion an exception makes the most sense), either that or it should never check the expiry itself in which case we should disallow the lazy model and say that once a CacheItem exists its state of cache hit is determined and its value is determined. The latter has the added bonus of the CacheItem not needing the expiry.
Hi,
New to posting on PHP-FIG, apologies if I've got procedure wrong. I have a few niggles with the proposal, but on the whole it looks very good.
My first concern is with the inconsistency between storage, deletion and retrieval of multiple items. For retrieval and deletion there is a multiple items at once strategy, for storage there is a pipelining strategy. I feel that it would make more sense to have just one strategy, either staging CacheItems for retrieval/deletion (And performing the retrieval itself through commit or otherwise) or replacing deferred saving with a saveItems -esque approach where a whole list is persisted at once. Either the Pool should be responsible for maintaining a list of pending operations or the user - not a mix.
I'm personally a fan of pipelining with a combined use 'commit' as some caching platforms support mixed style operations arriving in a single batch.
My second concern is that there is no way to cancel deferred saving, once you have deferred an item you must either commit it or stop using the pool. It is also not clear what the behaviour should be if a an item is saved with save() whilst there are deferred saves, should you save all of them or just the one?
I'm also extremely concerned with returning false on failure. I often joke at work that returning false/null on failure is php's way of doing things, mostly however I understand it as a relic of compatibility in the standard library. It's 2015 we should be using exceptions for this. Therefore if I commit and one of the items fails rather than just getting false I *could* be receiving an exception which will give me useful information regarding what the hell happened. In addition we seem to have inconsistency again, behaviour on a commit failure is defined, behaviour on save failure is undefined.
There is also undefined behaviour for the CacheItemInterface, if I set expiresAt beyond what my caching system can handle should that fallback to 'as long as possible' or should it error (Returning false or exception?). And if it's possible to set indefinite caching for expiresAt, then why not expiresAfter. And if we can support an integer delta with expiresAfter then why not an integer absolute (epoch) with expiresAt? It's also unclear whether the translation between the two should be handled at item level or the system level. For instance memcached (At a protocol level) can support expiry deltas (Upto 30 days) and absolute expiry - should the library be translating it at the PHP level or letting memcached sort it out?
I'm also not a fan of the 'isHit' method, I feel a better solution to race conditions would be for 'get' to throw an exception on cache miss. This way we can allow an item to be regarded as 'expired' in a more consistent manner by allowing expiry after retrieval.
There are a few methods that return false on failure; however, their only other return value is true. That is, they return "yay, it worked!" or "crap, didn't work". I am *completely and vehemently in agreement* that returning "the value you wanted or false" is an anti-pattern that PHP needs to stop using. See also:
http://www.garfieldtech.com/blog/empty-return-values
However, as I note there it's not so much returning false on error as returning "yep/nope" on "did you do what I told you to do?" That's still type safe, so a reasonable use of a boolean.
(If there are any places where the spec is still returning "the value you wanted or false", let us know because I thought we exterminated all of those.)
It is inconsistent, if commit() and clear() return a boolean then why not save() or delete()? If a save() or deleteItems() does not complete then the only way of signalling that (And it absolutely should in the case of delete) would be to throw an exception, and if save() or deleteItems() throw exceptions then it seems weird that commit() and clear() wouldn't.
There is also undefined behaviour for the CacheItemInterface, if I set expiresAt beyond what my caching system can handle should that fallback to 'as long as possible' or should it error (Returning false or exception?). And if it's possible to set indefinite caching for expiresAt, then why not expiresAfter. And if we can support an integer delta with expiresAfter then why not an integer absolute (epoch) with expiresAt? It's also unclear whether the translation between the two should be handled at item level or the system level. For instance memcached (At a protocol level) can support expiry deltas (Upto 30 days) and absolute expiry - should the library be translating it at the PHP level or letting memcached sort it out?
Hm, a couple of points here. Let me try to break them out.
1) expiresAfter()/expiresAt() past the underlying system's capability. - Since an implementation is permitted to expire an item before the set expiration time, just not after, I would say in this case an implementation should cache for "as long as possible". I believe that's already covered in the Expiration definition:
" Implementing Libraries MAY expire an item before its requested Expiration Time, but MUST treat an item as expired once its Expiration Time is reached."
That is, the requested expiration time is technically "cache for no longer than". The same logic applies to caching "forever", if the underlying system can't handle "forever" (say, it's memory-resident only). Do you think that needs to be clarified, perhaps a parenthetical in the part I quoted above? I believe that would be a Review-safe change, as it's not changing behavior, just clarifying. Or possibly a note in the metadoc. Paul, your thoughts?
2) What happens if expiresAt() and expiresAfter() are both never called? - I would argue that the current description is still valid. That is, if the calling library never specifies a an expiration, cache forever. Perhaps that can be worded a bit better. In fact, now that there's 2 methods, it may make sense to move that rule from the interface to the description of expiration further up. And then yes, calling either method with null should set it back to forever. I believe that is a Review-safe change, since it's not changing any behavior, just clarifying. Paul, do you agree? I can file a PR if so.
While reading the spec again while answering this part, I also noted that Item::set() still has mention of TTL in its docblock. That seems vestigial since set() no longer has a $ttl parameter. I believe it's safe to just remove that block, as it's redundant with the docblock of expiresAt() now (or whatever we refactor that to). Again, that's not changing behavior so I believe it's Review-safe.
2) What happens if expiresAt() and expiresAfter() are both never called? - I would argue that the current description is still valid. That is, if the calling library never specifies a an expiration, cache forever. Perhaps that can be worded a bit better. In fact, now that there's 2 methods, it may make sense to move that rule from the interface to the description of expiration further up. And then yes, calling either method with null should set it back to forever. I believe that is a Review-safe change, since it's not changing any behavior, just clarifying. Paul, do you agree? I can file a PR if so.
While reading the spec again while answering this part, I also noted that Item::set() still has mention of TTL in its docblock. That seems vestigial since set() no longer has a $ttl parameter. I believe it's safe to just remove that block, as it's redundant with the docblock of expiresAt() now (or whatever we refactor that to). Again, that's not changing behavior so I believe it's Review-safe.
3) expiresAt(int) - In what timezone? DateTime always carries a timezone, so the underlying system can do any timezone translation necessary. An epoc timestamp doesn't. That's why, in practice, epoc timestamp ints are unreliable and should pretty much never be used ever. If the timezones do not match, the intended time of expiration and what actually gets recorded could be off by as much as a day, resulting in the value never being cached (time is in the past, in that timezone) or cached for too long. All kinds of bad things happen when you don't know your timezone. I've been bitten by about a third of them at some point, and am crippled for life as a result. :-)
4) Translation between expiration time and duration - This is left up to the implementation to figure out. As long as it happens it doesn't matter to the caller if it's PHP or Memcache that is doing "time = now + $duration" (or vice versa). That will likely vary depending on the details of the backend. Do whatever makes the most sense for your backend.
I'm also not a fan of the 'isHit' method, I feel a better solution to race conditions would be for 'get' to throw an exception on cache miss. This way we can allow an item to be regarded as 'expired' in a more consistent manner by allowing expiry after retrieval.
The problem there is that exceptions are expensive. They are also for, well, exceptional cases. Ie, errors, "something weird went wrong", etc. A requested cache item not being present is quite normal, so an exception is inappropriate.
In later messages you note that it puts more logic into isHit(). I don't think that's really a problem. As you note, this spec allows for CacheItem to be "smart" (and contain active logic) or "dumb" (and just be a holder for data given to it by the Pool). That's by design, as different implementations may need different trade-offs (eg, a single-load could lazy load, but multi-load front-load). Even a "dumb" item can still have logic in it.
For a non-lazy-loading implementation, see this (somewhat dated but still valid for this part of the spec) partial implementation:
https://github.com/Crell/Cache-Util/blob/master/BasicCacheItemTrait.php
That's really quite simple. For a lazy-loading implementation, both isHit() and get() can sub-call to a private method that talks back to the pool to actually load the data when either one is called. That seems fine to me, and not a burden on the implementer.
Thanks for your feedback!
--Larry Garfield
--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/5623BFFF.5010602%40garfieldtech.com.
All present felt that the expiration cleanup PR that was at the time outstanding was Review safe, and it has since been merged. Yay.
We also discussed the other concern, that some methods return true/false for "Worked/Didn't Work" rather than throwing an exception. Most of the discussion centered on the save() routines.
The general consensus was that while changing that would not be a Review-safe change, we shouldn't change it anyway for a number of reasons.
1) Cache backends are frequently unreliable by nature, so a save not working is not necessarily exceptional.
2) Given that, wrapping so many operations in a try-catch would be a DX fail.
3) Even with that aside, no one present really felt strongly enough about it to want to restart Review over it.
Thus, the recommendation is to not change those returns and continue as-is, with PSR-6 being ready for a vote really-soon-now-for-reals!
$myCachePool->save($myCacheItem); // Must throw exception as the caching layer has no defined behaviour on cache save failure$myCachePool->saveDeferred($myFirstCacheItem);
$myCachePool->saveDeferred($myFirstCacheItem);
if(!$myCachePool->commit()) {
// Returned false, not very helpful but at least it's defined
}--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/d1f39afe-57ff-49a8-8e0a-bf6d81fc9b2b%40googlegroups.com.
There is a huge difference between a cache value not being present and a whole cache subsystem being down imho.
You received this message because you are subscribed to a topic in the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/php-fig/8DOioT3eSYg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to php-fig+u...@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/562FC3DE.9070203%40garfieldtech.com.
isHit() and get() don't imply a transient state object.
There is a huge difference between a cache value not being present and a whole cache subsystem being down imho.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/CANamvv3oCW_ztvk3fhpUky%3DztYp_CdR%3Dmomua1SLhwsuVr3rHg%40mail.gmail.com.
Why you'd call isHit() long after calling get() I have no idea.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/f28061fa-dc71-4cbd-9db4-a7c10f084197%40googlegroups.com.
You cant make the argument that the cache library "obviously" has to log something. If that is the case the PSR should explicitly say that it at least SHOULD do that. Right now saying that the cache library uses PSR-3 to log stuff is "just saying". What if my app doesn't have a logger ? Is PSR-6 not usable by me then. You simply cant make assumptions like these.
Yo decide such things you have to look at real world libraries. All of the popular ones I took a look at (including non PHP ones) treat the cache service being down as an exception, and have been doing for years. In fact I cant think of a single library that does silent fails. So why come up with these arbitrary rules, when real life cases exist?
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/291E73D3-233A-4AFD-81A5-526C8B79FB91%40tedivm.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/14aa5826-5441-47d2-aca1-08a8276ac349%40googlegroups.com.
@Robert, yes, to properly react and debug a failure you need some information about it. Exceptions provide messages that can actually tell you what went wrong. Why returning false only tells you "something" went wrong. There is then no way to know that something without digging into the library. A message like "cannot reach host foo" is way more helpful than "false", especially in the case where you have multiple cache servers running and getting proper error message can lead to a quicker fix
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/395b8906-2bd8-4609-b0f4-88202c8739e3%40googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/CANamvv3RpYQEUUuyHbDbo0V5UWzXS05x%3DV12DwLR2UdmS8rptw%40mail.gmail.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/CANamvv3RpYQEUUuyHbDbo0V5UWzXS05x%3DV12DwLR2UdmS8rptw%40mail.gmail.com.
There should at least be an getLastError method or similar. Like Paul mentioned memcached has. Even json_decode() provides a way to get an error message. If such method is present I agree the user can construct the exception himself.
@Robert, since this PSR does not mention such logging you cannot just implicitly "expect it to be done". Either it is in the standard or not.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/19C20B18-2843-46C2-9E4B-661D6A164C0D%40tedivm.com.
/**
* Persists a cache item immediately.
*
* @param CacheItemInterface $item
* The cache item to save.
*
* @return static
* The invoked object.
*/
public function save(CacheItemInterface $item);
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/60e7b3a5-ab70-490d-8acd-8e1f178b1d7f%40googlegroups.com.
Can we actually have a quick vote on the whole exceptions vs silent fail approach? It would set the direction and then we would all stick with it. Without the vote it's just going to keep coming up.What do you think? We could start a vote thread now, and give it 3 days of lifetime. I am sure all the members interested in it going either way will manage to vote in that period?
On Wednesday, October 14, 2015 at 12:10:19 AM UTC+2, Paul Dragoonis wrote:Hello,
I am now putting PSR-6 forward for review.
I’m exhausted but also really happy to be able be able to put this up for review as we’re at a point where we feel this PSR is complete + viable enough to pave the way for a solid level of caching consistency in the php world.
If we decide to move forward with an acceptance vote, we can do so as early as Tuesday, 27th October 2015.
FROM BYLAW:
The goal is also not necessarily to have every PHP-FIG member agree with the approach chosen by the proposal. The goal however is to have all PHP-FIG members agree on the completeness and objectivity of the meta document.
For the current version see:
* https://github.com/php-fig/fig-standards/blob/master/proposed/cache.md
* https://github.com/php-fig/fig-standards/blob/master/proposed/cache-meta.md
Many thanks,
Paul
--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/f373689b-6c93-4b84-bd18-66cc78e7ad07%40googlegroups.com.
On Wed, Oct 28, 2015 at 4:48 PM, Dracony <draco...@gmail.com> wrote:Can we actually have a quick vote on the whole exceptions vs silent fail approach? It would set the direction and then we would all stick with it. Without the vote it's just going to keep coming up.What do you think? We could start a vote thread now, and give it 3 days of lifetime. I am sure all the members interested in it going either way will manage to vote in that period?The PSR-6 standard telling libraries to throw exceptions isn't going to happen, but we'll put more focus on ensuring that it's possible for implementors to throw them if they want to.The first step would be to make $pool->save() return a boolean instead of $self. As per what Will Gardner pointed out.Any other methods you guys feel should be returning booleans for success/fail instead of $self? Sorry if you've pointed out already I just need a condensed list of things to look at.Thanks!
At minimumsave()saveDeferred() (Since there's no requirement to *actually* defer the saving)
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/e116fd56-38a7-4526-b112-db66752ebe1d%40googlegroups.com.
We don't have a $pool->deleteItem() which would be easy and simple to have, with a boolean result. Easy win there. Why was it removed? It once was $pool->delete($key) before we introduced "Items".
--
I'm opposed to this. People have had almost four years to express their opinions on this, and we've already had a lot of conversations around this point. I don't think now is the time to change the strategy we've selected.
That being said, making sure that strategy actually works is a reason to make modifications. For this reason I am not opposed to a refactoring/cleanup of the return values.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/12E0388D-AD88-4854-AACF-92BFA4822045%40tedivm.com.
The PSR-6 standard telling libraries to throw exceptions isn't going to happen, but we'll put more focus on ensuring that it's possible for implementors to throw them if they want to.
--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/c4609330-f12c-41e2-9fe5-ea887f89ceb2%40googlegroups.com.
Yes, that is exactly what I said yesterday, and it appears we have gone full circle. Now I should say: "for that the implementing library must provide some getLastError() method like json_decode does. This way you would get info on failure"
You received this message because you are subscribed to a topic in the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/php-fig/8DOioT3eSYg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to php-fig+u...@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/4fa2ade2-5be3-49c0-9e5c-81c5145af5eb%40googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/CAKxcST-GzX06PjZnnDY9iVCFaP6mJoLLyer42WStumFXT_jNRw%40mail.gmail.com.
--
--Larry Garfield
--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/563125D4.3090607%40garfieldtech.com.
a meaningful deleteItem() implementation would just do that itself anyway.
On 10/28/15 3:10 PM, Woody Gilk wrote:
Larry,
This is a really great summary.
In regards to #2, I do think it is important that write operations return a boolean because if the item is already persisted and we are attempting to refresh it, we will end up with a stale cache with no way to know that. With a boolean return, we gain the ability to do:
if (!$pool->save($item)) {
$pool->deleteItems([$item->getKey()]);
}
PS: If there the multi method deleteItems, why is there no singular deleteItem?
Because $pool->deleteItems([$key]); is trivial enough for a Calling Library, and a meaningful deleteItem() implementation would just do that itself anyway.
I don't want to derail the process (more than I already have) but has anyone thought about how this will work with extension? Right now it looks like CacheItemPool is going be creating CacheItem via getItem(), but there is no definition of a factory in the current spec. This means that a custom CacheItem cannot be fetched without modifying CacheItemPool to an unknown degree, which ends up being a bit of a mess.
We've thought about extension. A lot. Like, for months. :-) See the metadoc for an example of how one might write a cache tag extension, for instance.
Items and Pools in this model always come as a matched set. Always. If you ever write $item = new CacheItem() outside of a Pool object, You're Doing It Wrong(tm). That is, "a custom CacheItem cannot be fetched", period. That's not a thing.
--
--Larry Garfield
--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/56312EA4.4050609%40garfieldtech.com.
--
--Larry Garfield
--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/563125D4.3090607%40garfieldtech.com.
Definitely save() and deleteItem(s)() need boolean. I want this passed as much as you, but I'd like to reset REVIEW to make such necessary changes go through. This is why we have a review process in the first place.