lox/pheasant — pull request #123 by pda (2014-09-02 21:19:23+00:00)
Title: Collection::last() handles empty results

`Collection::last()` is implemented as an attempted seek to `$count - 1`, which provides the zero-based index of the last result for non-empty collections, but returns `-1` for empty sets.

That `-1` case was propagating through to `mysqli_stmt::data_seek()` which does not expect negative offsets. As a result, `Collection::last()` on an empty collection returned an empty record instead of throwing an exception.

This is fixed at two layers:

* `Pheasant\Database\Mysqli\ResultIterator::seek()` throws `OutOfBoundsException` for negative offsets.
* `Pheasant\Collection::offsetExists()` catches `OutOfBoundsException` and returns false.

A regression test has been added for this case.

---

Additionally, this PR makes `Collection` `::first()` and `::last()` throw `NotFoundException` instead of `ConstraintException`. The former is a subclass of the latter, so it's backwards compatible for code catching `ConstraintException`. I've found a case in the wild of `catch (NotFoundException $e)` already, but it was never actually hit due to `last()` returning an empty record instead of throwing an exception.

I'm happy to drop the `NotFoundException` commit from this PR, and/or move it to a separate PR, if that makes more sense.

---

Bonus: a tidy-up of the code in `Pheasant\Database\Mysqli\ResultSet::getIterator()` which returns `EmptyIterator`, and a warning: `EmptyIterator` does not implement `seek()`, which is called by various things including the `seek()` method of that same `ResultSet` class.

---
ping: @lox @harto