Fork me on GitHub

Collection

Entities collections are 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 a 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 the same as getBy, but throws NoResultException when no entity is found
getById($primaryValue): ?IEntity a shortcut for quick getting entity by primary value
getByIdChecked($primaryValue): IEntity the same as getById, but throws NoResultException when no entity is found
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, optionally sets the starting offset
fetch(): ?IEntity returns the unfetched result's entity, repeated calls iterate over the whole result set
fetchAll(): IEntity[] returns the whole result's entities as an array
fetchPairs($key, $value): array process the whole result and returns it as an 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. 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 using 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 a OR logical conjunction. Unshift the operator ICollection::OR as a first value of the query 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 conjunction.

// 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. Learn more in Repository chapter.
  • Relationship filtering is currently supported only over the persisted (non-virtual) properties. Support for virtual properties has not been implemented yet.

Single result fetching#

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

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

$author = $orm->author->getByChecked(['name' => 'Peter', 'age' => 23]); // 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); // Author or throws NoResultException
// equals
$author = $orm->author->getByChecked(['id' => 2]);

Sorting#

You can easily sort the collection by orderBy() method; orderBy() method accepts a property name and a sorting direction. By default, properties 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 first property argument 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 that will be used if the previously defined ordering properties will be evaluated as equal. To do this, call orderBy method repeatedly or simple use orderBy method and pass an array of property names and their sorting directions. The sorting may be reset 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.

// 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 entities from the storage and counts them in PHP,
  • countStored() asks the storage for the collection count; the implementation depends on the mapper layer, basically, the countStored() method runs an SQL query.

The count() method is quite useful if you know that you will need the fetched entities in the collection. 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 accepts a property name that will be used as an key. If a null is provided, the result array will be indexed naturally (from zero). The second argument accepts a property name that will be used as a value. 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');