Skip to content

PHPLIB-81: Implement CachingIterator #424

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

Merged
merged 4 commits into from
Oct 27, 2017
Merged

Conversation

jmikola
Copy link
Member

@jmikola jmikola commented Oct 26, 2017

@jmikola jmikola force-pushed the phplib-81 branch 2 times, most recently from 56bb401 to ce7879a Compare October 26, 2017 20:39
* effectively rewinds the Traversable and the wrapping Generator, which
* will execute up to its first yield statement. Additionally, this mimics
* behavior of the SPL iterators and allows users to omit an explicit call
* to rewind() before using the other methods.
Copy link
Member Author

Choose a reason for hiding this comment

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

@alcaeus: Strictly speaking, Iterators must call rewind() before iterating; however, most SPL iterators relax that restriction and rewind during construction. I kept that behavior, while adding $iteratorAdvanced to ensure that the initial call to rewind() does not exhaust all results. That may be why @bjori originally assumed you had implemented a BufferedIterator. Based on testPartialIterationDoesNotExhaust(), the original implementation was actually buffering up front!

*/
class CachingIterator implements Countable, Iterator
{
private $items = [];
Copy link
Member Author

Choose a reason for hiding this comment

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

This helps if we count an empty Traversable, as the early return in storeCurrentItem() would have prevented this from ever being initialized from null to an array type.

{
foreach ($traversable as $key => $value) {
yield $key => $value;
$this->iteratorAdvanced = true;
Copy link
Member Author

Choose a reason for hiding this comment

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

My understanding (and observation) is that the first call to wrapTraversable() from our constructor is going to immediately execute this function up to (and including?) the yield statement. The $iteratorAdvanced assignment will take place once we re-enter, immediately before foreach advances the Traversable (e.g. Iterator::next() if it happens to be an Iterator).

Copy link
Member

Choose a reason for hiding this comment

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

Correct - this could be tested by adding an assertion on the iteratorAdvanced property on the testConstructorRewinds test:

$this->assertAttributeSame(false, 'iteratorAdvanced', $iterator);

Copy link
Member Author

Choose a reason for hiding this comment

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

I didn't realize those assertions existed :)

I think I'll do without them here, since we can get by testing with just the public API. Will keep it in mind for future use, though!

*/
private function wrapTraversable(Traversable $traversable)
{
foreach ($traversable as $key => $value) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Just to recap, each foreach starts with invoking the internal iterator's rewind handler, followed by a validity check. If valid, keys and values are fetched. On subsequent iterations, a call to the next handler takes place instead of rewind.

{
foreach ($items as $item) {
if ($item instanceof Exception) {
throw $item;
Copy link
Member Author

Choose a reason for hiding this comment

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

The stack trace of the exception only reports when it was constructed (it's not generated/updated when the exception is thrown). I was surprised to learn that, but it's not too important as we only ever assert that nothing is thrown (testing that initial rewind no longer exhausts the internal traversable).

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe I tried fixing that in Xdebug, but adding the previous exception to a special property.

{
foreach ($traversable as $key => $value) {
yield $key => $value;
$this->iteratorAdvanced = true;
Copy link
Member

Choose a reason for hiding this comment

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

Correct - this could be tested by adding an assertion on the iteratorAdvanced property on the testConstructorRewinds test:

$this->assertAttributeSame(false, 'iteratorAdvanced', $iterator);

use Countable;
use Generator;
use Iterator;
use Traversable;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do this if if you're not aliasing them, or plucking them out of another namespace? And if you want the root namespace, shouldn't it be use \Countable ?

Copy link
Member Author

Choose a reason for hiding this comment

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

use statements use fully qualified names, so the leading backslash isn't necessary. Out of habit, I list all used classes at the top of the file, rather than making a special case of \Countable for classes in the global namespace.

{
foreach ($items as $item) {
if ($item instanceof Exception) {
throw $item;
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe I tried fixing that in Xdebug, but adding the previous exception to a special property.

alcaeus and others added 4 commits October 27, 2017 09:15
Ensure that a call to CachingIterator::rewind() no longer exhausts results up front, which would impose a BufferedIterator design.

Initializing the cache to an empty array also ensures that we can count an empty result set.
This will allow the returned CollectionInfoIterator and IndexInfoIterator classes to be rewound and iterated multiple times.
@jmikola jmikola merged commit 5ea6f35 into mongodb:master Oct 27, 2017
jmikola added a commit that referenced this pull request Oct 27, 2017
@jmikola jmikola deleted the phplib-81 branch October 27, 2017 13:16
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.

3 participants