[Caching] A Walkthrough of PSR-6

300 views
Skip to first unread message

Robert Hafner

unread,
Oct 22, 2014, 4:05:05 PM10/22/14
to php...@googlegroups.com

I wanted to make it easier for everyone to understand what PSR-6 is about and why the decisions around it were made so I put together a blog post for that purpose-

http://blog.tedivm.com/rants/2014/10/a-walkthrough-of-psr-6-caching/

I’d really appreciate it if people would give that a read through while we continue the discussion on caching.

Thanks!

Rob

Evert Pot

unread,
Oct 22, 2014, 4:25:44 PM10/22/14
to php...@googlegroups.com
Reading that post, it kinda seems that the entire argument for using Item classes is the ability to store null or false.
If this is true, I find that very hard to believe. And a 'lot of people need this' a very weak argument.

Because:

1. I've yet to hear from anyone else that they absolutely must have this.
2. A lot of suggestions have been around to add an &$exists argument to get(). For the getMultiple() case this can be solved by not having the array keys appear in the result.
3. Semantically what null or false means in the consumer of the api, can just as easily be expressed by storing a slightly different value, and put the responsibility on the caller.

For instance, it may be a common use-case to to store in a cache a situation where a certain item was not found.
This is how we do this with redis:

$cache->set([
  false, // indicates not found
]);

$cache->set([
  true, // indicates the data exists
  $data
]);

I have no doubt you won't love this, but for the sake of geting PSR-6 done and putting this one to bed, I think it would make a lot of people happy.
A slightly inferior solution is imho still better than a no solution at all.

Evert

Robert Hafner

unread,
Oct 22, 2014, 4:31:34 PM10/22/14
to php...@googlegroups.com

If that’s what you got out of the post you clearly didn’t read the post. That was one point among many, many points.

I also strongly disagree with the logic that we should pick an inferior proposal simply because it’s easier. I have talked to a lot of people in this group, and while there are a few vocal opponents of this standard there are, in my opinion, more than enough people to pass it. We were in a review phase just recently but popped out not because of lack of support, because someone pointed out a couple of typos and decided to have a discussion about stampede protection. We’re 95% of the way there.

Rob


-- 
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/d9d3dada-c9f9-40f8-83b5-69f55eb5fb8f%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Korvin Szanto

unread,
Oct 22, 2014, 4:31:46 PM10/22/14
to php...@googlegroups.com

A slightly inferior solution is imho still better than a no solution at all.

Why are these the only two options? I don’t understand why we wouldn’t just stick with what we have today. IMO the Pool/Item model is the most intuitive model proposed, and adding a referenced variable only serves to make it less intuitive.

Can you articulate the reasons against?

Evert Pot

unread,
Oct 22, 2014, 4:41:03 PM10/22/14
to php...@googlegroups.com
It's a more intuitive, simpler API and by reducing the surface area, I think it would be easier for people to support it and finally bring to completion.
I realize this is mostly a subjective argument, but I'm also kinda bored of PSR-6 still being a topic, after 3 years.

Can we just bring this to a vote? I would still +1 the current approach.

Evert

Paul Dragoonis

unread,
Oct 22, 2014, 5:24:44 PM10/22/14
to php...@googlegroups.com
Yes, we can.
 

Evert

--
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.

Evert Pot

unread,
Oct 22, 2014, 5:30:36 PM10/22/14
to php...@googlegroups.com


On Wednesday, October 22, 2014 5:24:44 PM UTC-4, Paul Dragoonis wrote:


On Wed, Oct 22, 2014 at 9:41 PM, Evert Pot <ever...@gmail.com> wrote:

On Wednesday, October 22, 2014 4:31:46 PM UTC-4, Korvin Szanto wrote:

A slightly inferior solution is imho still better than a no solution at all.

Why are these the only two options? I don’t understand why we wouldn’t just stick with what we have today. IMO the Pool/Item model is the most intuitive model proposed, and adding a referenced variable only serves to make it less intuitive.

Can you articulate the reasons against?

It's a more intuitive, simpler API and by reducing the surface area, I think it would be easier for people to support it and finally bring to completion.
I realize this is mostly a subjective argument, but I'm also kinda bored of PSR-6 still being a topic, after 3 years.

Can we just bring this to a vote? I would still +1 the current approach.

Yes, we can.

Alright, PSR-6 has been in review since September 11. According to the bylaws this is enough time to move it to voting.

Lets do this!

Evert


Robert Hafner

unread,
Oct 22, 2014, 5:38:13 PM10/22/14
to php...@googlegroups.com

Not possible- it was pulled from Review about a month ago. This was sent to the list.

There needs to be a new review phase before a vote can go through. 

Robert Hafner

unread,
Oct 22, 2014, 5:39:09 PM10/22/14
to php...@googlegroups.com
Keep in mind that I would support moving it to review as soon as tomorrow, and having it get voted on immediately after.

