Boosting field in the mapping file not working with QueryString - symfony

I have an application where the users (clients) can activate different modules.
On the search, the user should be able to search a value in the entire index (for the modules he has activated).
The problem is that I also need to boost some field.
Currently I have this.
mapping.yml
indexes:
traveler:
client: default
finder: ~
types:
Country:
mappings:
name: { boost: 10 }
code:
# additional info (irrelevant)
persistence:
driver: orm
model: CoreBundle\Entity\Country
provider: ~
listener: ~
finder: ~
serializer:
groups: [Default, elastica]
Places:
mappings:
name:
address:
type: "object"
properties:
city: ~
region: ~
country: ~
persistence:
driver: orm
model: CoreBundle\Entity\Places
provider: ~
listener: ~
finder: ~
serializer:
groups: [Default, elastica]
And in my service I have this :
$index = $this->get('fos_elastica.finder.traveler');
$query = new \Elastica\Query\QueryString($search);
# if the client has the module activated
foreach ($this->getClient()->getServices() as $service) {
switch ($service->getService()->getTag()) {
case "countries":
$filters->addShould(
new Type('Country')
);
break;
case "places":
$filters->addShould(
new Type('Places')
);
break;
}
}
$query = new \Elastica\Query\Filtered($query, $filters);
// set result limit
$globalQuery = new Query();
$globalQuery->setQuery($query);
$globalQuery->setSize($limit);
// return result
return $index->find($query);
Now, for example, if I search "Germany", the "Country" result should be first in the result set, because I applied a boost to it in the yml mapping.
Instead, the first 5 or 6 results are for "Places" which have in their country address "Germany".
The boost is not applied (I tried with higher values, removing it ... results are not changed).
I found a way where it works, if I change my service like this, but I would prefer that it use the boost property defined in the mapping.
$query = new QueryString($search);
$query->setFields(array(
'_all',
'Country.name^10'
));
Am I doing something wrong ?
Is there another way to do this ?
Thanks !

Related

Empty relations when serializing with JMSSerializer

I am having troubles while writing a controller-action inside a Symfony project, that should return data (in this particular case orders of a web-shop). Yeah ... It's a kind of a REST-API. That route just get's called from some JavaScript. And the data has to be visualized on the client-side.
The Problem:
I cannot find out, why the serialization of related entities results in empty objects. In this example it is the user of an order, which is empty.
This is a sample output:
orders: [
{
id: 2,
created: '2016-05-04T11:40:27+00:00',
user: {},
}
]
When I do something like
$orders = $this->getDoctrine()->getRepository('AppBundle:Order')
->findAllCompleted();
$serializationContext->setSerializeNull(true);
$serializationContext->setGroups(['statistics']);
$json = $serializer->serialize($orders, 'json', $serializationContext);
$response = new Response($json, $statusCode, [
'Content-Type' => 'application/json',
]);
return $response;
... i get a nice JSON response from the server, but every related entity of each order, like let's say user is {} (empty).
Even if I dump the related entity before it gets serialized like so:
[...]
$myOrder = array_filter($orders, function($order) {
if ($order->getId() == 2) {
return true;
}
return false;
});
dump($myOrder[0]->getUser());
die();
... it results in an empty (unhydrated) entity.
But if I change this debugging code to:
$myOrder = array_filter($orders, function($order) {
if ($order->getId() == 2) {
return true;
}
return false;
});
dump($myOrder[0]->getUser()->getUsername());
die();
... I get a clear output (string) with the value of the username of that entity.
So I think the issue is about a non hydrated entity, and not the serializer or its wrong configuration.
How can I get the JMSSerializer to take care of the hydration of those related entities?
I didn't find any hint in the docs ...
BTW, this are the JMS entity configs of order and user.
AppBundle\Entity\User:
exclusion_policy: ALL
properties:
userMeta:
expose: true
address:
expose: true
username:
expose: true
email:
expose: true
isReseller:
expose: true
acceptPublicOrders:
expose: true
vatNumber:
expose: true
taxRate:
expose: true
AppBundle\Entity\Order:
exclusion_policy: NONE
properties:
id:
groups: ['statistics']
user:
groups: ['statistics']
configuration:
groups: ['statistics']
created:
groups: ['statistics']
invoiceItems:
groups: ['statistics']
exclude: true
I think your problem is caused by doctrine lazy loading maybe you can try to change the fetch mode of the User association to EAGER in your Order entity
#ManyToOne(targetEntity="Cart", cascade={"all"}, fetch="EAGER")
By default i think it doesn't fetch the associations unless you call it directly like you did here
dump($myOrder[0]->getUser()->getUsername());
https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html#annref-onetoone
Or this if you use DQL
14.7.6.6. Temporarily change fetch mode in DQL
http://doctrine-orm.readthedocs.io/en/latest/reference/dql-doctrine-query-language.html#temporarily-change-fetch-mode-in-dql
Edit : i was wrong
I made some tests everything worked fine with lazy loading or eager until i tried with groups, even if the fields are exposed you don't use the Default group so it only take the things with the 'statistics' group on it
Try to add the default group here
$serializationContext->setGroups(['Default','statistics']);
Or add the statistics group on your user fields both worked for me

