Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Zaawansowane filtrowanie Symfony + Doctrine
Forum PHP.pl > Forum > PHP > Object-oriented programming
Pyton_000
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)
kayman
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
Pyton_000
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.
rad11
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
kayman
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
viking
Symfony ma coś takiego? https://laravelproject.com/laravel-filterin...sing-pipelines/
Pyton_000
@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/d...ce/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 static USERNAME_ALIAS = 'up';
  11.  
  12. public function apply(QueryBuilder $qb, mixed $bind) {
  13. $aliases = $qb->getAliases();
  14. if(!(\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 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(\in_array($query, $this->contextCollection)) {
  43. $this->contextCollection[$query]->apply($qb);
  44. }
  45. }
  46. }
  47. }
kayman
IN LIKE >= <= >< etc

trzeba jeszcze przemyśleć pary filtr-znak smile.gif
LowiczakPL
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(): 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. ], ...
emillo91
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.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2024 Invision Power Services, Inc.