Rob

Jon Whelan

unread,
Oct 23, 2014, 12:00:07 AM10/23/14
to php...@googlegroups.com
On Wednesday, October 22, 2014 2:39:09 PM UTC-7, Robert Hafner wrote:
Keep in mind that I would support moving it to review as soon as tomorrow, and having it get voted on immediately after.


I'm concerned about this if the motivation to move the current proposal (https://github.com/php-fig/fig-standards/blob/master/proposed/cache.md) to review, and to a vote shortly thereafter, is to accept it.
 
The current proposal is bloated. There's no need for both the CacheItem and CacheItemPool. Also the pool is clearly violating separation of concerns with methods such as save(), saveDeferred() and commit(). These methods simply aren't needed. You can have all the same functionality that these provide with a simpler interface (and more accurate model of cache in general)

I thought Anthony did a superb job at presenting arguments about this and favoring composition over bloated and extended interfaces in this thread: https://groups.google.com/d/msg/php-fig/FzCFECFjF1o/9AFg9hjO6zsJ, his open letter which is referenced therein, and his follow up letter later on in the thread.

I also voiced similar concerns in this thread:


And again in this thread:


which provided a link to a more appropriate interface (along with implementations) and an example usage.


and a link showing how to use it (within the context of stampede protection) here: https://github.com/jonwhelan/cache

This interface more accurately models cache than the current proposal, is simpler, and intuitive. All the implementations are as simple as can be that a beginner dev would be able to understand them, yet they allow for complex behavior (stampede protection, tags, deferred saving) by composing these simple objects together.

We, as a community, must not be afraid of object composition. Keeping interfaces 'tight' and focused gives us a better chance that any future and unforeseen requirements will fit nicely alongside our existing models.

I would be happy to answer questions about it, and would love to be challenged on it as well if someone believes that there are cases which cannot be handled by this interface.

Jon

Robert Hafner

unread,
Oct 23, 2014, 12:08:09 AM10/23/14
to php...@googlegroups.com
I reject the assertion that the current standard is “afraid of composition”. There is nothing about it that can’t also be extended by composition.

You’ve also basically ignored every part of my post and are just reiterating the same points. I’ve already brought up why some things are easier to do with the current standard, so if you’re looking to be challenged on things simply read the post.

Rob




--
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.

Jon Whelan

unread,
Oct 23, 2014, 12:25:30 AM10/23/14
to php...@googlegroups.com
I have read the post, I have read the threads, and I have read the meta document. Have you looked at what I've proposed?

Why would you want to have extra methods and have to extend an interface everytime you want to add a new feature such as stampede protection, and tags. It defeats the purpose of having a well-defined interface if everytime you need to add a feature it needs to be extended.

And perhaps the current proposal can do everything with composotion, but what I've proposed can also do that, with a simpler interface and no need for an ItemInterface.

Some points need to be reiterated when they are not listenened to the first time around.

Bernhard Schussek

unread,
Oct 23, 2014, 5:19:33 AM10/23/14
to php...@googlegroups.com
Hi Robert,

Thank you for taking the time to write this blog post!

I read through the post and the proposal and am inclined to vote +1 for the proposal. As an outsider of the working group, I felt some of the naming unintuitive or inconsistent. I guess that I'm not the only one, so I've created a PR: https://github.com/php-fig/fig-standards/pull/361

There are some other minor concerns, but I don't want to pollute this thread. We can discuss the details in the PR.

Cheers,
Bernhard

--

--
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.

Anthony Ferrara

unread,
Oct 23, 2014, 11:17:58 AM10/23/14
to php...@googlegroups.com
I've replied on Reddit, rebutting much of the post:
https://www.reddit.com/r/PHP/comments/2k0y23/a_walkthrough_of_the_phpfig_caching_proposal_psr6/clhjcrs

I also rebutted against the concept that because Java has this level
of complexity in its interfaces means that we should as well, with a
pair of examples (I/O and Caching):
https://www.reddit.com/r/PHP/comments/2k0y23/a_walkthrough_of_the_phpfig_caching_proposal_psr6/clhjfpb

Pushing to vote is your prerogative.

I just hope that you're able to divorce yourself from the emotional
attachment to the hard-work that you've done to understand why some of
us take issue with it.

Personally, I'd rather nothing go through than to get something
contentious through, just to get it through. But again, that's your
collective prerogative. I can only make my points and hope that you've
understood them.

Anthony
> https://groups.google.com/d/msgid/php-fig/CAHjTTqNbobHLOitkbV-00QY85nSDT0iCwcCK6Vvhk0_HA6kp%2Bw%40mail.gmail.com.

Pádraic Brady

unread,
Oct 23, 2014, 1:42:37 PM10/23/14
to php...@googlegroups.com
Hi all,

