Query builder join on one to many relationship - symfony

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...

Related

Search for Entity that has multiple tags in doctrine

I have a many to many relation between Document and Tag. So a Document can have several Tags's, and one Tag can be assigned to different Document's.
This is Tag
AppBundle\Entity\Tag:
type: entity
table: tags
repositoryClass: AppBundle\Repository\TagRepository
manyToMany:
documents:
targetEntity: Document
mappedBy: tags
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
label:
type: string
length: 255
unique: true
And Document
AppBundle\Entity\Document:
type: entity
table: documents
repositoryClass: AppBundle\Repository\DocumentRepository
manyToMany:
tags:
targetEntity: Tag
inversedBy: documents
joinTable:
name: documents_tags
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
title:
type: string
length: 255
Now I want to search for all Documents that has the tags animal and fiction. How can I achieve that with doctrine?
Something like
$repository = $this->getDoctrine()->getRepository('AppBundle:Document');
$query = $repository->createQueryBuilder('d');
$query ->join('d.tags', 't')
->where($query->expr()->orX(
$query->expr()->eq('t.label', ':tag'),
$query->expr()->eq('t.label', ':tag2')
))
->setParameter('tag', $tag)
->setParameter('tag2', $tag2)
wont do the job, because it returns all Documents that have either tag1 or tag2. But andX won't work too, because there is no single tag that has both labels.
You can achieve this with additional inner joins for each tag:
Example:
$em = $this->getDoctrine()->getManager();
$repository = $this->getDoctrine()->getRepository('AppBundle:Document');
$query = $repository->createQueryBuilder('d');
$query->innerJoin('d.tags', 't1', Join::WITH, 't1.label = :tag1');
$query->innerJoin('d.tags', 't2', Join::WITH, 't2.label = :tag2');
$dql = $query->getDql();
$result = $em->createQuery($dql)
->setParameter('tag1', 'LabelForTag1')
->setParameter('tag2', 'LabelForTag2')
->getResult();
Maybe this little image helps understanding what this query does. The whole circle represent all your documents. If you are only using one single join, the query will return either the green+red or the blue+red part.
Using an additional inner join, you will only get the intersection of the joins seen individually (which is only the red part).
If you have even more tags to search for, you can simply add another join for that.

Weird Doctrine Entity Behaviour

I have two entities, Invoice and InvoiceItem definined as follows in yml.
Invoice.orm.yml
AppBundle\Entity\Invoice:
type: entity
table: invoices
repositoryClass: AppBundle\Repository\InvoiceRepository
fields:
id:
id: true
type: integer
generator:
strategy: AUTO
......
oneToMany:
items:
targetEntity: InvoiceItem
mappedBy: invoice
deductions:
targetEntity: InvoiceDeduction
mappedBy: invoice
payments:
targetEntity: InvoicePayment
mappedBy: invoice
InvoiceItem.orm.yml
AppBundle\Entity\InvoiceItem:
type: entity
table: invoice_items
repositoryClass: AppBundle\Repository\InvoiceItemRepository
fields:
id:
id: true
type: integer
generator:
strategy: AUTO
....
manyToOne:
invoice:
targetEntity: Invoice
inversedBy: items
joinColumn:
invoice_id:
referencedColumnName: id
onDelete: CASCADE
My controller receives invoice details and saves the elements as illustrated below
public function createInvoiceAction(Request $request)
{
....
$invoice = new Invoice();
$invoice->setReference($ref);
$invoice->setInvoiceDate(\DateTime::createFromFormat('d/m/Y',$request->query->get('invoiceDate')));
$invoice->setDescription($request->query->get('summary'));
$invoice->setAttn($request->query->get('att'));
$invoice->setIsVatable($request->query->get('vattable') == 'true' ? 1 : 0);
$invoice->setAdditionalInstructions($request->query->get('additional'));
$invoice->setJob($job);
$invoice->setCurrency($request->query->get('currency'));
$invoice->setGeneratedBy($createdBy);
$invoice->setFile($filename);
$job->setStatus('Invoiced');
$em->persist($invoice);
$em->persist($job);
$em->flush();
$items = $request->query->get('items');
for($i=0;$i<count($items);$i++)
{
if($items[$i]['description']!= '' && $items[$i]['amount'] != '')
{
$item = new InvoiceItem();
$item->setDescription($items[$i]['description']);
$item->setAmount($items[$i]['amount']);
$item->setInvoice($invoice);
$em->persist($item);
$em->flush();
}
}
$deductions = $request->query->get('deductions');
for($i=0;$i<count($deductions);$i++)
{
if($deductions[$i]['description'] != '' && $deductions[$i]['value'] != '')
{
$deduction = new InvoiceDeduction();
$deduction->setDescription($deductions[$i]['description']);
$deduction->setValue($deductions[$i]['value']);
$deduction->setIsPercentage($deductions[$i]['isPercentage'] == 'true' ? 1 : 0);
$deduction->setInvoice($invoice);
$em->persist($deduction);
$em->flush();
}
}
$html = $this->renderView('AppBundle:Default:invoice.html.twig', array('invoice' => $invoice));
return new Response(
$html
);
}
invoice.html.twig which renders the invoice details including items and deductions.
.....
{% for item in invoice.items %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ item.description }}</td>
<td>{{ item.amount|number_format(0, '', ',') }}</td>
</tr>
{% endfor %}
.....
The challenge I'm having is that the $invoice object sent to the template is has no items or deductions which are supposed to be collections. I've checked in the database and can confirm that the items and deductions are persisted into the respective tables so I don't understand why there are no invoice.items or invoice.deductions in the tables.
Out of desperation I flushed after every InvoiceItem object creation thinking that maybe the template was rendered before the persist transaction was completed but this didn't help either.
UPDATE:
Atleast one other error I've spotted is the onDelete option being defined under joinColumn. It should be:
manyToOne:
invoice:
targetEntity: Invoice
inversedBy: items
joinColumn:
invoice_id:
referencedColumnName: id
onDelete: CASCADE
--------
If you are having this problem only when the view is rendered during createInvoiceAction, then the problem might be that since you don't actually add the InvoiceItem(s) to the Invoice object via a method called something like addItem() (the naming depends on how you name the relation in the invoice entity), those items might only be present in the Invoice entity the next time you query the invoice, because even though you have set the invoice of each item, it might be that if those entities were created just now, doctrine might not be able to retrieve them using the proxy classes. So the solution would be simple: use the addItem method on invoice entity to add the items to the invoice and then don't forget to add the logic that sets the invoice for the given item inside that method
public function addItem(InvoiceItem $item)
{
$item->setInvoice($this);
$this->items[] = $item;
return $this;
}
However if this problem is still present the next time you query for the already persisted invoice and its items, I would suggest you check whether the object actually contains the getter methods and you aren't simply trying to access the items via a public property. In other words inside the invoice class the property named items should be defined as private, and you should have a getItems() method, which retrieves the items.
Objects used with doctrine must have these methods, since it overrides the logic inside those methods using its proxy classes in order to add code that would also send out the query to retrieve the, in your case, InvoiceItem(s) from the database and place them inside of the private property of the object and only then run the getter code inside of your class. Without these methods private properties would stay empty, since there is no other way for doctrine to retrieve them form the database for you (unless maybe fetch: "EAGER").

