Hi, another post on some Engine theories, from a design perspective. Next bigger thing that tops my TODO before Alpha 2 is a new helper so that the helper system available as 1.1.0 will not be empty when Engine hits gold. I think one of the most commonly used helper classes besides mailing, file uploads and so on is Pagination.
Design goals
The design I have so far for how the pagination class should work is way too over complexed, so I thought I would break it up in small parts for features I want to go in and features I like to be present but is not mandatory.
Mandatory:
- Iterators should be able to be passed to the class instance and looped over
- Must have a clean way to specify options, such as limits, surrounding pages to calculate and so on
- Must not depend on the database layer (conflicts first item on this list)
Optional:
- Iterating over the pagination object would be nice when generating the pages
- If above is implemented, it would be great to have each page iterated carry information such as “is this the current page?”, “is this a previous page?”
- A specialized helper written on top of this for exposing iterations as datamanager items
- Some magic if not none to allow database results to work flawlessly without too much hacking around, clashing it into one big class would be really bad
So as you can see, theres many different items I want it to be able to cover, while some are sane, I’m sure many seems insane too at first. Creating a bridge between all these items that I decide to put in takes a great deal of time to figure out the most optimal way for current and future usage without breaking the interface each time a new feature is added or a bug is fixed.
Dependencies
As written above, I don’t want the pagination class to depend on the database layer, while all helpers depend on the registry class is not an issue at all. However to achieve gold, I might need to write an additional helper thats written on top to not clash everything into the main pagination class, although its just a matter of one additional method.
Initializing…
I thought I would cook the most ideal way to deal with many of these aspects as I go with this blog post, so I can review it after I’m done and take the best of it and fill in the missing pieces. Lets begin with how initializing the helper works and should be done:
<?php use Tuxxedo\Helper; $pagination = Helper::factory($registry, 'pagination'); ?>
As you can see, the $registry is required for initializing the class, this is mandatory for all helpers. The code cannot be more simple, and thats good for now.
Configuration
Configuration is in many places around Engine using the \Tuxxedo\InfoAccess class thats a wrapper around the built-in \ArrayAccess interface from SPL in PHP. This might be suitable for pagination, so we would be able to configure the class in the following manner:
<?php
$pagination['option1'] = 'value1';
$pagination['option2'] = 'value2';
if(expression_is_true())
{
$pagination['option1'] = 'value3';
}
?>
It’s easy to implement, and have a nice and clean interface for the programmer.
Next is a list of configuration values that should be possible to define, naming and functions:
- limit – Limits the number of results, mandatory
- page - Which page we’re currently at, optional (defaults to 1)
- pages - Number of pages to show before and after when iterating, optional (defaults to 3)
Now, the pages option might clash names with the page option, but its the shortest name that gives most meaning rather than a long word like surrounding_pages (as just surrounding doesn’t give much of a clue). All these values are signed integers, so thats easy and simple to use both internally and externally.
Defining the iterator
Defining the iterator to iterate pages over must have its own setter method; setIterator(). This method must define a key from where the iteration should start, this must be sent as the second parameter to the method, meaning its prototype should look something like:
public function setIterator(\Iterator $iterator, $identifier)
The $identifier parameter is for when to start using the results, this is internally compared with the value of key(), like:
<?php
$skipping = true;
foreach($iterator as $key => $value)
{
if($skipping && $key == $identifier)
{
$skipping = false;
}
elseif($skipping)
{
continue;
}
/* ... */
}
?>
But wait! Is that really sane to loop over all these many useless records in order to capture the ones we need? No it is not, it should be up to the developer to supply that data, so lets rewind a bit and re-do the first part from when defining setIterator().
setIterator(\Iterator $iterator)
Thats how simple our prototype should be, we should only supply the records for iterating. Since iterators do not support a reliable counting method, we have to implement our own but fear not, 1.1.0 Alpha 2 makes \Tuxxedo\Database\Result implement the built-in \Countable interface from SPL in PHP. But since not all iterators may implement both, we need to have an interface “flag” (sort of like some Datamanager hooks) to tell the setIterator method that its possible to both iterate and count the results. As this is an abstract concept it should be placed in the core package of Engine, outside helpers.
By using this syntax we effectively eliminates the need for special treatment for database results as they implement all the interfaces and also ONLY return the results possible, thats nice, however we still need to some logic so we can expose from which record, and how many ahead we want so that queries can use this information. I can imagine an effective method to fetch such information would be:
<?php
printf('Displaying %d record(s), starting from %d', $pagination['limit'], $pagination->getStartLimit());
?>
Note that only one new method were made available here, that is getStartLimit, since we can effectively read the current limit thanks to the \Tuxxedo\InfoAccess class.
Iterating
So now that the definition of how an iterator should be made, its time to look at how we suppose to iterate over the results, since we are setting an iterate, it makes somewhat sense to expose it too for an encapsulated design in some cases. What is needed here is a new method called getIterator to comply with the previously defined method setIterator.
Below is two examples of how setting and getting would work, depending on the context:
<?php
$pagination->setIterator($iterator);
foreach($iterator as $row)
{
/* ... */
}
/* ... */
?>
However, say we need to dispatch the $pagination instance somewhere, for example to some MVC code or whatnot, then the code would looks like this:
<?php
function build_results(Pagination $pagination)
{
/* ... */
foreach($pagination->getIterator() as $row)
{
/* ... */
}
/* ... */
}
$pagination = Helper::factory($registry, 'pagination');
/* ... */
$pagination->setIterator($pagination);
if(expression_is_true())
{
build_results($pagination);
}
/* ... */
?>
When iterating database results, the iterator inside is of course still the same so it will continue to inherit the fetching mode set by the setFetchMode method in the \Tuxxedo\Database\Result class.
Building the pagination
Now we are down to the solo purpose of the helper itself, while theres a lot of stuff of how the above code works together then this part is rather straight forward.
The $pagination instance implements the \Iterator interface too, meaning it will be able to run in foreach statements, before the \Iterator::valid() method will return true the configuration and iterator must be set. \Iterator::rewind will rewind the inner iterator too.
So lets skip the talking for a second and lets see an example of how this would look:
<?php
/* ... */
foreach($pagination as $page)
{
/* ... */
}
?>
The important part here is the $page variable that is an instance of \Tuxxedo\Helper\Pagination\Page. Now before we continue, you may ask why we would have a new namespace, just for that single class? That all boils down to the concept of design, and usability of the object oriented features that PHP provides us with. An stdClass object could do, but it would seem way too hacky, using a separate class gives us more future expansion if needed.
The $page object contains 4 important features, defined in methods:
| Method | Purpose |
isPrevious() |
Checks if the page being iterated is before the current page (current meaning the one set in $pagination['page']) |
isNext() |
Checks if the page being iterated is after the current page (using the same technique as above) |
isCurrent() |
Checks if this is the current page, aka the value of $pagination['page'] |
__toString() & getPageNumber() |
Returns the current page number, in numeric format. The __toString() method returns it as a string to allow echo $page; and getPageNumber() returns it as a proper integer |
The descriptions in the above table is pretty verbose and straight forward to understand, so I don’t think any example of use for that would be needed here.
Round up
Rounding it all up, it took some time to write this blog post as I had to play around with the possibilities as I wrote most of the theory here, whilst this might not end up in the final product, theres a good reason that most of it will.
Stay tuned for more theories and updates!