EntityReader
allows to securely pass select-queries from repository to user runtime.
By select-query we assume an instance of \Cycle\ORM\Select
or \Cycle\Database\Query\SelectQuery
.
You need to know the following about EntityReader
:
EntityReader
implementsIteratorAggregate
. It allows usingEntityReader
instance inforeach
.- Using
EntityReader
you can adjust select-query:- Add
Limit
andOffset
manually or usingOffsetPaginator
- Specify sorting. Note that
EntityReader
sorting does not replace initial query sorting but adds sorting on top of it. Each nextwithSort()
call is replacingEntityReader
sorting options. - Apply filter. Filtration conditions in
EntityReader
also, do not replace filtration conditions in initial query, but adds conditions on top of it. Therefore, by using filtration inSeletecDataReader
you can only refine the selection, but NOT expand.
- Add
EntityReader
queries database only when you actually read the data.- In case you're using
read()
,readOne()
orcount()
, data will be cached byEntityReader
. - The
count()
method returns the number of elements without taking limit and offset into account. - In case you want to avoid caching, use
getIterator()
. Note that if cache is already there,getIterator()
uses it.
Let's implement a repository to work with articles table. We want a method to get public articles findPublic()
but
it would not return ready articles collection or select query. Instead, it will return EntityReader
:
use Yiisoft\Data\Cycle\Data\Reader\EntityReader;
use Yiisoft\Data\Reader\DataReaderInterface;
class ArticleRepository extends \Cycle\ORM\Select\Repository
{
/**
* @return EntityReader
*/
public function findPublic(): DataReaderInterface
{
return new EntityReader($this->select()->where(['public' => true]));
}
}
Now we can use EntityReader
for pagination like the following:
/**
* @var ArticleRepository $repository
* @var \Yiisoft\Data\Cycle\Reader\EntityReade $articles
*/
$articles = $repository->findPublic();
// Offset and limit could be specified explicitly.
// Third page:
$pageReader = $articles->withLimit(10)->withOffset(20);
// Paginator could be used instead.
$paginator = new \Yiisoft\Data\Paginator\OffsetPaginator($articles);
$paginator->withPageSize(10)->withCurrentPage(3);
// Getting articles from EntityReader with caching:
foreach ($pageReader->read() as $article) {
// ...
}
// Same without caching:
foreach ($pageReader as $article) {
// ...
}
// Getting articles from paginator:
foreach ($paginator->read() as $article) {
// ...
}
Now we'll query for 20 latest published articles, then for 20 first articles.
/**
* @var \Yiisoft\Data\Cycle\Data\Reader\EntityReader $articles
*/
// The order of specifying parameters is not important so let's start with limit
$lastPublicReader = $articles->withLimit(20);
// Ordering is specified with Sort object:
$sort = \Yiisoft\Data\Reader\Sort::any()->withOrder(['published_at' => 'desc']);
// Note that EntityReader would not check Sort field correctness.
// Specifying non-existing fields would result in an error in Cycle code
// Don't forget about immutability when applying sorting rules
$lastPublicReader = $lastPublicReader->withSort($sort);
printf(
"Last %d published articles of %d total:",
count($lastPublicReader->read()),
$lastPublicReader->count()
);
foreach ($lastPublicReader->read() as $article) {
// ...
}
// Now let's obtain 20 first published articles
$sort = $lastPublicReader->getSort()->withOrder(['published_at' => 'asc']);
// Because of immutability Sort object won't be modified and current
// sorting for $lastPublicReader will stay the same.
// To apply new sorting rules call withSort() once again:
$lastPublicReader = $lastPublicReader->withSort($sort);
printf(
"First %d published articles of %d total:",
count($lastPublicReader->read()),
$lastPublicReader->count()
);
foreach ($lastPublicReader->read() as $article) {
// ...
}
Sorting through EntityReader
does not replace sorting in initial query but adds more to it.
If you need to set default sorting in a repository method but want to be able to change it in a controller, you
can do it like the following:
use Yiisoft\Data\Cycle\Data\Reader\EntityReader;
use Yiisoft\Data\Reader\DataReaderInterface;
use Yiisoft\Data\Reader\Sort;
class ArticleRepository extends \Cycle\ORM\Select\Repository
{
/**
* @return EntityReader
*/
public function findPublic(): DataReaderInterface
{
$sort = Sort::any()->withOrder(['published_at' => 'desc']);
return (new EntityReader($this->select()->where(['public' => true])))->withSort($sort);
}
}
// class SiteController ... {
function index(ArticleRepository $repository)
{
$articlesReader = $repository
// Getting EntityReader
->findPublic()
// Applying new sorting
->withSort(Sort::any()->withOrder(['published_at' => 'asc']));
}
You may refine query conditions with filters. This filtering conditions are adding to original select query conditions, but NOT replace them.
use Yiisoft\Data\Cycle\Data\Reader\EntityReader;
use Yiisoft\Data\Reader\DataReaderInterface;
use Yiisoft\Data\Reader\Filter\Equals;
class ArticleRepository extends \Cycle\ORM\Select\Repository
{
public function findUserArticles(int $userId): DataReaderInterface
{
return (new EntityReader($this->select()->where('user_id', $userId)))
//Adding filter by default - only public articles.
->withFilter(new Equals('public', '1'));
// condition `public` = "1" doesnt replace `user_id` = "$userId"
}
}
Use filters from yiisoft/data package, or any others, having previously written the appropriate handlers(processors) for them.