Skip to content

Commit

Permalink
Fix #89: Add EntityWriter
Browse files Browse the repository at this point in the history
- added \Yiisoft\Yii\Cycle\Data\Writer\EntityWriter
- \Yiisoft\Yii\Cycle\DataReader\SelectDataReader moved to \Yiisoft\Yii\Cycle\Data\Reader\EntityReader
  • Loading branch information
roxblnfk committed Jan 13, 2021
1 parent 046edcf commit 7f8fa32
Show file tree
Hide file tree
Showing 20 changed files with 114 additions and 87 deletions.
2 changes: 1 addition & 1 deletion docs/en/README.md
Expand Up @@ -9,5 +9,5 @@ This package provides a convenient way to integrate it with Yii framework.

- [Installation and configuring](installation.md)
- [Console commands](console-commands.md)
- [SelectDataReader](select-data-reader.md)
- [EntityReader](entity-reader.md)
- [Reading DB schema](reading-schema.md)
55 changes: 27 additions & 28 deletions docs/en/select-data-reader.md → docs/en/entity-reader.md
@@ -1,53 +1,53 @@
# SelectDataReader
# EntityReader

`SelectDataReader` allows to securely pass select-queries from repository to user runtime.
`EntityReader` allows to securely pass select-queries from repository to user runtime.
By select-query we assume an instance of `\Cycle\ORM\Select` or `\Spiral\Database\Query\SelectQuery`.

You need to know the following about `SelectDataReader`:
You need to know the following about `EntityReader`:

* `SelectDataReader` implements `IteratorAggregate`.
It allows using `SelectDataReader` instance in `foreach`.
* Using `SelectDataReader` you can adjust select-query:
* `EntityReader` implements `IteratorAggregate`.
It allows using `EntityReader` instance in `foreach`.
* Using `EntityReader` you can adjust select-query:
- Add `Limit` and `Offset` manually or using `OffsetPaginator`
- Specify sorting. Note that `SelectDataReader` sorting does
- Specify sorting. Note that `EntityReader` sorting does
not replace initial query sorting but adds sorting on top of it.
Each next `withSort()` call is replacing `SelectDataReader` sorting options.
- Apply filter. Filtration conditions in `SelectDataReader` also, do not replace filtration conditions
Each next `withSort()` call is replacing `EntityReader` 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 in `SeletecDataReader`
you can only refine the selection, but NOT expand.
* `SelectDataReader` queries database only when you actually read the data.
* In case you're using `read()`, `readOne()` or `count()`, data will be cached by `SelectDataReader`.
* `EntityReader` queries database only when you actually read the data.
* In case you're using `read()`, `readOne()` or `count()`, data will be cached by `EntityReader`.
* 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.

## Examples

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 `SelectDataReader`:
it would not return ready articles collection or select query. Instead, it will return `EntityReader`:

```php
use Yiisoft\Data\Reader\DataReaderInterface;
use \Yiisoft\Yii\Cycle\DataReader\SelectDataReader;
use \Yiisoft\Yii\Cycle\Data\Reader\EntityReader;

class ArticleRepository extends \Cycle\ORM\Select\Repository
{
/**
* @return SelectDataReader
* @return EntityReader
*/
public function findPublic(): DataReaderInterface
{
return new SelectDataReader($this->select()->where(['public' => true]));
return new EntityReader($this->select()->where(['public' => true]));
}
}
```

Now we can use `SelectDataReader` for pagination like the following:
Now we can use `EntityReader` for pagination like the following:

