FOSElasticaBundle sort not working - symfony

The configuration of my elastica type looks like following:
acme_article:
mappings:
title: {type:string, index_analyzer:acme_analyzer}
content: {type:string, index_analyzer:acme_analyzer}
slug: ~
media: {type:string, index_analyzer:acme_analyzer}
categories:
type: "object"
properties:
name: ~
id: ~
instance:
type: "object"
properties:
name: ~
created_by: ~
created_at: ~
I have Repository class which extends FOS\ElasticaBundle\Repository and everything works well except sorting.
$query = new \Elastica\Query();
$query->setSort(array('created_at' => array('order' => 'desc')));
return $this->find($query);
Getting some irelevant result, totally without order. Then, I tried to add model id in index and try to sort by id but also without success.

Solution is to set {type:date} on created_at field. In that case ES sorting by timestamp and everything is ok.
Sorting by id is not working because in some way categories.id override main id and then I got results ordered by category.id instead entity id.

Related

Doctrine - Unable to persist entity with composite key

I'm using Doctrine with YML mappings. I have two entities. One Group entity and one User entity.
I'm trying to set it up so Users have unique names within a group.
I can create a User, assign it a Group, and save it to the DB. But when I try to create a User with the same name and a different Group, then I get an error saying the unique constraint on name is violated.
Why can't I persist that User?
Their mappings look like this:
Entity\Group:
type: entity
table: groups
id:
id:
type: guid
nullable: false
id: true
generator:
strategy: AUTO
fields:
name:
type: text
nullable: true
Entity\User:
type: entity
table: users
id:
group:
associationKey: true
nullable: false
name:
type: string
manyToOne:
Group:
targetEntity: Entity\Group
joinColumn:
name: group
referencedColumnName: id
I eventually figured this out. I was confusing Doctrine.
The non-standard thing was that I'm using a capitalized parameter name to represent an object and changing that to lowercase for the table column. I did this in the manyToOne: section using name:.
The documentation doesn't really do this, so I didn't know that I still had to reference the capitalized property name in the id: section and separately define the column name there.
So, I changed the User mapping to this:
Entity\User:
type: entity
table: users
id:
Group:
column: group
associationKey: true
nullable: false
name:
type: string
manyToOne:
Group:
targetEntity: Entity\Group
joinColumn:
name: group
referencedColumnName: id

ElasticSearch filter by wildcard

I'm trying to filter my results by wildcard character.
example of my records:
....
"_source" : {
"urlSlug" : "entry-title",
"revisions" : [
{
"title" : "Entry title",
"context" : "NKD"
}
]
}
each revision can have different context with different order.
And when I search for record I want to search only entities with context like "N". So I perform nested query with match_all and wildcard.
{"query":{"bool":{"must":[{"query_string":{"query":"*entry*"}},{"nested":{"path":"revisions","query":{"bool":{"should":[{"match_all":{}}],"filter":[{"wildcard":{"revisions.context":{"value":"*N*","boost":1}}}]}}}}]}},"size":10}
When I run query I get zero results. And Can't figure out how to restrict results.
I'm using for this FosElastica with following config:
indexes:
app:
types:
entity:
properties:
urlSlug: ~
revisions:
type: "nested"
properties:
title: { type: text,boost: 10 }
context: { type: text }
and my query builder looks like this:
$boolQuery = new ESQuery\BoolQuery();
$fieldQuery = new ESQuery\QueryString();
$fieldQuery->setQuery('*' . $query . '*');
$boolQuery->addMust($fieldQuery);
$nestedQuery = new ESQuery\Nested();
$nestedQuery->setPath('revisions');
$nestedBoolQuery = new ESQuery\BoolQuery();
$matchAllQuery = new ESQuery\MatchAll();
$nestedBoolQuery->addShould($matchAllQuery);
$filterQuery = new ESQuery\Wildcard();
$filterQuery->setValue('revisions.context','*N*');
$nestedBoolQuery->addFilter($filterQuery);
$nestedQuery->setQuery($nestedBoolQuery);
$boolQuery->addMust($nestedQuery);
$result = $finder->findHybrid($boolQuery,self::AUTOCOMPLETE_MAX_RESULTS);
ElasticSearch version 5.2.2
Well I did find out what is the problem in my query.
this is working one:
{"query":{"bool":{"must":[{"query_string":{"query":"*komi*"}}],"filter":[{"nested":{"path":"revisions","query":{"wildcard":{"revisions.context":{"value":"*n*","boost":1}}}}}]}},"size":10}
whole problem was uppercase wildcard search. I was looking for *N* and have zero results and with *n* it fine.

Self joins in Batmanjs

How can I implement the self-joins in Batmanjs?
In rails, as found here, it goes like this:
class Employee < ActiveRecord::Base
has_many :subordinates, class_name: "Employee", foreign_key: "manager_id"
belongs_to :manager, class_name: "Employee"
end
My current Batmanjs equivalent model looks like this:
class Employee extends Batman.Model
#resourceName: 'employees'
#storageKey: 'employees'
#persist Batman.LocalStorage
#has_many 'subordinates', name: "Employees", foreignKey: "manager_id"
#belongs_to 'manager', name: "Employee"
I think that should work, if you just switch:
has_many/belongs_to => hasMany/belongsTo
name: "Employees" => name: "Employee".
Also, you may have to add an encoder for id with the LocalStorage adapter. LocalStorage converts the value to a string, but batman.js expects an integer, so you have to coerce it back to integer in the encoder.
Here's an example of self-joins (you can copy-paste the encoder from there, too):
http://jsbin.com/cukapedo/18/edit
Pasted here for posterity:
class App.Color extends Batman.Model
#resourceName: 'color'
#persist Batman.LocalStorage
#encode 'name', 'source_color_id'
# required for numbers in localStorage:
#encode 'id',
encode: (val) -> +val
decode: (val) -> +val
#hasMany 'child_colors', name: 'Color', foreignKey: 'source_color_id'
#belongsTo 'source_color', name: 'Color'

Query builder join on one to many relationship

