Refactor documentation

This commit is contained in:
Tim Nagel 2013-12-17 21:09:58 +11:00
parent 90022b0d0a
commit 5f8b8003d1
10 changed files with 838 additions and 854 deletions

View file

@ -8,6 +8,11 @@ use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
/**
* Stores supported database drivers.
*
* @var array
*/
private $supportedDrivers = array('orm', 'mongodb', 'propel');
private $configArray = array();
@ -32,8 +37,12 @@ class Configuration implements ConfigurationInterface
$rootNode
->children()
->scalarNode('default_client')->end()
->scalarNode('default_index')->end()
->scalarNode('default_client')
->info('Defaults to the first client defined')
->end()
->scalarNode('default_index')
->info('Defaults to the first index defined')
->end()
->scalarNode('default_manager')->defaultValue('orm')->end()
->arrayNode('serializer')
->treatNullLike(array())
@ -105,6 +114,7 @@ class Configuration implements ConfigurationInterface
->scalarNode('host')->end()
->scalarNode('port')->end()
->scalarNode('logger')
->info('Set your own logger service for this client or disable the logger with false.')
->defaultValue('fos_elastica.logger')
->treatNullLike('fos_elastica.logger')
->treatTrueLike('fos_elastica.logger')
@ -134,11 +144,14 @@ class Configuration implements ConfigurationInterface
->prototype('array')
->performNoDeepMerging()
->children()
->scalarNode('index_name')->end()
->scalarNode('index_name')
->info('Defaults to the name of the index, but can be modified if the index name is different in ElasticSearch')
->end()
->scalarNode('client')->end()
->scalarNode('finder')
->info('Defines an index wide finder that will search all types.')
->treatNullLike(true)
->defaultTrue()
->defaultFalse()
->end()
->append($this->getTypePrototypeNode())
->variableNode('settings')->defaultValue(array())->end()
@ -157,6 +170,7 @@ class Configuration implements ConfigurationInterface
{
$builder = new TreeBuilder();
$node = $builder->root('type_prototype');
$node->info('Allows a prototype type definition that can be applied to all types.');
$this->applyTypeConfiguration($node);

868
README.md
View file

@ -1,862 +1,30 @@
[Elastica](https://github.com/ruflin/Elastica) integration in Symfony2
FOSElasticaBundle
=================
### Installation
This bundle provides integration with [ElasticSearch](http://www.elasticsearch.org) and [Elastica](https://github.com/ruflin/Elastica) with
Symfony2. Features include:
#### Bundle and Dependencies
- Features...
For Symfony 2.0.x projects, you must use a 1.x release of this bundle. Please
check the bundle
[tags](https://github.com/FriendsOfSymfony/FOSElasticaBundle/tags) or the
[Packagist](https://packagist.org/packages/friendsofsymfony/elastica-bundle)
page for information on Symfony and Elastica compatibility.
[![Build Status](https://secure.travis-ci.org/FriendsOfSymfony/FOSElasticaBundle.png?branch=master)](http://travis-ci.org/FriendsOfSymfony/FOSElasticaBundle) [![Total Downloads](https://poser.pugx.org/FriendsOfSymfony/elastica-bundle/downloads.png)](https://packagist.org/packages/FriendsOfSymfony/elastica-bundle) [![Latest Stable Version](https://poser.pugx.org/FriendsOfSymfony/elastica-bundle/v/stable.png)](https://packagist.org/packages/FriendsOfSymfony/elastica-bundle)
Add FOSElasticaBundle to your application's `composer.json` file:
Documentation
-------------
```json
{
"require": {
"friendsofsymfony/elastica-bundle": "3.0.*@dev"
}
}
```
Documentation for FOSElasticaBundle is in `Resources/doc/index.md`
Install the bundle and its dependencies with the following command:
[Read the documentation for 3.0.x (master))](https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/master/Resources/doc/index.md)
```bash
$ php composer.phar update friendsofsymfony/elastica-bundle
```
[Read the documentation for 2.1.x](https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/2.1.x/README.md)
You may rely on Composer to fetch the appropriate version of Elastica. Lastly,
enable the bundle in your application kernel:
Installation
------------
```php
// app/AppKernel.php
Installation instructions can be found in the [documentation](https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/master/Resources/doc/setup.md)
public function registerBundles()
{
$bundles = array(
// ...
new FOS\ElasticaBundle\FOSElasticaBundle(),
);
}
```
License
-------
#### Elasticsearch
This bundle is under the MIT license. See the complete license in the bundle:
Instructions for installing and deploying Elasticsearch may be found
[here](http://www.elasticsearch.org/guide/reference/setup/installation/).
### Basic configuration
#### Declare a client
Elasticsearch client is comparable to a database connection.
Most of the time, you will need only one.
#app/config/config.yml
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
#### Declare a serializer
Elastica can handle objects instead of data arrays if a serializer callable is configured
#app/config/config.yml
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
serializer:
callback_class: callback_class
serializer: serializer
``callback_class`` is the name of a class having a public method serialize($object) and should
extends from ``FOS\ElasticaBundle\Serializer\Callback``.
``serializer`` is the service id for the actual serializer, e.g. ``serializer`` if you're using
JMSSerializerBundle. If this is configured you can use ``\Elastica\Type::addObject`` instead of
``\Elastica\Type::addDocument`` to add data to the index. The bundle provides a default implementation
with a serializer service id 'serializer' that can be turned on by adding the following line to your config.
#app/config/config.yml
fos_elastica:
serializer: ~
#### Declare an index
Elasticsearch index is comparable to Doctrine entity manager.
Most of the time, you will need only one.
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
serializer:
callback_class: FOS\ElasticaBundle\Serializer\Callback
serializer: serializer
indexes:
website:
client: default
Here we created a "website" index, that uses our "default" client.
Our index is now available as a service: `fos_elastica.index.website`. It is an instance of `\Elastica\Index`.
If you need to have different index name from the service name, for example,
in order to have different indexes for different environments then you can
use the ```index_name``` key to change the index name. The service name will
remain the same across the environments:
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
website:
client: default
index_name: website_qa
The service id will be `fos_elastica.index.website` but the underlying index name is website_qa.
#### Declare a type
Elasticsearch type is comparable to Doctrine entity repository.
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
serializer:
callback_class: FOS\ElasticaBundle\Serializer\Callback
serializer: serializer
indexes:
website:
client: default
types:
user:
mappings:
username: { boost: 5 }
firstName: { boost: 3 }
lastName: { boost: 3 }
aboutMe: ~
Our type is now available as a service: `fos_elastica.index.website.user`. It is an instance of `\Elastica\Type`.
### Declaring serializer groups
If you are using the JMSSerializerBundle for serializing objects passed to elastica you can define serializer groups
per type.
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
serializer:
callback_class: %classname%
serializer: serializer
indexes:
website:
client: default
types:
user:
mappings:
username: { boost: 5 }
firstName: { boost: 3 }
lastName: { boost: 3 }
aboutMe:
serializer:
groups: [elastica, Default]
### Declaring parent field
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
serializer:
callback_class: FOS\ElasticaBundle\Serializer\Callback
serializer: serializer
indexes:
website:
client: default
types:
comment:
mappings:
date: { boost: 5 }
content: ~
_parent: { type: "post", property: "post", identifier: "id" }
The parent field declaration has the following values:
* `type`: The parent type.
* `property`: The property in the child entity where to look for the parent entity. It may be ignored if is equal to the parent type.
* `identifier`: The property in the parent entity which has the parent identifier. Defaults to `id`.
Note that to create a document with a parent, you need to call `setParent` on the document rather than setting a _parent field.
If you do this wrong, you will see a `RoutingMissingException` as elasticsearch does not know where to store a document that should have a parent but does not specify it.
### Declaring `nested` or `object`
Note that object can autodetect properties
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
serializer:
callback_class: FOS\ElasticaBundle\Serializer\Callback
serializer: serializer
indexes:
website:
client: default
types:
post:
mappings:
date: { boost: 5 }
title: { boost: 3 }
content: ~
comments:
type: "nested"
properties:
date: { boost: 5 }
content: ~
user:
type: "object"
approver:
type: "object"
properties:
date: { boost: 5 }
#### Doctrine ORM and `object` mappings
Objects operate in the same way as the nested results but they need to have associations set up in Doctrine ORM so that they can be referenced correctly when indexing.
If an "Entity was not found" error occurs while indexing, a null association has been discovered in the database. A custom Doctrine query must be used to utilize left joins instead of the default inner join.
### Populate the types
php app/console fos:elastica:populate
This command deletes and creates the declared indexes and types.
It applies the configured mappings to the types.
This command needs providers to insert new documents in the elasticsearch types.
There are 2 ways to create providers.
If your elasticsearch type matches a Doctrine repository or a Propel query, go for the persistence automatic provider.
Or, for complete flexibility, go for a manual provider.
#### Persistence automatic provider
If we want to index the entities from a Doctrine repository or a Propel query,
some configuration will let ElasticaBundle do it for us.
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
serializer:
callback_class: FOS\ElasticaBundle\Serializer\Callback
serializer: serializer
indexes:
website:
client: default
types:
user:
mappings:
username: { boost: 5 }
firstName: { boost: 3 }
# more mappings...
persistence:
driver: orm # orm, mongodb, propel are available
model: Application\UserBundle\Entity\User
provider: ~
Three drivers are actually supported: orm, mongodb, and propel.
##### Use a custom Doctrine query builder
You can control which entities will be indexed by specifying a custom query builder method.
persistence:
driver: orm
model: Application\UserBundle\Entity\User
provider:
query_builder_method: createIsActiveQueryBuilder
Your repository must implement this method and return a Doctrine query builder.
> **Propel** doesn't support this feature yet.
##### Change the batch size
By default, ElasticaBundle will index documents by packets of 100.
You can change this value in the provider configuration.
persistence:
driver: orm
model: Application\UserBundle\Entity\User
provider:
batch_size: 100
##### Change the document identifier field
By default, ElasticaBundle will use the `id` field of your entities as the elasticsearch document identifier.
You can change this value in the persistence configuration.
persistence:
driver: orm
model: Application\UserBundle\Entity\User
identifier: id
#### Manual provider
Create a service with the tag "fos_elastica.provider" and attributes for the
index and type for which the service will provide.
<service id="acme.search_provider.user" class="Acme\UserBundle\Search\UserProvider">
<tag name="fos_elastica.provider" index="website" type="user" />
<argument type="service" id="fos_elastica.index.website.user" />
</service>
Its class must implement `FOS\ElasticaBundle\Provider\ProviderInterface`.
<?php
namespace Acme\UserBundle\Provider;
use FOS\ElasticaBundle\Provider\ProviderInterface;
use Elastica\Type;
use Elastica\Document;
class UserProvider implements ProviderInterface
{
protected $userType;
public function __construct(Type $userType)
{
$this->userType = $userType;
}
/**
* Insert the repository objects in the type index
*
* @param \Closure $loggerClosure
* @param array $options
*/
public function populate(\Closure $loggerClosure = null, array $options = array())
{
if ($loggerClosure) {
$loggerClosure('Indexing users');
}
$document = new Document();
$document->setData(array('username' => 'Bob'));
$this->userType->addDocuments(array($document));
}
}
You will find a more complete implementation example in `src/FOS/ElasticaBundle/Doctrine/AbstractProvider.php`.
### Search
You can just use the index and type Elastica objects, provided as services, to perform searches.
/** var Elastica\Type */
$userType = $this->container->get('fos_elastica.index.website.user');
/** var Elastica\ResultSet */
$resultSet = $userType->search('bob');
#### Doctrine/Propel finder
If your elasticsearch type is bound to a Doctrine entity repository or a Propel query,
you can get your entities instead of Elastica results when you perform a search.
Declare that you want a Doctrine/Propel finder in your configuration:
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
serializer:
callback_class: FOS\ElasticaBundle\Serializer\Callback
serializer: serializer
indexes:
website:
client: default
types:
user:
mappings:
# your mappings
persistence:
driver: orm
model: Application\UserBundle\Entity\User
provider: ~
finder: ~
You can now use the `fos_elastica.finder.website.user` service:
/** var FOS\ElasticaBundle\Finder\TransformedFinder */
$finder = $container->get('fos_elastica.finder.website.user');
/** var array of Acme\UserBundle\Entity\User */
$users = $finder->find('bob');
/** var array of Acme\UserBundle\Entity\User limited to 10 results */
$users = $finder->find('bob', 10);
You can even get paginated results!
Pagerfanta:
/** var Pagerfanta\Pagerfanta */
$userPaginator = $finder->findPaginated('bob');
/** Number of results to be used for paging the results */
$countOfResults = $userPaginator->getNbResults();
Knp paginator:
$paginator = $this->get('knp_paginator');
$userPaginator = $paginator->paginate($finder->createPaginatorAdapter('bob'));
You can also get both the Elastica results and the entities together from the finder.
You can then access the score, highlights etc. from the Elastica\Result whilst
still also getting the entity.
/** var array of FOS\ElasticaBundle\HybridResult */
$hybridResults = $finder->findHybrid('bob');
foreach ($hybridResults as $hybridResult) {
/** var Acme\UserBundle\Entity\User */
$user = $hybridResult->getTransformed();
/** var Elastica\Result */
$result = $hybridResult->getResult();
}
If you would like to access facets while using Pagerfanta they can be accessed through
the Adapter seen in the example below.
```php
$query = new \Elastica\Query();
$facet = new \Elastica\Facet\Terms('tags');
$facet->setField('companyGroup');
$query->addFacet($facet);
$companies = $finder->findPaginated($query);
$companies->setMaxPerPage($params['limit']);
$companies->setCurrentPage($params['page']);
$facets = $companies->getAdapter()->getFacets());
```
##### Index wide finder
You can also define a finder that will work on the entire index. Adjust your index
configuration as per below:
fos_elastica:
indexes:
website:
client: default
finder: ~
You can now use the index wide finder service `fos_elastica.finder.website`:
/** var FOS\ElasticaBundle\Finder\MappedFinder */
$finder = $container->get('fos_elastica.finder.website');
// Returns a mixed array of any objects mapped
$results = $finder->find('bob');
#### Repositories
As well as using the finder service for a particular Doctrine/Propel entity you
can use a manager service for each driver and get a repository for an entity to search
against. This allows you to use the same service rather than the particular finder. For
example:
/** var FOS\ElasticaBundle\Manager\RepositoryManager */
$repositoryManager = $container->get('fos_elastica.manager.orm');
/** var FOS\ElasticaBundle\Repository */
$repository = $repositoryManager->getRepository('UserBundle:User');
/** var array of Acme\UserBundle\Entity\User */
$users = $repository->find('bob');
You can also specify the full name of the entity instead of the shortcut syntax:
/** var FOS\ElasticaBundle\Repository */
$repository = $repositoryManager->getRepository('Application\UserBundle\Entity\User');
> The **2.0** branch doesn't support using `UserBundle:User` style syntax and you must use the full name of the entity. .
##### Default Manager
If you are only using one driver then its manager service is automatically aliased
to `fos_elastica.manager`. So the above example could be simplified to:
/** var FOS\ElasticaBundle\Manager\RepositoryManager */
$repositoryManager = $container->get('fos_elastica.manager');
/** var FOS\ElasticaBundle\Repository */
$repository = $repositoryManager->getRepository('UserBundle:User');
/** var array of Acme\UserBundle\Entity\User */
$users = $repository->find('bob');
If you use multiple drivers then you can choose which one is aliased to `fos_elastica.manager`
using the `default_manager` parameter:
fos_elastica:
default_manager: mongodb #defaults to orm
clients:
default: { host: localhost, port: 9200 }
#--
##### Custom Repositories
As well as the default repository you can create a custom repository for an entity and add
methods for particular searches. These need to extend `FOS\ElasticaBundle\Repository` to have
access to the finder:
```
<?php
namespace Acme\ElasticaBundle\SearchRepository;
use FOS\ElasticaBundle\Repository;
class UserRepository extends Repository
{
public function findWithCustomQuery($searchText)
{
// build $query with Elastica objects
$this->find($query);
}
}
```
To use the custom repository specify it in the mapping for the entity:
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
website:
client: default
types:
user:
mappings:
# your mappings
persistence:
driver: orm
model: Application\UserBundle\Entity\User
provider: ~
finder: ~
repository: Acme\ElasticaBundle\SearchRepository\UserRepository
Then the custom queries will be available when using the repository returned from the manager:
/** var FOS\ElasticaBundle\Manager\RepositoryManager */
$repositoryManager = $container->get('fos_elastica.manager');
/** var FOS\ElasticaBundle\Repository */
$repository = $repositoryManager->getRepository('UserBundle:User');
/** var array of Acme\UserBundle\Entity\User */
$users = $repository->findWithCustomQuery('bob');
Alternatively you can specify the custom repository using an annotation in the entity:
```
<?php
namespace Application\UserBundle\Entity;
use FOS\ElasticaBundle\Configuration\Search;
/**
* @Search(repositoryClass="Acme\ElasticaBundle\SearchRepository\UserRepository")
*/
class User
{
//---
}
```
### Realtime, selective index update
If you use the Doctrine integration, you can let ElasticaBundle update the indexes automatically
when an object is added, updated or removed. It uses Doctrine lifecycle events.
Declare that you want to update the index in real time:
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
website:
client: default
types:
user:
mappings:
# your mappings
persistence:
driver: orm
model: Application\UserBundle\Entity\User
listener: ~ # by default, listens to "insert", "update" and "delete"
Now the index is automatically updated each time the state of the bound Doctrine repository changes.
No need to repopulate the whole "user" index when a new `User` is created.
You can also choose to only listen for some of the events:
persistence:
listener:
insert: true
update: false
delete: true
> **Propel** doesn't support this feature yet.
### Checking an entity method for listener
If you use listeners to update your index, you may need to validate your
entities before you index them (e.g. only index "public" entities). Typically,
you'll want the listener to be consistent with the provider's query criteria.
This may be achieved by using the `is_indexable_callback` config parameter:
persistence:
listener:
is_indexable_callback: "isPublic"
If `is_indexable_callback` is a string and the entity has a method with the
specified name, the listener will only index entities for which the method
returns `true`. Additionally, you may provide a service and method name pair:
persistence:
listener:
is_indexable_callback: [ "%custom_service_id%", "isIndexable" ]
In this case, the callback_class will be the `isIndexable()` method on the specified
service and the object being considered for indexing will be passed as the only
argument. This allows you to do more complex validation (e.g. ACL checks).
If you have the [Symfony ExpressionLanguage](https://github.com/symfony/expression-language) component installed, you can use expressions
to evaluate the callback:
persistence:
listener:
is_indexable_callback: "user.isActive() && user.hasRole('ROLE_USER')"
As you might expect, new entities will only be indexed if the callback_class returns
`true`. Additionally, modified entities will be updated or removed from the
index depending on whether the callback_class returns `true` or `false`, respectively.
The delete listener disregards the callback_class.
> **Propel** doesn't support this feature yet.
### Ignoring missing index results
By default, FOSElasticaBundle will throw an exception if the results returned from
Elasticsearch are different from the results it finds from the chosen persistence
provider. This may pose problems for a large index where updates do not occur instantly
or another process has removed the results from your persistence provider without
updating Elasticsearch.
The error you're likely to see is something like:
'Cannot find corresponding Doctrine objects for all Elastica results.'
To solve this issue, each mapped object can be configured to ignore the missing results:
persistence:
elastica_to_model_transformer:
ignore_missing: true
### Advanced elasticsearch configuration
Any setting can be specified when declaring a type. For example, to enable a custom analyzer, you could write:
fos_elastica:
indexes:
doc:
settings:
index:
analysis:
analyzer:
my_analyzer:
type: custom
tokenizer: lowercase
filter : [my_ngram]
filter:
my_ngram:
type: "nGram"
min_gram: 3
max_gram: 5
types:
blog:
mappings:
title: { boost: 8, analyzer: my_analyzer }
### Overriding the Client class to suppress exceptions
By default, exceptions from the Elastica client library will propagate through
the bundle's Client class. For instance, if the elasticsearch server is offline,
issuing a request will result in an `Elastica\Exception\Connection` being thrown.
Depending on your needs, it may be desirable to suppress these exceptions and
allow searches to fail silently.
One way to achieve this is to override the `fos_elastica.client.class` service
container parameter with a custom class. In the following example, we override
the `Client::request()` method and return the equivalent of an empty search
response if an exception occurred.
```
<?php
namespace Acme\ElasticaBundle;
use FOS\ElasticaBundle\Client as BaseClient;
use Elastica\Exception\ExceptionInterface;
use Elastica\Response;
class Client extends BaseClient
{
public function request($path, $method, $data = array())
{
try {
return parent::request($path, $method, $data);
} catch (ExceptionInterface $e) {
return new Response('{"took":0,"timed_out":false,"hits":{"total":0,"max_score":0,"hits":[]}}');
}
}
}
```
### Clients as Tagged Services
Clients will be tagged as `fos_elastica.client`, which makes it possible to
retrieve all clients from the service container and interact with them via a
compiler pass. See
[Working with Tagged Services](http://symfony.com/doc/current/components/dependency_injection/tags.html)
for more information.
### Example of Advanced Query
If you would like to perform more advanced queries, here is one example using
the snowball stemming algorithm.
It searches for Article entities using `title`, `tags`, and `categoryIds`.
Results must match at least one specified `categoryIds`, and should match the
`title` or `tags` criteria. Additionally, we define a snowball analyzer to
apply to queries against the `title` field.
```php
$finder = $this->container->get('fos_elastica.finder.website.article');
$boolQuery = new \Elastica\Query\Bool();
$fieldQuery = new \Elastica\Query\Text();
$fieldQuery->setFieldQuery('title', 'I am a title string');
$fieldQuery->setFieldParam('title', 'analyzer', 'my_analyzer');
$boolQuery->addShould($fieldQuery);
$tagsQuery = new \Elastica\Query\Terms();
$tagsQuery->setTerms('tags', array('tag1', 'tag2'));
$boolQuery->addShould($tagsQuery);
$categoryQuery = new \Elastica\Query\Terms();
$categoryQuery->setTerms('categoryIds', array('1', '2', '3'));
$boolQuery->addMust($categoryQuery);
$data = $finder->find($boolQuery);
```
Configuration:
```yaml
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
site:
settings:
index:
analysis:
analyzer:
my_analyzer:
type: snowball
language: English
types:
article:
mappings:
title: { boost: 10, analyzer: my_analyzer }
tags:
categoryIds:
persistence:
driver: orm
model: Acme\DemoBundle\Entity\Article
provider:
finder:
```
### Filtering Results and Executing a Default Query
If may want to omit certain results from a query, filtering can be more
performant than a basic query because the filter results can be cached. In turn,
the query is run against only a subset of the results. A common use case for
filtering would be if your data has fields that indicate whether records are
"active" or "inactive". The following example illustrates how to issue such a
query with Elastica:
```php
$query = new \Elastica\Query\QueryString($queryString);
$term = new \Elastica\Filter\Term(array('active' => true));
$filteredQuery = new \Elastica\Query\Filtered($query, $term);
$results = $this->container->get('fos_elastica.finder.index.type')->find($filteredQuery);
```
### Date format example
If you want to specify a [date format](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping-date-format.html):
```yaml
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
site:
types:
user:
mappings:
username: { type: string }
lastlogin: { type: date, format: basic_date_time }
birthday: { type: date, format: "yyyy-MM-dd" }
```
#### Dynamic templates
Dynamic templates allow to define mapping templates that will be
applied when dynamic introduction of fields / objects happens.
[Documentation](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping-root-object-type.html#_dynamic_templates)
```yaml
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
site:
types:
user:
dynamic_templates:
my_template_1:
match: apples_*
mapping:
type: float
my_template_2:
match: *
match_mapping_type: string
mapping:
type: string
index: not_analyzed
mappings:
username: { type: string }
```
Resources/meta/LICENSE

View file

@ -0,0 +1,72 @@
##### Custom Repositories
As well as the default repository you can create a custom repository for an entity and add
methods for particular searches. These need to extend `FOS\ElasticaBundle\Repository` to have
access to the finder:
```
<?php
namespace Acme\ElasticaBundle\SearchRepository;
use FOS\ElasticaBundle\Repository;
class UserRepository extends Repository
{
public function findWithCustomQuery($searchText)
{
// build $query with Elastica objects
$this->find($query);
}
}
```
To use the custom repository specify it in the mapping for the entity:
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
website:
client: default
types:
user:
mappings:
# your mappings
persistence:
driver: orm
model: Application\UserBundle\Entity\User
provider: ~
finder: ~
repository: Acme\ElasticaBundle\SearchRepository\UserRepository
Then the custom queries will be available when using the repository returned from the manager:
/** var FOS\ElasticaBundle\Manager\RepositoryManager */
$repositoryManager = $container->get('fos_elastica.manager');
/** var FOS\ElasticaBundle\Repository */
$repository = $repositoryManager->getRepository('UserBundle:User');
/** var array of Acme\UserBundle\Entity\User */
$users = $repository->findWithCustomQuery('bob');
Alternatively you can specify the custom repository using an annotation in the entity:
```
<?php
namespace Application\UserBundle\Entity;
use FOS\ElasticaBundle\Configuration\Search;
/**
* @Search(repositoryClass="Acme\ElasticaBundle\SearchRepository\UserRepository")
*/
class User
{
//---
}
```

View file

@ -0,0 +1,56 @@
Manual provider
===============
Create a service with the tag "fos_elastica.provider" and attributes for the
index and type for which the service will provide.
```yaml
# app/config/config.yml
services:
acme.search_provider.user:
class: Acme\UserBundle\Search\UserProvider
arguments:
- @fos_elastica.index.website.user
tags:
- { name: fos_elastica.provider, index: website, type: user }
```
Its class must implement `FOS\ElasticaBundle\Provider\ProviderInterface`.
```php
namespace Acme\UserBundle\Provider;
use FOS\ElasticaBundle\Provider\ProviderInterface;
use Elastica\Type;
use Elastica\Document;
class UserProvider implements ProviderInterface
{
protected $userType;
public function __construct(Type $userType)
{
$this->userType = $userType;
}
/**
* Insert the repository objects in the type index
*
* @param \Closure $loggerClosure
* @param array $options
*/
public function populate(\Closure $loggerClosure = null, array $options = array())
{
if ($loggerClosure) {
$loggerClosure('Indexing users');
}
$document = new Document();
$document->setData(array('username' => 'Bob'));
$this->userType->addDocuments(array($document));
}
}
```
You will find a more complete implementation example in `src/FOS/ElasticaBundle/Doctrine/AbstractProvider.php`.

View file

@ -0,0 +1,36 @@
Suppressing Server Errors
========================
By default, exceptions from the Elastica client library will propagate through
the bundle's Client class. For instance, if the Elasticsearch server is offline,
issuing a request will result in an `Elastica\Exception\Connection` being thrown.
Depending on your needs, it may be desirable to suppress these exceptions and
allow searches to fail silently.
One way to achieve this is to override the `fos_elastica.client.class` service
container parameter with a custom class. In the following example, we override
the `Client::request()` method and return the equivalent of an empty search
response if an exception occurred.
```
<?php
namespace Acme\ElasticaBundle;
use FOS\ElasticaBundle\Client as BaseClient;
use Elastica\Exception\ExceptionInterface;
use Elastica\Response;
class Client extends BaseClient
{
public function request($path, $method, $data = array())
{
try {
return parent::request($path, $method, $data);
} catch (ExceptionInterface $e) {
return new Response('{"took":0,"timed_out":false,"hits":{"total":0,"max_score":0,"hits":[]}}');
}
}
}
```

14
Resources/doc/index.md Normal file
View file

@ -0,0 +1,14 @@
FOSElasticaBundle Documentation
===============================
Available documentation for FOSElasticaBundle:
* [Setup](setup.md)
* [Usage](usage.md)
* [Using a Serializer](serializer.md)
* [Types](types.md)
Cookbook Entries
* [Custom Repositories](cookbook/custom-repositories.md)
* [Suppressing server errors](cookbook/suppress-server-errors.md)

View file

@ -0,0 +1,39 @@
Using a Serializer in FOSElasticaBundle
=======================================
FOSElasticaBundle supports using a Serializer component to serialize your objects to JSON
which will be sent directly to the Elasticsearch server. Combined with automatic mapping
it means types do not have to be mapped.
A) Install and declare the serializer
-------------------------
Follow the installation instructions for [JMSSerializerBundle](http://jmsyst.com/bundles/JMSSerializerBundle).
Enable the serializer configuration for the bundle:
```yaml
#app/config/config.yml
fos_elastica:
serializer: ~
```
The default configuration that comes with FOSElasticaBundle supports both the JMS Serializer
and the Symfony Serializer. If JMSSerializerBundle is installed, additional support for
serialization groups and versions are added to the bundle.
B) Set up each defined type to support serialization
----------------------------------------------------
A type does not need to have mappings defined when using a serializer. An example configuration
for a type in this case:
```yaml
fos_elastica:
indexes:
search:
types:
user:
serializer:
groups: [elastica, Default]
```

144
Resources/doc/setup.md Normal file
View file

@ -0,0 +1,144 @@
Step 1: Setting up the bundle
=============================
A) Install FOSElasticaBundle
----------------------------
FOSElasticaBundle is installed using [Composer](https://getcomposer.org).
```bash
$ php composer.phar require friendsofsymfony/elastica-bundle "3.0.*"
```
### Elasticsearch
Instructions for installing and deploying Elasticsearch may be found
[here](http://www.elasticsearch.org/guide/reference/setup/installation/).
B) Enable FOSElasticaBundle
---------------------------
Enable FOSElasticaBundle in your AppKernel:
```php
<?php
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new FOS\RestBundle\FOSRestBundle(),
);
}
```
C) Basic Bundle Configuration
-----------------------------
The basic minimal configuration for FOSElasticaBundle is one client with one Elasticsearch
index. In almost all cases, an application will only need a single index. An index can
be considered comparable to a Doctrine Entity Manager, where the index will hold multiple
type definitions.
```yaml
#app/config/config.yml
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
search: ~
```
In this example, an Elastica index (an instance of `Elastica\Index`) is available as a
service with the key `fos_elastica.index.search`.
If the Elasticsearch index name needs to be different to the service name in your
application, for example, renaming the search index based on different environments.
```yaml
#app/config/config.yml
fos_elastica:
indexes:
search:
index_name: search_dev
```
In this case, the service `fos_elastica.index.search` will be using an Elasticsearch
index of search_dev.
D) Defining index types
-----------------------
By default, FOSElasticaBundle requires each type that is to be indexed to be mapped.
It is possible to use a serializer to avoid this requirement. To use a serializer, see
the [serializer documentation](serializer.md)
An Elasticsearch type needs to be defined with each field of a related PHP object that
will end up being indexed.
```yaml
fos_elastica:
indexes:
search:
types:
user:
mappings:
username: ~
firstName: ~
lastName: ~
email: ~
```
Each defined type is made available as a service, and in this case the service key is
`fos_elastica.index.search.user` and is an instance of `Elastica\Type`.
FOSElasticaBundle requires a provider for each type that will notify when an object
that maps to a type has been modified. The bundle ships with support for Doctrine and
Propel objects.
Below is an example for the Doctrine ORM. For additional information regarding
integration with Doctrine or Propel or how to create a custom provider, see
[type-providers.md].
```yaml
user:
mappings:
username: ~
firstName: ~
lastName: ~
email: ~
persistence:
# the driver can be orm, mongodb or propel
# listener and finder are not supported by
# propel and should be removed
driver: orm
model: Acme\ApplicationBundle\Entity\User
provider: ~
listener: ~
finder: ~
```
There are a significant number of options available for types, that can be
[found here](types.md)
E) Populating the Elasticsearch index
-------------------------------------
When using the providers and listeners that come with the bundle, any new or modified
object will be indexed automatically. In some cases, where the database is modified
externally, the Elasticsearch index must be updated manually. This can be achieved by
running the console command:
```bash
$ php app/console fos:elastica:populate
```
The command will also create all indexes and types defined if they do not already exist
on the Elasticsearch server.
F) Usage
--------
Usage documentation for the bundle is available [here](usage.md)

264
Resources/doc/types.md Normal file
View file

@ -0,0 +1,264 @@
Type configuration
==================
Handling missing results with FOSElasticaBundle
-----------------------------------------------
By default, FOSElasticaBundle will throw an exception if the results returned from
Elasticsearch are different from the results it finds from the chosen persistence
provider. This may pose problems for a large index where updates do not occur instantly
or another process has removed the results from your persistence provider without
updating Elasticsearch.
The error you're likely to see is something like:
'Cannot find corresponding Doctrine objects for all Elastica results.'
To solve this issue, each type can be configured to ignore the missing results:
```yaml
user:
persistence:
elastica_to_model_transformer:
ignore_missing: true
```
Dynamic templates
-----------------
Dynamic templates allow to define mapping templates that will be
applied when dynamic introduction of fields / objects happens.
[Documentation](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping-root-object-type.html#_dynamic_templates)
```yaml
fos_elastica:
indexes:
site:
types:
user:
dynamic_templates:
my_template_1:
match: apples_*
mapping:
type: float
my_template_2:
match: *
match_mapping_type: string
mapping:
type: string
index: not_analyzed
mappings:
username: { type: string }
```
Nested objects in FOSElasticaBundle
-----------------------------------
Note that object can autodetect properties
```yaml
fos_elastica:
indexes:
website:
types:
post:
mappings:
date: { boost: 5 }
title: { boost: 3 }
content: ~
comments:
type: "nested"
properties:
date: { boost: 5 }
content: ~
user:
type: "object"
approver:
type: "object"
properties:
date: { boost: 5 }
```
Parent fields
-------------
```yaml
fos_elastica:
indexes:
website:
types:
comment:
mappings:
date: { boost: 5 }
content: ~
_parent:
type: "post"
property: "post"
identifier: "id"
```
The parent field declaration has the following values:
* `type`: The parent type.
* `property`: The property in the child entity where to look for the parent entity. It may be ignored if is equal to
the parent type.
* `identifier`: The property in the parent entity which has the parent identifier. Defaults to `id`.
Note that to create a document with a parent, you need to call `setParent` on the document rather than setting a
_parent field. If you do this wrong, you will see a `RoutingMissingException` as Elasticsearch does not know where
to store a document that should have a parent but does not specify it.
Date format example
-------------------
If you want to specify a [date format](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping-date-format.html):
```yaml
user:
mappings:
username: { type: string }
lastlogin: { type: date, format: basic_date_time }
birthday: { type: date, format: "yyyy-MM-dd" }
```
Custom settings
---------------
Any setting can be specified when declaring a type. For example, to enable a custom
analyzer, you could write:
```yaml
indexes:
search:
settings:
index:
analysis:
analyzer:
my_analyzer:
type: custom
tokenizer: lowercase
filter : [my_ngram]
filter:
my_ngram:
type: "nGram"
min_gram: 3
max_gram: 5
types:
blog:
mappings:
title: { boost: 8, analyzer: my_analyzer }
```
Provider Configuration
----------------------
### Specifying a custom query builder for populating indexes
When populating an index, it may be required to use a different query builder method
to define which entities should be queried.
```yaml
user:
persistence:
provider:
query_builder_method: createIsActiveQueryBuilder
```
### Populating batch size
By default, ElasticaBundle will index documents by packets of 100.
You can change this value in the provider configuration.
```yaml
user:
persistence:
provider:
batch_size: 10
```
### Changing the document identifier
By default, ElasticaBundle will use the `id` field of your entities as
the Elasticsearch document identifier. You can change this value in the
persistence configuration.
```yaml
user:
persistence:
identifier: searchId
```
Listener Configuration
----------------------
### Realtime, selective index update
If you use the Doctrine integration, you can let ElasticaBundle update the indexes automatically
when an object is added, updated or removed. It uses Doctrine lifecycle events.
Declare that you want to update the index in real time:
```yaml
user:
persistence:
driver: orm
model: Application\UserBundle\Entity\User
listener: ~ # by default, listens to "insert", "update" and "delete"
```
Now the index is automatically updated each time the state of the bound Doctrine repository changes.
No need to repopulate the whole "user" index when a new `User` is created.
You can also choose to only listen for some of the events:
```yaml
persistence:
listener:
insert: true
update: false
delete: true
```
> **Propel** doesn't support this feature yet.
### Checking an entity method for listener
If you use listeners to update your index, you may need to validate your
entities before you index them (e.g. only index "public" entities). Typically,
you'll want the listener to be consistent with the provider's query criteria.
This may be achieved by using the `is_indexable_callback` config parameter:
```yaml
persistence:
listener:
is_indexable_callback: "isPublic"
```
If `is_indexable_callback` is a string and the entity has a method with the
specified name, the listener will only index entities for which the method
returns `true`. Additionally, you may provide a service and method name pair:
```yaml
persistence:
listener:
is_indexable_callback: [ "%custom_service_id%", "isIndexable" ]
```
In this case, the callback_class will be the `isIndexable()` method on the specified
service and the object being considered for indexing will be passed as the only
argument. This allows you to do more complex validation (e.g. ACL checks).
If you have the [Symfony ExpressionLanguage](https://github.com/symfony/expression-language)
component installed, you can use expressions to evaluate the callback:
```yaml
persistence:
listener:
is_indexable_callback: "user.isActive() && user.hasRole('ROLE_USER')"
```
As you might expect, new entities will only be indexed if the callback_class returns
`true`. Additionally, modified entities will be updated or removed from the
index depending on whether the callback_class returns `true` or `false`, respectively.
The delete listener disregards the callback_class.
> **Propel** doesn't support this feature yet.

177
Resources/doc/usage.md Normal file
View file

@ -0,0 +1,177 @@
FOSElasticaBundle Usage
=======================
Basic Searching with a Finder
-----------------------------
The most useful searching method is to use a finder defined by the type configuration.
A finder will return results that have been hydrated by the configured persistence backend,
allowing you to use relationships of returned entities. For more information about
configuration options for this kind of searching, please see the [types](types.md)
documentation.
```php
$finder = $this->container->get('fos_elastica.finder.search.user');
// Option 1. Returns all users who have example.net in any of their mapped fields
$results = $finder->find('example.net');
// Option 2. Returns a set of hybrid results that contain all Elasticsearch results
// and their transformed counterparts. Each result is an instance of a HybridResult
$results = $finder->findHybrid('example.net');
// Option 3a. Pagerfanta'd resultset
/** var Pagerfanta\Pagerfanta */
$userPaginator = $finder->findPaginated('bob');
$countOfResults = $userPaginator->getNbResults();
// Option 3b. KnpPaginator resultset
```
Faceted Searching
-----------------
When searching with facets, the facets can be retrieved when using the paginated
methods on the finder.
```php
$query = new \Elastica\Query();
$facet = new \Elastica\Facet\Terms('tags');
$facet->setField('companyGroup');
$query->addFacet($facet);
$companies = $finder->findPaginated($query);
$companies->setMaxPerPage($params['limit']);
$companies->setCurrentPage($params['page']);
$facets = $companies->getAdapter()->getFacets());
```
Searching the entire index
--------------------------
You can also define a finder that will work on the entire index. Adjust your index
configuration as per below:
```yaml
fos_elastica:
indexes:
website:
finder: ~
```
You can now use the index wide finder service `fos_elastica.finder.website`:
```php
/** var FOS\ElasticaBundle\Finder\MappedFinder */
$finder = $container->get('fos_elastica.finder.website');
// Returns a mixed array of any objects mapped
$results = $finder->find('bob');
```
Type Repositories
-----------------
In the case where you need many different methods for different searching terms, it
may be better to separate methods for each type into their own dedicated repository
classes, just like Doctrine ORM's EntityRepository classes.
The manager class that handles repositories has a service key of `fos_elastica.manager`.
The manager will default to handling ORM entities, and the configuration must be changed
for MongoDB users.
```yaml
fos_elastica:
default_manager: mongodb
```
An example for using a repository:
```php
/** var FOS\ElasticaBundle\Manager\RepositoryManager */
$repositoryManager = $container->get('fos_elastica.manager');
/** var FOS\ElasticaBundle\Repository */
$repository = $repositoryManager->getRepository('UserBundle:User');
/** var array of Acme\UserBundle\Entity\User */
$users = $repository->find('bob');
```
For more information about customising repositories, see the cookbook entry
[Custom Repositories](custom-repositories.md).
Using a custom query builder method for transforming results
------------------------------------------------------------
When returning results from Elasticsearch to be transformed by the bundle, the default
`createQueryBuilder` method on each objects Repository class will be called. In many
circumstances this is not ideal and you'd prefer to use a different method to join in
any entity relations that are required on the page that will be displaying the results.
```yaml
user:
elastica_to_model_transformer:
query_builder_method: createSearchQueryBuilder
```
Advanced Searching Example
--------------------------
If you would like to perform more advanced queries, here is one example using
the snowball stemming algorithm.
It searches for Article entities using `title`, `tags`, and `categoryIds`.
Results must match at least one specified `categoryIds`, and should match the
`title` or `tags` criteria. Additionally, we define a snowball analyzer to
apply to queries against the `title` field.
Assuming a type is configured as follows:
```yaml
fos_elastica:
indexes:
site:
settings:
index:
analysis:
analyzer:
my_analyzer:
type: snowball
language: English
types:
article:
mappings:
title: { boost: 10, analyzer: my_analyzer }
tags:
categoryIds:
persistence:
driver: orm
model: Acme\DemoBundle\Entity\Article
provider: ~
finder: ~
```
The following code will execute a search against the Elasticsearch server:
```php
$finder = $this->container->get('fos_elastica.finder.site.article');
$boolQuery = new \Elastica\Query\Bool();
$fieldQuery = new \Elastica\Query\Text();
$fieldQuery->setFieldQuery('title', 'I am a title string');
$fieldQuery->setFieldParam('title', 'analyzer', 'my_analyzer');
$boolQuery->addShould($fieldQuery);
$tagsQuery = new \Elastica\Query\Terms();
$tagsQuery->setTerms('tags', array('tag1', 'tag2'));
$boolQuery->addShould($tagsQuery);
$categoryQuery = new \Elastica\Query\Terms();
$categoryQuery->setTerms('categoryIds', array('1', '2', '3'));
$boolQuery->addMust($categoryQuery);
$data = $finder->find($boolQuery);
```