1) No accusations, please. Let's keep the debate civil and on topic.
2) Moving to the review stage is at the discretion of the Editor and Coordinator, so there is no immediate push for that at this time while a healthy debate is ongoing.

Paddy


For more options, visit https://groups.google.com/d/optout.



--

--
Pádraic Brady

http://blog.astrumfutura.com
http://www.survivethedeepend.com
Zend Framework Community Review Team
Zend Framework PHP-FIG Representative

Robert Hafner

unread,
Oct 23, 2014, 2:50:41 PM10/23/14
to php...@googlegroups.com
This response is based off of one big assumption- that my post was a direct response to yours. What it was, in fact, was "A Walkthrough of PSR-6"- that's why I started with the Driver Model, based off of Evert Pot's work, and discussed how that evolved into the form that it is today.

You're right, I don't address many of your points. That was never my intention. Instead I wanted to make sure that everyone in the conversation was aware of how we got to where we are so that we could have a conversation where everyone was informed about what the current proposal is doing.

Rob
> To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/CAAyV7nHdmB%3DXcnbBq1avpuTWEW3oSAigtW8cgimX_oQhUOXeTw%40mail.gmail.com.

Philip Sturgeon

unread,
Oct 23, 2014, 6:36:44 PM10/23/14
to php...@googlegroups.com

On Thursday, October 23, 2014 2:50:41 PM UTC-4, Robert Hafner wrote:
This response is based off of one big assumption- that my post was a direct response to yours. What it was, in fact, was "A Walkthrough of PSR-6"- that's why I started with the Driver Model, based off of Evert Pot's work, and discussed how that evolved into the form that it is today.

You're right, I don't address many of your points. That was never my intention. Instead I wanted to make sure that everyone in the conversation was aware of how we got to where we are so that we could have a conversation where everyone was informed about what the current proposal is doing.

Rob

Your blog certainly didn't link to or mention Anthony's post, but it did seem to be answering a lot of concerns from "people who have said X."

If your blog was in response to Anthony - which it really looked like - then his responses were technically fair, regardless of the tone used. I can see how he'd be annoyed.

If your blog was not in response to Anthony, then I have no idea why you chose to ignore it. The timing for that seems odd.

One of the biggest points of confusion for you two seems to be about naked values. Lets see if I can outline it in a way that you can both meet and discuss the problem.

Anthony, you say we should just return null for everything. That's ok, we can define that in our interface. Regardless of driver, we can say to people: null - even if its a value - means a miss. Cool?

The trouble is, WinCache and APC return false on a miss/failure. The imlementing library will have to look at that false and convert it to null to match our interface rules. That means that both false and null now mean miss. 

The confusion that Robert outlines comes from If you use the same library with WinCache backend, then switch it out to a different one, then false may or may not come back as a valid result, or a miss. 

Switching to a different cache system should not mean that false is suddenly out of bounds.

If another system uses -1 as a miss value, then that will also be converted to to comply with our interface.

This means that now -1, false and null in the cache, might mean a miss and return a null.  Sometimes. That could be hell to debug.


Anthony: does this make sense? Do you have a way around that problem?

This might only be part of the issue, but its fairly fundamental to the rest of it all.

Anthony Ferrara

unread,
Oct 23, 2014, 6:48:50 PM10/23/14
to php...@googlegroups.com
Phil

Anthony, you say we should just return null for everything. That's ok, we can define that in our interface. Regardless of driver, we can say to people: null - even if its a value - means a miss. Cool?

The trouble is, WinCache and APC return false on a miss/failure. The imlementing library will have to look at that false and convert it to null to match our interface rules. That means that both false and null now mean miss. 

The confusion that Robert outlines comes from If you use the same library with WinCache backend, then switch it out to a different one, then false may or may not come back as a valid result, or a miss. 
 
No, null always means a miss. Let's take APC, since it's easier:

class APCCache implements SimpleCache {
    public function get($key) {
        $value = apc_fetch($key);
        if ($value !== false) {
            return unserialize($value); // hit, which can unserialize to any value
        }
        return null; // miss
    }
    public function set($key, $value) {
        apc_store($key, serialize($value));
    }
    public function delete($key) {
        apc_delete($key);
    }
}
 
So now, you can distinguish a false store from a miss. Since the false will be preserved with the serialization method. Hence there's no need to worry about it.

Switching to a different cache system should not mean that false is suddenly out of bounds.

If another system uses -1 as a miss value, then that will also be converted to to comply with our interface.

This means that now -1, false and null in the cache, might mean a miss and return a null.  Sometimes. That could be hell to debug.

Nope, only null, as it's always possible to build the functionality into the implementation to do the pivoting for you.

But let's say that you don't want to add a second serializer. What if you just wanted to do something else. 

