Страница 1 из 1

Пакет data

Добавлено: 2019.09.03, 17:30
samdark
Работаю над новым аналого data-провайдеров и гридов для Yii 3. В Yii 2 с ними было почти всё нормально, но можно лучше.

Что не так в Yii 2

1. Провайдеры и связанные классы делали слишком много: работали с запросом, геренили URL, получали данные, валидировали их и так далее.
2. Много всего было завязано на Model и active record.

Текущий код

https://github.com/yiisoft/data/pull/3


Идеи

Главная идея в том, чтобы представить данные в простой табличной форме. То есть все строки одного вида со сходными именованными полями.

Для того, чтобы поддержать максимальное количество источников данных, data reader не читает данные до вызова read(). Все остальные методы меняют только критерий для чтения. Это позволяет эффективно работать с SQL и другими источниками, поддерживающими свой язык запросов. Реализовать своё хранилище такой подход также не мешает.

Интерфейсы

Интерфейсы делятся на три группы:

1. DataProcessorInterface для унификации групповой обработки.
2. DataWriterInterface для записи данных.
3. DataReaderInteface и связанные интерфейсы для поддержки дополнительных возможностей:

- CountableDataInterface - для получения общего числа данных в провайдере.
- FilterableDataInterface - для фильтрации данных на основе критериев.
- SortableDataInterface - для сортировки по одному или нескольким полям.
- OffsetableDataInterface - для пропуска первых N строк данных.

Сортировка данных

Сортировка сделана похоже на Yii 2, но не работает с запросом:

Код: Выделить всё

$sort = (new Sort([ // <--- задаём конфиг
    'b' => [
        'asc' => ['bee' => SORT_ASC],
        'desc' => ['bee' => SORT_DESC],
        'default' => 'asc',
        'label' => 'B',
    ]
]))
    ->withOrder([ // <--- применяем сортировку
        'a' => 'desc',
        'b' => 'asc',
    ]);
Дополнительный метод позволяет задавать сортировку из строки:

Код: Выделить всё

$sort->withOrderString('-a, b'); // a DESC, b ASC
Объект класса Sort передаётся в data reader до чтения данных и влияет на их чтение.

Фильтрация

Здесь я немного застопорился. Сейчас есть набор критериев, которые достаточно общие для всех data reader (Reader\Criterion):

Код: Выделить всё

$criteria = new AndAll(
    new Compare('test', 42),
    new Compare('test2', 34),
    new OrAny(
        new LessThan('temperature', 10),
        new GreaterThan('temperature', 30)
    )
);
Так как фильтр будет чаще всего приходит из запроса в виде JSON, фильтр существует отдельно и принимает массив:

Код: Выделить всё

$filter = new Filter($criteria->toArray());
Фильтр передаётся в data reader и reader обязан вернуть фильтрованные данные.

С кодом есть проблемы:

1. Критерии сейчас практически не расширяются.
2. Реализовать применение критериев в data reader не просто.

По этой части хочется услышать идей и, возможно, получить помощь.

Постраничная разбивка

В пакете есть два вида постраничной разбивки. Классический offset и keyset. Data reader должен реализовать набор необходимых для конкретного вида постраничной разбивки интерфейсов. Классы постраничной разбивки не работают с запросом, но все необходимые данные передавать и получать достаточно легко.

Re: Пакет data

Добавлено: 2019.09.07, 19:28
samdark
Довёл до рабочего состояния. Примеры в readme pull request. Как вам?

Re: Пакет data

Добавлено: 2019.09.08, 20:20
uEhlO4a
а что там такое "generated from yiisoft/template" в репозитории? это какая-то функция github?


по сути, я не понял этот код

Код: Выделить всё

$filter = new All(
    new GreaterThan('id', 3),   <----- зачем это?
    new Like('name', 'agent') <----- и это.
);
а как сделать ИЛИ , так?

