2015-04-23 14 views
6

Zajmuję się tworzeniem aplikacji internetowych z osią czasu, podobnie jak na osi czasu Facebooka. Sama oś czasu jest całkowicie ogólna i możliwa do podłączenia. To tylko ogólny zbiór przedmiotów. Wszystko z odpowiednim interfejsem (Dateable) można dodać do kolekcji i wyświetlić na osi czasu.Jak zaprojektować filtry dla ogólnej, wtykowej kolekcji?

Inne komponenty (pakiety Symfony) definiują modele implementujące interfejs Dateable i konfigurują dostawcę, który może znaleźć i zwrócić te modele. Kod jest bardzo podobny:

class Timeline 
{ 
    private $providers = []; // Filled by DI 

    public function find(/* ... */) 
    { 
     $result = []; 
     foreach ($this->providers as $provider) { 
      $result = array_merge($result, $provider->find(/* ... */)); 
     } 

     return $result; 
    } 
} 

Problem polega na tym, że na osi czasu musi być zestaw filtrów. Niektóre opcje filtrowania (np. date) mają zastosowanie do wszystkich dostawców. Ale większość opcji tego nie robi. Na przykład większość dostawców będzie mogła korzystać z opcji filtru author, ale nie wszystkich. Niektóre elementy powiadomienia są generowane dynamicznie i nie mają autora.

Niektóre opcje filtrowania dotyczą tylko jednego dostawcy. Na przykład tylko elementy zdarzeń mają właściwość location.

Nie mogę wymyślić, jak zaprojektować formę filtra, która jest tak samo modułowa, jak sama oś czasu. Gdzie należy zdefiniować dostępne opcje filtrowania? Opcje filtrów specyficzne dla zestawu prawdopodobnie pochodziłyby z samego pakietu, ale co z opcjami filtrowania (takimi jak user), które mogą być używane przez wiele pakietów? A co, jeśli niektóre opcje filtrów staną się później dostępne dla wielu pakietów? Na przykład teraz tylko wydarzenia mają teraz location, ale co się stanie, jeśli zostanie dodany inny moduł, który ma również elementy z lokalizacją?

I w jaki sposób każdy dostawca ustali, czy przesłany formularz filtra zawiera tylko te opcje, które rozumie? Jeśli ustawię lokalizację w filtrze, BlogPostProvider nie powinien zwracać żadnych wiadomości, ponieważ posty na blogu nie mają lokalizacji. Ale nie mogę sprawdzić w filtrze location, ponieważ BlogPostBundle nie powinien wiedzieć o innych dostawcach i ich opcjach filtrowania.

Jakieś pomysły, w jaki sposób mogę zaprojektować taką formę filtra?

Odpowiedz

0

Tak to rozwiązałem na końcu.

Po pierwsze, mam FilterRegistry. Każdy pakiet może dodawać do niego filtry za pomocą znacznika Symfony DI. Filtr to po prostu rodzaj formularza.Przykład filtr: konfiguracja

class LocationFilterType extends AbstractType 
{ 
    public function buildForm(FormBuilderInterface $builder, array $options) 
    { 
     $builder->add('location', 'choice', [ /* ... */ ]); 
    } 
} 

DI:

<service id="filter.location" class="My\Bundle\Form\LocationFilterType"> 
    <tag name="form.type" alias="filter_location" /> 
    <tag name="filter.type" alias="location" /> 
</service> 

FilterRegistry wie jak uzyskać te rodzaje formularzy z kontenera DI:

class FilterRegistry 
{ 
    public function getFormType($name) 
    { 
     if (!isset($this->types[$name])) { 
      throw new \InvalidArgumentException(sprintf('Unknown filter type "%s"', $name)); 
     } 

     return $this->container->get($this->types[$name]); 
    } 
} 

Klasa Timeline i dostawców użyć FilterBuilder aby dodać nowe filtry do formularza filtra. Konstruktor wygląda następująco:

class FilterBuilder 
{ 
    public function __construct(FilterRegistry $filterRegistry, FormBuilderInterface $formBuilder) 
    { 
     $this->filterRegistry = $filterRegistry; 
     $this->formBuilder = $formBuilder; 
    } 