Apply different filters on multiple index search

I'm using FOSElasticaBundle with Symfony 3. I want to search on different index in ES6.
I have 2 entities Dogs, Cats with a field name "owner" (entity User). Dogs and Cats have a field "name" (string), I want to search every Dogs and only Cats that have it owner set at userId.
Example:
User: #1 Bob
User: #2 Charle
Cat: #1 Ruf, owner #1
Cat: #2 Pat, owner #2
Dog: #1 Ruf
Dog: #2 Pat
If I'm Bob, and I write "Ruf". I want as result Cat#1, Dog#1 but if i write "Pat", I want as result Dog#2.
elastica.yml
fos_elastica:
clients:
default:
host: %elastic_host%
port: %elastic_port%
indexes:
dog:
finder: ~
client: default
types:
dog:
indexable_callback: 'getEnabled'
properties:
id:
type: integer
name: ~
persistence:
driver: orm
model: AppBundle\Entity\Dog
finder: ~
elastica_to_model_transformer:
ignore_missing: true
cat:
finder: ~
client: default
types:
cat:
indexable_callback: 'getEnabled'
properties:
name: ~
owner:
type: "object"
properties:
id: integer
persistence:
driver: orm
model: AppBundle\Entity\Cat
finder: ~
elastica_to_model_transformer:
ignore_missing: true
I'm searching in ES with the method:
public function search(User $user, $query)
{
$search = $this->indexManager->getIndex('dog')->createSearch();
$search->addIndex('cat');
$search->addType('dog');
$search->addType('cat');
$resultSet = $search->search($query);
return $this->formatResult($resultSet);
}
How can i do my search ? Should i use Filter on Cat ? Could i use one repository per indexes ?
You can search using multiple indexes using ruflin/elastica.
It would be something like:
$search = new Elastica\Search($client);
$search->addIndex('dog')->addIndex('cat');
try {
$searchResponse = $search->search($q);
return $searchResponse->getResults();
} catch (ResponseException $exception) {
return [];
}
I'm sorry, but I do not know how to do this with FOSElasticaBundle.
finder should search by all indexes:
#fos_elastica.finder.app
result query look like:
$boolQuery = new \Elastica\Query\BoolQuery();
/*****************DOG PART**********************/
$dogBoolQuery = new \Elastica\Query\BoolQuery();
$dogNameMatchQuery = new \Elastica\Query\Term();
$dogNameMatchQuery->setTerm('name', $query);
$dogTypeFilter = new \Elastica\Query\Type();
$dogTypeFilter->setType('dog');
$dogBoolQuery->addMust($dogNameMatchQuery);
$dogBoolQuery->addFilter($dogTypeFilter);
/***************************************/
/*****************CAT PART**********************/
$catBoolQuery = new \Elastica\Query\BoolQuery();
$catTypeFilter = new \Elastica\Query\Type();
$catTypeFilter->setType('cat');
$ownerNameTermQuery = new \Elastica\Query\Term();
$ownerNameTermQuery->setTerm('id', $user->getId());
$ownerQuery = new \Elastica\Query\HasParent($ownerNameTermQuery, 'owner');
$catBoolQuery->addFilter($catTypeFilter);
$catBoolQuery->addFilter($ownerQuery);
/***************************************/
$boolQuery->addShould($dogBoolQuery);
$boolQuery->addShould($catBoolQuery);
$searchQuery = new \Elastica\Query();
$searchQuery->setQuery($boolQuery);
$results = $this->finder->find($searchQuery);
I would do two separate searches, one for each index. If you need a single query for pagination purposes, then I would use the Elasticsearch api directly, as I'm not sure if the FosElasticaBundle supports this kind of searches.
You can find how to do multiple indices search in the Elasticsearch documentation using Curl calls to the ES api.

FOSElasticaBundle NumberFormatException error on populate

I use FOSElasticaBundle in my Symfony 2 project. Since today reindexing is resulting in the below error:
index: /app/hotel/1 caused MapperParsingException[failed to parse
[priceFrom]]; nested: NumberFormatException[For input string:
"410.00"];
In my doctrine orm yml the priceFrom field is defined as followed:
priceFrom:
type: decimal
nullable: true
precision: 7
scale: 2
comment: ''
column: price_from
My fos_elastica config looks like this (config.yml):
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
app:
types:
hotel:
mappings:
id: ~
active: ~
priceFrom: { type: integer }
persistence:
driver: orm
model: XXX\XXXBundle\Entity\Hotel
provider: ~
listener:
immediate: ~
finder: ~
The command I use to reindex: php app/console fos:elastica:populate
The above setup has worked until now. I hope someone can point my to the good direction to solve this problem.
Versions:
ruflin/elastica (2.1.0)
friendsofsymfony/elastica-bundle (v3.1.5)
symfony/symfony (v2.6.11)
PS: No other entities in my project are using a priceFrom field.
In mappings, you define PriceFrom as integer but then you pass a decimal.
I haven't tested it but it definitely seems the major candidate as the culprit.
Francesco Abeni is right with answer. If you are already pushed something in ES as integer (or ES defined it as integer) it will generate exception when you will try to save decimal data here.
I always explicitly specify type in mapping like:
id: {"type" : "integer"}
shop_id: {"type" : "integer"}
source: {"type" : "string", "index" : "not_analyzed"}
There I see two ways to solve problem.
index alias and index merge
specify type in mapping; kill index; populate in again
I used second variant on a dev :)

