SonataAdminBundle preload SQL objects - symfony

When using SonataAdminBundle, I am trying to visualize the leaf nodes of a database. My tables are A -> B -> C (-> = contains some). The leaf nodes I try to visualize is C.
C has a __toString() which will call B.__toString(), which in turns calls A.__toString().
The problem: I end up showing 30 lines and making 700 calls to the database.
Sometimes, I can avoid this problem by adding a filter, so it would make a request with the filter first and would "preload" some objects, but in this case, I can't add a filter as such.
Is there a way to preload my objects beforehand? The answer would probably contain 2 parts:
Where in Sonata should I do this?
What code should I execute to preload a hierarchy of objects which would minimize the amount of calls to the database?

You should override method createQuery where you can place your custom joins.
Example:
<?php
namespace Acme\DemoBundle\Admin;
use Sonata\AdminBundle\Admin\Admin;
class CarAdmin extends Admin
{
public function createQuery($context = 'list')
{
$query = parent::createQuery($context);
$query->leftJoin($query->getRootAlias . '.Model', 'mo');
$query->leftJoin('mo.Make', 'ma');
return $query;
}
}
Remember that, createQuery method returns Sonata ProxyQuery object, not Doctrine Query. So you should operate on query returned by Admin::createQuery.

Related

Doctrine 2, prevent getting unjoined entities

given a user and his coupons, I want to get a user and all of his coupons:
foreach ($this->createQueryBuilder('x')->select('u, c')->where('x.email = ?0')->setParameter(0, $email)->leftJoin('u.coupons', 'c')->getQuery()->getResult() as $entity)
{
$entity->getCoupons();
}
this is very good until I forget to join the coupons:
foreach ($this->createQueryBuilder('x')->select('u')->where('x.email = ?0')->setParameter(0, $email)->getQuery()->getResult() as $entity)
{
$entity->getCoupons();
}
sadly this still works even though no coupons were joined. Here it does an other SELECT. In additional, this 2nd select will be wrong. Id rather want to get a exception or AT LEAST an empty array instead. Is there any workaround for this?
What you're experiencing is expected doctrine behavior.
When you select a User entity, Doctrine will get the record from the database. If you aren't explicitly joining the Coupon entity (or any other entities with relationship to User), Doctrine will create a Proxy object. Once you access this proxy object by calling $user->getCoupons(), Doctrine will fire a new query to the database to get the coupons for your User entity. This is called lazy-loading.
I'm not sure if there is a way to change this in the way you described.
What you can do is to create a method in your UserRepository called findUserAndCoupons($email) and have your query there. Whenever you need to find a user and his coupons, you could simply retrieve it in your controller using:
class MyController extends Controller {
public function myAction(){
$user = $this->getDoctrine()->getRepository('UserRepository')->findUserAndCoupons($email);
foreach($user->getCoupons() as $coupon) {
// ....
}
}
}
This way you won't need to remember the actual query and copy/paste it all over the place. :)

$em->remove() symfony2 erasing all rows

I have an issue when erasing something from a BD.
The problem is that it not only erase the object i looked for (using findOneBy), but all the objects related to the principal id.
//---Controller
$new = $this->getDoctrine()->getManager();
$OBJcar = $new->getRepository('SomeOtherBundle:CarEntityClass')
->findOneBy(array('idOwner' => $idowner, 'idCar' => $idcar));
if($OBJcar){
$new->remove($OBJcar);
$new->flush();
$msj="The car for an specific owner has been erased.";
}
//---Profiler (Query)
"START TRANSACTION"
Parameters: { }
Time: 0.22 ms
DELETE FROM schema.CarTable WHERE id_owner = ?
Parameters: ['123456']
Time: 0.63 ms
"COMMIT"
Parameters: { }
Time: 0.63 ms
How to erase the one row i am getting from the db?
I voted down the answer above, because I'm tired of people using string DQLs.
It's not standartized, non-object oriented(even though in background dql operates with objects), it doesn't use caching mechanisms query builder provides, it's non-flexible and simply looks unclean.
Here is the "right way"(IMHO):
You add the repository class for entity
You add the method you need with a query builder in it
You call the method while passing parameters needed for specific REPOSITORY OBJECT ORIENTED ACTION
You get an easy-to-handle result
Here's the code:
namespace ProjectName\BundleName\Repository;
use Doctrine\ORM\EntityRepository;
class CarRepository extends EntityRepository
{
public function deleteCarWithOwner($ownerId,$carId)
{
$isDeleted = $this->createQueryBuilder("car")
->delete()
->where('car.id = :carId')->setParameter("carId", $carId)
->andWhere('car.idOwner = :ownerId')->setParameter("ownerId", $ownerId)
->getQuery()->execute();
return $isDeleted;
}
}
Also, refer to http://doctrine-orm.readthedocs.org/en/latest/reference/query-builder.html for query builder details. There are a lot of "pros" and I see no "cons" for using builder.
LATE UPDATE
Also, a lot of Doctrine's entity events are not dispatched when using DQL.
Use DQL
$query = $em->createQuery('DELETE SomeOtherBundle:CarEntityClass c WHERE c.idOwner = 4 AND c.id = 10');
$query->execute();
This will remove only single car with ID 10 and owner with ID 4.