Well, you could create a private sentinel value: class NegativeOne {}. This would never be exposed. Then, your get/set become:
 
class OtherCache implements SimpleCache {
    public function get($key) {
        $value = other_fetch($key);
        if ($value === -1) {
            return null; // was a miss
        } elseif ($value instanceof NegativeOne) {
            return -1; // was a hit against our sentinal
        }
        return $value; // was a hit
    }
    public function set($key, $value) {
        if ($value === -1) {
            $value = new NegativeOne;
        }
        other_store($key, $value);
    }
    public function delete($key) {
        other_delete($key);
    }
}

And there are other approaches as well. Point being that the implementation should always be able to distinguish a hit from a miss, **and** know what its return values are, hence it can always return the appropriate value.

So no, "both false and null now mean miss" is not true. Null means miss, and everything else means hit, even using a backend that can't store `false` directly.

Robert Hafner

unread,
Oct 23, 2014, 7:04:30 PM10/23/14
to php...@googlegroups.com


The reason I wrote the post that I did was because it seemed to me that a lot of criticism over the current standard come from misunderstandings. Phil, your last email is one of them- at no point do the different backends having different return values step into this. In fact that problem isn’t unique to any model of caching- whether the Pool/Item, Driver or the SimpleCache variant Anthony is proposing you are still going to have to standardize the results of a miss from the backend.

The confusion that Robert outlines comes from If you use the same library with WinCache backend, then switch it out to a different one, then false may or may not come back as a valid result, or a miss. 

That to me has *never* been a problem, for the reasons I mention above and that Anthony outlines.

Rob




--
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.

Philip Sturgeon

unread,
Oct 23, 2014, 10:42:49 PM10/23/14
to php...@googlegroups.com
So no, "both false and null now mean miss" is not true.
 
Yes, it is true. If you have a false or a null in your backend, then the cache class will return a null. A -1 in another backend could also lead to a null being returned by the class. Because of that variation, none of those values can be considered safe if you expect to be able to swap backends ever.


Trying to avoid this by infecting what could be a generic cache with sentinel values, or enforcing serialize() is less than ideal when that cache could be shared with systems that are not PHP based at all.

Philip Sturgeon

unread,
Oct 23, 2014, 10:49:42 PM10/23/14
to php...@googlegroups.com


On Thursday, October 23, 2014 7:04:30 PM UTC-4, Robert Hafner wrote:


The reason I wrote the post that I did was because it seemed to me that a lot of criticism over the current standard come from misunderstandings. Phil, your last email is one of them- at no point do the different backends having different return values step into this. In fact that problem isn’t unique to any model of caching- whether the Pool/Item, Driver or the SimpleCache variant Anthony is proposing you are still going to have to standardize the results of a miss from the backend.

The confusion that Robert outlines comes from If you use the same library with WinCache backend, then switch it out to a different one, then false may or may not come back as a valid result, or a miss. 

That to me has *never* been a problem, for the reasons I mention above and that Anthony outlines.

Rob

Huh? 

So you two are not in any sort of difference over wether naked values are a problem or not? Because that seems to be the crux of the conversation.

One of you says null can always be returned and thats fine, but the other says that sometimes -1, false or 0 could be a confusion, then the two of you argue about the specifics of what that means and I try to clear it up.

You said this in your article:

Sometimes developers want to store negative values such as false, null or 0. This is perfectly acceptable behavior and a lot more common than people think.
The naive approach is to say that you look for one value (say, null) in most situations but in the situation where you want null as an accepted output you look for something else (like false). This ignores times where both results are acceptable, but that may be rare enough to not worry about. However, it does make for sloppy API design as developers will be expecting different return values in different places to represent the same thing. That being said, PHP has never gotten flack from people over inconsistent API expectations so there’s no point in worrying about that now right? 

Anthony didn't have a clue what that meant it seems and responded with this one Reddit:

Sometimes developers want to store negative values such as false, null or 0.
Ok, let's look at this in detail. The API I proposed in my blog posts has 0 and false and [] defined to be perfectly acceptable values. null is the only one that's not.
So you're already showing that you don't understand what I said. Or you do, but are purposely ignoring it.
This is perfectly acceptable behavior and a lot more common than people think.
For false and 0, completely fair. But null... Why would you want to cache null? 

This all seems to be a big confusion over the conversation of null, false, -1, etc and im trying to clear it up.

To me, the only possible problem, is using multiple backends, where various values that are acceptable in one backend are forced to convert to null in others, meaning sometimes an implementing library will happily bubble a -1 through as a valid value and when using another backend will give you a null instead.



If that is not what you two are talking about then I'm going to peace the hell out of this conversation, because you two are not concerned about something I believe to be a genuine concern, but arguing about things that I haven't got a clue about.

Robert Hafner