Sorting in FOSElasticaBundle

I use FOSElasticaBundle in my project to search on my Player entity. As i only want to search entities with the property isactive on a value 1, i followed the documentation on "Filtering Results and Executing a Default Query": FriendsOfSymfony/FOSElasticaBundle/README.md
$query = new \Elastica\Query\QueryString($searchterm);
$term = new \Elastica\Filter\Term(array('isactive' => true));
$filteredQuery = new \Elastica\Query\Filtered($query, $term);
$players = $this->get('fos_elastica.finder.xxx.player')->find($filteredQuery);
The configuration of my bundle looks like following:
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
serializer:
callback_class: FOS\ElasticaBundle\Serializer\Callback
serializer: serializer
indexes:
xxx:
client: default
types:
player:
mappings:
firstname: { boost: 3 }
lastname: { boost: 3 }
serializer:
groups: [elastica, Default]
persistence:
driver: orm
model: xxx\FrontendBundle\Entity\Player
listener: ~
provider: ~
finder: ~
Now i want to do some sorting and cut back the result with limit and offset. How can i achieve this?
I found a solution like
$finalQuery = new \Elastica\Query($boolQuery);
$finalQuery->setSort(array('price' => array('order' => 'asc')));
But i dont have an Elastica\Query object and the AbstractQuery didn't support this method. Same with
$elasticaQuery->addSort($sort);
What to do? Where to read?? ://
(in addition, if we are here already: what does {boost: 3} really do exactly?)
you have to create a generic Elastica\Query() object.
Then you can add sort to this query with ->addSort($sort)
And later you can assign a proper query with ->setQuery();
Your example should look like this
$query = new \Elastica\Query();
$query->addSort(array('price' => array('order' => 'asc')));
$q = new \Elastica\Query\QueryString($searchterm);
$term = new \Elastica\Filter\Term(array('isactive' => true));
$filteredQuery = new \Elastica\Query\Filtered($q, $term);
$query->setQuery($filteredQuery);
$players = $this->get('fos_elastica.finder.xxx.player')->find($query);
Boost allows you to make one field more\less important than other within a query.

How to get groups working for JMSSerializerBundle and FOSRestBundle?

I am trying to set up different groups to achieve different types of serialization of my entities depending on the context.
My config looks like this:
My\FooBundle\Entity\Asset:
exclusion_policy: ALL
access_type: public_method
properties:
id:
access_type: property
expose: true
groups: [fnord]
name:
expose: true
path:
expose: true
isInQuarantine:
expose: true
groups: [baz]
I expect that the group having properties should not be exposed unless the group is set.
I am trying to set the group in my controller via:
$view->setSerializationContext(SerializationContext::create()->setGroups(array('fnord')));
Yet there is no effect on what is exposed and what isn't. Even if I do not try to change the SerializationContext, the groups options seems to be always ignored.
I know that my config is working because I can toggle the properties via the expose flag.
Yet what am I doing wrong here?
I know this question is a bit old, but it may help others.
I encountered a similar issue.
This was because I had in my controler (that extends FOSRestControler) a method with multiple calls to
$this->getView()
You have to notice that this method creates a new View object.
That means, if you call multiple getView methods, the context get reset.
Have a look at the following code that worked for my app :
use FOS\RestBundle\Controller\FOSRestController as Controller;
class RestController extends Controller
{
public function getUserAction($username)
{
$view = $this->view();
$view->setSerializationContext(SerializationContext::create()->setGroups(array('Product')));
$user = $this->getDoctrine()->getManager()->getRepository('VendorUserBundle:User')->findOneByUsername($username);
if(!is_object($user))
{
throw $this->createNotFoundException();
}
$view->setData($user);
return $view;
}
}
In Model.User.yml file :
FOS\UserBundle\Model\User:
exclusion_policy: ALL
properties:
username:
expose: true
groups: [fnord]
email:
expose: true
groups: [Product]
enabled:
expose: true
groups: [Product]
roles:
expose: true
groups: [Product]
Gives the following output :
{"email":"guiguiboy#xxx.com","enabled":true,"roles":["ROLE_XXX"]}
I didn't have any cache related problems (dev env used).

Resources