How to get data from a column with this annotation #ORM\index - php

Good afternoon, please tell me how to get the column data:
{#ORM\Index(name="localities_names_idx", columns={"name_ru", "name_cn", "name_en"})
I tried using queryBuilder :
$qb
->select('a')
->from(Locality::class, 'a')
->where('a.name_ru = :name_ru')
->andWhere('a.area is null')
->setParameter('name', 'Moscow');
$query = $qb->getQuery();
Without success
I need it for:
$em = $this->entityManager;
$qb = $em->createQueryBuilder();
$qb
->select('a')
->from(Locality::class, 'a')
->where('a.name_ru = :name_ru')
->andWhere('a.area is null')
->setParameter('name', 'Москва');
$query = $qb->getQuery();
$result = $query->getResult(Query::HYDRATE_SCALAR);
$location = new Location();
$location->setLocality($result[0]['a_id']);
$location->setAddress($address);
$em->persist($location);
$em->flush();
return $location->getId();

Edit: This turned into a review, but might as well keep it here
You're using index wrong.
I'm guessing you're using it incorrectly. I'm assuming you want an index for faster search. However, the way you do now you've created a combined index. Example:
{#ORM\Index(name="thing_colour_idx", columns={"thing", "colour"})
Thing | Colour
--------------
Car Blue
Car Red
Bike Green
Bike Yellow
If you always (or at least most of the time) select by both columns, eg you always search for a BLUE+CAR, or a GREEN+BIKE, than this is the way to go.
However, if you to select all Thing=Car, without colour, then this index does nothing. You want this:
indexes={
#ORM\Index(name="thing_idx", columns={"thing"}),
#ORM\Index(name="colour_idx", columns={"colour"})
}
You're not using getResult as intended
You do ->getResult(Query::HYDRATE_SCALAR), but then follow it up with a ->setLocality($result[0]['a_id']). Unless you have performace issues, you dont work with IDs, thats a problem for Doctrine, not you. You should only care about objects:
$locality = $em
->createQueryBuilder()
->from(Locality::class, 'a')
->where('a.name_ru = :name_ru')
->andWhere('a.area is null')
->setParameter('name', 'Москва') // <- btw, this should be 'name_ru', same as two lines heigher
->getQuery()
->getSingleResult();
$location = new Location();
$location->setLocality($locality);
Dont use the querybuilder like that, use a repository
You're now placing service logic and query logic in one code. That is incorrect. An example of the way Symfony is intented:
class LocalityRepository extends ServiceEntityRepository{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Locality::class);
}
public function getLocalityByRuName(string $name): Locality
{
return $this->createQueryBuilder('a')
->where('a.name_ru = :name_ru')
->andWhere('a.area is null')
->setParameter('name_ru', $name);
->getQuery();
->getSingleResult();
}
}
class YourService
{
public function __construct(
private LocalityRepository $localityRepository
){}
public function somethingSomething()
{
$locality = $this->localityRepository->getLocalityByRuName('Москва');
$location = new Location();
$location->setLocality($locality);
}
}
Final note: Try not to use a ->flush() in a service. The job of a service is to do work, not to decide what to do it. A controller decides when something is done. Suppose you have another service which in which you alter things, but DONT want to flush them. You can now no longer use any service method that flushes.

The error was that there were no name properties Ru

Related

Doctrine many to many: has user liked the article?

I have 2 Entities
User
Article
and a “likedByUsers” Many To Many relationship between both.
When I show an article, I want to know if the user has liked it so a heart icon is shown.
I've got this in the ArticleRepository:
public function findOneBySlug($slug,$userId): ?Pack
{
return $this->createQueryBuilder('p')
->andWhere('p.slug = :val')
->setParameter('val', $slug)
->addSelect('COUNT(u) AS userLike', 'p')
->leftJoin("p.users", 'u', 'WITH', 'u.id = :userId')
->setParameter('userId', $userId)
->getQuery()
->getOneOrNullResult()
;
}
But it throws an error:
Return value of App\Repository\ArticleRepository::findOneBySlug() must be
an instance of App\Entity\Article or null, array returned
I want to add "userLike" (bool) to the Article returned entity. Anyone can help me out?
calling addSelect(...) on a query builder might change the return type / format.
in your particular case, the former db result was something like [... all the article properties ...] which hydration and the getOneOrNullResult turns into one Article or null.
the new format looks like
[... all the article properties ..., userlike], which hydration turns into [Article, userlike] which can't possibly turned into one Article or a null result, because it's a "more complex" array.
So you have to use a different result fetcher. Depending on what the caller of your function expects as a return value (I would expect an article ^^) you maybe should rename the function or add a virtual property on article to hide the userlike or something, so you can return just the Article or null.
So the solution that I would choose:
$result = $this->createQueryBuilder(...)
//...
->getSingleResult();
if(!$result) {
// empty result, obviously
return $result;
}
// $result[0] is usually the object.
$result[0]->userLike = $result['userLike'];
// or $result[0]->setUserLike($result['userLike'])
return $result[0];
btw: $this->createQueryBuilder($alias) in a repository automatically calls ->select($alias), so you don't have to addSelect('... userLike', 'p') and just do addSelect('... userLike')

