Пакет data

Обсуждаем разработку фреймворка: дизайн компонентов, API, пакеты
Ответить
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Пакет data

Сообщение 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 должен реализовать набор необходимых для конкретного вида постраничной разбивки интерфейсов. Классы постраничной разбивки не работают с запросом, но все необходимые данные передавать и получать достаточно легко.
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Пакет data

Сообщение samdark »

Довёл до рабочего состояния. Примеры в readme pull request. Как вам?
uEhlO4a
Сообщения: 70
Зарегистрирован: 2017.08.12, 19:19

Re: Пакет data

Сообщение uEhlO4a »

а что там такое "generated from yiisoft/template" в репозитории? это какая-то функция github?


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

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

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

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

 new All(
 new OR(
  new Greater
не понимаю смысла, или это расчитано на тех, кто не понимает что знак > означает больше? хе
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Пакет data

Сообщение 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, не выдумывать как эскейпить >, <, = в строках и иметь в общем меньше неожиданностей.
pumi
Сообщения: 1
Зарегистрирован: 2019.09.09, 09:52

Re: Пакет data

Сообщение pumi »

не выдумывать как эскейпить >, <, = в строках и иметь в общем меньше неожиданностей.
Зато надо выдумывать название методов '<=' , '>='
uEhlO4a
Сообщения: 70
Зарегистрирован: 2017.08.12, 19:19

Re: Пакет data

Сообщение 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 ?
uEhlO4a
Сообщения: 70
Зарегистрирован: 2017.08.12, 19:19

Re: Пакет data

Сообщение 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

как видишь, там я не смогу ничего поломать впринципе. (наверно, хе-хе)
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Пакет data

Сообщение samdark »

uEhlO4a
Сообщения: 70
Зарегистрирован: 2017.08.12, 19:19

Re: Пакет data

Сообщение uEhlO4a »

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

Re: Пакет data

Сообщение 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.

Значения ссылок не понял :( И там и там немного другая ситуация. Источник данных вполне конкретен и всегда один, так что двух различных реализаций там нет и не планируется.
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

Re: Пакет data

Сообщение Loveorigami »

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

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

$models = User::find()->with('status')->all();
myks1992@mail.ru
Сообщения: 147
Зарегистрирован: 2017.11.15, 23:54

Re: Пакет data

Сообщение 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, то будет сильно каша. Разделение по своим классам - это правильно) Это радует)
skynin
Сообщения: 400
Зарегистрирован: 2017.12.12, 10:09

Re: Пакет data

Сообщение 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')
Не желайте странного, и не будет у вас головной боли чтобы достичь этого странного.
Тем более что окажется что оно вам и не нужно было, странное это.
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Пакет data

Сообщение samdark »

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

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