Symfony FS/SolrBundle search within multiple entities - symfony

Is it possible to set up a search for multiple entities, using the Florian Semm Solr Bundle in Symfony? I'm completely lost, have looked in the documentation of Solarium itself, but couldn't figure out how to set up a search for our Symfony project.
Here's what I did so far:
Solarium and the SolrBundle are both successfully installed.
I indexed 3 entities (for now) with the Solr annotations like that:
/**
* #Solr\Document(repository="UserBundle\Entity\User")
* #Solr\Document(index="*")
*/
class User {
/**
* #Solr\Id
*/
protected $id;
/**
* #Solr\Field(type="string")
*/
protected $firstName;
/**
* #Solr\Field(type="string")
*/
protected $lastName;
}
I set up a controller where I call the solr.client but that's basically how far I got. I can show you my code but it's throwing error messages, because I'm basically just trying around:
class SearchController extends Controller {
/**
* #Route("/search-result/", name="searchBundle")
*/
public function searchAction(Request $request) {
$client = $this->get('solr.client');
$query = $client->createSelect();
$query->setQuery($request->query->all());
// this executes the query and returns the result
$resultset = $client->execute($query);
return $this->render('SearchBundle::search.html.twig', array(
'resultset' => $resultset
));
}
}
How do I get the controller/the bundle to search within all the three bundles/indexed properties?
How do I structure the output?
Couldn't find any tutorials/example codes/guidelines for that specific bundle unfortunately.

What you probably want to do inside your controller is get a repository for the search;
$resultset = $this->get('solr.client')->getRepository('UserBundle:User')->findAll();
return $this->render('SearchBundle::search.html.twig', array(
'resultset' => $resultset
));
Source: https://github.com/floriansemm/SolrBundle#query-a-field-of-a-document

Related

FOSElasticaBundle and FPNTagBudle index tags

I am using FPNTagBudle for tagging my documents and FOSElasticaBundle for indexing them into elasticsearch index.
With FPNTagBudle to load tags into object you need to use loadTagging method like so:
$tagManager = $this->get('fpn_tag.tag_manager');
$tagManager->loadTagging($object);
When a object is edited from form, I have tags loaded so everything works fine and when object is saved index is build up properly. The problem is when I run fos:elastica:populate command to populate all object the tagging is skipped, becasue tagging is not loaded then.
I tried to hook to PRE_TRANSFORM event and loadTagging there but then it messes with the form, because new tags added from the form are wiped by calling loadTagging the second time.
Is it possible to recognize in PRE_TRANSFORM hook that this is populate command so I could loadTagging only then? Or maybe my problem is more fundamental?
I decided to go with PRE_TRANSFORM event and distinguishing whether it comes from form or populate command with php_sapi_name(). Below whole solution:
Subscriber:
<?php
namespace AppBundle\EventSubscriber;
use FOS\ElasticaBundle\Event\TransformEvent;
use FPN\TagBundle\Entity\TagManager;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Class ElasticaTransformSubscriber
* #package AppBundle\EventSubscriber
*/
class ElasticaTransformSubscriber implements EventSubscriberInterface
{
/** #var TagManager */
private $tagManager;
/**
* ElasticaTransformSubscriber constructor.
* #param $tagManager
*/
public function __construct(TagManager $tagManager)
{
$this->tagManager = $tagManager;
}
/**
* #param TransformEvent $event
*/
public function preTransformOperations(TransformEvent $event)
{
if (php_sapi_name() === 'cli') {
$object = $event->getObject();
$this->tagManager->loadTagging($object);
}
}
/**
* #return array
*/
public static function getSubscribedEvents()
{
return array(
TransformEvent::PRE_TRANSFORM => 'preTransformOperations',
);
}
}
Service:
app.subscriber.object_transformer_elastica:
class: AppBundle\EventSubscriber\ElasticaTransformSubscriber
arguments: ["#fpn_tag.tag_manager"]
tags:
- { name: kernel.event_subscriber }

