Skip to content

[VOTE] Explicit Paginator return type #35

@ronan-gloo

Description

@ronan-gloo

Summary

We are used to use abstractions as return type in our method declaration. In most of the cases it is a good practice because we can change implementation without breaking the client.

Usually, presentation repository interfaces declare fetchAll like methods, which allow to retrieve a collection of models based on filters. This is a typical declaration we can find in API project :

<?php
namespace ShoppingFeed\Data\Presentation;

interface DataAccessInterface
{
    public function fetchAllBy(int $userId): iterable
}

The assumption and expectations for someone who call $access->fetchAllBy(123) is to get an iterable set for all elements that match the constrain, here $userId.

As the return type provides an iterable type, the only thing the client can do is to... iterates on:

<?php
$set = $access->fetchAllBy(123);

// Only possible thing we can do with iterables
foreach ($set as $item) {
    echo $item->get('id') . "\n"
}

Looking the code above, we all expect to loop accross an array or \Iterator which provides what we asked for: all data for $userId.

But this is not what we do : Our DBAL (or whatever) implementations usually provide a ShoppingFeed\Paginator\PaginatorAdapterInterface or ShoppingFeed\Paginator\PaginatorInterface, which limit the number of items to fetch.

So, if we process the same client code with a paginator :

<?php
$set = $access->fetchAllBy(123);
foreach ($set as $item) {
    echo $item->get('id') . "\n"
}

We only got the first 10 results of the set by default.

  • This is clearly an unexpected behavior that can lead to buggy / broken implementation.
  • This is a code smell as infrastructure implémentations does not respect what the interface suggest

Valid Code

As we still want to break result in chunk in most of the cases (paginated API's), we need to change the interface signatures and make the contract clear between client and implementation.

interface DataAccessInterface
{
    public function fetchAllBy(int $userId): \ShoppingFeed\Paginator\PaginatorInterface
}

Now the clients nows it will be limited results when iterating on, and may controls it if desired.

PaginatorAdapterInterfaces have to stay to implementation level and not be exposed to interfaces, as we have no guaranties about their default behavior when they are not wrapper into a Paginator instance.

Invalid Code

Do not type againts iterable, \Traversable,\Iterator or \IteratorAggregate types when expecting a limited set.
Example:

<?php
namespace ShoppingFeed\Data\Presentation;

interface DataAccessInterface
{
    public function fetchAllBy(int $userId): iterable
}

Exemples of code to fix

Documentation

Metadata

Metadata

Assignees

Labels

Style RequirementThe style is required, and code should conform to it

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions