A napiszę coś może ciekawszego niż pytania o BOM, czy headers already sent
Opis tego co chciałbym uzyskać opiszę w skali micro. Musi to mieć przełożenie na skalę macro.
Mamy sobie stronę z raportem.
Raport dotyczy użytkowników (bazowa tabela)
Muszę teraz dodać do tego filtrowanie - prościzna
Jest sobie N filtrów po których mogę filtrować userów. Na cele demonstracyjne przyjmiemy:
- filtrowanie po statusie użytkowania (tabela users)
- filtrowanie po zamówieniach np. użytkownicy mający zamówienia w statusie pedding (tabela orders)
- filtrowanie po produktach z zamówieć np: użytkownicy którzy zamówili zestaw X (tabela order_positions)
- filtrowanie po adresach użytkowników (tabela user_adresses)
Jak widać do każdego niemal filtra potrzebny jest LEFT JOIN z tabelką + odpowiedni WHERE
Muszę zbudować w miarę przyjazne rozwiązanie które pozwoli mi zdefiniować Criteria filtrów które będą miały w sobie JOIN + CONDITIONS.
Problemy które widzę to:
- Kolejność JOIN ma znaczenie (chyba oczowiste)
- Każde Criteria musi zawierać pełny zestaw danych typu JOIN i WHERE
- Podczas aplikowania wielu Criterias JOIN muszą być odfiltrowane i wrzucone w odpowiedniej kolejności (jak określić wagę...)
- i pewnie wiele innych
Generalnie idealne rozwiązanie byłoby zrobić
ja takie zaczynam od zapytania do bazy, takiego gołego bez filtrów, potem dodaję w tym zapytaniu filtry i na tej podstawie kod niemalże "pisze się sam", co prawda takie często kończy się serwisem chociaż jest "tylko" sql'em ale kto by się przejmował
Case jest taki że jest raport i jest 15-20 filtrów które można dowolnie modyfikować, do tego wyszukiwanie. Jest rozwiązanie które jest napisane jak mówisz ale to nie wygląda i utrzynanie tego jest straszne. Dla tego szukam jakiegoś lepszego rozwiązania.
A może utworzyć odpowiednie traity z metodami ktore przyjmą query buildera i będą do niego doklejać odpowiednie filtry oczywiście taka metoda by zwracała voida jakby nie było np podanego filtra. Potem wystarczyło by w odpowiedniej kolejności uruchomić metody tych traitow.
Tak jeszcze mysle ze te metody w traitach mogły by sprawdzac czy istnieją jakieś joiny i jeśli istnieją to nie dodawać kolejnych
nie wyglądają to moje serwisy do datatables, chociaż w utrzymaniu są całkiem spoko
innymi słowy myślę zawsze to będzie potworek i skupiłbym się raczej na prawidłowym działaniu + w miarę łatwej możliwości rozszerzania/zmiany zarówno filtrów jak i zestawu odbieranych danych
Symfony ma coś takiego? https://laravelproject.com/laravel-filtering-query-using-pipelines/
@rad11
Chciałbym uniknąć Trait bo byłoby ich N w raporcie, a aktualnie i tak są tam metody które sprawdzają `if(filterBy_X) then $qb->where(...)` a joiny są wpakowane na sztywno.
@kayman
Tak, to już jest potworek Działa i ma się dobrze, ale sposób w jaki to zostało napisane powoduje spore problemy ze zrozumieniem, dodawaniem itd.
@viking
Nie dokładnie to samo ale Doctrine ma https://www.doctrine-project.org/projects/doctrine-orm/en/2.8/reference/filters.html
Wpadł mi jeszcze do głowy pomysł ad moich Contexts.
Mogę zbudować sobie ContextCollection ze wszystkimi dostępnymi Contextami. Potem odbierając request odpalę sobie CotextBuilder i wyciągnę wszystkie contexty które będą odpowiadały paramsom.
W Context mogę sprawdzić czy QueryBuilder ma już wpisany alias do join i jeśli nie ma takowego to dodać, a jeśli już ma to nie a na końcu dodać tylko condition.
Taki draft mojej myśli:
<?php class ContextInterface { public function apply(QueryBuilder $qb); public function queryParam(): string; } class UsernameFilterContext extends ContextInterface { private http://www.php.net/static USERNAME_ALIAS = 'up'; public function apply(QueryBuilder $qb, mixed $bind) { $aliases = $qb->getAliases(); if(!(\http://www.php.net/in_array(self::USERNAME_ALIAS, $aliases))) { $qb->join(UserProfile::class, self::USERNAME, 'WITH', 'u.id = ' . self::USERNAME_ALIAS .'.user_id'); } $qb->where(self::USERNAME_ALIAS .'.username = ?1') ->bindParam(1, $bind); } public function queryParam(): string { request 'username'; } } class ContextCollection { private http://www.php.net/array $contextCollection = []; public function addContext(ContextCollection $context) { $this->contextCollection[$context->queryParam()] = $context; } } class ContextBuilder { public function __construct(ServerRequestInterface $request, ConextCollection $contextCollection) { $this->contextCollection = $contextCollection; $this->request = $request; } public function build(QueryBuilder $qb) { foreach($this->request->getQuery() as $query) { if(\http://www.php.net/in_array($query, $this->contextCollection)) { $this->contextCollection[$query]->apply($qb); } } } }
IN LIKE >= <= >< etc
trzeba jeszcze przemyśleć pary filtr-znak
Na potrzeby automatycznego generowania datatables z zaawansowanymi filtrami i wyszukiwarką napisałem system.
Wykorzystałem traits jednak wystarczył mi 1 wykorzystujący Query Bulidera dołączany jest w konkretnych repozytoriach, które korzystają z tego filtrowania.
Do tego jest abstrakcja dodawana do serwisu który wykorzystuje filtrowanie.
A w samym serwisie filtry definiowane są w tablicy.
Tabela datatables oraz filtry i wyszukiwarki generowane są automatycznie na podstawie tablicy.
/** * @return array[] */ public function dataTableVariables(): http://www.php.net/array { return [ 'service_date' => [ 'title' => 'Data:godz.', 'type' => self::TYPE_SHOW_VALUE_BY_GETTER_NAME, 'isSearchable' => false, 'isOrdered' => true, 'isDateTimeField' => true, 'dateFormat' => 'Y-m-d H:i', 'valueName' => 'service_date', 'getterName' => 'serviceDate', 'className' => 'text-center', ], 'container_type' => [ 'title' => 'Kontener', 'type' => self::TYPE_SHOW_VALUE_BY_MULTIPLE_GETTER_NAME, 'isSearchable' => true, 'isOrdered' => true, 'valueName' => 'container_type', 'getterNames' => ['conSize->name', 'conType->name'], 'separator' => ' ', 'className' => 'text-center', ], ...
Wydaje mi się, że to co napisał LowiczakPL ma sens ze względu na to, że od razu widać czego dotyczy filtrowanie. Ja pokusiłbym się o modyfikację tego rozwiązania, polegającą na utworzeniu interface-u definiującego zachowanie filtra. Wtedy miałbyś możliwość wstawienia nazwy klasy i wywoływania z niej metody buildQuery(), która mogłaby zwracać odpowiednie zapytanie filtrujące a nawet obiekt QueryBuilder, z którym możesz dalej robić co chcesz. Widziałem już coś na kształt rozwiązania LowiczakPL to w tym przypadku może dojść do sytuacji, plik zbiorczy znacznie się rozrośnie, więc tutaj popatrzyłbym na pojedynczy filtr jako obiekt, który byłby dołączany do kolejki filtrowania.
Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)