Symfony - FOSRestBundle - show selected fields

I'm trying to show only selected fields in my REST action in controller.
I've found one solution - I can set groups in Entities/Models and select this group in annotation above action in my Controller.
But actually i don't want use groups, i want determine which fields i wanna expose.
I see one solution - I can create one group for every field in my Entities/Model. Like this:
class User
{
/**
* #var integer
*
* #Groups({"entity_user_id"})
*/
protected $id;
/**
* #var string
*
* #Groups({"entity_user_firstName"})
*/
protected $firstName;
/**
* #var string
*
* #Groups({"entity_user_lastName"})
*/
protected $lastName;
}
And then i can list fields above controller action.
My questions are:
Can I use better solution for this?
Can I list all groups? Like I can list all routes or all services.
This is mainly about serialization not about fosrestbundle itself.
The right way would be to create your own fieldserialization strategy.
This article got it down really nicely:
http://jolicode.com/blog/how-to-implement-your-own-fields-inclusion-rules-with-jms-serializer
It build a custom exclusion strategy as describeted here:
How do I create a custom exclusion strategy for JMS Serializer that allows me to make run-time decisions about whether to include a particular field?
Example code from first link for reference:
custom FieldExclusion strategy:
namespace Acme\Bundle\ApiBundle\Serializer\Exclusion;
use JMS\Serializer\Exclusion\ExclusionStrategyInterface;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;
use JMS\Serializer\Context;
class FieldsListExclusionStrategy implements ExclusionStrategyInterface
{
private $fields = array();
public function __construct(array $fields)
{
$this->fields = $fields;
}
/**
* {#inheritDoc}
*/
public function shouldSkipClass(ClassMetadata $metadata, Context $navigatorContext)
{
return false;
}
/**
* {#inheritDoc}
*/
public function shouldSkipProperty(PropertyMetadata $property, Context $navigatorContext)
{
if (empty($this->fields)) {
return false;
}
$name = $property->serializedName ?: $property->name;
return !in_array($name, $this->fields);
}
}
Interface
interface ExclusionStrategyInterface
{
public function shouldSkipClass(ClassMetadata $metadata, Context $context);
public function shouldSkipProperty(PropertyMetadata $property, Context $context);
}
usage
in controller or where you need it:
$context = new SerializationContext();
$fieldList = ['id', 'title']; // fields to return
$context->addExclusionStrategy(
new FieldsListExclusionStrategy($fieldList)
);
// serialization
$serializer->serialize(new Pony(), 'json', $context);
You should be also able to mix and match with groups eg. you can also set $content->setGroups(['myGroup']) together with the fieldExclusio

Symfony2 entity relationships not working as expected

