Git Product home page Git Product logo

search-bundle's Introduction

Symfony Search Bundle

This package will help you get your data indexed in a dedicated Search Engine


New package

You're looking at the new major version of this package. If your looking for the previous one, it was moved to the 2.x branch.


Table of Contents

Compatibility

This package is compatible with Symfony 3.4 and higher.

If your app runs an older version, you can use the previous version, available on the 2.x branch.

Install

With composer

composer require algolia/search-bundle

Configuration

The following configuration assume you are using Symfony/demo project.

algolia_search:
  prefix: demoapp_
  indices:
    - name: posts
      class: App\Entity\Post

    - name: comments
      class: App\Entity\Comment
      enable_serializer_groups: true

Credentials

You will also need to provide Algolia App ID and Admin API key. By default they are loaded from env variables ALGOLIA_APP_ID and ALGOLIA_API_KEY.

If you don't use env variable, you can set them in your parameters.yml.

parameters:
    env(ALGOLIA_APP_ID): K7MLRQH1JG
    env(ALGOLIA_API_KEY): 0d7036b75416ad0c811f30536134b313

Search

In this example we'll search for posts.

$em           = $this->getDoctrine()->getManager();
$indexManager = $this->get('search.index_manager');

$posts = $indexManager->search('query', Post::class, $em);

Note that this method will return an array of entities retrieved by Doctrine object manager (data are pulled from the database).

If you want to get the raw result from Algolia, use the rawSearch method.

$indexManager = $this->get('search.index_manager');

$posts = $indexManager->rawSearch('query', Post::class);

Pagination

To get a specific page, define the page (and nbResults if you want).

$em           = $this->getDoctrine()->getManager();
$indexManager = $this->get('search.index_manager');

$posts = $indexManager->search('query', Post::class, $em, 2);
// Or
$posts = $indexManager->search('query', Post::class, $em, 2, 100);

Count

$indexManager = $this->get('search.index_manager');

$posts = $indexManager->count('query', Post::class);

Advanced search

Pass anything you want in the parameters array. You can pass it in any search-related method.

$indexManager = $this->get('search.index_manager');

$posts = $indexManager->count('query', Post::class, 0, 10, ['filters' => 'comment_count>10']);

Index entities

Automatically

The bundle will listen to postPersist and preRemove doctrine events to keep your data in sync. You have nothing to do.

Manually

If you want to update a post manually, you can get the IndexManager from the container and call the index method manually.

$em           = $this->getDoctrine()->getManager();
$indexManager = $this->get('search.index_manager');
$post         = $em->getRepository(Post::class)->findBy(['author' => 1]);

$indexManager->index($post, $em);

Normalizers