Error: 'c' is used outside the scope of its declaration

An application has a page of statistics representing dozens of calculations. To avoid duplicating code in a repository the error
Error: 'c' is used outside the scope of its declaration
occurs when attempting to insert DQL with conditions into a QueryBuilder.
The basic entities include Household and Contact. Calculations are based on contact date range, site (location of contact), and type (type of contact). There is a service that creates an array of where clauses and query parameters, as will be evident in the code below.
I know the calculation works if all the code occurs in a single function. It seems the problem arises from the join with the Contact entity and its necessary constraints. Can DRY be accomplished in this scenario?
All of the following appear in the Household entity's repository.
The DQL is:
private function reportHousehold($criteria)
{
return $this->createQueryBuilder('i')
->select('i.id')
->join('TruckeeProjectmanaBundle:Contact', 'c', 'WITH',
'c.household = i')
->where($criteria['betweenWhereClause'])
->andWhere($criteria['siteWhereClause'])
->andWhere($criteria['contactWhereClause'])
->getDQL()
;
}
Example of $criteria: $criteria['betweenWhereClause'] = 'c.contactDate BETWEEN :startDate AND :endDate'
One of the calculations on Household:
public function res($criteria)
{
$parameters = array_merge(
$criteria['betweenParameters'], $criteria['siteParameters'],
$criteria['startParameters'], $criteria['startParameters'],
$criteria['contactParameters']);
$qb = $this->getEntityManager()->createQueryBuilder();
return $this->getEntityManager()->createQueryBuilder()
->select('h.id, 12*(YEAR(:startDate) - h.arrivalyear) + (MONTH(:startDate) - h.arrivalmonth) Mos')
->from('TruckeeProjectmanaBundle:Household', 'h')
->distinct()
//DQL inserted here:
->where($qb->expr()->in('h.id', $this->reportHousehold($criteria)))
->andWhere($qb->expr()->isNotNull('h.arrivalyear'))
->andWhere($qb->expr()->isNotNull('h.arrivalmonth'))
->andWhere($criteria['startWhereClause'])
->setParameters($parameters)
->getQuery()->getResult()
;
}
You're either missing getRepository() or from()
Try this (my prefered choice) :
private function reportHousehold($criteria) {
return $this->getEntityManager
->createQueryBuilder()
->select("i.id")
->from(YourEntity::class, "i")
->join("TruckeeProjectmanaBundle:Contact", "c", "WITH", "c.household=i.id")
->where($criteria['betweenWhereClause'])
->andWhere($criteria['siteWhereClause'])
->andWhere($criteria['contactWhereClause'])
->getQuery()
->execute();
}
Or this
private function reportHousehold($criteria) {
return $this->getEntityManager
->getRepository(YourEntity::class)
->createQueryBuilder("i")
->select("i.id")
->join("TruckeeProjectmanaBundle:Contact", "c", "WITH", "c.household=i.id")
->where($criteria['betweenWhereClause'])
->andWhere($criteria['siteWhereClause'])
->andWhere($criteria['contactWhereClause'])
->getQuery()
->execute();
}
Careful, I'm assuming you're on Symfony 3 or above.
If not, replace YourEntity::class by Symfony 2 syntax which is "YourBundle:YourEntity"
In a sense Preciel is correct: the solution does require the use of $this->getEntityManager()->createQueryBuilder(). Instead of injecting DQL as a subquery, the trick is to return an array of ids and use the array in an IN clause. The effect is to remove any consideration of entities other than the Household entity from the calculation. Here's the result:
public function res($criteria)
{
$parameters = array_merge($criteria['startParameters'], $criteria['startParameters'], ['hArray' => $this->reportHousehold($criteria)]);
$qb = $this->getEntityManager()->createQueryBuilder();
return $this->getEntityManager()->createQueryBuilder()
->select('h.id, 12*(YEAR(:startDate) - h.arrivalyear) + (MONTH(:startDate) - h.arrivalmonth) Mos')
->from('TruckeeProjectmanaBundle:Household', 'h')
->distinct()
->where('h.id IN (:hArray)')
->andWhere($qb->expr()->isNotNull('h.arrivalyear'))
->andWhere($qb->expr()->isNotNull('h.arrivalmonth'))
->setParameters($parameters)
->getQuery()->getResult()
;
}
private function reportHousehold($criteria)
{
$parameters = array_merge($criteria['betweenParameters'], $criteria['siteParameters'], $criteria['contactParameters']);
return $this->createQueryBuilder('i')
->select('i.id')
->join('TruckeeProjectmanaBundle:Contact', 'c', 'WITH', 'c.household = i')
->where($criteria['betweenWhereClause'])
->andWhere($criteria['siteWhereClause'])
->andWhere($criteria['contactWhereClause'])
->setParameters($parameters)
->getQuery()->getResult()
;
}

