Witaj Gościu! ( Zaloguj | Rejestruj )

Forum PHP.pl

 
Reply to this topicStart new topic
> Zaawansowane filtrowanie Symfony + Doctrine
Pyton_000
post 19.04.2021, 18:55:31
Post #1





Grupa: Zarejestrowani
Postów: 8 068
Pomógł: 1414
Dołączył: 26.10.2005

Ostrzeżenie: (0%)
-----


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)
Go to the top of the page
+Quote Post
kayman
post 20.04.2021, 12:27:10
Post #2





Grupa: Zarejestrowani
Postów: 556
Pomógł: 40
Dołączył: 20.07.2012
Skąd: Warszawa

Ostrzeżenie: (0%)
-----


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
Go to the top of the page
+Quote Post
Pyton_000
post 20.04.2021, 14:05:52
Post #3





Grupa: Zarejestrowani
Postów: 8 068
Pomógł: 1414
Dołączył: 26.10.2005

Ostrzeżenie: (0%)
-----


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.
Go to the top of the page
+Quote Post
rad11
post 20.04.2021, 15:56:29
Post #4





Grupa: Zarejestrowani
Postów: 1 270
Pomógł: 184
Dołączył: 7.10.2012
Skąd: Warszawa

Ostrzeżenie: (0%)
-----


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

Ten post edytował rad11 20.04.2021, 16:09:36
Go to the top of the page
+Quote Post
kayman
post 20.04.2021, 16:05:35
Post #5





Grupa: Zarejestrowani
Postów: 556
Pomógł: 40
Dołączył: 20.07.2012
Skąd: Warszawa

Ostrzeżenie: (0%)
-----


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
Go to the top of the page
+Quote Post
viking
post 20.04.2021, 17:57:12
Post #6





Grupa: Zarejestrowani
Postów: 6 365
Pomógł: 1114
Dołączył: 30.08.2006

Ostrzeżenie: (0%)
-----


Symfony ma coś takiego? https://laravelproject.com/laravel-filterin...sing-pipelines/


--------------------
Go to the top of the page
+Quote Post
Pyton_000
post 20.04.2021, 18:39:13
Post #7





Grupa: Zarejestrowani
Postów: 8 068
Pomógł: 1414
Dołączył: 26.10.2005

Ostrzeżenie: (0%)
-----


@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. }


Ten post edytował Pyton_000 20.04.2021, 18:39:35
Go to the top of the page
+Quote Post
kayman
post 20.04.2021, 19:30:22
Post #8





Grupa: Zarejestrowani
Postów: 556
Pomógł: 40
Dołączył: 20.07.2012
Skąd: Warszawa

Ostrzeżenie: (0%)
-----


IN LIKE >= <= >< etc

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

Ten post edytował kayman 20.04.2021, 19:53:52
Go to the top of the page
+Quote Post
LowiczakPL
post 20.04.2021, 21:37:34
Post #9





Grupa: Zarejestrowani
Postów: 531
Pomógł: 55
Dołączył: 3.01.2016
Skąd: Łowicz

Ostrzeżenie: (0%)
-----


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. ], ...


Ten post edytował LowiczakPL 20.04.2021, 21:42:39


--------------------
Szukam zleceń Symfony, Laravel, Back-End, Front-End, PHP, MySQL ...
Go to the top of the page
+Quote Post
emillo91
post 24.05.2021, 07:51:51
Post #10





Grupa: Zarejestrowani
Postów: 129
Pomógł: 13
Dołączył: 29.03.2012

Ostrzeżenie: (0%)
-----


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.

Ten post edytował emillo91 24.05.2021, 08:01:15
Go to the top of the page
+Quote Post

Reply to this topicStart new topic
2 Użytkowników czyta ten temat (2 Gości i 0 Anonimowych użytkowników)
0 Zarejestrowanych:

 



RSS Wersja Lo-Fi Aktualny czas: 27.04.2024 - 15:01