No entity manager defined for class DateTime

I have started using the Sonata Admin bundle and I was following the example on how to map an entity with the bundle to create an administrative interface.
I created an entity called Post and this is the configuration yml file:
Emiliano\PostsBundle\Entity\Post:
type: entity
table: null
repositoryClass: Emiliano\PostsBundle\Entity\PostRepository
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
title:
type: string
column: Title
lenght: 100
published:
type: boolean
column: Published
publishingDate:
type: datetime
column: Publishing_Date
nullable: TRUE
lifecycleCallbacks: { }
Then in my Admin class I have the configureFormFields method:
protected function configureFormFields(FormMapper $formMapper) {
$formMapper->add('title', 'text')
->add('published', 'checkbox', array('required' => false))
->add('publishingDate', 'sonata_type_model_hidden');
}
I found the sonata_type_model_hidden on the sonata admin documentation. What I would like to achieve is to programmatically handle the publishing date (e.g. set the date only if the checkbox published is checked) hiding the implementation to the user.
Everything works fine for create, delete and read, when it comes to modify an entity I get this message in the stacktrace:
No entity manager defined for class DateTime
In sonata-project/doctrine-orm-admin-bundle/Sonata/DoctrineORMAdminBundle/Model/ModelManager.php at line 214
If I show the field everything works fine, I tried also to use:
->add('publishingDate', 'hidden');
without success.
What is exactly the problem here? Is it because Sonata Admin tries to fill a form with the entity values and for publishingDate there's a DateTime while in the form specification I wrote a sonata_type_model_hidden? If so, how can I circumvent this?
sonata_type_model_hidden isn't just hidden field genereator, according to documentation:
sonata_type_model_hidden will use an instance of ModelHiddenType to render hidden field. The value of hidden field is identifier of related entity.
If I understand your problem, You want to set publishing date only when field published == true
You could use entity preSave/preUpdate lifecycle callback for eaxmple
public function preSave()
{
/**
* Check if item is published
*/
if($this->getPublished()) {
$this->setPublishingDate(new \DateTime());
} else {
$this->setPublishingDate(null);
}
}
and remove publishingDate field from SonataAdmin form.

How to update counter field on the inverse side in many-to-many relation in Symfony?

I have two enteties User and Category, which are connected each other by many-to-many relation.
manyToMany:
categories:
targetEntity: Category
joinTable:
name: users_categories
joinColumns:
user_id:
referencedColumnName: id
inverseJoinColumns:
category_id:
referencedColumnName: id
In my UserAdmin.php (I'm using Sonata Admin and Sonata User bundles) they are handled by this field:
->add('categories', 'sonata_type_model', array('required' => false, 'expanded' => false, 'multiple' => true, 'label' => 'Chose your categories'))
Now I need to add extra field to my Category entity - user_counter, which store number of users, linked with it. It should be updated every time User add, update or delete his relations with Categories.
One idea was to make method in User entity which would get his categories before saving admin form, comparing them to current input and then making decision (categories counter to make +1 or -1). And switch on this method by lifecycleCallbacks (prePersist and preUpdate).
The problem: my code will get all User categories and compare them to current input on each form saving. How I can avoid such overhead? Maybe there is another solution for this task?
Thank you for help.
One way you could keep track is to modify your collection methods in your entity. For instance:
/*
* #ORM\Column(name="user_counter", type="integer")
*/
protected $user_counter;
public function addUser(User $user)
{
$this->users[] = $user;
$this->user_counter++;
}
public function removeUser(User $user)
{
$this->users->remove($user);
$this->user_counter--;
}
Alternatively, if you don't need the counts in the database, just do a $category->getUsers()->count();
Edit:
If you want to use an event listener to track this instead of modifying your entity setters, use a combination of $unitOfWork->getScheduledCollectionUpdates() and getDeleteDiff() and getInsertDiff().

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