Код: Выделить всё

 new All(
 new OR(
  new Greater
не понимаю смысла, или это расчитано на тех, кто не понимает что знак > означает больше? хе

Re: Пакет data

Добавлено: 2019.09.09, 00:33
samdark
а что там такое "generated from yiisoft/template" в репозитории? это какая-то функция github?
Да. Чтобы быстро стартовать пакет можно использовать другой репозиторий как начальный шаблон.
new GreaterThan('id', 3), <----- зачем это?
Так описывается условие id > 3.
new Like('name', 'agent') <----- и это.
Так описывается условие "Строка в name содержит agent".
а как сделать ИЛИ , так?
Почти. Вот так:

Код: Выделить всё

$filter = new Any(
    new GreaterThan('id', 3),
    new Like('name', 'agent')
);
не понимаю смысла, или это расчитано на тех, кто не понимает что знак > означает больше? хе
Смысл только чтобы не писать парсер чего-то вроде SQL, не выдумывать как эскейпить >, <, = в строках и иметь в общем меньше неожиданностей.

Re: Пакет data

Добавлено: 2019.09.09, 09:56
pumi
не выдумывать как эскейпить >, <, = в строках и иметь в общем меньше неожиданностей.
Зато надо выдумывать название методов '<=' , '>='

Re: Пакет data

Добавлено: 2019.09.09, 17:50
uEhlO4a
спасибо за ответ. я ни на что не предендую, просто визуально глянул код.

как помню, раньше можно было записать

Код: Выделить всё

$filter = new All(
    new GreaterThan('id', 3),
    new OrAny(
        new LessThan('temperature', 10),
        new GreaterThan('temperature', 30)
    ),
    new Like('name', 'agent')
);
как

Код: Выделить всё

$filter = $aaa->where([
  ['and', 'id', '>', 3],
  ['and', ['or', ['temperature', '<', 10], ['temperature', '>', 30]] ],
  ['and', 'name', like', '%agent%'],
]


или там фильтры будут не поддерживаться какие-то?
1. Как будет вести себя програмист, если есть данные которым нельзя делать " new Like(..)" ? Хардкодить " private function matchFilter(array $item, array $filter): bool" ?
2. Что если код "case Not::getOperator()" имеет такой же ключ в "MyMegaFilter::getOperator()" согласно FilterInterface ?

Re: Пакет data

Добавлено: 2019.09.09, 18:20
uEhlO4a
п.с. меня как разработчика все эти "new" очень напрягают, если даже писать, чтобы защитить от опечаток, то можно сделать

Код: Выделить всё

$filter = $aaa->And(function($where) {
    $where->And( 'id', '>', 3)
     ->And(function($where2){
       $where2->Or('temperature', '<', 10)->Or('temperature', '>', 30)
     })
     ->And('name', like', '%agent%');
потому что нигде " new GreaterThan('id', 3)" не будет использован в коде, а зависимость мы уже себе обеспечили, при чем на какую-то ерунду.

в общем, я веду к тому, что здесь фильтром называется именование операции сравнения, а сама логика фильтра сделана в
"final class ArrayDataReader { private function matchFilter(array $item, array $filter): bool "

я имею в виду это https://github.com/symfony/finder/blob/ ... r.php#L680 или это https://github.com/thephpleague/csv/blo ... r.php#L284

как видишь, там я не смогу ничего поломать впринципе. (наверно, хе-хе)

Re: Пакет data

Добавлено: 2019.09.09, 18:39
samdark

Re: Пакет data

Добавлено: 2019.09.09, 18:46
uEhlO4a
samdark писал(а): 2019.09.09, 18:39 https://github.com/yiisoft/data/issues/15
эх.. я наверно плохо обьясняюсь. как это называется, "brain storming" , когда я озвучиваю всё что взбредет в голову в попытке найти что-то полезное в этом потоке бреда. не факт что это нужно.. ладно, молчу, может кто еще какой коментарий добавит :oops:

Re: Пакет data

Добавлено: 2019.09.09, 19:23
samdark
1. Как будет вести себя програмист, если есть данные которым нельзя делать " new Like(..)" ? Хардкодить " private function matchFilter(array $item, array $filter): bool" ?
Не строить запрос с like.
2. Что если код "case Not::getOperator()" имеет такой же ключ в "MyMegaFilter::getOperator()" согласно FilterInterface ?
MyMegaFilter не будет работать.

Вот ваш вариант, если его записать полностью:

Код: Выделить всё

$filter = (new Filter())->and(function($where) {
    $where
        ->compare( 'id', '>', 3)
        ->or(function($where) {
             $where
             ->compare('temperature', '<', 10)
             ->compare('temperature', '>', 30);
        })
        ->compare('name', like', '%agent%');
});
Вот вариант как сейчас:

Код: Выделить всё

$filter = new All(
    new GreaterThan('id', 3),
    new OrAny(
        new LessThan('temperature', 10),
        new GreaterThan('temperature', 30)
    ),
    new Like('name', 'agent')
);
Не уверен, что вариант с замыканиями читается лучше...
в общем, я веду к тому, что здесь фильтром называется именование операции сравнения, а сама логика фильтра сделана в
"final class ArrayDataReader { private function matchFilter(array $item, array $filter): bool "
Так и есть. Причём логика будет разной в зависимости от того, какой это DataReader. Для Array будет фильтроваться. Для SQL составляться WHERE.

Значения ссылок не понял :( И там и там немного другая ситуация. Источник данных вполне конкретен и всегда один, так что двух различных реализаций там нет и не планируется.

Re: Пакет data

Добавлено: 2019.09.09, 23:08
Loveorigami
Источник данных вполне конкретен и всегда один, так что двух различных реализаций там нет и не планируется.
У Павла Климова было видео.
Там в одном моменте он соединял данные пользователей из sql и статусы из файлового хранилища. Т.е. источников данных получается 2.
https://youtu.be/U27PwaYS-nQ?t=1037

Код: Выделить всё

$models = User::find()->with('status')->all();

Re: Пакет data

Добавлено: 2019.09.11, 00:17
myks1992@mail.ru
Мне кажется, что лучше использовать как было ранее, только немного модифицировав, как это используется в других местах:

Код: Выделить всё

$qb = $this->connection->createQueryBuilder()
///....
if ($filter->status) {
            $qb->andWhere('status = :status');
            $qb->setParameter(':status', $filter->status);
        }
        ///....
        

Код: Выделить всё


$qb = $this->connection->createQueryBuilder()
///....
 if ($filter->name) {
            $qb->andWhere($qb->expr()->like('LOWER(CONCAT(m.name_first, \' \', m.name_last))', ':name'));
            $qb->setParameter(':name', '%' . mb_strtolower($filter->name) . '%');
        }
        ///....
        
Если делать всё на new, то будет сильно каша. Разделение по своим классам - это правильно) Это радует)

Re: Пакет data

Добавлено: 2019.09.11, 09:47
skynin
С разными построителями запросов приходилось работать.

Пожелание к ним обычно такие:
1. Параметры запроса устанавливаются, накапливаются в разных местах. и в этих местах не хочется знать о специфических классах билдера
2. как и даже и о самой схеме данных не хочется знать. а хочется спрашивать систему в терминах домена

На примере живого кода:

Я хочу отобрать спортсменов участвовавших в таком-то турнире (сезоне)
ну и где-то добавляю к запросу:

Код: Выделить всё

...->filterTourney($someTourney)
в самом ActiveQuery::filterTourney вот такая "красота"

Код: Выделить всё

 $result = $this->alias('s')
            ->innerJoin(SportMemberAR::tableName()." tm","tm.sman_id=s.id")
            ->innerJoin(SportTeamAR::tableName(). " t", 't.id=tm.team_id')
            ->innerJoin(SportMatchAR::tableName(). " m", 't.id=m.team_owner OR t.id=m.team_guest')
            ->innerJoin(GameTouMatchAR::tableName()." gtm", 'gtm.match_id=m.id')
            ->andWhere([
                'gtm.tou_id' => $touId, // id турнира
            ]);
Как пишущего бизнес логику, меня не волнует эффекивность запроса.
А если надо будет - я перепишу filterTourney, добавлю индексных таблиц и код их обновления, перекрою в конце концов all() и one() и т.п.

Но везде где было filterTourney($someTourney) ничего переписывать не придется.

Проблема ж не в том чтобы сформировать запрос, любым способом. (хотя бывает и головняк, понятный SQL ты поди еще билдеру объясни...)
Проблема чтобы его формирование было написано таким образом, что не нужно было бы потом переписывать, если даже немного поменяется сама схема персистентного хранилища

Код: Выделить всё

$filter = new All(
    new GreaterThan('id', 3),
    new OrAny(
        new LessThan('temperature', 10),
        new GreaterThan('temperature', 30)
    ),
    new Like('name', 'agent')
);
Выглядит приятно.

Вопросы к такому DSL подходу, как будет выглядеть работа прикладного программиста, если
1. Каждая строчка добавляется в частях кода которые друге о друге ничего не знают
2. Как добавить условие бизнес логики
3. Сколько надо будет кода если понадобятся критерии повыше уровнем абстракции чем нечто просто по полю

Попробую трансформировать, что хочется с таким подходом, раз уж "DSL"

Код: Выделить всё

$filter = new All(
    new NotReservedID('gateway'), // не интересуют "службы датчиков", для них зарезверированы id с 1 по 1000
    new OrAny(
		new Temperature('hot')
    ),
    new LikeAs('europe')
);
Успех подхода будет зависеть от того, как сложно будет создавать вот такие запросы, сколько кода нужно будет написать для этого

Сейчас, для ActiveQuery немного, только в самом filterTemperature($criteria = 'hot')

Re: Пакет data

Добавлено: 2019.09.11, 14:40
samdark
1. Каждая строчка добавляется в частях кода которые друге о друге ничего не знают
Это вряд-ли.
2. Как добавить условие бизнес логики
3. Сколько надо будет кода если понадобятся критерии повыше уровнем абстракции чем нечто просто по полю
Никак. Пакет не предназначен для этого. Он для работы с табличными данными и, в частности, для сортировки и фильтрации их по полям таблицы.

ActiveQuery — это другое. Это на уровень ниже.