Best / fasted solution to delete a large number of user data in Symfony / Doctrine

I am working on a Symfony 2.8 project that allows registered users to manage contacts, documents, appointments, etc.
Now I would like to implement a feature that allows users to reset/delete all their data. So the user account itself should stay intact but all the data entities should be deleted.
Currently I am using a Doctrine/ORM approach to loop though all entities and delete them:
public function deleteAllData($user) {
$this->currentBatch = 0;
$this->deleteEntities('MyBundle:Contact', $user);
$this->deleteEntities('MyBundle:Document', $user);
...
$this->deleteEntities('MyBundle:...', $user);
$this->flushIfNecessary(true);
}
private function deleteEntities($type, $user) {
$repo = $this->em->getRepository($type);
$entities = $repo->findByUser($user);
foreach ($entities as $entity) {
$repo->delete($entity);
$this->flushIfNecessary();
}
}
private function flushIfNecessary($force = false) {
$this->currentBatch++;
if ($force || $this->currentBatch % 100 == 0) {
$this->em->flush();
$this->em->clear();
}
}
While this works fine, it is very slow. Using different batch sizes I was able to speed the process up a little bit, but it still takes quite long to delete a large number of entries. Of course this is due to all the ORM handling that is done in the background.
It would be much faster to use simple SQL queries:
private function deleteEntities($type, $user) {
$tableName = null;
$userId = $user->getId();
switch ($type) {
case 'MyBundle:Document':
$tableName = 'document';
break;
...
}
if ($tableName && $userId) {
$this->em->getConnection()->query("
DELETE FROM $tableName
WHERE user_id = '$userId'
");
}
}
This solution works as well, but it require to know the internal DB structure, e.g the table names of the different entities, the name of the userId field, etc.
Of course it is not a big deal to get this information but to me this solution seems to be not as clean an the first one.
Is there a way to get the necessary information from Doctrine while still working plain query / bypassing ORM?
Or is there even a better solution to get this done`?
Your first approach is slow because of object hydration. You don't really need to load all records and transform them into objects. You can simply use QueryBuilder:
$qb = $em->createQueryBuilder('c');
$qb->delete('MyBundle:Contact', 'c')
->where('c.user = :user')
->setParameter('user', $user)
->getQuery()
->execute()
;
Or DQL:
$query = $em->createQuery(
'DELETE MyBundle:Contact c
WHERE c.user = :user')
->setParameter('user', $user);
$query->execute();

QueryBuilder/Doctrine Select join groupby

So recently I have been thinking and can't find a solution yet to this problem since my lack of development with doctrine2 and symfony query builder.
I have 2 tables:
Goals: id,user_id,target_value...
Savings: id,goal_id,amount
And I need to make a select from goals (all the informations in my table are from the goals table, except that I need to make a SUM(amount) from the savings table on each goal, so I can show the user how much did he saved for his goal)
This is the MySQL query:
select
admin_goals.created,
admin_goals.description,
admin_goals.goal_date,
admin_goals.value,
admin_goals.budget_categ,
sum(admin_savings.value)
from admin_goals
inner join admin_savings on admin_savings.goal_id=admin_goals.id
where admin_goals.user_id=1
group by admin_goals.id
It returns what I want but I have no idea how to implement it with doctrine or query builder, can you please show me an example in both ways?
I highly appreciate it !
I am going to assume you need this fields only and not your AdminGoals entity. On your AdminGoalsRepository you can do something like this:
public function getGoalsByUser(User $user)
{
$qb = $this->createQueryBuilder('goal');
$qb->select('SUM(savings.value) AS savings_value')
->addSelect('goal.created')
->addSelect('goal.description')
->addSelect('goal.goalDate')
->addSelect('goal.value')
->addSelect('goal.budgetCat') //is this an entity? it will be just an ID
->join('goal.adminSavings', 'savings', Join::WITH))
->where($qb->expr()->eq('goal.user', ':user'))
->groupBy('goal.id')
->setParameter('user', $user);
return $qb->getQuery()->getScalarResult();
}
Keep in mind that the return object will be an array of rows, each row is an associated array with keys like the mappings above.
Edit
After updating the question, I am going to change my suggested function but going to leave the above example if other people would like to see the difference.
First things first, since this is a unidirectional ManyToOne between AdminSavings and AdminGoals, the custom query should be in AdminSavingsRepository (not like above). Also, since you want an aggregated field this will "break" some of your data fetching. Try to stay as much OOP when you are not just rendering templates.
public function getSavingsByUser(User $user)
{
$qb = $this->createQueryBuilder('savings');
//now we can use the expr() function
$qb->select('SUM(savings.value) AS savings_value')
->addSelect('goal.created')
->addSelect('goal.description')
->addSelect('goal.goalDate')
->addSelect('goal.value')
->addSelect('goal.budgetCat') //this will be just an ID
->join('savings.goal', 'goal', Join::WITH))
->where($qb->expr()->eq('goal.user', ':user'))
->groupBy('goal.id')
->setParameter('user', $user);
return $qb->getQuery()->getScalarResult();
}
Bonus
public function FooAction($args)
{
$em = $this->getDoctrine()->getManager();
$user = $this->getUser();
//check if user is User etc depends on your config
...
$savings = $em->getRepository('AcmeBundle:AdminSavings')->getSavingsByUser($user);
foreach($savings as $row) {
$savings = $row['savings_value'];
$goalId = $row['id'];
$goalCreated = $row['created'];
[...]
}
[...]
}
If you use createQuery(), then you can do something like this:
$dqlStr = <<<"DSQL"
select
admin_goals.created,
admin_goals.description,
admin_goals.goal_date,
admin_goals.value,
admin_goals.budget_categ,
sum(admin_savings.value)
from admin_goals
inner join admin_savings on admin_savings.goal_id=admin_goals.id
where admin_goals.user_id=1
group by admin_goals.id
DSQL;
$em = $this->getDoctrine()->getManager();
$query = $em->createQuery($dqlStr);
$query->getResult();
On the other hand, if you would like to use createQueryBuilder(), you can check this link: http://inchoo.net/dev-talk/symfony2-dbal-querybuilder/

Symfony2 Query Builder Not Returning Many To One Value

I have an entity that has a column "inventoryLcoation_id" that has a many to one relationship. For some reason when I use createQueryBuilder() it is not returning that value in my ajax call, but it returns everything else: id, name, etc etc.. This is not an issue with Symfony2, its an issue with my lack of knowledge :)
Here is my query builder code:
$qb = $this
->createQueryBuilder('p')
->select('p.id', 'p.name')
->where('p.inventoryLocation = :inventoryId')
->andWhere('p.account = :account_id')
->setParameter('inventoryId', $value)
->setParameter('account_id', $account_id)
->orderBy('p.id', 'DESC');
if($qb->getQuery()->getArrayResult()){
return $qb->getQuery()->getArrayResult();
}else{
return false;
}
What do I need to code to have it also include the value from the inventoryLocation table which is mapped to the column value "inventoryLocation_id"?
Thanks so much for your help in enlightening my understanding to the awesome world of Symfony2!
try a join:
$qb->join('p.inventoryLocation','i')
and in the select especify both entities
$qb->select('p','i')
it should look like this:
$qb = $this
->createQueryBuilder('p')
->select('p','i')
->join('p.inventoryLocation','i')
->where('p.inventoryLocation = :inventoryId') // or ->where('i = :inventoryId')
->andWhere('p.account = :account_id')
->setParameter('inventoryId', $value)
->setParameter('account_id', $account_id)
->orderBy('p.id', 'DESC');

Categories