unread,
Oct 23, 2014, 11:33:49 PM10/23/14
to php...@googlegroups.com

We’re approaching the problem from different aspects I think. My point is that the backend driver is irrelevant, we can do different things to normalize those. However, without using an item class you’d have to sacrifice a value- either null or false (choice of the standard) would essentially be unusable.

This isn’t the only reason for a value wrapper, but it is a reason.

Rob



--
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.

Korvin Szanto

unread,
Oct 23, 2014, 11:53:18 PM10/23/14
to php...@googlegroups.com

Anthony is arguing that the driver interface dropping support for null is not enough reason to add an item interface. I'd say this is backwards, because we do not agree that the driver class is by default better, and we are already near voting stage with the pool item interfaces.

Anthony should instead be talking about why the pool/item proposal is such a detriment that it warrents dropping support for null.

The main argument I've read against it is that its unintuitive, and I simply do not agree. Can anyone help convince me that what we have is actually WRONG and not subjectively "less right"?

--
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.

Jon Whelan

unread,
Oct 23, 2014, 11:54:40 PM10/23/14
to php...@googlegroups.com
I'd agree that the backend driver is irrelevant.. that's a lower level of abstraction than what this PSR is addressing. However you can accomodate both the null and false values without a wrapping item class. As I've demonstrated in the implemenation that I've propose, you can just return an iterator from the cache. The calling code checks the validitiy of the iterator... if it's empty (i.e. not valid) then it's a cache miss... if it's valid.. then it's a hit and can be ANY value.

Using this you can have any value associated with a key

$cache->set('key', false|null);

$iterator = $cache->find('key');
if ($iterator->valid()) { // cache hit
return $iterator->current();
}
// call underlying datasource

What are the other reasons to have wrapping item class that you mention?


--
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/fUwLr4TVNok/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.

Korvin Szanto

unread,
Oct 24, 2014, 12:07:46 AM10/24/14
to php...@googlegroups.com
On October 23, 2014 at 8:54:39 PM, Jon Whelan (jon.m....@gmail.com) wrote:
However you can accomodate both the null and false values without a wrapping item class. As I've demonstrated in the implemenation that I've propose, you can just return an iterator from the cache.

How is this any different from the item interface other than it being less semantic and more confusing?

Jon Whelan

unread,
Oct 24, 2014, 12:53:45 AM10/24/14
to php...@googlegroups.com
It's different because the client of the cache doesn't need to do any wrapping of a value itself. From my understanding one of the arguments for a CacheItem is to be able to extend it to enable more features such as tagging. Well if that's the case then whatever code is interacting with the cache must now know how to construct this CacheItem object, setting things such as ttl, and tags, when that stuff should really be none of it's concern. Any time you want to change an implementation of a CacheItem that you're using, you've got to go change the code that interacts with the cache.. When really you should just be able to compose your object graph differently and add in additional objects and not have to change existing implementations.

When designing interfaces it's good to think: 'what is the objective of the client, and what is the absolute minimum knowledge a client of this interface should have and still be able to accomplish this objective'.

As a client to a cache pool (although map is really more appropriate, but that's another topic) all I should care about... is getting a value from it if it has it, removing values from it.. and adding a value to it that's associated with a key. That's it. That's the absolute minimum I need and should know. Anything else that's cache related (ttl, tags) needn't be my concern and all that stuff can be added further down the object graph and further down in the abstraction ladder. As a client to the cache... I'm just using it as a place to put values. Whatever that cache wants to do with, for instance, expire it after 10 minute, is it's perogative.

I would expect a client to a cache to be able to just do this:

$cache->set('key', 'anyRawValue');

Also returning an iterator is not unintuitive nor confusing. As a client you have two options, either 'get' an object from the cache.. in which case if it doesn't exist then instead of returning null, an exception should be thrown.. as this is no different than accessing a nonexistent index of an array (i know it doesn't throw an exception, but it triggers an error and is something that can always be avoided). This is also consistent with other maps such as SplObjectStorage.. which if you do an offsetGet of a key that doesn't exist, an exception is thrown.

The other option is to 'query' the cache or ask it a question—that question being..can you return the values you have associated with this key. Returning an iterator is consistent with how we deal with database queries as well.. when there are no results.. we return an empty set/array. When there are results, we return a set with those results. It just so happens that the nature of cache means that there is (or at least should be) only one result that is going to be returned in the set.

Korvin Szanto

unread,
Oct 24, 2014, 1:07:03 AM10/24/14
to php...@googlegroups.com

The concern with requesting whether it exists before retrieving is the race condition it may cause.

Why is returning an iterator different than defining a minimal interface that has an isMiss method and a getValue? Returning an iterator that shouldn't ever be iterated upon is definitely confusing and not semantic.

I'd more readily throw an exception on a miss than return an iterator that isn't for iterating, but I don't see why this is needed when we can just have an item interface defined.

