I am using doctrine and created a User entity with a OneToMany relation on Meal
User class
/**
* #ORM\Entity(repositoryClass=UserRepository::class)
*/
class User
/**
* #ORM\OneToMany(targetEntity=Meal::class, mappedBy="user")
*/
private $meals;
Meal class
/**
* #ORM\Entity(repositoryClass=MealRepository::class)
*/
class Meal
{
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="meals")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* #ORM\Column(type="date")
* #Groups({"read:softdiet:item"})
*/
private $day;
I want to find meals from a specific user and date.
I tried
$meal = $entityManager->getRepository('App\Entity\Meal')->findBy(['user.id' => $userId, 'date' => $date]);
but I get an error: "Unrecognized field: user.id'"
Help me please!
In the findBy method, you do not have access to the properties of the User entity, since you are searching in the Meal entity.
You can do it like this:
$meal = $entityManager->getRepository('App\Entity\Meal')->findBy(['user' => $userId, 'date' => $date]);
With ORM you use objects and properties, not column names, so in your case it's:
$meal = $entityManager
->getRepository('App\Entity\Meal')
->findBy(['user' => $user, 'date' => $date]);
Related
I have this in a form builder:
$form->add('region', EntityType::class, array(
'class' => 'AppBundle:Regions',
'placeholder' => '',
'required' => false,
'query_builder' => function (EntityRepository $er) use ($country_2a) {
return $er->createQueryBuilder('s')
->where('s.countryId= :country_id')
->setParameter('country_id', $country_2a)
->addOrderBy('s.name');
}
));
The case is that in the entity Regions I have the country_id but I receive the country_2a, then before running the createBuilder I need to search in the entity Countries which is the id for the code_2a.
I have declared in the entities this relations:
Countries Entity:
/**
* #ORM\OneToMany(targetEntity="Regions", mappedBy="country_id")
*/
private $regions;
public function __construct() {
$this->regions = new ArrayCollection();
}
Regions Entity:
/**
* #var int
*
* #ORM\Column(name="country_id", type="integer")
* #ORM\ManyToOne(targetEntity="Countries", inversedBy="id")
* #ORM\JoinColumn(name="country_id", referencedColumnName="id")
*/
private $countryId;
I don't know how to manage to use this relationship between entities, I've tried a JOIN LEFT and a subselect but nothing worked.
Perhaps my problem is that I need to access to the country entity but the $er is connected to the Regions entity, or how to create an createQueryBuilder sentence to exploit the relationship shown.
Any suggestion?.
you Regions Entity should be like this:
/**
* #var int
*
* #ORM\Column(name="country_id", type="integer")
* #ORM\ManyToOne(targetEntity="Countries", inversedBy="regions")
* #ORM\JoinColumn(name="country_id", referencedColumnName="id")
*/
private $countryId;
Try that change.
Let's say I have a "Person" entity. A person can belong to a "Group". They are associated through a ManyToMany, Join Table strategy.
The general code looks like this:
/**
* Vendor\AcmeBundle\Entity\Person
*
* #ORM\Entity(repositoryClass="Vendor\AcmeBundle\Entity\PersonRepository")
*/
class Person extends BaseUser
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToMany(targetEntity="Vendor\AcmeBundle\Entity\Group")
*/
protected $groups;
}
and the group entity
/**
* #ORM\Entity
*/
class Group extends BaseGroup
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", nullable=true)
*/
protected $publicName;
}
What do I want to achieve?
Given a group, list users belonging to that group in a consistent way, including pagination options (aka limit and offset)
Something like this:
function getUserFromGroup(Group $group, $criteria, $limit, $offset){};
Considerations:
The entities are mutable, they can be adapted to achieve this requisite (e.g. the association could be changed from unidirectional to bidirectional)
The amount of person entities is in the thousands (2000~8000)
The amount of groups is less than 10
This is explained in the Symfony2 Book, chapter on Doctrine
For your case, I would suggest using the findBy() method.
From the official doctrine documentation:
function getUserFromGroup($group, $criteria, $limit, $offset){
// You should probably build the criteria into a paramaters array,
// but I'll just asume it's "fieldName" => "valueToFilterBy"
$criteria['groups'] = $group;
$users = $em->getRepository('AppBundle\Entity\User')
->findBy(
$criteria, // Filter by columns
array('name' => 'ASC'), // Sorting
$limit, // How many entries to select
$offset // Offset
);
return $users;
};
I would not use the associations to list group's members, but a custom repository call. This should be close enough:
class PersonRepository extends EntityRepository
{
public function findPeopleInGroup(Group $group, $criteria, $limit, $offset){
$qb = $this->createQueryBuilder('p');
$qb->join('p.groups', 'g')
->where(':group MEMBER OF p.groups')
->setParameter('group', $group)
->orderBy('p.'.$criteria);
$qb->setFirstResult($offset);
$qb->setMaxResults($limit);
return $qb->getQuery()->getResult();
}
}
Here is the situation, I have a form in which I need an entity field type. Inside the BenefitGroup entity I have a BenefitGroupCategory selection.
My buildform is:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('BenefitGroupCategories', 'entity', array(
'class' => 'AppBundle:BenefitGroupCategory',
'property' => 'name',
'label' => false,
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('c')
->orderBy('c.name', 'ASC');
},))
->add('benefitsubitems', 'collection', array('type' => new BenefitSubItemFormType(), 'allow_add' => true, 'label' => false,));
}
It's almost a typical product-category relationship. A BenefitGroup can have only one category and a category can belong to many BenefitGroups (the only complication, not implemented yet, but that's the reason I need the query builder, is that all will depend on another parameter (project) so that some categories will be the default ones (always available), others will be available only for specific projects (see below the reference to project in the BenefitGroupCategory entity)).
You'll notice another field, benefitsubitems, which is not relevant for the question at hand.
As far I understand it, from the Doctrine perspective, I have to set up a One-To-Many, Unidirectional with Join Table.
The two entities are:
<?php
// src/AppBundle/Entity/BenefitGroup.php
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="AppBundle\Entity\BenefitGroupRepository")
* #ORM\Table(name="benefit_groups")
*/
class BenefitGroup
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="BenefitItem", cascade={"persist"}, inversedBy="BenefitGroups")
*/
protected $benefitItem;
/**
* #ORM\oneToMany(targetEntity="BenefitSubItem", mappedBy="benefitGroup")
*/
protected $BenefitSubItems;
/**
* #ORM\ManyToMany(targetEntity="BenefitGroupCategory")
* #ORM\JoinTable(name="BenefitGroup_BenefitGroupCategory", joinColumns={#ORM\JoinColumn(name="BenefitGroup_id", referencedColumnName="id")}, inverseJoinColumns={#ORM\JoinColumn(name="BenefitGroupCategory_id", referencedColumnName="id", unique=true)})
*/
protected $BenefitGroupCategories;
// HERE I HAVE SOME IRRELEVANT GETTERS AND SETTERS
/**
* Constructor
*/
public function __construct()
{
$this->BenefitSubItems = new ArrayCollection();
$this->BenefitGroupCategories = new ArrayCollection();
}
/**
* Add BenefitGroupCategories
*
* #param \AppBundle\Entity\BenefitGroupCategory $benefitGroupCategories
* #return BenefitGroup
*/
public function addBenefitGroupCategory(\AppBundle\Entity\BenefitGroupCategory $benefitGroupCategories)
{
$this->BenefitGroupCategories[] = $benefitGroupCategories;
return $this;
}
/**
* Remove BenefitGroupCategories
*
* #param \AppBundle\Entity\BenefitGroupCategory $benefitGroupCategories
*/
public function removeBenefitGroupCategory(\AppBundle\Entity\BenefitGroupCategory $benefitGroupCategories)
{
$this->BenefitGroupCategories->removeElement($benefitGroupCategories);
}
/**
* Get BenefitGroupCategories
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getBenefitGroupCategories()
{
return $this->BenefitGroupCategories;
}
}
You'll also notice another entity, BenefitItem, which is the "father" of BenefitGroup.
And
<?php
// src/AppBundle/Entity/BenefitGroupCategory.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity()
* #ORM\Table(name="benefit_group_category")
* #UniqueEntity(fields={"name", "project"}, ignoreNull=false, message="Duplicated group category for this project")
*/
class BenefitGroupCategory
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=50)
*/
protected $name;
/**
* #ORM\ManyToOne(targetEntity="Project")
*/
protected $project;
// HERE I HAVE SOME IRRELEVANT GETTERS AND SETTERS
}
In the controller (you'll see several embedded collections, which work ok) I have:
/**
* #Route("/benefit/show/{projectID}", name="benefit_show")
*/
public function showAction(Request $request, $projectID)
{
$id=4; //the Id of the CVC to look for
$storedCVC = $this->getDoctrine()
->getRepository('AppBundle:CVC')
->find($id);
$form = $this->createForm(new CVCFormType(), clone $storedCVC);
$form->handleRequest($request);
if ($form->isValid())
{
$em = $this->getDoctrine()->getManager();
//$benefitGroupCategoryRepository = $this->getDoctrine()->getRepository('AppBundle:BenefitGroupCategory');
$formCVC = $form->getData();
$em->persist($formCVC);
foreach ($formCVC->getBenefitItems() as $formBI)
{
$newBI = new BenefitItem();
$newBI->setCVC($formCVC);
$newBI->setComment($formBI->getComment());
$em->persist($newBI);
foreach ($formBI->getBenefitGroups() as $formBG)
{
$newBG = new BenefitGroup();
$newBG->setBenefitItem($newBI);
$newBG->setBenefitGroupCategories($formBG->getBenefitGroupCategories());
$em->persist($newBG);
foreach ($formBG->getBenefitSubItems() as $formSI)
{
$newSI = new BenefitSubItem();
$newSI->setBenefitGroup($newBG);
$newSI->setComment($formSI->getComment());
$em->persist($newSI);
}
}
}
$em->flush();
}
return $this->render('benefit/show.html.twig', array(
'form' => $form->createView(),
));
}
The problem is: in visualization it visualizes correctly the form (even though it does not retrieve correctly the category. I have a choice of categories, which is ok, but it does not retrieve the right one. Do I have to set the default value in the form?
The problem gets way worse when I sumbit the form it's supposed to create a new entity (notice the clone) with all the nested ones. The problem is that it crashes saying:
Neither the property "BenefitGroupCategories" nor one of the methods
"addBenefitGroupCategory()"/"removeBenefitGroupCategory()",
"setBenefitGroupCategories()", "benefitGroupCategories()", "__set()" or
"__call()" exist and have public access in class
"AppBundle\Entity\BenefitGroup".
The "beauty" is that even if I comment completeley the (nasty) part inside the "isValid" it behaves exactly the same.
I'm lost :(
About the cloning you have to unset the id of the cloned entity, look here: https://stackoverflow.com/a/14158815/4723525
EDIT:
Yes, but PHP just do shallow copy, you have to clone other objects. Look at Example #1 Cloning an object in http://php.net/manual/en/language.oop5.cloning.php. You have to clone your objects by defining __clone method (for Doctrine lower than 2.0.2 you have to do this by calling own method after cloning because proxy defines it's own __clone method). So for example:
function __clone() {
$oldCollection = $this->collection;
$this->collection = new ArrayCollection();
foreach ($oldCollection as $oldElement) {
$newElement = clone $oldElement;
// additional actions for example setting this object to owning side
$newElement->setParent($this);
$this->collection->add($newElement);
}
}
I have 3 Entities (Group, GroupCategory and GroupLanguage)
Group Table
id (pk)
id_group_category (fk)
GroupCategory Table
id (pk)
GroupCategoryLanguage Table
id (pk)
id_language (fk)
id_group_category (fk)
I have created a GroupType which takes in GroupCategory as a subform.
$builder->add('id_group_category', 'entity', array(
'class' => 'BazaarBundle:GroupCategory',
'property' => 'id',
'query_builder' => function(EntityRepository $a) {
return $a->createQueryBuilder('a')
->innerJoin('BazaarBundle:GroupCategoryLanguage', 'b')
->where('b.id_group_category = a.id')
->orderBy('a.id', 'ASC');
}
)
)
->add('Add', 'submit');
I'm trying to innerJoin the language table so that the dropdownlist would be populated with text and not the ids of the category.
I'm quite new to Symfony2 and have already looked up to their documentation and sorry to say it was quite puzzling for me. Am I doing it right because i'm having some errors with the code.
The error message:
[Semantical Error] line 0, col 111 near 'id_group_category': Error: Class Karl\BazaarBundle\Entity\GroupCategoryLanguage has no field or association named id_group_category
GroupCategory.php
class GroupCategory
{
public function __construct()
{
$this->groupCategoryLanguage = new ArrayCollection();
}
public function __toString(){
return $this->groupCategoryLanguage->getName();
}
/**
* #ORM\OneToMany(targetEntity="GroupCategoryLanguage", mappedBy="idGroupCategory")
* #ORM\JoinColumn(nullable=false,referencedColumnName="id_group_category")
*/
protected $groupCategoryLanguage;
}
GroupCategoryLanguage.php
class GroupCategoryLanguage
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer
*
* #ORM\Column(name="id_language", type="integer")
*/
private $idLanguage;
/**
* #var integer
*
* #ORM\JoinColumn(name="id_group_category", nullable=false)
* #ORM\ManyToOne(targetEntity="Karl\BazaarBundle\Entity\GroupCategory", inversedBy="groupCategoryLanguage")
*/
private $idGroupCategory;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=32)
*/
private $name;
}
i think you need to add GroupCategoryLanguage data into your query by adding:
->addSelect('b')
to your query builer object.
Exemple below.
Please note i have deleted the where condition because it seems to be a join condition, adn this is not needed bacause Doctrine is suposed to know all about relations. If i'm wrong, don't delete it...
$builder->add('id_group_category', 'entity', array(
'class' => 'BazaarBundle:GroupCategory',
'property' => 'id',
'query_builder' => function(EntityRepository $a) {
return $a->createQueryBuilder('a')
->innerJoin('a.languages', 'b')
->addSelect('b')
->orderBy('a.id', 'ASC');
}
))
->add('Add', 'submit');
EDIT:
Regarding our discussions, i update my answer:.
Let's start from the beginning:
You have a relation between GroupCategory and GroupCategoryLanguage and the GroupCategoryLanguage is the owner of this relation (it have to FK).
Here you want to get languages from the GroupCategory so it's $owner->getSlave() and you need a bidirectionnal relation.
For that you need to add a field into the slave entity:
So in GroupCategory entity:
/**
* #ORM\OneToMany(targetEntity="Karl\BazaarBundle\Entity\GroupCategoryLanguage",referencedColumnName="id_group_category", mappedBy="category")
* #ORM\JoinColumn(nullable=false)
*/
private $languages;
And i assume that in GroupCategoryLanguages you have:
/**
* #ORM\ManyToOne(targetEntity="Karl\BazaarBundle\Entity\GroupCategory", inversedBy="languages")
* #ORM\JoinColumn(name="id_group_category", nullable=false)
*/
private $category;
I think one of your problems is that you think in terms of tables, am i wrong ?
You really need to think in term of objects (entities) and let Doctrine manage the boring things :)
Display language in place of id
You can totally delete the 'property' option and add a __toString method into your GroupCategory entity, which one will be called and the returned value will appear in your form.
I think we are good :)
Cheers
I am trying to persist an user entity with a profile entity from a single form submit. Following the instructions at the Doctrine2 documentation and after adding additional attributes this seemed to be sufficient to achieve the goal.
Entities
Setting up the entites in accordance is pretty straight forward and resulted in this (I left out the generated getter/setter):
// ...
/**
* #ORM\Entity
*/
class User
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\Column(type="string", length=64)
*/
private $data;
/**
* #ORM\OneToOne(targetEntity="Profile", mappedBy="user", cascade={"persist", "remove"})
*/
private $Profile;
// ...
}
// ...
/**
* #ORM\Entity
*/
class Profile
{
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="User")
*/
private $user;
/**
* #ORM\Column(type="string", length=64)
*/
private $data;
// ...
}
Forms
Now modifiying the forms is not too difficult as well:
// ...
class ProfileType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('data')
;
}
public function getName()
{
return 'profile';
}
public function getDefaultOptions(array $options)
{
return array('data_class' => 'Acme\TestBundle\Entity\Profile');
}
}
// ...
class TestUserType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('data')
->add('Profile', new ProfileType())
;
}
public function getName()
{
return 'user';
}
}
Controller
class UserController extends Controller
{
// ...
public function newAction()
{
$entity = new User();
$form = $this->createForm(new UserType(), $entity);
return array(
'entity' => $entity,
'form' => $form->createView()
);
}
public function createAction()
{
$entity = new User();
$request = $this->getRequest();
$form = $this->createForm(new UserType(), $entity);
$form->bindRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('user_show',
array('id' => $entity->getId())));
}
return array(
'entity' => $entity,
'form' => $form->createView()
);
}
// ...
}
But now comes the part where testing takes place. I start to create a new user-object, the embedded form shows up as expected, but hitting submit returns this:
Exception
Entity of type Acme\TestBundle\Entity\Profile is missing an
assigned ID. The identifier generation strategy for this entity
requires the ID field to be populated before EntityManager#persist()
is called. If you want automatically generated identifiers instead
you need to adjust the metadata mapping accordingly.
A possible solution I am already aware of is to add an additional column for a stand-alone primary key on the Profile entity.
However I wonder if there is a way to keep the mapping roughly the same but deal with persisting the embedded form instead?
After debating for quite a while with a couple of people via IRC I modified the mapping and came up with this:
Entities
// ...
/**
* #ORM\Entity
*/
class User
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=64)
*/
private $data;
/**
* #ORM\OneToOne(targetEntity="Profile", cascade={"persist", "remove"})
*/
private $Profile;
// ...
}
// ...
/**
* #ORM\Entity
*/
class Profile
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=64)
*/
private $data;
// ...
}
So what does this change? First of all I removed the mappedBy and inversedBy options for the relation. In addition the OneToOne annotation on the Profile-entity was not needed.
The relation between User and Profile can be bi-directional however a uni-directional relation with User being the owning side is sufficient to have control over the data. Due to the cascade option you can be sure there are no left-over Profiles without Users and Users can maintain a Profile but do not have to.
If you want to use a bi-directional relation I recommand taking a look at Github: Doctrine2 - Tests - DDC117 and especially pay attention to Article and ArticleDetails' OneToOne relation. However you need to be aware that saving this bi-directional relation is a bit more tricky as can be seen from the test file (link provided in comment): you need to persist the Article first and setup the constructor in ArticleDetails::__construct accordingly to cover the bi-directional nature of the relationship.
The problem from what I can see is that you're only creating / saving a User object.
As the User / Profile is a One to One relation (with User being the owning side) would it be safe to assume that a User will always have a Profile relation, and so could be initialised in the Users construction
class User
{
public function __construct()
{
$this->profile = new Profile();
}
}
After all you've set User up to cascade persistence of the related Profile object. This will then have your entity manager create both Entities and establish the relation.