2015-04-23 5 views
6

Sto sviluppando un'applicazione web che presenta una timeline, proprio come una timeline di Facebook. La timeline stessa è completamente generica e collegabile. È solo una collezione generica di oggetti. Qualsiasi cosa con l'interfaccia appropriata (Dateable) può essere aggiunta alla raccolta e visualizzata sulla timeline.Come progettare i filtri per una raccolta generica e collegabile?

Altri componenti (bundle Symfony) definiscono i modelli che implementano l'interfaccia Dateable e impostano un provider in grado di trovare e restituire questi modelli. Il codice è molto simile a questo:

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

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

     return $result; 
    } 
} 

Il problema è che è necessario un set di filtri accanto alla timeline. Alcune opzioni di filtro (come date) si applicano a tutti i provider. Ma la maggior parte delle opzioni no. Ad esempio, la maggior parte dei provider sarà in grado di utilizzare l'opzione di filtro author, ma non tutti. Alcuni elementi di notifica sono generati dinamicamente e non hanno un autore.

Alcune opzioni di filtro si applicano solo a un singolo fornitore. Ad esempio, solo gli articoli evento hanno una proprietà location.

Non riesco a capire come progettare un modulo di filtro che sia modulare come la timeline stessa. Dove dovrebbero essere definite le opzioni di filtro disponibili? Probabilmente le opzioni di filtro specifiche del pacchetto provengono dal bundle stesso, ma che ne è delle opzioni di filtro (come user) che possono essere utilizzate da più bundle? E se alcune opzioni di filtro fossero più tardi utilizzabili da più pacchetti? Ad esempio, solo gli eventi hanno uno location ora, ma cosa succede se viene aggiunto un altro modulo che ha anche elementi con una posizione?

E in che modo ciascun provider stabilirà se il modulo del filtro inviato contiene solo opzioni che comprende? Se imposto una posizione nel filtro, il BlogPostProvider non dovrebbe restituire alcun messaggio, perché i post del blog non hanno alcuna posizione. Ma non riesco a verificare il numero di location nel filtro perché BlogPostBundle non dovrebbe conoscere altri provider e le loro opzioni di filtro.

Qualche idea su come posso progettare una tale forma di filtro?

risposta

0

Questo è il modo in cui l'ho risolto alla fine.

Prima di tutto, ho un FilterRegistry. Qualsiasi pacchetto può aggiungere filtri usando un tag DI Symfony. Un filtro è semplicemente un tipo di modulo.Filtro Esempio: configurazione

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> 

Il FilterRegistry sa come ottenere questi tipi di modulo dal contenitore 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]); 
    } 
} 

La classe Timeline ei fornitori di utilizzare il FilterBuilder aggiungere nuovi filtri al modulo filtro. Il costruttore si presenta così:

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; 
    } 
} 

Per visualizzare il modulo, un filtro è costruire utilizzando le opzioni di tutti i fornitori. Questo succede in Timeline->getFilterForm(). Si noti che non v'è alcun oggetto dati associata al form:

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(); 
    } 
} 

Ogni provider implementa il metodo configureFilter:

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

Il find metodo della classe temporale è anche modificato. Invece di costruire un filtro con tutte le opzioni, crea un nuovo modulo di filtro con solo le opzioni per quel provider. Se la convalida del modulo fallisce, il provider non può gestire la combinazione di filtri attualmente inviata. Di solito questo è dovuto al fatto che viene impostata un'opzione di filtro che il provider non capisce. In tal caso, la convalida del modulo non riesce a causa di dati aggiuntivi impostati.

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; 
    } 
} 

In questo caso, non v'è una classe di dati associata al form. $provider->createFilter() restituisce semplicemente un oggetto con proprietà corrispondenti ai filtri. L'oggetto filtro riempito e convalidato viene quindi passato al metodo find() del provider. E.g:

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; 
} 

Questo insieme rende molto semplice la gestione dei filtri.

Per aggiungere un nuovo tipo di filtro:

  • Implementare un FormType
  • Tag nel DI come form.typee come filter.type

Per iniziare a utilizzare un filtro:

  • Aggiungere alla Filte rbuilder in configureFilters()
  • Aggiungi un alloggio ai modello di filtro
  • Maneggiare la proprietà nel metodo find()
0

Aggiungere una centrale FilterHandler in cui è possibile registrare tutti i filtri disponibili. I filtri generici possono essere conservati nello stesso bundle del gestore e registrati da lì e i bundle possono anche registrare i filtri.

Tutti i provider devono sapere se e quando utilizzare i filtri (in base al nome del filtro). Anche DI l'handler in loro.

Dal gestore è possibile ottenere l'elenco completo dei filtri registrati e quindi creare il modulo filtro su questo.

Durante il filtraggio, chiamare $provider->filter($requestedFiltersWithValues) che verificherà se i filtri che utilizza sono effettivamente richiesti e registrati (tramite il gestore inserito) e restituiscono i risultati secondo necessità.