Skip to content

Commit

Permalink
collection: add getByIdChecked() & getByChecked() [closes #319]
Browse files Browse the repository at this point in the history
  • Loading branch information
hrach committed Jan 12, 2020
1 parent 3066fca commit de520c9
Show file tree
Hide file tree
Showing 11 changed files with 276 additions and 46 deletions.
97 changes: 68 additions & 29 deletions doc/collection.texy
@@ -1,40 +1,29 @@
Collection
##########

Each entity selection 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.
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.

/--div .[advice]
In Orm, we use coding standard which assumes, that
- `get*` methods return an `IEntity` instance or a null,
- `get*` methods return an `IEntity` instance or (a null or throws),
- `find*` methods return a `ICollection` instance.
\--
<br>

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

- `getBy(array $conds): ?IEntity` - applies an additional filtering and returns the first result's entity or a `null`,
- `findBy(array $conds): ICollection` - applies an additional filtering,
- `orderBy($property, $direction): ICollection` - applies an additional ordering,
- `orderByMultiple($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. 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.

/--php
// 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');
\--

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`
| `getBy(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
| `orderByMultiple($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
=========
Expand Down Expand Up @@ -75,7 +64,7 @@ You may nest the query array structure; use the same syntax repeatedly:

/--php
// find all man older than 10 years and woman younger than 10 years
$books = $orm->author->findBy([
$authors = $orm->author->findBy([
ICollection::OR,
[
ICollection::AND,
Expand All @@ -94,7 +83,7 @@ The previous example can be shortened because the `AND` operator is the default

/--php
// find all man older than 10 years and woman younger than 12 years
$books = $orm->author->findBy([
$authors = $orm->author->findBy([
ICollection::OR,
[
'age>=' => 10,
Expand All @@ -111,6 +100,34 @@ 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 | repository].
- 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.

/--php
$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()`.

/--php
$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]);
\--

--------------

Expand Down Expand Up @@ -184,3 +201,25 @@ public function renderArticles($categoryId)
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.

/--php
// 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');
\--
4 changes: 2 additions & 2 deletions doc/repository.texy
Expand Up @@ -5,8 +5,8 @@ Repository provides interface for entities retrieving, persisting and removing.

/--div .[advice]
In Orm, we use coding standard which assumes, that
- `get*` methods return IEntity instance or null,
- `find*` methods return ICollection instance.
- `get*` methods return an `IEntity` instance or (a null or throws),
- `find*` methods return a `ICollection` instance.
\--

Retrieving
Expand Down
23 changes: 23 additions & 0 deletions src/Collection/ArrayCollection.php
Expand Up @@ -15,6 +15,7 @@
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Mapper\IRelationshipMapper;
use Nextras\Orm\MemberAccessException;
use Nextras\Orm\NoResultException;
use Nextras\Orm\Repository\IRepository;


Expand Down Expand Up @@ -70,12 +71,34 @@ public function getBy(array $where): ?IEntity
}


/** {@inheritDoc} */
public function getByChecked(array $conds): IEntity
{
$entity = $this->getBy($conds);
if ($entity === null) {
throw new NoResultException();
}
return $entity;
}


public function getById($id): ?IEntity
{
return $this->getBy(['id' => $id]);
}


/** {@inheritdoc} */
public function getByIdChecked($primaryValue): IEntity
{
$entity = $this->getById($primaryValue);
if ($entity === null) {
throw new NoResultException();
}
return $entity;
}


public function findBy(array $where): ICollection
{
$collection = clone $this;
Expand Down
13 changes: 13 additions & 0 deletions src/Collection/EmptyCollection.php
Expand Up @@ -12,6 +12,7 @@
use Iterator;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Mapper\IRelationshipMapper;
use Nextras\Orm\NoResultException;


final class EmptyCollection implements ICollection
Expand All @@ -26,12 +27,24 @@ public function getBy(array $where): ?IEntity
}


public function getByChecked(array $conds): IEntity
{
throw new NoResultException();
}


public function getById($id): ?IEntity
{
return null;
}


public function getByIdChecked($primaryValue): IEntity
{
throw new NoResultException();
}


public function findBy(array $where): ICollection
{
return clone $this;
Expand Down
27 changes: 21 additions & 6 deletions src/Collection/ICollection.php
Expand Up @@ -12,6 +12,7 @@
use IteratorAggregate;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Mapper\IRelationshipMapper;
use Nextras\Orm\NoResultException;
use Nextras\Orm\Repository\Functions\ConjunctionOperatorFunction;
use Nextras\Orm\Repository\Functions\DisjunctionOperatorFunction;

Expand Down Expand Up @@ -41,17 +42,31 @@ interface ICollection extends IteratorAggregate, Countable


/**
* Returns IEntity filtered by conditions.
* @param array $where
* Returns IEntity filtered by conditions, null if none found.
*/
public function getBy(array $where): ?IEntity;
public function getBy(array $conds): ?IEntity;


/**
* Returns entity by primary value.
* @param mixed $id
* Returns IEntity filtered by conditions, throw if none found.
* @throws NoResultException
*/
public function getById($id): ?IEntity;
public function getByChecked(array $conds): IEntity;


/**
* Returns entity by primary value, null if none found.
* @param mixed $primaryValue
*/
public function getById($primaryValue): ?IEntity;


/**
* Returns entity by primary value, throws if none found.
* @param mixed $primaryValue
* @throws NoResultException
*/
public function getByIdChecked($primaryValue): IEntity;


/**
Expand Down
23 changes: 23 additions & 0 deletions src/Mapper/Dbal/DbalCollection.php
Expand Up @@ -18,6 +18,7 @@
use Nextras\Orm\Mapper\Dbal\Conventions\Conventions;
use Nextras\Orm\Mapper\IRelationshipMapper;
use Nextras\Orm\MemberAccessException;
use Nextras\Orm\NoResultException;


class DbalCollection implements ICollection
Expand Down Expand Up @@ -70,12 +71,34 @@ public function getBy(array $where): ?IEntity
}


/** {@inheritDoc} */
public function getByChecked(array $conds): IEntity
{
$entity = $this->getBy($conds);
if ($entity === null) {
throw new NoResultException();
}
return $entity;
}


public function getById($id): ?IEntity
{
return $this->getBy(['id' => $id]);
}


/** {@inheritdoc} */
public function getByIdChecked($primaryValue): IEntity
{
$entity = $this->getById($primaryValue);
if ($entity === null) {
throw new NoResultException();
}
return $entity;
}


public function findBy(array $where): ICollection
{
$collection = clone $this;
Expand Down
18 changes: 18 additions & 0 deletions src/NoResultException.php
@@ -0,0 +1,18 @@
<?php declare(strict_types = 1);

/**
* This file is part of the Nextras\Orm library.
* @license MIT
* @link https://github.com/nextras/orm
*/

namespace Nextras\Orm;


class NoResultException extends RuntimeException
{
public function __construct()
{
parent::__construct('No result was found, at least one entity was expected.');
}
}

0 comments on commit de520c9

Please sign in to comment.