By default all entities are converted to an array with the built-in (Symfony Normalizers)[https://symfony.com/doc/current/components/serializer.html#normalizers] (GetSetMethodNormalizer, DateTimeNormalizer, ObjectNormalizer...) which should be enough for simple use case, but we encourage you to write your own Normalizer to have more control on what you send to Algolia or to simply avoid (circular references)[https://symfony.com/doc/current/components/serializer.html#handling-circular-references].

Symfony will use the first one to support your entity or format.

Note that the normalizer is called with searchableArray format.

Custom Normalizers

Using a dedicated normalizer

You can create a custom normalizer for any entity. The following snippet shows a simple CommentNormalizer. Normalizer must implement Symfony\Component\Serializer\Normalizer\NormalizerInterface interface.

<?php

namespace App\Serializer\Normalizers;

use App\Entity\Comment;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

class CommentNormalizer implements NormalizerInterface, SerializerAwareInterface
{
    use SerializerAwareTrait;
    
    /**
     * Normalizes an Comment into a set of arrays/scalars.
     */
    public function normalize($object, $format = null, array $context = array())
    {
        return [
            'post_id'   => $object->getPost()->getId(),
            'content'   => $object->getContent(),
            'createdAt' => $this->serializer->normalize($object->getCreatedAt(), $format, $context),
            'author'    => $this->serializer->normalize($object->getAuthor(), $format, $context),
        ];
    }

    public function supportsNormalization($data, $format = null)
    {
        return $data instanceof Comment;
    }
}
<?php

namespace App\Serializer\Normalizers;

use App\Entity\User;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

class UserNormalizer implements NormalizerInterface
{
    /**
     * Normalizes an Comment into a set of arrays/scalars.
     */
    public function normalize($object, $format = null, array $context = array())
    {
        return [
            'username' => $object->getUsername(),
            'id'       => $object->getAuthor()->getFullName(),
        ];
    }

    public function supportsNormalization($data, $format = null)
    {
        return $data instanceof User;
    }
}

Don't forget to create the new services for your newly created Normalizers (if you don't rely on autowiring). You can find an example on the (Symfony documentation)[http://symfony.com/doc/current/serializer.html#adding-normalizers-and-encoders].

In our case, it will be:

<service id="comment_normalizer" class="App\Serializer\Normalizer\CommentNormalizer" public="false">
    <tag name="serializer.normalizer" />
</service>
<service id="comment_normalizer" class="App\Serializer\Normalizer\UserNormalizer" public="false">
    <tag name="serializer.normalizer" />
</service>

Using normalize method in entity

To define the normalize method in the entity class.

  1. Implement Symfony\Component\Serializer\Normalizer\NormalizableInterface
  2. Define normalize method

Example

<?php

public function normalize(NormalizerInterface $normalizer, $format = null, array $context = array()): array
{
	return [
		'title'   => $this->getTitle(),
		'content' => $this->getContent(),
		'author'  => $this->getAuthor()->getFullName(),
	];
}

Create Normalizer for Algolia\SearchBundle only

Sometimes, you want to create a specific Normalizer just to send data to Algolia. But what happens if you have 2 normalizers for the same class ? Well you can add a condition so your Normalizer will only be called when Indexing through Algolia.

When you create your Normalizers, you can add an additionnal check for the format :

<?php

use Algolia\SearchBundle\Searchable; 

class UserNormalizer implements NormalizerInterface
{
    ... 

    public function supportsNormalization($data, $format = null)
    {
        return $data instanceof User && $format == Searchable::NORMALIZATION_FORMAT;
    }
}

Though it's not really encouraged to add this kind of logic inside the normalize method, you could also add/remove some specific fields on your entity for Algolia this way:

<?php 

use Algolia\SearchBundle\Searchable; 

class UserNormalizer implements NormalizerInterface
{
    public function normalize($object, $format = null, array $context = array())
    {
        if ($format == Searchable::NORMALIZATION_FORMAT) {
            // 'id' of user will be provided only in Algolia usage
            return ['id' => $object->getId()];
        }
        
        return [
            'username' => $object->getUsername(),
            'email'    => $object->getEmail(),
        ];
    }
    
    ...
}

Using normalizer groups

You can also rely on (@Group annotation)[https://symfony.com/doc/current/components/serializer.html]. The name of the group is searchable.

You have to explicitly enable this feature on your configuration:

algolia_search:
  prefix: demoapp_
  indices:
    - name: posts
      class: App\Entity\Post

    - name: comments
      class: App\Entity\Comment
      enable_serializer_groups: true

In the example below, $comment, $author and $createdAt data will be sent to Algolia.

<?php

namespace App\Entity;

use Symfony\Component\Serializer\Annotation\Groups;

class Comment
{
    public $createdAt;
    public $reviewer;

    /**
     * @Groups({"searchable"})
     */
    public $comment;
    
    /**
     * @Groups({"searchable"})
     */
    public $author;
    
    /**
     * @Groups({"searchable"})
     */
    public function getCreatedAt()
    {
        return $this->createdAt;
    }
}

Engine

The NullEngine

The package ships with a NullEngine. This engine implements the EngineInterface and return an empty array, zero or null depending on the method.

You can use it for your test for instance but also in dev environment.

Using another engine

Let's say you want to use the NullEngine in your dev environment. You can override the service definition in your config/dev/serices.yaml this way:

services:
    search.engine:
        class: Algolia\SearchBundle\Engine\NullEngine

Or in XML

<services>
    <service id="search.engine" class="Algolia\SearchBundle\Engine\NullEngine" />
</services>

This is also how you can use a custom engine, to handle another search engine or extend Algolia' default engine.

Using the Algolia Client (Advanced)

In some cases, you may want to access the Algolia client directly to perform advanced operations (like manage API keys, manage indices and such).

By default the AlgoliaSearch\Client in not public in the container, but you can easily expose it. In the service file of your project, config/serices.yaml in a typical Symfony4 app, you can alias it and make it public with the following code:

services:
    algolia.client:
        alias: algolia_client
        public: true

Or in XML

<services>
    <service id="algolia.client" alias="algolia_client" public="true" />
</services>

Example

Here is an example of how to use the client after your registered it publicly.

class TestController extends Controller
{
    public function testAction()
    {
        $algoliaClient = $this->get('algolia.client');
        var_dump($algoliaClient->listIndexes());

        $indexManager = $this->get('search.index_manager');
        $index = $algoliaClient->initIndex(
            $indexManager->getFullIndexName(Post::class)
        );

        var_dump($index->listApiKeys());

        die;
    }
}

Tests

The tests require ALGOLIA_APP_ID and ALGOLIA_API_KEY to be defined in the environment variables.

ALGOLIA_APP_ID=XXXXXXXXXX ALGOLIA_API_KEY=4b31300d70d70b75811f413366ad0c ./vendor/bin/phpunit

About AlgoliaSyncEngine

In Algolia, all indexing operations are asynchronous. The API will return a taskID and you can check if this task is completed or not via another API endpoint.

For test purposes, we use the AlgoliaSyncEngine. It will always wait for task to be completed before returning. This engine is only autoloaded during in the tests, if you will to use it in your project, you can copy it into your app and modify the search.engine service definition.

search-bundle's People

Contributors

alcaeus avatar alessandrominoccheri avatar algoliareadmebot avatar brunonic avatar chrjean avatar djfm avatar gregquat avatar julienbourdeau avatar matts2cant avatar maxiloc avatar mpclarkson avatar mrardon avatar nainterceptor avatar piotrplenik avatar rayrutjes avatar redox avatar tristanbes avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.