Fork me on GitHub
Edit  

Collection

Collection of entities is returned as an instance implementing Nextras\Orm\Collection\ICollection interface. ICollection extends \Traversable interface and adds other API to do further operations with the collection.

In Orm, we use coding standard which assumes that

  • get* methods return an IEntity instance or (a null or throws),
  • find* methods return an ICollection instance.

Collection itself is immutable, all methods that modify the collection return a new ICollection instance. Collection provides following methods:

getBy(array $conds): ?IEntity applies an additional filtering and returns the first result's entity or a null
getByChecked(array $conds): IEntity applies an additional filtering and returns the first result's entity or a throws NoResultException
getById($primaryValue): ?IEntity applies filtering by id property and returns the first result's entity or a null
getByIdChecked($primaryValue): IEntity applies filtering by id property and returns the first result's entity or a throws NoResultException
findBy(array $conds): ICollection applies an additional filtering
orderBy($property, $direction): ICollection applies an additional ordering
orderBy($propertyExpression, $direction): ICollection applies an additional ordering using collection function
orderBy(array $properties): ICollection applies an additional multiple ordering
resetOrderBy(): ICollection removes all defined orderings
limitBy($limit, $offset): ICollection limits the collection and sets the starting offset
fetch(): ?IEntity returns the next unprocessed result's entity, repeated calls iterate over the whole result-set
fetchAll(): IEntity[] returns the all result's entities as an array
fetchPairs($key, $value): array process the whole result and returns it as an associative array

Filtering#

Each collection can be filtered by an array of conditions. These conditions are passed as a parameter of the findBy() method. The array consists of entity property names and values. Keys can contain an optional operator. The default operator is equality operator. Let's see the example:

$books = $orm->books->findBy([
    'author' => $author->id,
    'publishedAt<=' => new DateTimeImmutable(),
]);

Allowed operators are =, !=, <=, <, >= and >.

You can filter the collection using conditions over entity relationships. To filter collection by a relationship, use a traversing expression: it consists of the path delimited by -> – the same arrow you use in PHP.

// find all books which were authored by Jon Snow
$orm->books->findBy(['author->name' => 'Jon Snow']);

// find all books which were not translated by Jon Snow
$orm->books->findBy(['translator->name!=' => 'Jon Snow']);

The described syntax may be expanded to support the OR logical disjunction. Prepend the ICollection::OR constant as a first value of the filtering array:

// finds all books which were authored od translated by one specific person
$books = $orm->books->findBy([
    ICollection::OR,
    'author' => $person->id,
    'translator' => $person->id,
]);

You may nest the query array structure; use the same syntax repeatedly:

// find all man older than 10 years and woman younger than 10 years
$authors = $orm->author->findBy([
    ICollection::OR,
    [
        ICollection::AND,
        'age>=' => 10,
        'sex' => 'male',
    ],
    [
        ICollection::AND,
        'age<=' => 10,
        'sex' => 'female',
    ],
]);

The previous example can be shortened because the AND operator is the default logical operator.

// find all man older than 10 years and woman younger than 12 years
$authors = $orm->author->findBy([
    ICollection::OR,
    [
        'age>=' => 10,
        'gender' => 'male',
    ],
    [
        'age<=' => 12,
        'gender' => 'female',
    ],
]);

There are few restrictions:

  • Filtering does not support any kind of aggregation. If you need to write more complex queries, proxy your methods to a mapper layer or write custom collection function.
  • Relationship filtering is currently supported only over the persisted (non-virtual) properties. Support for virtual properties is unsupported.

Single result fetching#

The same condition format may be applied to retrieve just the first collection's result.

$author = $orm->author->getBy(['name' => 'Peter', 'age' => 23]); // Author|null
if ($author !== null) {
    echo $author->name;
}

$author = $orm->author->getByChecked(['name' => 'Peter', 'age' => 23]); // returns Author or throws NoResultException
echo $author->name;

The most common use-case to retrieve entity by its primary value has a shortcut getById() and getByIdChecked().

$author = $orm->author->getById(1); // Author|null
// equals
$author = $orm->author->getBy(['id' => 1]);

$author = $orm->author->getByIdChecked(2); // returns Author or throws NoResultException
// equals
$author = $orm->author->getByChecked(['id' => 2]);

Sorting#

You can easily sort the collection by an orderBy() method; The orderBy() method accepts a property name and a sorting direction. By default, values are sorted in an ascending order.

To change the order, use ICollection::ASC or ICollection::DESC constants. If the sorting property (or property expression) may contain a null value, use more specific sorting constants: ICollection::ASC_NULLS_LAST, ICollection::ASC_NULLS_FIRST, ICollection::DESC_NULLS_LAST, or ICollection::DESC_NULLS_FIRST.

$orm->books->findAll()->orderBy('title'); // ORDER BY title ASC
$orm->books->findAll()->orderBy('title', ICollection::DESC); // ORDER BY title DESC

The orderBy method also accepts a property expression. See filtering in this chapter for further description.

// ORDER BY age = 2
$orm->books->findAll()->orderBy([
    CompareEqualsFunction::class,
    'age',
    '2',
]);

You can add more ordering rules; they will be used if the previously defined ordering properties will be evaluated as equal. To add more ordering rules, call orderBy method repeatedly or simply use orderBy method with an array of property names and their sorting directions. All already defined ordering rules may be removed by resetOrderBy() method.

// ORDER BY title DESC, publishedYear DESC
$orm->books->findAll()->orderBy([
    'title' => ICollection::ASC,
    'publishedYear' => ICollection::DESC,
]);

Limiting#

To limit the data collection, just use limitBy() method. The first argumnet is a limit, the second optional argument is a starting offset.

// get the last 10 published books
$orm->books->findAll()->orderBy('publishedAt', ICollection::DESC)->limitBy(10);

// get the 10 penultimate published books
$orm->books->findAll()->orderBy('publishedAt', ICollection::DESC)->limitBy(10, 10);

Counting#

It is easy to count entities returned in a collection. There are two methods:

  • count() fetches the queried entities from the storage and counts them in PHP,
  • countStored() asks the storage for the matching entities' count; the implementation depends on the mapper layer, basically, the countStored() method runs an COUNT SQL query.

The count() method is quite useful if you know that you will need the fetched entities later. The countStored() is needed if you do a pagination, etc.

public function renderArticles($categoryId)
{
    $articles = $this->orm->articles->findBy(['category' => $categoryId]);

    $limit = 10;
    $offset = $this->page * 10;

    $this->paginator->totalCount = $articles->countStored();
    $this->template->articles = $articles->limitBy($limit, $offset);
}
{if $articles->count()}
    {foreach $articles} ... {/foreach}
{else}
    You have no articles.
{/if}

Pairs fetching#

The fetchPairs() method accept two arguments: the first argument is a property name that will be used as an array key. If a null is provided, the result array will be as a list (i.e. from zero). The second argument is a property name that the value will be read from. If a null is provided, then the whole entity will be used as the value.

// all book entities indexed by their primary key
$orm->books
    ->findAll()
    ->fetchPairs('id', null);

// all books' titles sorted backward and naturally indexed
$orm->books
    ->findAll()
    ->orderBy('title', ICollection::DESC)
    ->fetchPairs(null, 'title');