I have a LastTrait object that has a many to one relationship with my user object: one user can have many different lastTrait:
YOP\YourOwnPoetBundle\Entity\LastTrait:
type: entity
table: null
fields:
id:
type: integer
id: true
generator:
strategy: AUTO
traitCategoryID:
type: integer
verseID:
type: integer
manyToOne:
user:
targetEntity: User
inversedBy: lastTrait
joinColumn:
name: user_id
referencedColumnName: id
I need to retrieve the verseID knowing the userID and traitCategoryID. So I created a Repository for my LastTrait object:
<?php
namespace YOP\YourOwnPoetBundle\Repository;
use Doctrine\ORM\EntityRepository;
class LastTraitRepository extends EntityRepository {
public function findOneByTraitCategoryID($userID, $traitCategoryID)
{
return $this->getEntityManager()
->createQuery('SELECT l
FROM YOPYourOwnPoetBundle:LastTrait l
JOIN l.user u
WHERE l.traitCategoryID = :categoryID AND u.id = :userID')
->setParameters(array(
'categoryID' => $traitCategoryID,
'userID' => $userID,
))
->getResult();
}
}
?>
However, when I call :
$lastTrait = $this->getDoctrine()
->getRepository('YOPYourOwnPoetBundle:LastTrait')
->findOneByTraitCategoryID($user->getID(), $collector->getTraitCategory()->getID());
I always get NULL, even though I should get a LastTrait object back!
What am I doing wrong here?
I think you should be thinking more in entities and less in foreign keys.
Try this in your method.
public function findOneByTraitCategoryID($userID, $traitCategoryID)
{
return $this->findOneBy(
array(
'traitcategoryID' => $traitCategoryID,
'user' => $userID
));
}
You can call ->getVerse()->getId(); after this or return the id directly on the method (too narrow for future uses IMO).
This only works if a user only has one trait per category, otherwise you don't have enough data to get a specific trait.
The issue was that I forgot to register the repository in my entity:
YOP\YourOwnPoetBundle\Entity\LastTrait:
repositoryClass: YOP\YourOwnPoetBundle\Repository\LastTraitRepository
type: entity
table: null
fields:
id:
type: integer
id: true
generator:
strategy: AUTO
traitCategoryID:
type: integer
verseID:
type: integer
manyToOne:
user:
targetEntity: User
inversedBy: lastTrait
joinColumn:
name: user_id
referencedColumnName: id
After adding the repositoryClass line, my custom Repository class is called and I get the desired result.
However, #dhunter's answer made me realize that I didn't even need a custom Repository class. I can get the desired by simply using the regular findOneBy function from my controller:
$lastTrait = $this->getDoctrine()
->getRepository('YOPYourOwnPoetBundle:LastTrait')
->findOneBy(
array('user' => $user->getId(), 'traitCategoryID' => $collector->getTraitCategory()->getID())
);
Hard to tell. Try echoing either DQL or SQL before retreiving results:
$q = $this->getEntityManager()->createQuery(....);
die($q->getSql()); //or getDql();
If, for some reason, you have invalid mapping (which I don't see above) you should see it here.
Also, try wrapping method with try-catch with most general Exception catch and seeing if it catches anything...
Furthermore, what is bugging me is the fact that you get NULL back...

Symfony2 - EntityNotFoundException

I have a Stats entity that is related to a Journey entity through a ManyToOne association.
id:
index:
type: integer
fields:
idJourney:
type: string
// data fields...
manyToOne:
journey:
targetEntity: Journey
joinColumn:
name: idJourney
referencedColumnName: idJourney
The Journey is related to the Station entity through two ManyToOne association: one for the first Station of the Journey, one for the last.
id:
idjourney:
type: string
fields:
idFirstStation:
type: string
idLastStation:
type: string
// other info fields
manyToOne:
firstStation:
targetEntity: Station
joinColumn:
name: idFirstStation
referencedColumnName: idStation
lastStation:
targetEntity: Station
joinColumn:
name: idLastStation
referencedColumnName: idStation
Finally, the Station entity :
id:
idStation:
type: string
fields:
name:
type: string
// other station info
I retrieve a collection of Stats objects with all related sub-objects via a custom Repository method which works fine.
$statCollection = $statsRepository->getStatsByDateAndArea($date, $area);
//This retrieves the expected data
$statCollection[0]->getJourney()->getFirstStation()->getName();
However, iterating through the collection with a foreach loop doesn't work:
foreach($statCollection as $stat) {
$journey = $stat->getJourney(); // works fine
$firstStationId = $journey->getFirstStationId(); // works too
$firstStation = $journey->getFirstStation(); // still works, but returns a Proxies\AcmeAppPathWhateverBundleEntityStationProxy object instead of a AcmeAppPathWhateverBundleEntityStation, but this should be transparent (as per Doctrine documentation)
$firstStationName = $firstStation->getName(); // throws an EntityNotFoundException
}
Any idea what's going on ? Should I force Doctrine to fetch all sub entities ?
EDIT
The error message is fairly laconic :
EntityNotFoundException: Entity was not found.
Not very helpful...
I ended up querying explicitly for the full set of sub-entities in my custom repository method...
I changed this query :
->select('stats')
->leftJoin('stats.journey', 'j')
->leftJoin('j.firstStation', 'fs')
->leftJoin('j.lastStation', 'ls')
to :
->select('stats, j, fs, ls')
->leftJoin('stats.journey', 'j')
->leftJoin('j.firstStation', 'fs')
->leftJoin('j.lastStation', 'ls')
I guess Doctrine's use of Proxy objects are not all that transparent...

Resources