Change ES data on nested object changes automatically - symfony

types:
product:
mappings:
title: { search_analyzer: custom_search_analyzer, index_analyzer: custom_index_analyzer, type: string }
status:
brand.name: { search_analyzer: custom_search_analyzer, index_analyzer: custom_index_analyzer, type: string }
brand:
type: "nested"
properties:
status: ~
persistence:
driver: orm
model: MyBundle\Entity\Product\Product
provider:
query_builder_method: customProductQueryBuilderElastica
listener: ~
finder: ~
This is my mappings for type product. customProductQueryBuilderElastica contains code which populates only products which have active status and have related brand status active. It is working perfectly if i change products from my admin.
what i want to do is when i change my brand status to inactive, all related products should be removed from ES.
For that i have used brand as nested of product and created listener for it as explained here and now i am able to change brand status for every products in my ES automatically but i want to remove such products when brand status sets to inactive. How can this be achieved in better way?.

After many tries. i finally achieved what i want. I am posting my code here and try to help others.
Thanks to #maercky. i have taken reference to his answer which is given here
Here is my config.yml file.
types:
product:
mappings:
title: { search_analyzer: custom_search_analyzer, index_analyzer: custom_index_analyzer, type: string }
status:
brand.name: { search_analyzer: custom_search_analyzer, index_analyzer: custom_index_analyzer, type: string }
brand:
type: "nested"
properties:
status: ~
persistence:
driver: orm
model: XXX\MyBundle\Entity\Product\Product
provider:
query_builder_method: customProductQueryBuilderElastica
listener: ~
finder: ~
This code will go to service.yml
fos_elastica.listener.brand.product:
class: 'XXX\MyBundle\Listener\ElasticaBrandListener'
arguments:
- #fos_elastica.object_persister.search.product
- ['postPersist', 'postUpdate', 'postRemove', 'preRemove']
- #fos_elastica.indexable
calls:
- [ setContainer, [ '#service_container', #fos_elastica.object_persister.search.product ] ]
tags:
- { name: 'doctrine.event_subscriber' }
and finally this is my Listener for Brand
<?php
namespace XXX\MyBundle\Listener;
use FOS\ElasticaBundle\Doctrine\Listener as BaseListener;
use Doctrine\Common\EventArgs;
use Symfony\Component\DependencyInjection\ContainerInterface;
use XXX\MyBundle\Entity\Supplier\Brand;
use FOS\ElasticaBundle\Persister\ObjectPersister;
class ElasticaBrandListener extends BaseListener
{
/** #var \Symfony\Component\DependencyInjection\ContainerInterface */
private $container;
private $objectPersisterProducts;
public function setContainer(ContainerInterface $container,ObjectPersister $objectPersisterProduct) {
$this->container = $container;
$this->objectPersisterProducts = $objectPersisterProduct;
}
/**
* #param Doctrine\Common\EventArgs $eventArgs
*/
public function postUpdate(EventArgs $eventArgs)
{
/** #var $brand Brand */
$brand = $eventArgs->getEntity();
if ($brand instanceof Brand) {
$this->scheduledForUpdate[] = $brand;
foreach ($brand->getProducts() as $product) {
$brand_status = $brand->getStatus();
$product_status = $product->getStatus();
if($brand_status == 'active' && $product_status == 'active'){
$this->objectPersisterProducts->replaceOne($product);
}else{
$this->objectPersisterProducts->deleteOne($product);
}
}
}
}
}
?>
All this works for me well and so i am contributing this for others.

Related

FOSElasticaBundle / Custom filter

I have an entity with
title, url, description, text, enabled,...
I want to search only through enabled = true articles.
How can I do it in elastica?
My config:
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
appletrh:
index_name: 'domain.com'
client: default
types:
products:
properties:
title: { type: string, analyzer: czech }
persistence:
driver: orm
model: Web\MagazineBundle\Entity\Article
elastica_to_model_transformer:
**query_builder_method: search**
provider: ~
listener: ~
finder: ~
EntityRepository search function
public function search()
{
$qb = $this->createQueryBuilder('p');
$qb->where('p.enabled = true');
return $qb;
}
Action:
public function searchAction(Request $request)
{
$keyword = $request->query->get('keyword');
$finder = $this->get('fos_elastica.finder.domain.articles');
$paginator = $this->get('knp_paginator');
$articles= $finder->createPaginatorAdapter($keyword);
$pagination = $paginator->paginate($articles, $request->query->get('page', 1), 12);
return $this->render('WebMagazineBundle:Search:search.html.twig', ['articles' => $pagination]);
}
I thought that this is the right solution, but it's still returning all of the data from DB.
I feel like you should use "provider", not "elastica_to_model_transformer" option in the bundle config for that purpose, like:
provider:
query_builder_method: search

elasticsearch doesn't returns all hits

I'm using Symfony 2.3 and ElasticSearchBundle 3.0. I implemented two fields for the search. The search works correctly but it doesn't display all results. For example: when I search for the a keyword, the number of hits are 33 hits but it returns only 10 results.
config.php
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
serializer:
callback_class: FOS\ElasticaBundle\Serializer\Callback
serializer: serializer
indexes:
hortis:
finder: ~
client: default
settings:
index:
analysis:
analyzer:
custom_search_analyzer:
type: custom
tokenizer: standard
filter : [standard, lowercase, asciifolding]
custom_index_analyzer:
type: custom
tokenizer: standard
filter : [standard, lowercase, asciifolding, custom_filter]
filter:
custom_filter:
type: edgeNGram
side: front
min_gram: 3
max_gram: 100
types:
business:
mappings:
name: { search_analyzer: custom_search_analyzer, index_analyzer: custom_index_analyzer, type:string }
enabled: ~
gouvernaurat: ~
delegation: ~
postal_code: ~
# activities.principal: { search_analyzer: custom_search_analyzer, index_analyzer: custom_index_analyzer, type:string }
activities :
type : object
properties :
principal : ~
persistence:
driver: orm
model: Toto\AdminBundle\Entity\EntityName
provider: ~
listener: ~
finder: ~
controller.php
public function searchEngineAction(Request $request) {
$finder = $this->container->get('fos_elastica.index.hortis.business');
// get data from both fields
$querystring = strip_tags($request->get('name'));
$querystring2 = strip_tags($request->get('location'));
$boolQuery = new \Elastica\Query\Bool();
// if both fields are empty then display all businesses
if (empty($querystring) and empty($querystring2)) {
$query = new \Elastica\Query\MatchAll();
$boolQuery->addMust($query);
} else {
// create a boolean query
if (!empty($querystring)) {
$fieldQuery = new \Elastica\Query\QueryString();
$fieldQuery->setFields(array('name', 'activities.principal'));
$fieldQuery->setQuery($querystring);
$boolQuery->addMust($fieldQuery);
}
if (!empty($querystring2)) {
$fieldQuery2 = new \Elastica\Query\QueryString();
$fieldQuery2->setFields(array(
'gouvernaurat', 'delegation', 'postal_code'));
$fieldQuery2->setQuery($querystring2);
$boolQuery->addMust($fieldQuery2);
}
}
// select only enbaled business
$enabled = new \Elastica\Query\Term();
$enabled->setTerm('enabled', true);
$boolQuery->addMust($enabled);
$findAll = \Elastica\Query::create($boolQuery);
$findAll->setSize(27);
// trigger search function
$elasticaResultSet = $finder->search($findAll);
dump($elasticaResultSet);
// get results from
$findbusinesses = $elasticaResultSet->getResults();
$noresult = '';
if (!$findbusinesses) {
$noresult = 'no result';
}
$em = $this->getDoctrine()->getManager();
$FrontSettings = $em->getRepository('TotoAdminBundle:FrontSettings')->getFrontSettings();
if (!$FrontSettings) {
throw $this->createNotFoundException('Unable to find frontSettings entity');
}
// get all categories and activities
$categories = $em->getRepository('TotoAdminBundle:Category')
->findBy(array(), array('order' => 'ASC'));
if (!$categories) {
throw $this->createNotFoundException('unable to find categories and activities');
}
$paginator = $this->get('knp_paginator');
$businesses = $paginator->paginate(
$findbusinesses, $this->get('request')->query->get('page', 1)/* page number */, 9/* limit per page */
);
return $this->render('TotoFrontBundle:Front:search_result.html.twig', array(
'querystring' => $querystring, 'businesses' => $businesses,
'FrontSettings' => $FrontSettings, 'noresult' => $noresult,
'categories' => $categories,
));
}
How can I display the all the hits?
Elasticsearch by default only returns the first 10 results. This setting can be modified by specifing the from and size parameters. Note that it rarely makes sense to display all results on one page, instead use a pagination with a controllable amount of viewed items.
If you want all hits on one page also consider using the scroll api as deep pagination can get very inefficiently when having a high amount of results.

Gedmo SoftdeleatableListener : No mapping found for field 'deletedAt'

I want to disable Gedmo\SoftDeleatable behaviour for some of my phpunit tests in Symfony2.
I wrote these lines to remove SoftDeleatableListener:
foreach ($em->getEventManager()->getListeners() as $eventName => $listeners) {
foreach ($listeners as $listener) {
if ($listener instanceof \Gedmo\SoftDeleteable\SoftDeleteableListener) {
$em->getEventManager()->removeEventListener($eventName, $listener);
}
}
}
But none of the listeners was identified as being an instance of SoftDeleteableListener.
So I added these lines in app/config.yml:
doctrine:
..
orm:
..
filters:
softdeleteable:
class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter
enabled: false
and these lines in Acme/MyBundle/Resources/config/services.yml:
services:
..
gedmo.listener.softdeleteable:
class: Gedmo\SoftDeleteable\SoftDeleteableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ #annotation_reader ] ]
But when I run php app/console doctrine:schema:update --dump-sql,
I get the following error message:
No mapping found for field 'deletedAt' on class 'Acme\MyBundle\Entity\Account'.
Any idea ?
Looks like you still need to add the property to the class.
/**
* #var \DateTime
* #ORM\Column(type="datetime")
*/
protected $deletedAt;
I have used the Trait. Only this Trait does not define the deletedAt. Where the trait TimestampableEntity defines the columns.
use SoftDeleteable;
I hope this helps

how to define variable in yaml symfony2

I'm not really familiar with YAML so I open parameters.yml and config.yml files to see example how to use parameters or variable in YAML.
parameters.yml:
parameters:
database_driver: pdo_mysql
database_host: 127.0.0.1
database_port: 3306
database_name: homlist
config.yml:
doctrine:
dbal:
driver: "%database_driver%"
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
But when I tried it with doctrine mapping yaml file like this:
parameters:
table_name: test
Mockizart\Bundle\BlogBundle\Entity\MockblogTag:
type: entity
table: "%table_name%"
it's error like this:
An exception occurred while executing 'SELECT count(DISTINCT %0_.id) AS sclr0 FROM %table_name% %0_':
this is my mapping file Resources\Config\Entity\MockblogTag
Mockizart\Bundle\BlogBundle\Entity\MockblogTag:
type: entity
table: mockblog_tag
indexes:
user_id:
columns:
- user_id
name:
columns:
- name
slug:
columns:
- slug
id:
id:
type: integer
nullable: false
unsigned: false
comment: ''
id: true
generator:
strategy: IDENTITY
fields:
dateCreated:
type: integer
nullable: false
unsigned: false
comment: ''
column: date_created
name:
type: string
nullable: false
length: 60
fixed: false
comment: ''
slug:
type: string
nullable: false
length: 100
fixed: false
comment: ''
totalPost:
type: integer
nullable: false
unsigned: false
comment: ''
column: total_post
manyToOne:
user:
targetEntity: ORD\UserBundle\Entity\User
joinColumn:
referencedColumnName: id
type: integer
nullable: false
unsigned: false
lifecycleCallbacks:
How to define variable in yaml symfony2 ?
The way of defining parameters it's correct, however I see from comments that your purpose is to configure the class used for User object:
As Cerad said you can't do that. But if you want to configure the class you use for the User, you can have a manager service class.
<?php
namespace YourNamespace\UserBundle\Manager;
use Doctrine\Common\Persistence\ObjectManager;
class UserManager
{
/**
* #var ObjectManager
*/
protected $em;
/**
* Your user class
*
* #var string
*/
protected $className;
public function __construct(ObjectManager $em, $class)
{
$this->em = $em;
$this->className = $class;
}
public function createInstance()
{
return new $this->className;
}
public function getRepository()
{
return $this->em->getRepository($this->className);
}
}
And the services definitions will be like this:
services:
your_user.manager:
class: YourNamespace\UserBundle\Manager\UserManager
arguments: ['#doctrine.orm.entity_manager', 'YourNamespace\UserBundle\Entity\User']
In your controller you can use this manager class like this:
$userManager = $this->get('your_user.manager');
$user = $userManager->createInstance();
I think this is a good way to have a central point when dealing with user object. And if someday for whatever reason you decide to use a different class for user you just modify the argument 'YourNamespace\UserBundle\Entity\User'.
Also in this way you can use 'YourNamespace\UserBundle\Entity\User' argument as parameter, so the definition will change to:
services:
your_user.manager:
class: Moneytablet\UserBundle\Manager\UserManager
arguments: ['#doctrine.orm.entity_manager', '%user_class%']
and in you parameters.yml you can have:
parameters:
user_class: YouNamespace\UserBundle\Entity\User
I really like working this way, you can create save(), remove() methods on manager class and so on. Also later on when creating new services you can inject this manager like a regular service if it's a dependency.
And if you want a new manager for a different entity, you can create a new service definition with different construct arguments, but with the same service class.

Gedmo\Translatable ignoring default locale that is set in config

I've decided to install "gedmo/doctrine-extensions" on Symfony to use Translatable.
It works fine, except that listener is ignoring default locale I've specified, always falling back to en_US.
Translatable is plugged in as service:
#config.yml
services:
gedmo.listener.translatable:
class: Gedmo\Translatable\TranslatableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ #annotation_reader ] ]
- [ setDefaultLocale, [ ru ] ]
- [ setTranslationFallback, [ true ] ]
And when I try to find() object in database it always fetches en_US instead of ru:
$test = $em->find('Vendor\Entity\Test', 1);
//outputs row with 'locale' = "en_US" from `ext_translations_test` table
Only if I specify language directly, like:
$test->setTranslatableLocale('ru');
$em->refresh($test);
It gives desired translation.
Is there any way to set default locale that will work?
UPDATE
Adding another call function in config.yml fixed the problem, altough now I'm not quite sure what setDefaultLocale() function actually does, as Gedmo\Translatable\TranslatableListener::$defaultLocale property provided with a most horrid commentary I've ever seen. Will try to find more...
services:
gedmo.listener.translatable:
class: Gedmo\Translatable\TranslatableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ #annotation_reader ] ]
- [ setDefaultLocale, [ ru ] ]
- [ setTranslatableLocale, [ ru ] ]
- [ setTranslationFallback, [ true ] ]
According to: https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/symfony2.md
Note: if you noticed, there's Acme\DemoBundle\Listener\DoctrineExtensionListener you will need to create this listener class if you use loggable or translatable behaviors. This listener will set the locale used from request and username to loggable. So, to finish the setup create Acme\DemoBundle\Listener\DoctrineExtensionListener
Make sure you have setup the kernel listener as well.
namespace Acme\DemoBundle\Listener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class DoctrineExtensionListener implements ContainerAwareInterface
{
/**
* #var ContainerInterface
*/
protected $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function onLateKernelRequest(GetResponseEvent $event)
{
$translatable = $this->container->get('gedmo.listener.translatable');
$translatable->setTranslatableLocale($event->getRequest()->getLocale());
}
public function onKernelRequest(GetResponseEvent $event)
{
$securityContext = $this->container->get('security.context', ContainerInterface::NULL_ON_INVALID_REFERENCE);
if (null !== $securityContext && null !== $securityContext->getToken() && $securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
$loggable = $this->container->get('gedmo.listener.loggable');
$loggable->setUsername($securityContext->getToken()->getUsername());
}
}
}
And add the following to your config file:
services:
extension.listener:
class: Acme\DemoBundle\Listener\DoctrineExtensionListener
calls:
- [ setContainer, [ #service_container ] ]
tags:
# translatable sets locale after router processing
- { name: kernel.event_listener, event: kernel.request, method: onLateKernelRequest, priority: -10 }
# loggable hooks user username if one is in security context
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }

Resources