Your point of the pool needing to know how create items is reasonable, but that sounds like less of an issue than having to normalize "not set", and can be handled by the implementation pretty simply by the implementation.

--
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.

Jon Whelan

unread,
Oct 24, 2014, 1:37:37 AM10/24/14
to php...@googlegroups.com


On Thursday, October 23, 2014 10:07:03 PM UTC-7, Korvin Szanto wrote: 

The concern with requesting whether it exists before retrieving is the race condition it may cause.

I'm not requesting whether it exists before. There is no race condition using find. By the time valid() is called.. the item was either returned or not returned and put into the iterator (result set).
 

Why is returning an iterator different than defining a minimal interface that has an isMiss method and a getValue? Returning an iterator that shouldn't ever be iterated upon is definitely confusing and not semantic.

It can be iterated on if you'd like. There's nothing saying that you can't iterate the result set. And I'll argue that it is indeed semantic. It's just a special case of a result set that's always going to have 0 or 1 result in it. Just as a square is a special case of a rectangle. A square is always going to have the same width and height, but it's still a rectangle. Just as in the case of our cache... our result set is always going to have either 0 or 1 results... but it's still a result set.

I'd more readily throw an exception on a miss than return an iterator that isn't for iterating, but I don't see why this is needed when we can just have an item interface defined.

That's totally something that's possible.. you'd just implement the client of the cache using the 'get' method instead of 'find' and it'd look like this:

try {
   return $cache->get('key');
} catch ($e) {
   // return from datasource
}
 
Certainly a viable option. But considering the fact that cache misses are common, it'd probably be wise to not be throwing a ton of exceptions. That's why using the 'find' method is useful.. since it's just asking a question and making a query for a result set. Which you can then check to see if it's got a result contained within it. Just another way to acheive the same goal.


Your point of the pool needing to know how create items is reasonable, but that sounds like less of an issue than having to normalize "not set", and can be handled by the implementation pretty simply by the implementation.

You may have misread or mistyped, but my point was not about the pool knowing how to create the items, in fact if a CacheItem wants to be created and used *within* the implementation of a pool, then that's fine too.. it's just an implementation detail. My point was about the *client* of the pool needing to know how to create the CacheItem, which is putting an unecessary burden upon the client and giving it too much knowledge. The client merely just wants to associate a value with a key. That's the minimal interface of the pool.

Robert Hafner

unread,
Oct 24, 2014, 2:30:59 AM10/24/14
to php...@googlegroups.com

Out of curiosity- do you believe PSR-3 should have been minimized down to one function, or do you think it was an appropriate standardization?


Rob



Alexander Makarov

unread,
Oct 24, 2014, 3:46:28 AM10/24/14
to php...@googlegroups.com
I think it could be minimized and I think that's why Yii isn't using it but what's done is done.

Jon Whelan

unread,
Oct 24, 2014, 3:51:38 AM10/24/14
to php...@googlegroups.com
Yes, one function would have been sufficient and would have been able to cover all use cases that the PSR-3 interface covers.

Anthony Ferrara

unread,
Oct 24, 2014, 11:19:55 AM10/24/14
to php...@googlegroups.com
A number of people have asked why I think PSR-6 is bad. Well, I don't. I don't think it's bad. In fact, I think for the problem its trying to solve, it is perfectly fine.

The problem is that I think that it's trying to solve the wrong problem. That was the fundamental point that I was trying to make in my initial blog post.

From the stated proposal:

> The goal of this PSR is to allow developers to create cache-aware libraries that can be integrated into existing frameworks and systems without the need for custom development.

With that as the goal, it's pretty easy to see how you'd get to the proposal that was made. In fact, Robert did a good job explaining it in his blog post.

The point I was trying to make is that I believe that the *without need for custom development* line is unrealistic and unachievable. It's even seen in the discussion around the proposal as things like stampeede protection and tags were added and stripped. So the current proposal does not meet the stated goal. 

I assert that this is not a problem with the proposal, but the stated goal itself. The goal tries to be all things to all people. And that's not realistic.

Instead, what happens if we re-state that goal. Rather than trying to solve the 100% solution (be all things to everyone), you could step back and try to do the 99% solution (be most things to most people). But even there it's still incredibly difficult. Hence why I suggested stepping back and doing the 50% problem.

Restating that goal to be a 50% problem, I would write something like this:

> The goal of this PSR is to allow for interoperability between cache libraries, cache-aware libraries and frameworks.

It's simpler. It focuses on interoperability rather than the implementation. But more so, it realizes that it's a first step, not an end solution.

And if you focus on that, then the required solution changes drastically. If you focus on interoperability and composition, the current proposal seems overly complex.