```php
/**
* @var ArticleRepository $repository
* @var \Yiisoft\Yii\Cycle\DataReader\SelectDataReader $articles
* @var \Yiisoft\Yii\Cycle\Data\Reader\EntityReader $articles
*/
$articles = $repository->findPublic();

Expand All @@ -60,7 +60,7 @@ $paginator = new \Yiisoft\Data\Paginator\OffsetPaginator($articles);
$paginator->withPageSize(10)->withCurrentPage(3);


// Getting articles from SelectDataReader with caching:
// Getting articles from EntityReader with caching:
foreach ($pageReader->read() as $article) {
// ...
}
Expand All @@ -79,15 +79,15 @@ Now we'll query for 20 latest published articles, then for 20 first articles.

```php
/**
* @var \Yiisoft\Yii\Cycle\DataReader\SelectDataReader $articles
* @var \Yiisoft\Yii\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 = (new \Yiisoft\Data\Reader\Sort(['published_at']))->withOrder(['published_at' => 'desc']);
// Note that SelectDataReader would not check Sort field correctness.
// 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
Expand Down Expand Up @@ -120,24 +120,24 @@ foreach ($lastPublicReader->read() as $article) {
}
```

Sorting through `SelectDataReader` does not replace sorting in initial query but adds more to it.
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:

```php
use Yiisoft\Data\Reader\DataReaderInterface;
use Yiisoft\Data\Reader\Sort;
use Yiisoft\Yii\Cycle\DataReader\SelectDataReader;
use Yiisoft\Yii\Cycle\Data\Reader\EntityReader;

class ArticleRepository extends \Cycle\ORM\Select\Repository
{
/**
* @return SelectDataReader
* @return EntityReader
*/
public function findPublic(): DataReaderInterface
{
$sort = (new Sort(['published_at']))->withOrder(['published_at' => 'desc']);
return (new SelectDataReader($this->select()->where(['public' => true])))->withSort($sort);
return (new EntityReader($this->select()->where(['public' => true])))->withSort($sort);
}
}

