Search code examples
symfonyfilterelasticsearchelastica

Fos elastica Filter


I try to filter my elastica query by language. My query works fine, but when i add the filter, i get 0 result.

My entity :

<?php

namespace Youmiam\RecipeBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use JMS\Serializer\Annotation\ExclusionPolicy;
use JMS\Serializer\Annotation\Expose;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\UploadedFile;

/**
 * Product
 *
 * @ORM\Table(name="product")
 * @ExclusionPolicy("all")
 * @ORM\Entity(repositoryClass="Youmiam\RecipeBundle\Entity\ProductRepository")
 */
class Product
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="createdAt", type="datetime")
     */
    private $createdAt;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255, nullable=true)
     */
    private $name;

    /**
     * @ORM\ManyToOne(targetEntity="Youmiam\RecipeBundle\Entity\Brand", inversedBy="products")
     * @ORM\JoinColumn(name="brand_id", referencedColumnName="id")
    */
    private $brand;

    /**
     * @ORM\ManyToOne(targetEntity="Youmiam\RecipeBundle\Entity\Ingredient", inversedBy="products")
     * @ORM\JoinColumn(name="ingredient_id", referencedColumnName="id")
     * @Expose
     */
    private $ingredient;

    /**
     * @ORM\ManyToMany(targetEntity="Youmiam\RecipeBundle\Entity\Recipe", inversedBy="products")
     * @ORM\JoinTable(name="products__recipes")
     */
    private $recipes;

    /**
     * @ORM\OneToMany(targetEntity="Youmiam\RecipeBundle\Entity\Quantity", mappedBy="product", orphanRemoval=true, cascade={"all"})
     */
    private $quantities;

    /**
     * @var \stdClass
     *
     * @ORM\Column(name="photo", type="string", length=255, nullable=true)
     * @Expose
     */
     private $photo;

    /**
     * @Assert\File(maxSize="6000000")
     */
     private $file;

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set createdAt
     *
     * @param \DateTime $createdAt
     * @return Product
     */
    public function setCreatedAt($createdAt)
    {
        $this->createdAt = $createdAt;

        return $this;
    }

    /**
     * Get createdAt
     *
     * @return \DateTime 
     */
    public function getCreatedAt()
    {
        return $this->createdAt;
    }

    /**
     * Set name
     *
     * @param string $name
     * @return Product
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string 
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set ingredient
     *
     * @param \Youmiam\RecipeBundle\Entity\Ingredient $ingredient
     * @return Product
     */
    public function setIngredient(\Youmiam\RecipeBundle\Entity\Ingredient $ingredient = null)
    {
        $this->ingredient = $ingredient;

        return $this;
    }

    /**
     * Get ingredient
     *
     * @return \Youmiam\RecipeBundle\Entity\Ingredient 
     */
    public function getIngredient()
    {
        return $this->ingredient;
    }

    /**
     * Set brand
     *
     * @param \Youmiam\RecipeBundle\Entity\Brand $brand
     * @return Product
     */
    public function setBrand(\Youmiam\RecipeBundle\Entity\Brand $brand = null)
    {
        $this->brand = $brand;

        return $this;
    }

    /**
     * Get brand
     *
     * @return \Youmiam\RecipeBundle\Entity\Brand 
     */
    public function getBrand()
    {
        return $this->brand;
    }

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->recipes = new \Doctrine\Common\Collections\ArrayCollection();
        $this->quantities = new \Doctrine\Common\Collections\ArrayCollection();
    }

    /**
     * Set photo
     *
     * @param string $photo
     * @return Product
     */
    public function setPhoto($photo)
    {
        $this->photo = $photo;

        return $this;
    }

    /**
     * Get photo
     *
     * @return string 
     */
    public function getPhoto()
    {
        return $this->photo;
    }

    /**
     * Sets file.
     *
     * @param UploadedFile $file
     */
    public function setFile(UploadedFile $file = null)
    {
        $this->file = $file;
    }

    /**
     * Get file.
     *
     * @return UploadedFile
     */
    public function getFile()
    {
        return $this->file;
    }
    /**
     * Add recipes
     *
     * @param \Youmiam\RecipeBundle\Entity\Recipe $recipes
     * @return Product
     */
    public function addRecipe(\Youmiam\RecipeBundle\Entity\Recipe $recipes)
    {
        $this->recipes[] = $recipes;

        return $this;
    }

    /**
     * Remove recipes
     *
     * @param \Youmiam\RecipeBundle\Entity\Recipe $recipes
     */
    public function removeRecipe(\Youmiam\RecipeBundle\Entity\Recipe $recipes)
    {
        $this->recipes->removeElement($recipes);
    }

    /**
     * Get recipes
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getRecipes()
    {
        return $this->recipes;
    }

    /**
     * Add quantities
     *
     * @param \Youmiam\RecipeBundle\Entity\Quantity $quantities
     * @return Product
     */
    public function addQuantity(\Youmiam\RecipeBundle\Entity\Quantity $quantities)
    {
        $this->quantities[] = $quantities;

        return $this;
    }

    /**
     * Remove quantities
     *
     * @param \Youmiam\RecipeBundle\Entity\Quantity $quantities
     */
    public function removeQuantity(\Youmiam\RecipeBundle\Entity\Quantity $quantities)
    {
        $this->quantities->removeElement($quantities);
    }

    /**
     * Get quantities
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getQuantities()
    {
        return $this->quantities;
    }

    /**
     * get the class Name
     * @return string $className
     */
    public function getClass()
    {
        return "Product";
    }

    /**
     * @return Language
     */
    public function getBrandLanguage()
    {
        return $this->brand->getLanguage();
    }
}

