Doctrine many-to-many relationship: failed to get data automatically from database - symfony

In my Symfony code I've used Doctrine. In an Entity ( AppBundle\Entity\Core\User ) I defined a column foodTypes, which is associated with another Entity (AppBundle\Entity\FoodRecording\FoodType). I've defined an Many-to-Many relationship between User and FoodType, with a linking table foodrecording_user, joining User.username and FoodType.foodtype_code. The code is shown below.
// Entity\Core\User
namespace AppBundle\Entity\Core;
......
class User implements AdvancedUserInterface, \Serializable {
......
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\FoodRecording\FoodType")
* #ORM\JoinTable(name="foodrecording_user",
* joinColumns={#ORM\JoinColumn(name="username", referencedColumnName="username", onDelete="CASCADE")},
* inverseJoinColumns={#ORM\JoinColumn(name="foodtype_code", referencedColumnName="code", onDelete="CASCADE")}
* )
*/
private $foodTypes;
public function getFoodTypes()
{
$this->foodTypes = new \Doctrine\Common\Collections\ArrayCollection();
return $this->foodTypes;
}
However, as I wanted to get directly all the food types of a certain user, using
$userFoodTypes = $this->get('security.token_storage')->getToken()->getUser()->getFoodTypes();
then I got
$userFoodTypes =====> array[]
I expected that as I've created the M-M relationship, Doctrine would automatically fetch the data I need, but it is not the case!
Therefore, I have to write my own code to retrieve the data from the DB / table like following:
public function fetchUserFoodTypes()
{
global $kernel;
$container = $kernel->getContainer();
$em = $container->get('doctrine.orm.entity_manager');
$conn = $em->getConnection();
$sql = 'SELECT * FROM foodrecording_user where username = :username';
$stmt = $conn->prepare($sql);
$stmt->execute([
'username' => $this->getUsername(),
]);
$data = $stmt->fetchAll();
$res = [];
foreach ($data as $item) {
$foodtype = $em->getRepository('AppBundle\Entity\FoodRecording\FoodType')->findByCode($item['foodtype_code']);
$res[] = $foodtype;
}
return $res;
}
public function getFoodTypes()
{
$this->foodTypes = $this->fetchUserFoodTypes();
//$this->foodTypes = new \Doctrine\Common\Collections\ArrayCollection();
return $this->foodTypes;
}
Only in this way I am able to get the food types associated with a user.
Could anyone explain to me, why I can't simply use the M-M definition and let doctrine do all the thing automatically for me? Why should I explicitly write my own function to retrieve data from DB? Is Doctrine not smart enough?

This part:
$this->foodTypes = new \Doctrine\Common\Collections\ArrayCollection();
Belongs to the __construct method, not getter.. You see, like this, every time you call your getter, you reset the property foodTypes to an empty instance of an ArrayCollection

Related

return random data form database in random order and with limit

I'm new with symfony and I'm trying to view data from one of my tables with random order and a limit of 4. I tried doing it in the repository but RAND() is not working so I'm trying in the controller.
The error is the following one:
"Warning: array_rand() expects parameter 1 to be array, object given"
And I don't understand why, when in the $response I set the data into an array.
This is my actual code:
/**
* #Route("/ws/random/superviviente", name="ws_random_survi")
*/
public function randomSurvi(Request $request): Response
{
$data = $request->request->all();
$entityManager = $this->getDoctrine()->getManager();
$randomPerks = $entityManager->getRepository(Perks::class)
->getRandomPerks();
$response = new JsonResponse();
$response -> setStatusCode(200);
$response -> setData(array('random perk' => $randomPerks));
$resultRandom = array_rand($response);
return $resultRandom;
}
You are trying to use array_rand on a doctrine array collection.
You could either convert it as array and back to a doctrine array :
use Doctrine\Common\Collections\ArrayCollection;
public function randomSurvi(Request $request): Response
{
$data = $request->request->all();
$entityManager = $this->getDoctrine()->getManager();
$randomPerks = $entityManager->getRepository(Perks::class)
->getRandomPerks();
$resultRandom = new ArrayCollection(array_rand($randomPerks->toArray()));
return new JsonResponse($resultRandom);
}
Otherwise it would work with shuffle :
$randomPerks = $entityManager->getRepository(Perks::class)->getRandomPerks();
$randomPerks = shuffle($randomPerks);
Or get random perks directly through your method in your repository.
See example from #Krzysztof Trzos:
public function getRandomProducts($amount = 7)
{
return $this->getRandomProductsNativeQuery($amount)->getResult();
}
/**
* #param int $amount
* #return ORM\NativeQuery
*/
public function getRandomProductsNativeQuery($amount = 7)
{
# set entity name
$table = $this->getClassMetadata()
->getTableName();
# create rsm object
$rsm = new ORM\Query\ResultSetMapping();
$rsm->addEntityResult($this->getEntityName(), 'p');
$rsm->addFieldResult('p', 'id', 'id');
# make query
return $this->getEntityManager()->createNativeQuery("
SELECT p.id FROM {$table} p ORDER BY RAND() LIMIT 0, {$amount}
", $rsm);
}
You could write your own query to achieve this, so create a new method inside the repository like so:
public function getRandomPerks(int $limit): array
{
$queryBuilder = $this->createQueryBuilder('p');
return $queryBuilder
->setMaxResults($limit)
->orderBy('RAND()')
->getQuery()
->getResult();
}
Then in your controller all you would to do is call the method and pass a limit:
$randomPerks = $entityManager->getRepository(Perks::class)
->getRandomPerks(4);

Doctrine hydrate not mapped field

I am making a query with Doctrine which calculates a custom field using a CASE WHEN like this:
public function findLatestPaginator($page = 1, $itemsPerPage)
{
$qb = $this->createQueryBuilder('n');
$qb = $qb
->select(['n AS news', 'CASE WHEN lu.id IS NOT NULL THEN 1 ELSE 0 END AS n.liked'])
->leftJoin('n.likingUsers', 'lu')
;
$qb = $qb
->orderBy('n.date', 'DESC')
->setFirstResult($itemsPerPage * ($page - 1))
->setMaxResults($itemsPerPage)
->getQuery()
;
return $qb->getResult();
}
In my entity I have a field called $liked which is not mapped. Is it possible to make the query (or the hydrator?) automatically set the field on the resulting entity?
Right now I am making a foreach loop and manually setting the property:
/**
* #return News[]
*/
private function convertNews(array $records)
{
$newsList = [];
foreach ($records as $record) {
if (isset($record['liked'], $record['news'])) {
/** #var News */
$news = $record['news'];
$news->liked = boolval($record['liked']);
$newsList[] = $news;
}
}
return $newsList;
}
Maybe a DTO could be useful here : doctrine documentation
Basically, you define a PHP class which is not mapped to your model.
You can then select what you need and trigger the instantiation of an object of this PHP class from the DQL query.
Hope this helps !
Default hydrator does not include unmapped properties (wow, right?).
You want to override/extend doctrine/orm/lib/Docrtrine/ORM/Internal/Hydration/AbstractHydrator
line 386 where it only checks for $classMetadata fieldMappings
Or you can hack around by using DTO and query in transformer, if performance is not needed

Persisting object with relationship, database not updating

I have 2 entities with relationship OneToMany.
entity Question:
/**
* #ORM\OneToMany(targetEntity="Quiz\CoreBundle\Entity\Answer", mappedBy="question", cascade={"persist"})
*/
private $answers;
entity answer:
/**
* #ORM\ManyToOne(targetEntity="Quiz\CoreBundle\Entity\Question", inversedBy="answers")
*/
private $question;
Here I try to persist :
$em = $this->getDoctrine()->getManager();
$question = new Question();
$answer = new Answer();
$answer2 = new Answer();
$answer->setAnswerText('Roterdam');
$answer2->setAnswerText('Amsterdam')
->SetCorrect(true);
$question->setQuestionText('What\'s the capital of Netherlands? ');
$question->addAnswer($answer);
$question->addAnswer($answer2);
$em->persist($question);
$em->flush();
When I run this code everything updates in the db except the foreign key in answer table, the question_id is null.
Any idea what am I doing wrong ?
This has got to be one of the top five most popular Doctrine 2 questions. But I'm too lazy to look up one to link to.
Ask yourself how the answer knows to which question does it belong? Where is the link at the object level?
class Question
{
function addAnswer($answer)
{
$this->answers[] = $answer;
$answer->setQuestion($this);
}
}

FOS bundle - How to select users with a specific role?

I am using the FOS bundle and I want to retrieve all users with a given ROLE from the database.
What is the best way to do this?
Just add this in your UserRepository or replace $this->_entityName by YourUserBundle:User:
/**
* #param string $role
*
* #return array
*/
public function findByRole($role)
{
$qb = $this->_em->createQueryBuilder();
$qb->select('u')
->from($this->_entityName, 'u')
->where('u.roles LIKE :roles')
->setParameter('roles', '%"'.$role.'"%');
return $qb->getQuery()->getResult();
}
If you are using FOSUser Groups you should use:
/**
* #param string $role
*
* #return array
*/
public function findByRole($role)
{
$qb = $this->_em->createQueryBuilder();
$qb->select('u')
->from($this->_entityName, 'u')
->leftJoin('u.groups', 'g')
->where($qb->expr()->orX(
$qb->expr()->like('u.roles', ':roles'),
$qb->expr()->like('g.roles', ':roles')
))
->setParameter('roles', '%"'.$role.'"%');
return $qb->getQuery()->getResult();
}
Well, if there is no better solution, I think I will go to a DQL query:
$query = $this->getDoctrine()->getEntityManager()
->createQuery(
'SELECT u FROM MyBundle:User u WHERE u.roles LIKE :role'
)->setParameter('role', '%"ROLE_MY_ADMIN"%');
$users = $query->getResult();
If you have this requirement and your user list will be extensive, you will have problems with performance. I think you should not store the roles in a field as a serialized array. You should create an entity roles and many to many relationship with the users table.
As #Tirithen states, the problem is that you will not get the users that have an implicit role due to role hierarchy. But there is a way to work around that!
The Symfony security component provides a service that gives us all child roles for a specific parent roles. We can create a service that does almost the same thing, only it gives us all parent roles for a given child role.
Create a new service:
namespace Foo\BarBundle\Role;
use Symfony\Component\Security\Core\Role\RoleHierarchy;
use Symfony\Component\Security\Core\Role\Role;
/**
* ReversedRoleHierarchy defines a reversed role hierarchy.
*/
class ReversedRoleHierarchy extends RoleHierarchy
{
/**
* Constructor.
*
* #param array $hierarchy An array defining the hierarchy
*/
public function __construct(array $hierarchy)
{
// Reverse the role hierarchy.
$reversed = [];
foreach ($hierarchy as $main => $roles) {
foreach ($roles as $role) {
$reversed[$role][] = $main;
}
}
// Use the original algorithm to build the role map.
parent::__construct($reversed);
}
/**
* Helper function to get an array of strings
*
* #param array $roleNames An array of string role names
*
* #return array An array of string role names
*/
public function getParentRoles(array $roleNames)
{
$roles = [];
foreach ($roleNames as $roleName) {
$roles[] = new Role($roleName);
}
$results = [];
foreach ($this->getReachableRoles($roles) as $parent) {
$results[] = $parent->getRole();
}
return $results;
}
}
Define your service for instance in yaml and inject the role hierarchy into it:
# Provide a service that gives you all parent roles for a given role.
foo.bar.reversed_role_hierarchy:
class: Foo\BarBundle\Role\ReversedRoleHierarchy
arguments: ["%security.role_hierarchy.roles%"]
Now you are ready to use the class in your own service. By calling $injectedService->getParentRoles(['ROLE_YOUR_ROLE']); you will get an array containing all parent roles that will lead to the 'ROLE_YOUR_ROLE' permission. Query for users that have one or more of those roles... profit!
For instance, when you use MongoDB you can add a method to your user document repository:
/**
* Find all users with a specific role.
*/
public function fetchByRoles($roles = [])
{
return $this->createQueryBuilder('u')
->field('roles')->in($roles)
->sort('email', 'asc');
}
I'm not into Doctrine ORM but I'm sure it won't be so different.
You can use just this on your DQL:
SELECT u FROM YourFavouriteBundle:User u WHERE u.roles [NOT] LIKE '%ROLE_YOUR_ROLE%'
Of course with QueryBuilder it's more elegant:
// $role = 'ROLE_YOUR_ROLE';
$qb->where('u.roles [NOT] LIKE :role')
->setParameter('role', "%$role%");
Finally i solved it, following is an exact solution:
public function searchUsers($formData)
{
$em = $this->getEntityManager();
$usersRepository = $em->getRepository('ModelBundle:User');
$qb = $usersRepository->createQueryBuilder('r');
foreach ($formData as $field => $value) {
if($field == "roles"){
$qb->andWhere(":value_$field MEMBER OF r.roles")->setParameter("value_$field", $value);
}else{
$qb->andWhere("r.$field = :value_$field")->setParameter("value_$field", $value);
}
}
return $qb->getQuery()->getResult();
}
Cheers!
In case you need to filter users by role using a DQL filter in a YAML file (In EasyAdminBundle for instance)
entities:
Admin:
class: App\Entity\User
list:
dql_filter: "entity.roles LIKE '%%ROLE_ADMIN%%'"
Here I give an alternative solution :
I find users of roles for a given array
In controller I call the function like that
$users = $userRepository->findUsersOfRoles(['ROLE_ADMIN', 'ROLE_SUPER_USER']);
Then in my repository I make a loop to generate condition and set the parameters :
public function findUsersOfRoles($roles)
{
$condition = 'u.roles LIKE :roles0';
foreach ($roles as $key => $role){
if ($key !== 0){
$condition .= " OR u.roles LIKE :roles".$key;
}
}
$query = $this->createQueryBuilder('u')
->where($condition);
foreach ($roles as $key => $role){
$query ->setParameter('roles'.$key, '%"'.$role.'"%');
}
return $query->getQuery() ->getResult();
}

Persisting multiple new associated objects in a self-referenced table with Doctrine2 and Symfony2

I'm quite new to Doctrine2 which I'm currently using in a Symfony2 project.
I'm trying to persist entities with a self-referencing foreign key such as the category example of the Doctrine documentation (http://www.doctrine-project.org/docs/orm/2.0/en/reference/association-mapping.html#one-to-many-self-referencing). There is probably a very easy solution to this but I just couldn't find it anywhere on the web.
For some reason, the value for the parent_id is not automatically stored by Doctrine (it's null), although I can access the id value of the object I am assigning as a parent.
Some code will probably be straightforward. Here's the relevant part :
The entity definition :
class Area
{
// id, name, type, etc...
/**
* #ORM\ManyToOne(targetEntity="Area", inversedBy="sub_areas", cascade={"persist"})
* #ORM\JoinColumn(name="parent_area_id", referencedColumnName="id")
*/
private $parent_area;
/**
* #ORM\OneToMany(targetEntity="Area", mappedBy="parent_area", cascade={"persist"})
*/
private $sub_areas;
/**
* Set parent area
*
* #param obj $area
*/
public function setParentArea(Area $area)
{
$this->aera = $area;
}
// Other getters and setters, etc.
}
In the action :
$results = array();
foreach($area_types as $key => $type) {
$area = new Area();
$area->setType($type);
$area->setName($location->getAddressComponent($type));
if(isset($parent_area)) {
$area->setParentArea($parent_area);
}
$this->em->persist($area);
$parent_area = $area;
$results[] = $area->getId();
}
$this->em->flush();
The results array will output the assigned ids in Twig. I also tried using :
$area->setParentAreaId($parent_area->getId());
Could anyone explain how Doctrine manages the database persistence sequence of self-referenced objects ?

Resources