As I demonstrated in my 2 blog posts and reddit threads, one class with 3 methods can do everything that the current proposal does with 2 classes, 14 methods and 2 exceptions. Further, I demonstrated how using composition can expand that functionality using other simple interfaces (stampede protection with 1 interface with 1 method, batched sets with 1 interface with 2 methods, etc). 

And I did that by only sacrificing the ability to persist the `null` value in the store. A value which it can be argued shouldn't be meaningful in the first place, but even if it is isn't the common case. And so far nobody has been able to show an example of it being meaningful...

When I use simple and complex, I don't mean easy and hard. Easy is subjective, and relative to personal experience. Complexity is an objective measure which is related to how many things are being done, and how interconnected those things are.

So I hope that clears up the point I was trying to make, and sheds some light on the conversation that has gotten off track (I am to blame for that, getting too caught up in the technical to lose the overall point I was trying to make).

Anthony

Crypto Compress

unread,
Oct 25, 2014, 5:23:25 AM10/25/14
to php...@googlegroups.com
Hello List!

Preface: An Item represents a single key/value pair within a Pool.

Discussion on isHit() and exists() will follow in separate email.

------------------------------

> public function getKey();

I perfectly understand why "setKey" is missing. However, as null values
are handled by existence of a key, consistency wise i would prefer:

- public function key($key = null);
- getter: $k = $item->key();
- setter: $k = $item->key($newKey);

alternativ:

- getter: $k = $item->getKey();
- setter: $item->setKey($newKey);

------------------------------

> public function get();
> public function set($value);

Get what? Same consistent approach here:

- public function value($value = null);
- getter: $v = $item->value();
- setter: $v = $item->value($newValue);

(if you want a null-value, assign a key!)

alternativ:

- getter: $v = $item->getValue();
- setter: $item->setValue($newValue);

------------------------------

> public function setExpiration($ttl = null);
> public function getExpiration();

always the same:

- public function ttl($ttl = null);
- getter: $dt = $item->ttl();
- setter: $dt = $item->ttl($newTtl); // look at return value: $dt maybe
!= $newTtl

(if you want a default expiration, do not set as with null-value!)

alternativ:

- getter: $dt = $item->getExpiration();
- setter: $item->setExpiration($ttl);

------------------------------

> public function isHit();
> public function exists();

Even more consistency. This one is purely *a hit*! :)

public function hit(); // this is a null check on $item->key()

(More on isHit() and exists() to come in separate mail.)

------------------------------



Conclusion:

interface CacheItemInterface
{
public function hit();
public function key($key = null);
public function value($value = null);
public function ttl($ttl = null);
}

and to avoid some discussion, the verbose alternativ (don't care enough):

interface CacheItemInterface
{
public function isHit();

public function getKey();
public function setKey($key = null);

public function getValue($value = null);
public function setValue($value = null);

public function getExpiration();
public function setExpiration($ttl = null);
}

cryptocompress

Crypto Compress

unread,
Oct 25, 2014, 5:23:30 AM10/25/14
to php...@googlegroups.com
Hello List!

Preface: The Pool represents a collection of items in a caching system.
The pool is a logical Repository of all items it contains.

In this email i cover only the repository (stateless service) aspect.
The collection part (stateful data object) will be in the next email.

------------------------------

> public function getItem($key);
> public function getItems(array $keys = array());

Looks consistent. I like! :)

------------------------------

> public function deleteItems(array $keys);

Woops. Where is deleteItem?

public function deleteItem($key);
public function deleteItems(array $keys);

------------------------------

> public function save(CacheItemInterface $item);

Same here.

public function saveItem(CacheItemInterface $item);
public function saveItems(array $items);

------------------------------

> public function saveDeferred(CacheItemInterface $item);
> public function commit();

More on this in a separate email.

------------------------------



Conclusion:

interface CacheItemPoolInterface
{
public function getItem($key);
public function getItems(array $keys = array());

public function deleteItem($key);
public function deleteItems(array $keys);

public function saveItems(CacheItemInterface $item);
public function saveItems(array $items);
}


cryptocompress

Crypto Compress

unread,
Oct 25, 2014, 6:20:48 AM10/25/14
to php...@googlegroups.com
Hello List!

Preface: There is a race condition between exists() and get() so we need
isHit() on Item iterface.
Definition: An Item represents a single key/value pair within a Pool.

Step-by-step:
1. One Item (key value pair) is fetched from remote (pool, what ever...).
2.1 Item has key and value set. Call to exists() and isHit() both return
true.
2.2 Item has key and value not set. Call to exists() and isHit() both
return false.

No race condition in Item object.

Conclusion: No need for separate checks on ItemInterface.

cryptocompress

Crypto Compress

unread,
Oct 25, 2014, 6:20:54 AM10/25/14
to php...@googlegroups.com
Hello List!

Preface: A Pool object MAY delay persisting [...] to take advantage of
bulk-set operations.
There is a need for bulk-set operation and so Pool delay my operations.

