Drukowana wersja tematu

Kliknij tu, aby zobaczyć temat w orginalnym formacie

Forum PHP.pl _ Object-oriented programming _ Zaawansowane filtrowanie Symfony + Doctrine

Napisany przez: Pyton_000 19.04.2021, 18:55:31

A napiszę coś może ciekawszego niż pytania o BOM, czy headers already sent smile.gif

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ć

Kod
new CriteriaCollection([
   new OrderStatusCriteria(...),
   new OrderProductSthCriteria(...),
])->applyOn($qb)


To taka rozkmina.
Ma ktoś jakieś koncepcje albo rozwiązania? Zapraszam doo dyskusji smile.gif

PS. Inną metodą byłoby zbudowanie Grafu gdzie Vertex jest tabelą a Edge jest relacją między Vertexami. Przy robieniu A - C wybirać wszystko i skleić w kolejności w jakiej algo wyszuka ścieżkę (nie musi to być A - B - C ale może być A - D - E - C)

Napisany przez: kayman 20.04.2021, 12:27:10

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ł smile.gif

Napisany przez: Pyton_000 20.04.2021, 14:05:52

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.

Napisany przez: rad11 20.04.2021, 15:56:29

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

Napisany przez: kayman 20.04.2021, 16:05:35

nie wyglądają to moje serwisy do datatables, chociaż w utrzymaniu są całkiem spoko smile.gif

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

Napisany przez: viking 20.04.2021, 17:57:12

Symfony ma coś takiego? https://laravelproject.com/laravel-filtering-query-using-pipelines/

Napisany przez: Pyton_000 20.04.2021, 18:39:13

@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 biggrin.gif 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:

  1. <?php
  2.  
  3. class ContextInterface {
  4. public function apply(QueryBuilder $qb);
  5. public function queryParam(): string;
  6. }
  7.  
  8. class UsernameFilterContext extends ContextInterface {
  9.  
  10. private http://www.php.net/static USERNAME_ALIAS = 'up';
  11.  
  12. public function apply(QueryBuilder $qb, mixed $bind) {
  13. $aliases = $qb->getAliases();
  14. if(!(\http://www.php.net/in_array(self::USERNAME_ALIAS, $aliases))) {
  15. $qb->join(UserProfile::class, self::USERNAME, 'WITH', 'u.id = ' . self::USERNAME_ALIAS .'.user_id');
  16. }
  17. $qb->where(self::USERNAME_ALIAS .'.username = ?1')
  18. ->bindParam(1, $bind);
  19. }
  20.  
  21. public function queryParam(): string {
  22. request 'username';
  23. }
  24. }
  25.  
  26. class ContextCollection {
  27. private http://www.php.net/array $contextCollection = [];
  28.  
  29. public function addContext(ContextCollection $context) {
  30. $this->contextCollection[$context->queryParam()] = $context;
  31. }
  32. }
  33.  
  34. class ContextBuilder {
  35. public function __construct(ServerRequestInterface $request, ConextCollection $contextCollection) {
  36. $this->contextCollection = $contextCollection;
  37. $this->request = $request;
  38. }
  39.  
  40. public function build(QueryBuilder $qb) {
  41. foreach($this->request->getQuery() as $query) {
  42. if(\http://www.php.net/in_array($query, $this->contextCollection)) {
  43. $this->contextCollection[$query]->apply($qb);
  44. }
  45. }
  46. }
  47. }

Napisany przez: kayman 20.04.2021, 19:30:22

IN LIKE >= <= >< etc

trzeba jeszcze przemyśleć pary filtr-znak smile.gif

Napisany przez: LowiczakPL 20.04.2021, 21:37:34

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.

  1. /**
  2.   * @return array[]
  3.   */
  4. public function dataTableVariables(): http://www.php.net/array
  5. {
  6. return [
  7. 'service_date' => [
  8. 'title' => 'Data:godz.',
  9. 'type' => self::TYPE_SHOW_VALUE_BY_GETTER_NAME,
  10. 'isSearchable' => false,
  11. 'isOrdered' => true,
  12. 'isDateTimeField' => true,
  13. 'dateFormat' => 'Y-m-d H:i',
  14. 'valueName' => 'service_date',
  15. 'getterName' => 'serviceDate',
  16. 'className' => 'text-center',
  17. ],
  18. 'container_type' => [
  19. 'title' => 'Kontener',
  20. 'type' => self::TYPE_SHOW_VALUE_BY_MULTIPLE_GETTER_NAME,
  21. 'isSearchable' => true,
  22. 'isOrdered' => true,
  23. 'valueName' => 'container_type',
  24. 'getterNames' => ['conSize->name', 'conType->name'],
  25. 'separator' => ' ',
  26. 'className' => 'text-center',
  27. ], ...

Napisany przez: emillo91 24.05.2021, 07:51:51

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)