###### Searching

Chill should provide information needed by users when they need it. Searching within bundle, entities, ... is an important feature to achieve this goal.

The Main Bundle provides interfaces to ease the developer's work. It will also attempt that search will work in the same way accross bundles.

   :local:

   [Our blog post about searching (in French) ](http://blog.champs-libres.coop/vie-des-champs/2015/01/06/va-chercher-chill-la-recherche-dans-chill-logiciel-libre-service-social.html)
      This blog post gives some information for end-users about searching.

   [The issue about search behaviour ](https://redmine.champs-libres.coop/issues/377)
      Where the search behavior is defined.

## Searching at a glance for developers

Chill suggests using an easy-to-learn language search.

   We are planning to provide a form to create an automatic search pattern according to this language. Watch the [issue regarding this feature ](https://redmine.champs-libres.coop/issues/389).

The language is an association of search terms. Search terms may contain :

- **a domain**: this is "the domain you want to search": it may some entities like people, reports, ... Example: [@person` to search accross people, `@report` to browse reports, ... The search pattern may have **a maximum of one** domain by search, providing more should throw an error, and trigger a warning for users.
- **arguments and their values** : This is "what you search." Arguments narrow the search to specific fields: username, date of birth, nationality, ... The syntax is `argument:value`. I.e.: ` birthdate:2014-12-15`, `firstname:Depardieu`, ... **Arguments are optional**. If the value of an argument contains spaces or characters like punctuation, quotes ("), the value should be provided between parenthesis : `firstname:(Van de snoeck)`, `firstname:(M'bola)`, ...
- **default value** : this the "rest" of the search, not linked with any arguments or domain. Example : `@person dep` (`dep` is the "default value"), or simply `dep` if any domain is provided (which is perfectly acceptable). If a string is not idenfied as argument or domain, it will be present in the "default" term.

If a search pattern (provided by the user) does not contain any domain, the search must be run across default domain/search modules.

A domain may be supported by different search modules. For instance, if you provide the domain `@person`, the end-user may receive results of exact firstname/lastname, but also result with spelling suggestion, ... **But** if results do not fit into the first page (if you have 75 results and the screen show only 50 results), the next page should contains only the results from the required module.

For instance: a user searches across people by firstname/lastname, the exact spelling contains 10 results, the "spelling suggestion" results contain 75 names, but show only the first 50. If the user want to see the last 25, the next screen should not contains the results by firstname/lastname.

## Allowed characters as arguments

To execute regular expression, the allowed characters in arguments are a-z characters, numbers, and the sign '-'. Spaces and special characters like accents are note allowed (the accents are removed during parsing).

## Special characters and uppercase

The search should not care about lowercase/uppercase and accented characters. Currently, they are removed automatically by the `chill.main.search_provider`.

## Implementing a search module for dev

To implement a search module, you should :

- create a class which implements the `Chill\MainBundle\Search\SearchInterface` class. An abstract class `Chill\MainBundle\Search\AbstractSearch` will provide useful assertions for parsing date string to `DateTime` objects, ...
- register the class as a service, and tag the service with `chill.search` and an appropriate alias

The search logic is provided under the  `/search` route.

   `The implementation of a search module in the Person bundle ](https://github.com/Chill-project/Person/blob/master/Search/PersonSearch.php)
      An example of implementation https://github.com/Chill-project/Main/blob/master/DependencyInjection/SearchableServicesCompilerPass.php

   **Internals explained** : the services tagged with [chill.search` are gathered into the `chill.main.search_provider` service during compilation (`see the compiler pass ](https://github.com/Chill-project/Main/blob/master/DependencyInjection/SearchableServicesCompilerPass.php)).

   The [chill.main.search_provider` service allows to :

   - retrieve all results (as HTML string) for all search modules concerned by the search (according to the domain provided or modules marked as default)
   - retrieve result for one search module

### The SearchInterface class

```php
   namespace Chill\PersonBundle\Search;

   use Chill\MainBundle\Search\AbstractSearch;
   use Doctrine\ORM\EntityManagerInterface;
   use Chill\PersonBundle\Entity\Person;
   use Symfony\Component\DependencyInjection\ContainerInterface;
   use Symfony\Component\DependencyInjection\ContainerAware;
   use Symfony\Component\DependencyInjection\ContainerAwareTrait;
   use Chill\MainBundle\Search\ParsingException;

   class PersonSearch extends AbstractSearch
   {

       // indicate which domain you support
       // you may respond TRUE to multiple domain, according to your logic
       public function supports($domain, $format='html')
       {
           return 'person' === $domain;
       }

       // if your domain must be called when no domain is provided, should return true
       public function isActiveByDefault()
       {
           return true;
       }

       // if multiple modules respond to the same domain, indicate an order for your search.
       public function getOrder()
       {
           return 100;
       }

       // This is where your search logic should be executed.
       // This method must return an HTML string (a string with HTML tags)
       // see below about the structure of the $term array
       public function renderResult(array $terms, $start = 0, $limit = 50, array $options = array(), $format = 'html')
       {
           return $this->container->get('templating')->render('ChillPersonBundle:Person:list.html.twig',
                   array(
                       // you should implement the `search` function somewhere :-)
                       'persons' => $this->search($terms, $start, $limit, $options),
                       // recomposePattern is available in AbstractSearch class
                       'pattern' => $this->recomposePattern($terms, array('nationality',
                           'firstname', 'lastname', 'birthdate', 'gender'), $terms['_domain']),
                       // you should implement the `count` function somewhere :-)
                       'total' => $this->count($terms)
                   ));
       }
   }
```

##### Values for `$options`

`$options` is an array with the following keys:

- `SearchInterface::SEARCH_PREVIEW_OPTION` (bool): if the current view is a preview (the first 5 results) or not ;
- `SearchInterface::REQUEST_QUERY_PARAMETERS` (bool): some parameters added to the query (under the key `SearchInterface::REQUEST_QUERY_KEY_ADD_PARAMETERS`) and that can be interpreted. Used, for instance, when calling a result in json format when searching for interactive picker form.

##### Structure of array `$term`

The array term is parsed automatically by the `main.chill.search_provider` service.

   If you need to parse a search pattern, you may use the function `parse($pattern)` provided by the service.

The array `$term` have the following structure after parsing :

```php
   array(
      '_domain' => 'person', //the domain, without the '@'
      'argument1' => 'value', //the argument1, with his value
      'argument2' => 'my value with spaces', //the argument2
      '_default' => 'abcde ef' // the default term
   );
```

The original search would have been : `@person argument1:value argument2:(my value with spaces) abcde ef`

   The search values are always unaccented.

##### Returning a result in JSON

The JSON format is mainly used by "select2" widgets.

When returning a result in JSON, the SearchInterface should only return an array with following keys:

- `more` (bool): if the search has more result than the current page ;
- `results` (array): a list of a result, where:

    - `text` (string): the text that should be displayed in browser ;
    - `id` (string): the id of the entity.

### Register the service

You should add your service in the configuration, and add a `chill.search` tag and an alias.

Example :

```yaml
   services:
      chill.person.search_person:
         class: Chill\PersonBundle\Search\PersonSearch
         #your logic here
         tags:
            - { name: chill.search, alias: 'person_regular' }
```

The alias will be used to get the results narrowed to this search module, in case of pagination (see above).

## Parsing date

The class `Chill\MainBundle\Search\AbstractSearch` provides a method to parse date :

   //from subclasses
   `$date = $this->parseDate($string);`

`$date` will be an instance of `DateTime ](http://php.net/manual/en/class.datetime.php).

   [The possibility to add periods instead of date ](https://redmine.champs-libres.coop/issues/390)
       Which may be a future improvement for search with date.

## Exceptions

The logic of the search is handled by the controller for the `/search` path.

You should throw those Exceptions from your instance of `SearchInterface` if needed :

Chill\MainBundle\Search\ParsingException
   If the terms do not fit your search logic (for instance, conflicting terms)

## Expected behaviour

### Operators between multiple terms

Multiple terms should be considered are "AND" instructions :

@person nationality:RU firstname:dep
   the people having the Russian nationality AND having DEP in their name

@person birthdate:2015-12-12 charles
   the people having 'charles' in their name or firstname AND born on December 12, 2015

### Spaces in default

Spaces in default terms should be considered as "AND" instruction

@person charle dep
   people have "dep" AND "charles" in their firstname or lastname. Match "Charles Depardieu" but not "Gérard Depardieu" ('charle' is not present)

### Rendering

The rendering should contain :

- the total number of results ;
- the search pattern in the search language. The aim of this is to let users learn the search language easily.
- a title

## Frequently Asked Questions (FAQ)

Why does renderResults return an HTML string and not a structured array?
   It seems that the form of results may vary (according to access-right logic, ...) and is not easily structurable