    public function add($name) 
    { 
     if ($this->formBuilder->has($name)) { 
      return; 
     } 

     $type = $this->filterRegistry->getFormType($name); 
     $type->buildForm($this->formBuilder, $this->formBuilder->getOptions()); 

     return $this; 
    } 
} 

W celu wyświetlenia formularza budowany jest filtr z opcjami wszystkich dostawców. Zdarza się to w Timeline->getFilterForm(). Zauważ, że nie ma żadnego obiektu dane związane z formularza:

class Timeline 
{ 
    public function getFilterForm() 
    { 
     $formBuilder = $this->formFactory->createNamedBuilder('', 'base_filter_type'); 

     foreach ($this->providers as $provider) { 
      $provider->configureFilter(new FilterBuilder($this->filterRegistry, $formBuilder)); 
     } 

     return $formBuilder->getForm(); 
    } 
} 

Każdy operator wdraża metodę configureFilter:

class EventProvider 
{ 
    public function configureFilter(FilterBuilder $builder) 
    { 
     $builder 
      ->add('location') 
      ->add('author') 
     ; 
    } 
} 

find metoda klasy Timeline jest również zmodyfikowany. Zamiast budować filtr ze wszystkimi opcjami, tworzy nowy formularz filtru z opcjami dla tego dostawcy. Jeśli weryfikacja formularza nie powiedzie się, dostawca nie może obsłużyć aktualnie przesłanej kombinacji filtrów. Zwykle dzieje się tak dlatego, że ustawiono opcję filtrowania, której dostawca nie rozumie. W takim przypadku sprawdzanie poprawności formularza nie powiedzie się z powodu ustawiania dodatkowych danych.

class Timeline 
{ 
    public function find(Request $request) 
    { 
     $result = []; 

     foreach ($this->providers as $provider) { 
      $filter = $provider->createFilter(); 
      $formBuilder = $this->formFactory->createNamedBuilder('', 'base_filter_type', $filter); 

      $provider->configureFilter(new FilterBuilder($this->filterRegistry, $formBuilder)); 

      $form = $formBuilder->getForm(); 
      $form->handleRequest($request); 

      if (!$form->isSubmitted() || $form->isValid()) { 
       $result = array_merge($result, $provider->find($filter)); 
      } 
     } 

     return $result; 
    } 
} 

W tym przypadku nie jest klasa dane związane z formularza. $provider->createFilter() po prostu zwraca obiekt, który ma właściwości pasujące do filtrów. Wypełniony i zatwierdzony obiekt filtru jest następnie przekazywany do metody dostawcy: find(). Np .:

class EventProvider 
{ 
    public function createFilter() 
    { 
     return new EventFilter(); 
    } 

    public function find(EventFilter $filter) 
    { 
     // Do something with $filter and return events 
    } 
} 

class EventFilter 
{ 
    public $location; 
    public $author; 
} 

Całość razem sprawia, że ​​zarządzanie filtrami jest bardzo łatwe.

Aby dodać nowy typ filtra:

  • Wdrożenie FormType
  • Tag go w DI jako form.typei jako filter.type

Aby rozpocząć korzystanie z filtra:

  • Dodaj do Filte rBuilder w configureFilters()
  • dodać obiekt do modelu filtra
  • Uchwyt właściwość w metodzie find()
0

Dodaj centralny FilterHandler, w którym można zarejestrować każdy dostępny filtr. Filtry ogólne mogą być przechowywane w tym samym pakiecie co program obsługi i rejestrowane stamtąd, a pakiety mogą również rejestrować filtry.

Wszyscy dostawcy powinni wiedzieć, kiedy filtry są używane (według nazw filtrów). Również DI obsługi w nich.

Z programu obsługi można uzyskać pełną listę zarejestrowanych filtrów, a następnie skompilować formularz filtra.

Podczas filtrowania połączenia $provider->filter($requestedFiltersWithValues), które sprawdzi, czy używane filtry są rzeczywiście wymagane i zarejestrowane (za pośrednictwem wtryskiwanego programu obsługi) i zwróć wyniki w razie potrzeby.

Powiązane problemy