Expand All @@ -146,25 +146,24 @@ class ArticleRepository extends \Cycle\ORM\Select\Repository
function index(ArticleRepository $repository)
{
$articlesReader = $repository
// Getting SelectDataReader
// Getting EntityReader
->findPublic()
// Applying new sorting
->withSort((new Sort(['published_at']))->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.


```php
use Yiisoft\Data\Reader\DataReaderInterface;
use Yiisoft\Data\Reader\Filter\Equals;
use Yiisoft\Yii\Cycle\DataReader\SelectDataReader;
use Yiisoft\Yii\Cycle\Data\Reader\EntityReader;

class ArticleRepository extends \Cycle\ORM\Select\Repository
{
public function findUserArticles(int $userId): DataReaderInterface
{
return (new SelectDataReader($this->select()->where('user_id', $userId)))
return (new EntityReader($this->select()->where('user_id', $userId)))
//Adding filter by default - only public articles.

->withFilter(new Equals('public', '1'));
Expand Down
2 changes: 1 addition & 1 deletion docs/ru/README.md
Expand Up @@ -9,5 +9,5 @@ Cycle — это DataMapper ORM, спроектированный для без

- [Установка и настройка](installation.md)
- [Консольные команды](console-commands.md)
- [Класс SelectDataReader](select-data-reader.md)
- [Класс EntityReader](entity-reader.md)
- [Чтение схемы данных](reading-schema.md)
54 changes: 28 additions & 26 deletions docs/ru/select-data-reader.md → docs/ru/entity-reader.md
@@ -1,27 +1,27 @@
# SelectDataReader
# EntityReader

`SelectDataReader` является полезным инструментом для безопасной передачи select-запросов
`EntityReader` является полезным инструментом для безопасной передачи select-запросов
из репозитория в пользовательскую среду выполнения.
Под select-запросом подразумевается экземпляр одного из
классов: `\Cycle\ORM\Select` или `\Spiral\Database\Query\SelectQuery`.

Что нужно знать о `SelectDataReader`:
Что нужно знать о `EntityReader`:

* Класс `SelectDataReader` реализует интерфейс `IteratorAggregate`.
Это позволяет использовать объект `SelectDataReader` в цикле `foreach`.
* С помощью `SelectDataReader` вы можете корректировать переданный select-запрос:
* Класс `EntityReader` реализует интерфейс `IteratorAggregate`.
Это позволяет использовать объект `EntityReader` в цикле `foreach`.
* С помощью `EntityReader` вы можете корректировать переданный select-запрос:
- Выставлять `Limit` и `Offset` вручную или с помощью `OffsetPaginator`.
- Задавать сортировку. Но учтите, что сортировка `SelectDataReader`
- Задавать сортировку. Но учтите, что сортировка `EntityReader`
не заменяет сортировку в исходном запросе, а лишь дополняет её.
Однако каждый следующий вызов метода `withSort()` будет заменять настройки
сортировки объекта `SelectDataReader`.
- Применять фильтр. Условия фильтрации `SelectDataReader` также не заменяют настройки
сортировки объекта `EntityReader`.
- Применять фильтр. Условия фильтрации `EntityReader` также не заменяют настройки
фильтрации в исходном запросе, а дополняют её. Таким образом, фильтрацией
в объекте `SelectDataReader` вы можете только уточнить выборку, но не расширить.
* `SelectDataReader` не вытягивает данные из БД сразу.
в объекте `EntityReader` вы можете только уточнить выборку, но не расширить.
* `EntityReader` не вытягивает данные из БД сразу.
Обращение к БД происходит только тогда, когда эти данные запрашиваются.
* Если вы будете использовать методы `read()` и `readOne()` для чтения данных,
то `SelectDataReader` сохранит их в кеше. Кешируется также и результат вызова `count()`.
то `EntityReader` сохранит их в кеше. Кешируется также и результат вызова `count()`.
* Метод `count()` возвращает кол-во всех элементов выборки без учёта ограничений `Limit` и `Offset`.
* Если вы не хотите, чтобы данные были записаны в кеш, то используйте метод `getIterator()`.
Однако если кеш уже заполнен, то `getIterator()` вернёт содержимое кеша.
Expand All @@ -31,25 +31,26 @@
Напишем свой репозиторий для работы с таблицей статей, в котором будет метод для получения
списка публичных статей `findPublic()`. Но метод `findPublic()` не будет
возвращать сразу готовую коллекцию статей или select-запрос.\
Вместо этого будет возвращаться `SelectDataReader` с select-запросом внутри:
Вместо этого будет возвращаться `EntityReader` с select-запросом внутри:

```php
use Yiisoft\Data\Reader\DataReaderInterface;
use \Yiisoft\Yii\Cycle\DataReader\SelectDataReader;
use \Yiisoft\Yii\Cycle\Data\Reader\EntityReader;

class ArticleRepository extends \Cycle\ORM\Select\Repository
{
public function findPublic(): DataReaderInterface
{
return new SelectDataReader($this->select()->where(['public' => true]));
return new EntityReader($this->select()->where(['public' => true]));
}
}
```
Рассмотрим примеры, как мы можем использовать SelectDataReader в постраничной разбивке.
Рассмотрим примеры, как мы можем использовать EntityReader в постраничной разбивке.

```php
/**
* @var ArticleRepository $repository
* @var \Yiisoft\Yii\Cycle\DataReader\SelectDataReader $articles
* @var \Yiisoft\Yii\Cycle\Data\Reader\EntityReader $articles
*/
$articles = $repository->findPublic();

Expand All @@ -62,7 +63,7 @@ $paginator = new \Yiisoft\Data\Paginator\OffsetPaginator($articles);
$paginator->withPageSize(10)->withCurrentPage(3);


// Обход статей из объекта SelectDataReader:
// Обход статей из объекта EntityReader:
// С сохранением в кеше:
foreach ($pageReader->read() as $article) {
// ...
Expand All @@ -82,15 +83,15 @@ foreach ($paginator->read() as $article) {

```php
/**
* @var \Yiisoft\Yii\Cycle\DataReader\SelectDataReader $articles
* @var \Yiisoft\Yii\Cycle\Data\Reader\EntityReader $articles
*/

// Порядок указания параметров не важен, так что начнём с установки лимита
$lastPublicReader = $articles->withLimit(20);

// Правила сортировки описываются в объекте класса Sort:
$sort = (new \Yiisoft\Data\Reader\Sort(['published_at']))->withOrder(['published_at' => 'desc']);
// Учтите, что SelectDataReader НЕ БУДЕТ проверять правильность указанных в Sort полей!
// Учтите, что EntityReader НЕ БУДЕТ проверять правильность указанных в Sort полей!
// Указание несуществующих в таблице полей приведёт к ошибке в коде Cycle

// Применяем правила сортировки и не забываем об иммутабельности
Expand Down Expand Up @@ -123,22 +124,23 @@ foreach ($lastPublicReader->read() as $article) {
}
```

Одна из особенностей сортировки запроса через `SelectDataReader` заключается в том, что
Одна из особенностей сортировки запроса через `EntityReader` заключается в том, что
она не заменяет сортировку в исходном select-запросе, а лишь дополняет её. \
Бывает так, что нужно задать сортировку по умолчанию в методе репозитория, но при этом
иметь возможность изменить её в коде контроллера. Добиться этого можно следующим образом:

```php
use Yiisoft\Data\Reader\DataReaderInterface;
use Yiisoft\Data\Reader\Sort;
use Yiisoft\Yii\Cycle\DataReader\SelectDataReader;
use Yiisoft\Yii\Cycle\Data\Reader\EntityReader;

class ArticleRepository extends \Cycle\ORM\Select\Repository
{
public function findPublic(): DataReaderInterface
{
$sort = (new Sort(['published_at']))->withOrder(['published_at' => 'desc']);
// Параметры сортировки присваиваются объекту DataReader, а не \Cycle\ORM\Select
return (new SelectDataReader($this->select()->where(['public' => true])))->withSort($sort);
return (new EntityReader($this->select()->where(['public' => true])))->withSort($sort);
}
}

Expand All @@ -147,7 +149,7 @@ class ArticleRepository extends \Cycle\ORM\Select\Repository
function index(ArticleRepository $repository)
{
$articlesReader = $repository
// Получаем объект SelectDataReader
// Получаем объект EntityReader
->findPublic()
// Применяем новое правило сортировки
->withSort((new Sort(['published_at']))->withOrder(['published_at' => 'asc']));
Expand All @@ -159,13 +161,13 @@ function index(ArticleRepository $repository)
```php
use Yiisoft\Data\Reader\DataReaderInterface;
use Yiisoft\Data\Reader\Filter\Equals;
use Yiisoft\Yii\Cycle\DataReader\SelectDataReader;
use Yiisoft\Yii\Cycle\Data\Reader\EntityReader;

class ArticleRepository extends \Cycle\ORM\Select\Repository
{
public function findUserArticles(int $userId): DataReaderInterface
{
return (new SelectDataReader($this->select()->where('user_id', $userId)))
return (new EntityReader($this->select()->where('user_id', $userId)))
// Добавим фильтр по умолчанию - только public статьи
->withFilter(new Equals('public', '1'));
// Условие `public` = "1" не заменит `user_id` = "$userId"
Expand Down
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Yiisoft\Yii\Cycle\DataReader\Cache;
namespace Yiisoft\Yii\Cycle\Data\Reader\Cache;

final class CachedCollection
{
Expand Down
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Yiisoft\Yii\Cycle\DataReader\Cache;
namespace Yiisoft\Yii\Cycle\Data\Reader\Cache;

use Countable;

Expand All @@ -17,7 +17,7 @@ public function __construct(Countable $collection)
}

/**
* @psalm-internal Yiisoft\Yii\Cycle\DataReader
* @psalm-internal Yiisoft\Yii\Cycle\Data\Reader
*/
public function getCount(): int
{
Expand Down

0 comments on commit 7f8fa32

Please sign in to comment.