Some code:

foreach($arr as $k => $v)
{
$pool->saveDeferred(new Item($k, $v));
}
$pool->commit();

in contrast to:

$items = [];
foreach($arr as $k => $v)
{
$items[] = new Item($k, $v);
}
$pool->saveItems($items);

Can't see any reason to delay nor the effort to implement this overhead
in pool.
If there are more advanced bulk operations, implement them on some
array-wrapper.

Conclusion: Do not save deferred. Leave the decision when to save to
external code.
Maybe discuss advanced ItemCollectionInterface.

cryptocompress

Larry Garfield

unread,
Oct 26, 2014, 9:28:47 PM10/26/14
to php...@googlegroups.com
On 10/25/2014 04:23 AM, Crypto Compress wrote:
> Conclusion:
>
> interface CacheItemInterface
> {
> public function hit();
> public function key($key = null);
> public function value($value = null);
> public function ttl($ttl = null);
> }
>
> and to avoid some discussion, the verbose alternativ (don't care enough):
>
> interface CacheItemInterface
> {
> public function isHit();
>
> public function getKey();
> public function setKey($key = null);
>
> public function getValue($value = null);
> public function setValue($value = null);
>
> public function getExpiration();
> public function setExpiration($ttl = null);
> }
>
> cryptocompress

Overloading get and set into a single method a la jQuery seems like a
bad idea; it's rarely done in PHP code as far as I know. Let's not go
there.

You said that you understand why setKey is not included, but then want
to add it. I don't get it. :-)

Otherwise, what you're suggesting is simply to rename get/set to
getValue/setValue? I have no strong preference there.

--Larry Garfield

Larry Garfield

unread,
Oct 26, 2014, 9:32:24 PM10/26/14
to php...@googlegroups.com
On 10/25/2014 04:23 AM, Crypto Compress wrote:
> Hello List!
>
> Preface: The Pool represents a collection of items in a caching
> system. The pool is a logical Repository of all items it contains.
>
> In this email i cover only the repository (stateless service) aspect.
> The collection part (stateful data object) will be in the next email.
>
> ------------------------------
>
> > public function getItem($key);
> > public function getItems(array $keys = array());
>
> Looks consistent. I like! :)
>
> ------------------------------
>
> > public function deleteItems(array $keys);
>
> Woops. Where is deleteItem?
>
> public function deleteItem($key);
> public function deleteItems(array $keys);

deleteItem() was deliberately omitted as typing
$pool->deleteItems(['my_key']) is easy enough and it eliminates an
unnecessary method.

> ------------------------------
>
> > public function save(CacheItemInterface $item);
>
> Same here.
>
> public function saveItem(CacheItemInterface $item);
> public function saveItems(array $items);

saveItems() becomes redundant once we have saveDeferred(). That's why it
was omitted.

--Larry Garfield

Larry Garfield

unread,
Oct 26, 2014, 9:36:43 PM10/26/14
to php...@googlegroups.com
You don't see a reason to delay...? Isn't bulk operations the reason?

Note that a deferred save doesn't have to be part of an explicit set.
It's legal to delay until "whenever you feel like it", and not call
commit() yourself. Whether the pool is then able to save all items in
one go or, for whatever reason, needs to chunk it up itself, it can do
so. That decision rests with the pool implmentation. saveDeferred() is
saying only "write this, but I don't care when you do so."

--Larry Garfield

Bernhard Schussek

unread,
Oct 27, 2014, 3:02:45 AM10/27/14
to php...@googlegroups.com
I had similar feelings when I read the proposal. The proposal is great! :) Improving the names, however, could reduce the number of "huh?"s when using it.

Btw. I made a PR about this a few days ago: https://github.com/php-fig/fig-standards/pull/361

2014-10-27 2:32 GMT+01:00 Larry Garfield <la...@garfieldtech.com>:
deleteItem() was deliberately omitted as typing $pool->deleteItems(['my_key']) is easy enough and it eliminates an unnecessary method.

You could argue the same for adding the method: It's simple enough to add. As a user of the interface, I'd expect it to be there, so it's a little confusing that it's missing.
 
Cheers,
Bernhard

Crypto Compress

unread,
Oct 27, 2014, 3:07:23 AM10/27/14
to php...@googlegroups.com
Yes, it's only a consistency clean up.

Crypto Compress

unread,
Oct 27, 2014, 3:07:26 AM10/27/14
to php...@googlegroups.com
This one is my biggest problem with current draft. Control is better
then uncertainty and it make the implementation more complex on both ends.

- I cannot see the "chunk" argument. Please elaborate. Maybe an example?
- In PHP context "write this, but I don't care when you do so." seems
like "write this, on termination (mostly dtor)"?
Reply all
Reply to author
Forward
0 new messages