In my app I have 3 entities; User, Booking and Room.
Booking entity:
namespace App\Entity;
/**
* #ORM\Table(name="booking")
* #ORM\Entity(repositoryClass="App\Repository\BookingRepository")
*/
class Booking
{
/**
* #ORM\Column(type="boolean")
* #Assert\NotBlank()
*/
private $isActive;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Room", inversedBy="bookings")
*/
private $room;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="bookings")
*/
private $user;
Room entity:
/**
* #ORM\Table(name="room")
* #ORM\Entity(repositoryClass="App\Repository\RoomRepository")
*/
class Room
{
/**
* #ORM\OneToMany(targetEntity="App\Entity\Booking", mappedBy="room")
* #Expose
*/
private $bookings;
User entity:
/**
* #ORM\Table(name="app_user")
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #UniqueEntity(fields="email", message="This email address is already in use")
*/
class User implements AdvancedUserInterface
{
/**
* #ORM\Column(type="string", length=255, unique=true)
* #Assert\NotBlank()
* #Assert\Email()
*/
private $email;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Booking", mappedBy="user")
* #Expose
*/
private $bookings;
Given a user's email I can get a room id like this:
$user = $this->getEntityManager()
->getRepository(User::class)
->findOneBy([
'email' => 'johndoe#domain.com'
]);
$booking = $this->getEntityManager()
->getRepository(Booking::class)
->findOneBy([
'user' => $user,
'isActive' => true,
]);
$roomId = $booking->getRoom()->getId();
However this seems like a long way to do it. Is it possible to optimise this and query for a room without having to make 2 databases calls?
Yes you can get it directly from the $user variable. I don't see any getters or setters listed here but I'm assuming you have created them. If so then you can do the following:
$bookings = $user->getBookings();
Bookings is an array so you will need to select which booking you want to get the room for. Let's just select the first:
$roomId = $bookings->first()->getRoom()->getId()
To make it cleaner you can add a getCurrentBooking method or something similar to your User class that will return the exact booking you want.
Then you would end up with something like this:
$roomId = $user->getCurrentBooking->getRoom()->getId()
You could use a single join query to get the room for a spcific user
$this->getEntityManager()
->createQueryBuilder()
->select('r')
->from(Room::class, 'r')
->join('r.bookings', 'b')
->join('b.user', 'u')
->where('u.email = :email')
->andWhere('b.isActive = :isActive')
->setParameter('isActive', true)
->setParameter('email', 'johndoe#domain.com')
->getQuery()
->getResult();
Related
I'm trying to get the "demands" of a user.
User can have some demands and a demand have only one user (OneToMany)
This is my User entity (Utilisateur in french) :
class Utilisateur extends AbstractEntity implements UserInterface, PasswordAuthenticatedUserInterface
{
/**
* #ORM\Id
* #ORM\Column(type="ulid", unique=true)
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class=UlidGenerator::class)
*/
private Ulid $id;
/**
* #ORM\OneToMany(targetEntity=DemandeTransport::class, mappedBy="utilisateur", orphanRemoval=true)
*/
private Collection $demandeTransports;
And my demands entity :
class DemandeTransport extends AbstractEntity
{
/**
* #ORM\Id
* #ORM\Column(type="ulid", unique=true)
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class=UlidGenerator::class)
*/
private Ulid $id;
/**
* #ORM\ManyToOne(targetEntity=Utilisateur::class, inversedBy="demandeTransports")
* #ORM\JoinColumn(nullable=false)
*/
private Utilisateur $utilisateur;
My controller receiving the request :
/**
* #throws Exception
*/
#[Route('/liste_propositions_transporteur', name: 'liste_propositions_transporteur', methods: ['GET'])]
public function listePropositionsTransporteur(Request $request): Response
{
return match ($request->getMethod()) {
'GET' => new Response(json_encode(['success' => true, 'data' => $this->propositionsTransportService->handleListePropositionsByUser($this->getUser())])),
default => new Response(404),
};
}
The service handling the request and retreiving the demands :
/**
* #param UserInterface $user
* #return array
*/
public function handleListePropositionsByUser(UserInterface $user) : array
{
$propositions = [];
foreach ($this->propositionTransportRepository->findPropositionsByUtilisateur($user) as $propositionTransport) {
$propositions[] = DemandeTransportHelper::serializePropositionDemande($propositionTransport);
}
return $propositions;
}
And the DQL :
/**
* #param UserInterface $user
* #return mixed
*/
public function findPropositionsByUtilisateur(UserInterface $user) : mixed
{
$q = $this->createQueryBuilder('p')
->where('p.utilisateur = :utilisateur')
->setParameters([
'utilisateur' => $user
])
->orderBy('p.dateCreation', 'DESC');
return $q->getQuery()->getResult();
}
So :
When i'm doing $utilisateur->getDemandesTransports() : it works by showing me all the demands.
Well, but when I'm trying to get them by DQL (cause I want them orderd by), it returns me 0 results...
Solved by setting the parameter type :
->setParameter('utilisateur', $utilisateur->getId(), 'ulid')
I'm using ULID (UUID like) on IDs.
https://symfony.com/doc/current/components/uid.html#working-with-ulids
With annotations
You can order your data by specifying sorting in your $demandeTransports property annotations.
/**
* #ORM\OneToMany(targetEntity=DemandeTransport::class, mappedBy="utilisateur", orphanRemoval=true)
* #ORM\OrderBy({"dateCreation" = "DESC"})
*/
private Collection $demandeTransports;
So when you call $utilisateur->getDemandesTransports() you will get ordered data.
With DQL
Also if you still want to use DQL then you should change your query to this as you need to join the Utilisateur entity then you can use the desired properties
$q = $this->createQueryBuilder('p')
->join('p.utilisateur', 'u')
->where('u.utilisateur = :utilisateur')
->setParameters([
'utilisateur' => $user
])
->orderBy('u.dateCreation', 'DESC');
I have two entities (User and Product) with a OneToMany relationship. I want to retrieve all Product entities related to a specific User filtering them by a field called "finished". I will post the relevant information of those entities:
User Entity:
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
*/
class User implements UserInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Product", mappedBy="user")
*/
private $products;
....
Product Entity:
/**
* #ORM\Entity(repositoryClass="App\Repository\Productepository")
*/
class Product
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="products")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* #ORM\Column(type="boolean")
*/
private $finished;
....
I am using the following code to filter my products, but is there any other more efficient way to do it?
$user = $this->getUser();
$products = $user->getProducts();
$filtered = [];
foreach ($products as $product){
if(!$product->getFinished()){
$filtered[] = $product;
}
}
Thanks for your help
One of the solutions would be to use a database query like the following :
- in the controller, you can do
<?php
namespace App\Controller\ProductController;
use App\Repository\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
class ProductController extends AbstractController
{
/**
* #Route("/products", methods={"GET"}, name="app_get_products")
* #IsGranted("IS_AUTHENTICATED_REMEMBERED")
*/
public function listProduct(ProductRepository $repository)
{
$user = $this->getUser();
$products = $repository->findBy([
'user' => $user,
'finished' => false
]);
return $this->render('Product/list.html.twig', [
'products' => $products,
]);
}
}
Notice the request that is made : this is an attempt at a solution.
I am trying to load friends of a specific user after asking why I get null values of the friend of the user data it was responded that it was because of the lazy loading. So I was advised to add JOIN and I admit this was a miss from my side. But after adding the JOIN I get the data of the friend in the result and then I receive all the users from my users table for which I have not asked.
I have already tried removing the myuser from the SELECT, but this way I get the lazy loading problem again. I have tried LEFT JOIN (I admit it was dumb try from my side). But how can I correct this when there is no ON in the Doctrine Query Language.
My Entity(Friends):
class Friends
{
/**
* #ORM\Id()
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="myfriends")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
*/
private $friendsWithMe;
/**
* #ORM\Id()
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="friendof")
* #ORM\JoinColumn(name="friend_id", referencedColumnName="id", nullable=false)
*/
private $afriendof;
/**
* #var integer
*
* #ORM\Column(name="status", type="smallint")
*/
private $status;
/**
* #return User
*/
public function getFriendsWithMe()
{
return $this->friendsWithMe;
}
/**
* #param mixed $friendsWithMe
*/
public function setFriendsWithMe($friendsWithMe)
{
$this->friendsWithMe = $friendsWithMe;
}
/**
* #return User
*/
public function getAfriendof()
{
return $this->afriendof;
}
/**
* #param mixed $afriendof
*/
public function setAfriendof($afriendof)
{
$this->afriendof = $afriendof;
}
/**
* #return integer
*/
public function getStatus()
{
return $this->status;
}
/**
* #param integer $status
*/
public function setStatus($status)
{
$this->status = $status;
}
}
My Entity(User):
class User implements UserInterface
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
public function __construct()
{
$this->userPosts = new ArrayCollection();
$this->myfriends = new ArrayCollection();
$this->friendof = new ArrayCollection();
}
/**
* #var
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Friends", mappedBy="afriendof")
*/
private $friendof;
/**
* #var
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Friends", mappedBy="friendsWithMe")
*/
private $myfriends;
My Repository(FriendsRepository):
public function personalFriends($userId){
$em = $this->getEntityManager();
$result = $em->createQuery('SELECT friends, myuser FROM AppBundle\Entity\Friends friends
INNER JOIN AppBundle\Entity\User myuser WHERE friends.friendsWithMe = :userId AND friends.status = 1');
$result->setParameter('userId', $userId);
return $result->getResult();
}
The place where I call the repository:
public function indexAction(Request $request)
{
$user = $this->get('security.token_storage')->getToken()->getUser();
$userId = $user->getId();
$friends = $this->getDoctrine()->getRepository(Friends::class)->personalFriends($userId);
dump($friends);
exit();
Results that I get now:
https://pastebin.com/2M4SYTLb
Results that I expect:
https://pastebin.com/BxsC9QbE
Hope I understand your problem.
But from what I see you are asking for the data of the friends AND the data of the users when you are doing 'SELECT friends, myuser.
Try only selecting friends
Like this:
SELECT friend FROM AppBundle\Entity\Friends friend INNER JOIN AppBundle\Entity\User user WHERE friend.friendsWithMe = :userId AND friend.status = 1
Then you'll only have as a result an array of Friends.
If there is still a problem you can add fetch="EAGER" so it wont be "LAZY"
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="myfriends", fetch="EAGER")
I'm trying to setup a many to many between fos Userbundle and my own group bundle so that I can group users. this is working fine. I can set a new group and can add as many users to this group as I like to. But when I want to check if a user is in a group, I get a Index join Column error. I think I didn't understand the usage of manytomany the correct way so it would be nice if you can help me getting the point.
My entities look like:
User:
class User extends BaseUser
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
protected $usergroups;
//....//
And my Group Entity looks like:
/**
* #ORM\ManyToMany(targetEntity="PrUserBundle\Entity\User", inversedBy="id")
* #ORM\JoinColumn(name="id", referencedColumnName="id")
* #var user
*/
private $user;
//....
/**
* Add user
*
* #param \PrUserBundle\Entity\User $user
* #return Lieferanten
*/
public function addUser(\PrUserBundle\Entity\User $user)
{
$this->user[] = $user;
return $this;
}
/**
* Remove user
*
* #param \PrUserBundle\Entity\User $user
*/
public function removeUser(\PrUserBundle\Entity\User $user)
{
$this->user->removeElement($user);
}
/**
* Get user
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getUser()
{
return $this->user;
}
When I try to catch all users in a group, I get an error:
$group=$em->getRepository('PrGroupBundle:Group')->findAll();
var_dump($lfr[0]->getUser()->getId());
I guess I missunderstood how to handle the bidirectional manytomany. Or can I use a manytoone also?
I have a Product class that has many fields on it for ManyToMany, such as ingredients, sizes, species, etc.. A total of about 14 different fields
Not all of the fields are are relevant to each product.
I have mapping set up like this
Class product {
/**
* #var Species[]
* #ORM\ManyToMany(targetEntity="Species")
* #ORM\JoinTable(name="product_species",
* joinColumns={#ORM\JoinColumn(name="productId", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="speciesId", referencedColumnName="id")}
* )
* #ORM\OrderBy({"name" = "asc"})
*/
private $species;
This works great for a manytomany/manyto one.
The problem is in my product_ingredients table I needed to add an additional field, meaning need to switch from ManyToMany to a OneToMany/ManyToOne
So like this
/**
* #var ProductIngredient[]
*
* #ORM\OneToMany(targetEntity="ProductIngredient", mappedBy="product")
* #ORM\JoinColumn(name="productId", referencedColumnName="id")
*/
private $ingredients;
Now my ProductIngredient Entity Looks like this
/**
* #var IngredientType
* #ORM\ManyToOne(targetEntity="IngredientType", fetch="EAGER")
* #ORM\JoinColumn(name="ingredientTypeId", referencedColumnName="id")
*/
private $ingredientType;
/**
* #var Ingredient
*
* #ORM\ManyToOne(targetEntity="Ingredient", inversedBy="products", fetch="EAGER")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="ingredientId", referencedColumnName="id")
* })
*/
private $ingredient;
/**
* #var Product
*
* #ORM\ManyToOne(targetEntity="Product", inversedBy="ingredients")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="productId", referencedColumnName="id")
* })
*/
private $product;
So in my product class for species I use the #ORM\OrderBy so that species is already ordered.. Is there a way I can somehow also do this for my ingredients field?
Or am I doing my logic wrong and these shouldn't even be fields on the product class and should just be looking up by the repository instead?
I was wanting it to be easy so I could loop through my objects like $product->getIngredients()
instead of doing
$ingredients = $this->getDoctrine()->getRepository('ProductIngredient')->findByProduct($product->getId());
in the Product entity just also aadd the orderBy to the ingredients relation
/**
* ...
* #ORM\OrderBy({"some_attribute" = "ASC", "another_attribute" = "DESC"})
*/
private $ingredients;
Well I came up with a hackish way.. Since I really only care about the sort on output, I have made a basic twig extension
use Doctrine\Common\Collections\Collection;
public function sort(Collection $objects, $name, $property = null)
{
$values = $objects->getValues();
usort($values, function ($a, $b) use ($name, $property) {
$name = 'get' . $name;
if ($property) {
$property = 'get' . $property;
return strcasecmp($a->$name()->$property(), $b->$name()->$property());
} else {
return strcasecmp($a->$name(), $b->$name());
}
});
return $values;
}
I would like to avoid this hack though and still would like to know a real solution
You should use 'query_builder' option in your Form: http://symfony.com/doc/master/reference/forms/types/entity.html#query-builder
The value of the option can be something like this:
function(EntityRepository $er) {
return $er->createQueryBuilder('i')->orderBy('i.name');
}
Don't forget to add the "use" statement for EntityRepository
If you use xml mapping, you could use
<order-by>
<order-by-field name="some_field" direction="ASC" />
</order-by>
inside your <one-to-many> tag.