Define custom method and access it from twig

I have two entities in 1:n relations: Race and Day - a race may have more days. This is the simple model:
Race (id)
Day (id, race_id, is_active, is_deleted)
I want to access the superior one - Race - within a Symfony2 project via Doctrine and display results in a Twig template. For the direct Race attributes it is easy.
However, it becomes trickier, when I want to use a custom defined method (sort of flag, let's call it hasActiveDays()) in Race that reflects if the race had any active and not deleted days. Simple Doctrine relation would not be enough, so I need to use a query like this:
SELECT d FROM mtboLibBundle:Day d WHERE d.isActive = 1 AND d.isDeleted = 0 AND d.raceId = :id
My question is basicly where/how to implement this query and how to invoke it in a twig template? Anything I tried resulted various errors so far, so I'd be grateful if someone could help.
I.e. this was a try:
class RaceRepository extends EntityRepository {
public function hasActiveDays() {
$em = $this->getEntityManager();
$query = $em->createQuery('SELECT f FROM mtboLibBundle:Day d
WHERE d.isActive = 1
AND d.isDeleted = 0
AND d.raceId = :id')
->setParameter('id', $this->id)
;
$days = $query->getResult();
return (count($days) == 0) ? false : true;
}
}
Method does not exist - when called from the template:
{{ race.hasActiveDays }}
I don't think you'll be able to call a function in a repository the way you are trying to do. One thing I've done is put a function on the entity class and you can call that from your template.
In your Race class:
public function hasActiveDays(){
// here, perhaps pull all of the days for this race - maybe from a doctrine relation
// loop through, filter, etc.
// return whatever is appropriate
}
... then, in your template, you'll be able to call that function the way you are trying to above.

Symfony2 Application Architecture - how to make a function available in all controllers?

I'm building an application where users are tied to accounts (as in, multiple users all share an account), then other entities - lets call them products are tied to the accounts. The products are associated with the accounts and only users that are tied to that account can edit the products.
The difference being in my case, there are many different entities being shared in the same model.
If it was just the one (product) entity, it wouldn't be a problem to have a method in my ProductRepository like:
public function checkOwnership($id, $account)
{
$count = $this->createQueryBuilder('s')
->select('count(s.id)')
->where('s.account = :acc')
->andWhere('s.id = :id')
->setParameters(array('id' => $id, 'acc' => $account))
->getQuery()
->getSingleScalarResult();
if($count == 0)
throw $this->createNotFoundException('ye..');
return;
}
To make sure the id of the product is tied to the user's account.
The account argument is passed to this function by using:
$this->getUser();
In the controller.
What is the best way for me to make this function available to the other entities to prevent code duplication? There are (currently) only 3 other entities, so it wouldn't be the end of the world to just have it in each repository, but I'm just wondering if there were a way to create a 'common' or global repository that follows good standards? I'd sure like to know about it.
Edit:
Or have I just completely over-thought this? Can I just create a 'Common' directory in my 'MainBundle', define a namespace and add a use statement at the start of my controllers that need access to the function?
I hope I fully understand your question.
Solution one, duplication, easy: let #ParamConverter do the job (automatic response to 404 if doesn't exist)
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* #Route("/pets/{id}")
*/
public function showAction(Pet $pet)
{
// Current logged user
$currentUser = $this->get('security.context')->getToken()->getUser();
// Owner
$ownerUser = $pet->getUser();
if(!$currentUser->equals($ownerUser)) {
// 401, 403 o 404 depends on you
}
}
Solution two, DRY, a bit harder: use JMSAOPBundle and define an interceptor that intercepts all request to you controller and actions (show, delete, etc.). The pointcut (connected to the interceptor) will get the current user from the context and the owner from the entity. Then you do the check...

Symfony/Doctrine: fetching data as object , still get array

I have in my controller $id it's a foreign key
$query = $em->getRepository('SurgeryPatientBundle:Patients')->findPatientByUserID($id);
And in my repository file this function
public function findPatientByUserID($id)
{
return $this->getEntityManager()
->createQuery('SELECT p FROM SurgeryPatientBundle:Patients p WHERE p.user ='.$id.'')
->execute();
}
I want get an instance of object but still get an array. Query with find($id) works good
edit
Problem solves , I'm so stupid , I had invoked to $query[0]
You can use $query->getSingleResult(); as well
see here
http://docs.doctrine-project.org/en/2.1/reference/dql-doctrine-query-language.html#query-result-formats
If you want to grab the object, you shouldn't be using DQL. Doctrine entities have a find function that takes care of this for you.
Instead of all that code, you can just use (in your controller):
$em->getRepository('SurgeryPatientBundle:Patients')->find($id);
DQL is very powerful, but for simple lookups like this using the built in find methods will be more efficient & provide the entities as doctrine objects.

Resources