This is either a huge bug in Symfony2 or I'm just not getting it. I've spent literally days trying to understand what is going on here. I have two entities:
Event
Date
I want a relationship where there are many dates to one event. Sounds simple enough, so in my Event entity I have:
/**
* #ORM\OneToMany(targetEntity="Date", mappedBy="event")
*/
protected $dates;
And in my Date entity I have:
/**
* #ORM\ManyToOne(targetEntity="Event", inversedBy="dates")
*/
private $event;
I have also generated a CRUD (doctrine:generate:crud) on the Event entity so that I may add events to the database. In the form builder in my EventType I have added:
->add('date', new DateType())
This is so that I may include the date field in the form, as per the Symfony documentation.
Now comes my problem.
Whenever I run doctrine:generate:entities my entities are created on the Event and Date entity, but they seem to be the wrong way around. On my Event entity I get:
/**
* Constructor
*/
public function __construct()
{
$this->dates = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add dates
*
* #param \Raygun\EventBundle\Entity\Date $dates
* #return Event
*/
public function addDate(\Raygun\EventBundle\Entity\Date $dates)
{
$this->dates[] = $dates;
return $this;
}
/**
* Remove dates
*
* #param \Raygun\EventBundle\Entity\Date $dates
*/
public function removeDate(\Raygun\EventBundle\Entity\Date $dates)
{
$this->dates->removeElement($dates);
}
/**
* Get dates
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getDates()
{
return $this->dates;
}
and on my Date entity I get:
/**
* Set event
*
* #param \Raygun\EventBundle\Entity\Event $event
* #return Date
*/
public function setEvent(\Raygun\EventBundle\Entity\Event $event = null)
{
$this->event = $event;
return $this;
}
/**
* Get event
*
* #return \Raygun\EventBundle\Entity\Event
*/
public function getEvent()
{
return $this->event;
}
Now when I try to load the form so I can add the event/date to the database I get
Neither the property "date" nor one of the methods "getDate()", "date()", "isDate()", "hasDate()", "__get()" exist and have public access in class "Raygun\EventBundle\Entity\Event".
It's like it should be adding getters and setters to the Event entity, NOT the Date entity. I'm really tearing my hair out with this and am thinking of ditching Symfony entirely, as it seems to go completely against logic.
if you want Form component automatically mappes the fields you should change date field to dates:
->add('dates', 'collection', [
'type' => new DateType()
])
or you can add mapped => false option to your date field to map it manually.
Your Event form type should contain a collection type for the protected $dates field, so this line is incorrect:
->add('date', new DateType())
It should be:
->add('dates', 'collection', array('type' => new DateType()))
Please have a look at this Symfony cookbook entry on how to work with form collections:
http://symfony.com/doc/current/cookbook/form/form_collections.html

Symfony2 best practices for stay DRY

I'm new to Symfony2. I have to learn it for my new job (it starts this monday). Before that, I used a lot CodeIgniter... so this change a bit.
After reading tons of documentations, tuts, best practices ... create my Own intranet for testing (customers has websites, websites has accesses, accesses has website, website has category, accesses has accesscategory) I still have some questions.
First Question :
When you have a website with frontend and backend you have all the time some repetitives actions like :
- create new entity
- read entity
- update entity
- delete entity
...
In CI, I create a BaseController and a BaseModel and with some extends, I was OK.
This practice is still OK for Symfony 2 or do Symfony have another way to handle that ?
Like AppBundle\Controller\BaseController extended by a AppBundle\Controller\AdminController (and FrontController) extended by AppBundle\Controller\MyEntityController ?
Because Actually, each time, in each controller I have the same code. When I edit an entity (for example), it's the same process : load the entity by id, throw exception if no entity, create and hydrate the form, handleRequest the post and valid the form, reidrect or display the view... but... I always cut/paste the same code... aweful T__T
So I'm searching for the best way to handle that
** Second Question : **
What is the best and elegent way to work with the DoctrineManager ?
Do I have to call it, each time in my actions ? $em = $this->get... or, can I create something like MyEntityManager which call the EntityManager and the repository of my entity ?
Actually, this is what I do :
I create an abstract AppBundle\Manager\BaseManager with loadAndFlush
<?php
namespace AppBundle\Manager;
abstract class BaseManager
{
protected function persistAndFlush($entity)
{
$this->em->persist($entity);
$this->em->flush();
}
}
Then, for each Entity, I create his own manager :
<?php
namespace AppBundle\Manager;
use Doctrine\ORM\EntityManager;
use AppBundle\Manager\BaseManager;
use AppBundle\Entity\Customer;
class CustomerManager extends BaseManager
{
/**
* #var EntityManager
*/
protected $em;
/**
* #param EntityManager $em
*/
public function __construct(EntityManager $em)
{
$this->em = $em;
}
/**
* #param $customerId
* #return null|object
*/
public function loadCustomer($customerId)
{
return $this->getRepository()
->findOneBy(array('id' => $customerId));
}
/**
* #param Customer $customer
*/
public function saveCustomer(Customer $customer)
{
$this->persistAndFlush($customer);
}
/**
* #return \Doctrine\ORM\EntityRepository
*/
public function getRepository()
{
return $this->em->getRepository('AppBundle:Customer');
}
}
Then, I define this manager as a service :
parameters:
app.customer_manager.class: AppBundle\Manager\CustomerManager
services:
app.customer_manager:
class: %app.customer_manager.class%
arguments: [#doctrine.orm.entity_manager]
And Then I use the service in my Controller :
/**
* #Route("/edit/{customerId}", name="customer_edit")
* #Security("has_role('ROLE_ADMIN')")
*/
public function editAction($customerId, Request $request)
{
if (!$customer = $this->get('app.customer_manager')->loadCustomer($customerId)) {
throw new NotFoundHttpException($this->get('translator')->trans('This customer does not exist.'));
}
$form = $this->get('form.factory')->create(new CustomerType(), $customer);
if($form->handleRequest($request)->isValid()) {
$this->get('app.customer_manager')->saveCustomer($customer);
$request->getSession()->getFlashBag()->add('notice', 'Client bien enregistré.');
return $this->redirect(
$this->generateUrl(
'customer_show', array(
'customerId' => $customer->getId()
)
)
);
}
return $this->render('default/customer/add.html.twig', array(
'form' => $form->createView(),
'customer' => $customer
));
}
Is it a good practice, is it too complicated ? Is there any better other way to process in symfony ?
For first question Symfony2 provides CRUD Generator, take a look at this.
For second one you should use Repository Pattern provided by framework, for more information about this checkout following links:
http://msdn.microsoft.com/en-us/library/ff649690.aspx
http://symfony.com/doc/current/book/doctrine.html#custom-repository-classes

entities in different bundles

I'm using Symfony 2 and I have two entities in different bundles like:
//this class overrides fos_user class
//User\UserBundle\Entity\User
class User extends BaseUser
{
//..
/**
* #ORM\OneToMany(targetEntity="News\AdminBundle\Entity\News", mappedBy="author_id")
*/
protected $news_author;
//...
}
//News\AdminBundle\Entity\News
class News
{
//...
/**
* #ORM\ManyToOne(targetEntity="\User\UserBundle\Entity\User", inversedBy="news_author")
* #ORM\JoinColumn(name="author_id", referencedColumnName="id")
*/
protected $news_author;
//...
}
Both classes (entities) works fine. I have successfully setup fos_user bundle with registration and other stuff. The same if for News class. Then I build relation between those two classes OneTo Many (User -> News) as it is shown in code. This also works fine without errors and I can add news that belongs to user. The problem is when I build a form with entity class like:
->add('year', 'entity', array(
'class' => 'NewsAdminBundle:News',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('u')
->groupBy('u.year')
->orderBy('u.year', 'DESC');
},))
This form shows me a years when news are posted (like archive). Years are showing fine, but when I submit (post) a form then I've got error:
Class User\UserBundle\Entity\News does not exist
I figure out that this error is connected with sentence
$form->bindRequest($request);
The problem is because I have two entities in different bundles. How can I solve this error?
Edit:
I solved the problem. When I run
php app/console doctrine:generate:entities User
php app/console doctrine:generate:entities News
then Doctrine generate getters and setters in User and News. In entity News it generates method
/**
* Add news_author
*
* #param User\UserBundle\Entity\News $newsAuthor
*/
public function addNews(User\UserBundle\Entity\News $newsAuthor)
{
$this->news_author[] = $newsAuthor;
}
I was not paying attention to this method and I change it to this
/**
* Add news_author
*
* #param News\AdminBundle\Entity\News $newsAuthor
*/
public function addNews(News\AdminBundle\Entity\News $newsAuthor)
{
$this->news_author[] = $newsAuthor;
}
Now everything works fine. Thanks for all answers.
/**
* #ORM\ManyToOne(targetEntity="User\UserBundle\Entity\User", inversedBy="news_author")
* #ORM\JoinColumn(name="author_id", referencedColumnName="id")
*/
protected $news_author;
You have to remove prefix backslash – see note in Doctrine documentation

Resources