My Config.yml

fos_elastica:
    clients:
        default: { host: localhost, port: 9200 }
    indexes:
        youmiam:
            settings:
                index:
                    analysis:
                        analyzer:
                            keyword_analyser:
                                type: custom
                                tokenizer: keyword
                            classic_analyser:
                                type: custom
                                tokenizer: lowercase
                                filter   : [my_snow,asciifolding]
                            ingr_analyser:
                                type: custom
                                tokenizer: lowercase
                                filter   : [my_ing_ngram,asciifolding]
                            recipe_analyser:
                                type: custom
                                tokenizer: lowercase
                                filter   : [my_recipe_ngram,asciifolding]
                            testfollower:
                                type: stop
                                stopwords : [',']
                        filter:
                            my_snow:
                                type : "snowball"
                                language : "French"
                            my_ing_ngram:
                                type: "nGram"
                                min_gram: 3
                                max_gram: 8
                            my_recipe_ngram:
                                type: "nGram"
                                min_gram: 4
                                max_gram: 10
                        char_filter:    
                            my_whtoa :
                                type : mapping
                                mappings : ["' '=>a",]
            client: default
            finder: ~
            types:
                product:
                    mappings:
                        name: { boost: 10, analyzer: classic_analyser }
                        brand: { boost: 10, analyzer: classic_analyser }
                        ingredient: { boost: 10, analyzer: classic_analyser }
                        brandLanguage: { boost: 10 }
                    persistence:
                        driver: orm
                        model: Youmiam\RecipeBundle\Entity\Product
                        provider: ~
                        listener: ~
                        finder: ~
                        repository: Youmiam\RecipeBundle\SearchRepository\ProductRepository

And then, in my fos elastica repository, i have this code :

$boolQuery = new \Elastica\Query\Bool();
        $query = new \Elastica\Query;
        $queryString = new \Elastica\Query\QueryString();
        $queryString->setQuery($searchText);
        $queryString->setAnalyzer('classic_analyser');
        $queryString->setFields(array('product.name', 'product.brand', 'product.ingredient'));
        $boolQuery->addMust($queryString);
        $query->setQuery($boolQuery);

        $filter = new \Elastica\Filter\Term();
        $filter->setTerm('brandLanguage', 'fr');

        $query->setPostFilter($filter);
        return $this->find($query);

I tried to put my query directly in a controller, but same result. I really don't know why my filter return no result.

Hope someone could help, cause i'm really don't see the answer


Solution

  • First thing to do is testing your query outside Elastica, you can get the JSON from the Sf2 Profiler or from the logs.

    Next, make sure your documents are sent in Elasticsearch the way you want (perform a simple GET /youmiam/product/_search request to see them, in a tool like Sense).

    I see that your brandLanguage field is using the standard analyzer, you can see how Elasticsearch index it with a query like this: GET /youmiam/_analyze?field=brandLanguage&text=FR - is there a token with the exact value fr? If not, there will be no match.

    Best practice is to set this kind of field as "not_analyzed" too.

    Beside that there is no issue with your code (you could use a FilteredQuery instead of postFilter but that's not the issue), you should look more closely at what is indexed and how to query it directly via Sense, and then translate it with Elastica.