Doctrine Multi-Tenancy Missing Value From Primary Key (With 2 PKs) - php

I am working on a multi-tenancy system for Doctrine. I ran into an exception that said
Missing value for primary key groupId on ContactBundle\Entity\Contact
The way my multi-tenancy works is by having the data from different organizatiosn seperated by their groupId. That way each organization can have an id=1 and not break any rules because the combination of their groupId and the Id is what makes the record unique.
This means that you can have
record 1:
id = 1
groupId = 1
record 2:
id = 1
groupId = 2
and it would be valid.
The problem that I am running into is the fact that I am not sure how to pass in the groupId for when it goes to do the joins for my associations, so it throws that error. Since the group_id of the currently viewed project should be the same as the ones listed for contact and organization, how would I go about passing the current project's groupId into the query for contact and organization? That way it pulls the right record and doesn't complain about missing a primary key.
Below is my Project Entity listed.
<?php
namespace ProjectBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Project
*
* #ORM\Table(name="project")
* #ORM\Entity(repositoryClass="ProjectBundle\Repository\ProjectRepository")
*/
class Project
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=2500)
*/
private $description;
/**
* #var int
*
* #ORM\OneToOne(targetEntity="ContactBundle\Entity\Contact", inversedBy="projects")
* #ORM\JoinColumn(name="contact_id", referencedColumnName="id")
*
*/
private $contactId;
/**
* #var int
*
* #ORM\OneToOne(targetEntity="ContactBundle\Entity\Organization", inversedBy="projects")
* #ORM\JoinColumn(name="organization_id", referencedColumnName="id")
*/
private $organizationId;
/**
* #var int
*
* #ORM\Column(name="group_id", type="integer")
* #ORM\Id
*/
private $groupId;
public function __construct($id, $groupId){
$this->id = $id;
$this->groupId = $groupId;
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Project
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set description
*
* #param string $description
*
* #return Project
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set contactId
*
* #param integer $contactId
*
* #return Project
*/
public function setContactId($contactId)
{
$this->contactId = $contactId;
return $this;
}
/**
* Get contactId
*
* #return int
*/
public function getContactId()
{
return $this->contactId;
}
/**
* Set organizationId
*
* #param integer $organizationId
*
* #return Project
*/
public function setOrganizationId($organizationId)
{
$this->organizationId = $organizationId;
return $this;
}
/**
* Get organizationId
*
* #return int
*/
public function getOrganizationId()
{
return $this->organizationId;
}
}
I will also give you guys my substitutes for find() & findAll() since my current system requires the groupId to get the right record.
/**
* #param integer $groupId
* #param integer $id
*/
public function findDataCollection(int $groupId)
{
$qb = $this->repository->createQueryBuilder('e');
$qb
->andWhere('e.groupId = :groupId')
->setParameter('groupId',$groupId);
return $qb->getQuery()->getResult();
}
/**
* #param integer $groupId
* #param integer $id
*/
public function findData(int $groupId, $id)
{
if(empty($id)){
return false;
}
$qb = $this->repository->createQueryBuilder('e');
$qb
->andWhere('e.id = :id')
->andWhere('e.groupId = :groupId')
->setParameter('id', $id)
->setParameter('groupId',$groupId);
$data = $qb->getQuery()->getOneorNullResult();
return $data;
}
Thank's lots ahead of time guys!

writing my comments down as an answer as they require somewhat more space here. So nope, I was recommending that: I was recommending that you would replace the second part of your composite key (groupid) to be an actual association to the group entity instead of being a manually managed id field (https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/tutorials/composite-primary-keys.html#use-case-1-dynamic-attributes).
As for your original question, I'm not actually sure what your issue is. If you would have the Project entity available, you can directly access the connected contact and organization via the related getters.
I'm not sure whether your associations are correctly defined for your use case or not; you have defined the associations as one-to-one for your Project, which basically means that in addition to one project having one contact and belonging to one organization, also the other way around that one contact can have only one project and one organization can only have only one project... I.e. they sound like they should be defined as ManyToOne associations (?)

Related

Doctrine 3 does not create the foreign key - PDOException: "Column 'tour_id' cannot be null"

I'm trying to insert a new record in the database.
I have two tables bootstrap_tour and bootstrap_tour_step.
id of boostrap_tour table is the foreign key tour_id in the bootstrap_tour_step table.
The corresponding entities look as follows:
BootstrapTour.php
/**
* #var int
*
* #ORM\Column(name="id", type="integer", options={"unsigned"=true})
* #ORM\Id
*
* #JMS\Groups({"auth_read_postbootstraptours"})
* #JMS\Type("integer")
* #JMS\Accessor(getter="getId")
*/
protected $id;
/**
* #var ArrayCollection[BootstrapTourStep]
*
* #ORM\OneToMany(targetEntity="BootstrapTourStep", mappedBy="bootstrapTour", cascade={"persist"})
*
* #JMS\Groups({"auth_read_postbootstraptours"})
*/
private $bootstrapTourSteps;
/**
* Object instantiation.
*/
public function __construct()
{
parent::__construct();
$this->bootstrapTourSteps = new ArrayCollection();
}
/**
* Sets a collection of BootstrapTourStep objects.
*
* #param ArrayCollection|null $bootstrapTourSteps
*
* #return BootstrapTour
*/
public function setBootstrapTourSteps(?ArrayCollection $bootstrapTourSteps): BootstrapTour
{
$this->bootstrapTourSteps = $bootstrapTourSteps;
return $this;
}
/**
* Returns a collection of BootstrapTourStep objects.
*
* #return Collection[BootstrapTourStep]|null
*/
public function getBootstrapTourSteps(): ?Collection
{
return $this->bootstrapTourSteps;
}
/**
* Adds a Step to the tour.
*
* #return BootstrapTour
*/
public function addBootstrapTourStep(BootstrapTourStep $bootstrapTourStep): BootstrapTour
{
$bootstrapTourStep->setBootstrapTour($this);
$this->bootstrapTourSteps[] = $bootstrapTourStep;
return $this;
}
BootstrapTourStep.php
/**
* #var int
*
* #ORM\Column(name="id", type="integer", options={"unsigned"=true})
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*
* #JMS\Groups({"auth_read_getbootstraptours"})
* #JMS\Type("integer")
* #JMS\Accessor(getter="getId")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="BootstrapTour", inversedBy="bootstrapTourSteps")
* #ORM\JoinColumn(name="tour_id", referencedColumnName="id", nullable=false)
*
* #JMS\Groups({"auth_read_postbootstraptours"})
* #JMS\Type("EN\CentralAdmin\DoctrineBundle\Entity\BootstrapTour")
* #JMS\Accessor(getter="getBootstrapTour", setter="setBootstrapTour")
*/
private $bootstrapTour;
/**
* Gets the BootstrapTour
*
* #return BootstrapTour|null
*/
public function getBootstrapTour(): ?BootstrapTour
{
return $this->bootstrapTour;
}
/**
* Sets a BootstrapTour
*
* #param BootstrapTour $bootstrapTour
* #return BootstrapTourStep
*/
public function setBootstrapTour(BootstrapTour $bootstrapTour): BootstrapTourStep
{
$this->bootstrapTour = $bootstrapTour;
return $this;
}
/**
* A list of reference proxies.
*
* #return array
*/
public function getReferenceProxies(): array
{
return [
'BootstrapTour'
];
}
My controller Action :
$bootstrapTourService = $this->getCentralAdminEntityService('BootstrapTour');
$bootstrapTourService->persist($tourType, true);
I am able to select the data using this but in case of adding new record I am getting the following exception:
PDOException: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'tour_id' cannot be null
How can I resolve this issue?
You're not setting the relation to the tour on the steps when adding a collection of steps. This way the step entities are added to the tour but the steps themselves don't know which tour they belong to.
If now doctrine tries to persist the steps their reference to the tour is missing and therefore you get the missing tour_id exception.
This ...
public function setBootstrapTourSteps(?ArrayCollection $bootstrapTourSteps): BootstrapTour
{
$this->bootstrapTourSteps = $bootstrapTourSteps;
return $this;
}
... should be:
public function setBootstrapTourSteps(?ArrayCollection $bootstrapTourSteps): BootstrapTour
{
$this->bootstrapTourSteps = new ArrayCollection();
foreach ($bootstrapTourSteps as $step) {
$step->setBootstrapTour($this);
$this->bootstrapTourSteps->add($step);
}
return $this;
}

“association refers to the inverse side field” & “mappings are inconsistent with self”

i have one entity and is relation with self.
category is related by self and field name is parent.
when load page Mapping errors show in profiler.
/**
* Category
*
* #ORM\Table(name="category")
* #ORM\Entity(repositoryClass="AdminBundle\Repository\CategoryRepository")
* #UniqueEntity("urlcode")
*/
class Category
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="Title", type="string", length=255)
*/
private $title;
/**
* #var string
*
* #ORM\Column(name="urlcode", type="string", length=255)
*/
private $urlcode;
/**
* #var string
*
* #ORM\Column(name="image", type="string", length=255)
*/
private $image;
/**
* #var int
*
* #ORM\Column(name="digiid", type="integer", unique=true)
*/
private $digiid;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="Category")
* #ORM\JoinColumn(name="parent", referencedColumnName="id")
*/
private $parent;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set title
*
* #param string $title
*
* #return Category
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set urlcode
*
* #param string $urlcode
*
* #return Category
*/
public function setUrlcode($urlcode)
{
$this->urlcode = $urlcode;
return $this;
}
/**
* Get urlcode
*
* #return string
*/
public function getUrlcode()
{
return $this->urlcode;
}
/**
* Set image
*
* #param string $image
*
* #return Category
*/
public function setImage($image)
{
$this->image = $image;
return $this;
}
/**
* Get image
*
* #return string
*/
public function getImage()
{
return $this->image;
}
/**
* Set digiid
*
* #param integer $digiid
*
* #return Category
*/
public function setDigiid($digiid)
{
$this->digiid = $digiid;
return $this;
}
/**
* Get digiid
*
* #return integer
*/
public function getDigiid()
{
return $this->digiid;
}
/**
* Set parent
*
* #param \AdminBundle\Entity\Category $parent
*
* #return Category
*/
public function setParent(\AdminBundle\Entity\Category $parent = null)
{
$this->parent = $parent;
return $this;
}
/**
* Get parent
*
* #return \AdminBundle\Entity\Category
*/
public function getParent()
{
return $this->parent;
}
public function __toString()
{
return $this->title;
}
}
profiler:
The association AdminBundle\Entity\Product#category refers to the
inverse side field AdminBundle\Entity\Category#Category which does not
exist.
The association AdminBundle\Entity\Product#brand refers to the inverse
side field AdminBundle\Entity\Brand#Brand which does not exist.
The mappings AdminBundle\Entity\Product#link and
AdminBundle\Entity\Link#product are inconsistent with each other.
The association AdminBundle\Entity\Category#parent refers to the
inverse side field AdminBundle\Entity\Category#Category which does not
exist.
The association AdminBundle\Entity\Category#category refers to the
owning side field AdminBundle\Entity\Category#Category which does not
exist.
Your issue is caused by inversedBy="Category". The error says, that there's no Category::$Category atribute, and indeed there isn't.
inversedBy parameters is used to define the other side of relation in order to create bidirectional relationship.
In your case it would be probably children, if you would want to have access from parent to its children categories.
Since you don't have it, you cane simply remove this parameter. And it looks like you have this parameter used incorrectly also in other entities.
If you want more information about how to define relationships in Doctrine ORM, take a look at documentation

Symfony doesnt auto increment id

I have a database called cardb which is populated by a SQL file which made some cars. They have ID's starting from 1 up to 30. When I try to create new Car it works the first time and creates a car with id 0 then when it tries to do it again it again tries to make an entry with 0.
Here is the error
An exception occurred while executing 'INSERT INTO cars (Make, Model, TravelledDistance) VALUES (?, ?, ?)' with params ["fock", "fock", 320]:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '0' for key 'PRIMARY'
Here is my entity
/**
* Cars
*
* #ORM\Table(name="cars")
* #ORM\Entity(repositoryClass="AppBundle\Repository\CarsRepository")
*/
class Cars
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="Make", type="string", length=255)
*/
private $make;
/**
* #var string
*
* #ORM\Column(name="Model", type="string", length=255)
*/
private $model;
/**
* #var int
*
* #ORM\Column(name="TravelledDistance", type="bigint")
*/
private $travelledDistance;
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Parts", inversedBy="cars")
* #ORM\JoinTable(
* name="PartCars",
* joinColumns={
* #ORM\JoinColumn(name="Part_Id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="Car_Id", referencedColumnName="id")
* })
*/
private $parts;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set make
*
* #param string $make
*
* #return Cars
*/
public function setMake($make)
{
$this->make = $make;
return $this;
}
/**
* Get make
*
* #return string
*/
public function getMake()
{
return $this->make;
}
/**
* Set model
*
* #param string $model
*
* #return Cars
*/
public function setModel($model)
{
$this->model = $model;
return $this;
}
/**
* Get model
*
* #return string
*/
public function getModel()
{
return $this->model;
}
/**
* Set travelledDistance
*
* #param integer $travelledDistance
*
* #return Cars
*/
public function setTravelledDistance($travelledDistance)
{
$this->travelledDistance = $travelledDistance;
return $this;
}
/**
* Get travelledDistance
*
* #return int
*/
public function getTravelledDistance()
{
return $this->travelledDistance;
}
/**
* #return mixed
*/
public function getParts()
{
return $this->parts;
}
}
It is auto generated Entity which has auto increment annotation.
What could be the problem?
The annotation
* #ORM\GeneratedValue(strategy="AUTO")
won't make PHP create a unique ID. You can see from the insert statement that PHP doesn't provide an ID value at all.
That annotation tells the ORM what kind of column it is, but it depends on the database to generate the value. You can check the documentation for more details.
Based on the way your code looks, it appears you used the console to generate the entity, but I assume the fact that the database isn't doing the id properly means that you created the cars table by hand rather than using doctrine:schema:update, which is fine if that's the way you need/want to do it, but you'll have to alter your table to make the id column an autoincrement.

Doctrine 2 Many to Many relation with additional columns in join table

So, I have been playing round with using doctrine for a while now and have it in some basic projects, but i decided to go back and have an in depth look into what it can do.
Ive now decided to switch to symfony 2 as my framework of choice and am looking into what doctrine 2 can do in more depth.
One thing i have been trying to get my head around is the many to many relationship within doctrine. I am starting to build a recipe system and am working on the relation between recipe and ingredients which gave me 3 entities, recipe, recipeIngredient and ingredient. The reason i cannot use a direct many to many relation is because i want to store two additional columns in the join table ( unit and quantity ) for each ingredient.
The problem i am having at the moment is that the entities persist ok, but the recipe_id in the join table is not inserted. I have tried everything i can think off and been through every thread and website looking for an answer . I am sure it is something completely obvious that i am missing. Please help, below is the code i have so far:
<?php
namespace Recipe\RecipeBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="recipe")
* #ORM\HasLifecycleCallbacks()
*/
class Recipe{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="RecipeIngredient", mappedBy="recipe", cascade= {"persist"})
*/
protected $ingredients;
/**
* #ORM\Column(type="string")
* #var string $title
*
*/
protected $title;
/**
* Constructor
*/
public function __construct()
{
$this->ingredients = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Add ingredients
*
* #param \Recipe\RecipeBundle\Entity\RecipeIngredient $ingredients
* #return Recipe
*/
public function addIngredient(\Recipe\RecipeBundle\Entity\RecipeIngredient $ingredients)
{
$ingredients->setRecipe($this);
$this->ingredients[] = $ingredients;
return $this;
}
/**
* Remove ingredients
*
* #param \Recipe\RecipeBundle\Entity\RecipeIngredient $ingredients
*/
public function removeIngredient(\Recipe\RecipeBundle\Entity\RecipeIngredient $ingredients)
{
$this->ingredients->removeElement($ingredients);
}
/**
* Get ingredients
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getIngredients()
{
return $this->ingredients;
}
/**
* Set title
*
* #param string $title
* #return Recipe
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
}
and recipeIngredient
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Recipe", inversedBy="ingredients")
* */
protected $recipe;
/**
* #ORM\ManyToOne(targetEntity="Ingredient", inversedBy="ingredients" , cascade={"persist"})
* */
protected $ingredient;
/**
* #ORM\Column(type="string")
* #var string $quantity
*
*/
protected $quantity;
/**
* #ORM\Column(type="string")
* #var string $unit
*
*/
protected $unit;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set quantity
*
* #param string $quantity
* #return RecipeIngredient
*/
public function setQuantity($quantity)
{
$this->quantity = $quantity;
return $this;
}
/**
* Get quantity
*
* #return string
*/
public function getQuantity()
{
return $this->quantity;
}
/**
* Set unit
*
* #param string $unit
* #return RecipeIngredient
*/
public function setUnit($unit)
{
$this->unit = $unit;
return $this;
}
/**
* Get unit
*
* #return string
*/
public function getUnit()
{
return $this->unit;
}
/**
* Set recipe
*
* #param \Recipe\RecipeBundle\Entity\Recipe $recipe
* #return RecipeIngredient
*/
public function setRecipe(\Recipe\RecipeBundle\Entity\Recipe $recipe = null)
{
$this->recipe = $recipe;
return $this;
}
/**
* Get recipe
*
* #return \Recipe\RecipeBundle\Entity\Recipe
*/
public function getRecipe()
{
return $this->recipe;
}
/**
* Set ingredient
*
* #param \Recipe\RecipeBundle\Entity\Ingredient $ingredient
* #return RecipeIngredient
*/
public function setIngredient(\Recipe\RecipeBundle\Entity\Ingredient $ingredient = null)
{
$this->ingredient = $ingredient;
return $this;
}
/**
* Get ingredient
*
* #return \Recipe\RecipeBundle\Entity\Ingredient
*/
public function getIngredient()
{
return $this->ingredient;
}
}
Your basic idea is the correct one. If you want to have a ManyToMany relation, but you need to add extra fields in the join table, the way to go is exactly as you have described: using a new entity having 2 ManyToOne relations and some additional fields.
Unfortunately you have not provided your controller code, because most likely your problem is there.
Basically if you do something like:
$ri = new RecipeIngredient;
$ri->setIngredient($i);
$ri->setRecipe($r);
$ri->setQuantity(1);
$em->persist($ri);
$em->flush();
You should always get a correct record in your database table having both recipe_id and ingredient_id filled out correctly.
Checking out your code the following should also work, although I personally think this is more sensitive to mistakes:
$ri = new RecipeIngredient;
$ri->setIngredient($i);
$ri->setQuantity(1);
// here we assume that Recipe->addIngredient also does the setRecipe() for us and
// that the cascade field is set correctly to cascade the persist on $ri
$r->addIngredient($ri);
$em->flush();
For further reading I would suggest the other topics on this subject, such as: Doctrine2: Best way to handle many-to-many with extra columns in reference table
If I understand this model correctly the construction of a recipe and its associated recipeIngredients are concurrent. You might not have an id until you persist and without an id if receipeIngredient->setRecipe() is called the default null will be place in the recipeIngredient->recipe field. This is often handled with cascade: "persist" (not present for the recipe field in your example, but you can handle it explicitly in the controller:
/**
* Creates a new Recipe entity.
*
*/
public function createAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$form = $this->createForm(new RecipeType());
$form->bind($request);
if ($form->isValid()){
$data = $form->getData();
$recipeId = $data->getId();
$recipeIngredients=$data->getIngredients();
$recipe=$em->getRepository('reciperecipeBundle:Recipe')
->findOneById($RecipeId);
if (null === $Recipe)
{$Recipe=new Recipe();}
foreach ($recipeIngredients->toArray() as $k => $i){
$recipeIngredient=$em->getRepository('reciperecipeBundle:recipeIngredient')
->findOneById($i->getId());
if (null === $recipeIngredient)
{$recipeIngrediente=new RecipeIngredient();}
$recipe->addIngredient($i);
// Next line *might* be handled by cascade: "persist"
$em->persist($recipeIngredient);
}
$em->persist($Recipe);
$em->flush();
return $this->redirect($this->generateUrl('Recipe', array()));
}
return $this->render('reciperecipeBundle:Recipe:new.html.twig'
,array('form' => $form->createView()));
}
Im not really sure if this would be a solution, but its easy yo try it, and probably it will help.
When I create a relationshiop of this kind, I use to write another anotation, the #ORM\JoinColumn, like in this example:
We have an entity A, an entity B, and an class AB wich represents the relationships, and adds some other fields, like in you case.
My relationship would be as follows:
use Doctrine\ORM\Mapping as ORM;
/**
*
*
* #ORM\Table(name="a_rel_b")
* #ORM\Entity
*/
class AB
{
/**
* #var integer
* #ORM\Id
* #ORM\ManyToOne(targetEntity="A", inversedBy="b")
* #ORM\JoinColumn(name="a_id", referencedColumnName="id")
**/
private $a;
/**
* #var integer
* #ORM\Id
* #ORM\ManyToOne(targetEntity="B", inversedBy="a")
* #ORM\JoinColumn(name="b_id", referencedColumnName="id")
**/
private $b;
// ...
name means the name of the field in the relationship table, while referencedColumnName is the name of the id field in the referenced entity table (i.e b_id is a column in a_rel_b that references the column id in the table B )
You can't, because it wouldn't be a relationship anymore [which is, by def, a subset of the cartesian product of the sets of the two original entities].
You need an intermediate entity, with references to both Recipe and Ingredient - call it RecipeElement, RecipeEntry or so, and add the fields you want.
Either, you can add a map to your Recipe, in which you save the attributes for each Ingredient you save, easy to maintain if there are no duplicates.
For further reading, have a look at this popular question.

Symfony2's Doctrine does not create one-to-many structure for me

I have two tables. First table is users and second is datas. Datas has useridx column which is foreign with user's idx. (primary unique key).
These are the table structures:
Table users
CREATE TABLE public.users (
idx bigint NOT NULL,
"name" varchar(250) DEFAULT NULL::character varying,
surname varchar(250) DEFAULT NULL::character varying,
isactive boolean NOT NULL DEFAULT false,
/* Keys */
CONSTRAINT users_pkey
PRIMARY KEY (idx),
CONSTRAINT users_idx_key
UNIQUE (idx)
) WITH (
OIDS = FALSE
);
Table datas:
CREATE TABLE public.datas (
idx bigint NOT NULL,
useridx bigint,
phrase varchar(100) DEFAULT NULL::character varying,
response varchar(100) DEFAULT NULL::character varying,
/* Keys */
CONSTRAINT datas_pkey
PRIMARY KEY (idx),
CONSTRAINT datas_idx_key
UNIQUE (idx),
/* Foreign keys */
CONSTRAINT fk_cf180c1a262768b5
FOREIGN KEY (useridx)
REFERENCES public.users(idx)
ON DELETE NO ACTION
ON UPDATE NO ACTION
) WITH (
OIDS = FALSE
);
Now when i run these commands:
app/console doctrine:mapping:convert yml
./src/Acme/DemoBundle/Resources/config/doctrine/metadata/orm
--from-database
--force
And;
app/console doctrine:mapping:import AcmeDemoBundle annotation
app/console doctrine:generate:entities AcmeDemoBundle
I got this result:
Datas.php
namespace Acme\DemoBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\DemoBundle\Entity\Datas
*
* #ORM\Table(name="datas")
* #ORM\Entity
*/
class Datas
{
/**
* #var bigint $idx
*
* #ORM\Column(name="idx", type="bigint", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="SEQUENCE")
* #ORM\SequenceGenerator(sequenceName="datas_idx_seq", allocationSize="1", initialValue="1")
*/
private $idx;
/**
* #var string $phrase
*
* #ORM\Column(name="phrase", type="string", length=100, nullable=true)
*/
private $phrase;
/**
* #var string $response
*
* #ORM\Column(name="response", type="string", length=100, nullable=true)
*/
private $response;
/**
* #var Users
*
* #ORM\ManyToOne(targetEntity="Users")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="useridx", referencedColumnName="idx")
* })
*/
private $useridx;
/**
* Get idx
*
* #return bigint
*/
public function getIdx()
{
return $this->idx;
}
/**
* Set phrase
*
* #param string $phrase
*/
public function setPhrase($phrase)
{
$this->phrase = $phrase;
}
/**
* Get phrase
*
* #return string
*/
public function getPhrase()
{
return $this->phrase;
}
/**
* Set response
*
* #param string $response
*/
public function setResponse($response)
{
$this->response = $response;
}
/**
* Get response
*
* #return string
*/
public function getResponse()
{
return $this->response;
}
/**
* Set useridx
*
* #param Acme\DemoBundle\Entity\Users $useridx
*/
public function setUseridx(\Acme\DemoBundle\Entity\Users $useridx)
{
$this->useridx = $useridx;
}
/**
* Get useridx
*
* #return Acme\DemoBundle\Entity\Users
*/
public function getUseridx()
{
return $this->useridx;
}
}
?>
Users.php
<?php
namespace Acme\DemoBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\DemoBundle\Entity\Users
*
* #ORM\Table(name="users")
* #ORM\Entity
*/
class Users
{
/**
* #var bigint $idx
*
* #ORM\Column(name="idx", type="bigint", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="SEQUENCE")
* #ORM\SequenceGenerator(sequenceName="users_idx_seq", allocationSize="1", initialValue="1")
*/
private $idx;
/**
* #var string $name
*
* #ORM\Column(name="name", type="string", length=250, nullable=true)
*/
private $name;
/**
* #var string $surname
*
* #ORM\Column(name="surname", type="string", length=250, nullable=true)
*/
private $surname;
/**
* #var boolean $isactive
*
* #ORM\Column(name="isactive", type="boolean", nullable=false)
*/
private $isactive;
/**
* Get idx
*
* #return bigint
*/
public function getIdx()
{
return $this->idx;
}
/**
* Set name
*
* #param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set surname
*
* #param string $surname
*/
public function setSurname($surname)
{
$this->surname = $surname;
}
/**
* Get surname
*
* #return string
*/
public function getSurname()
{
return $this->surname;
}
/**
* Set isactive
*
* #param boolean $isactive
*/
public function setIsactive($isactive)
{
$this->isactive = $isactive;
}
/**
* Get isactive
*
* #return boolean
*/
public function getIsactive()
{
return $this->isactive;
}
}
?>
I also have yml files but i dont think they are necessary in here only PHP files i posted here.
Now, when i run this command inside of my controller:
<?php
$user = $this->getDoctrine()
->getRepository('AcmeDemoBundle:Users')
->find(24);
$phrase = $user->getDatas()->getPhrase();
?>
I got an error that say Call to a member function getDatas() on a non-object.... I know it is clear. In Users.php i don't have getDatas().
But what i read from Symfony2 and Doctrine documentation is it should be there because they are related. All i want to do is get Datas inside of Users.
What is my mistake here? What im missing?
Update:
I added this lines to the Users.php
<?php
/**
* #var \Acme\DemoBundle\Entity\Datas
*
* #ORM\OneToMany(targetEntity="Datas", mappedBy="datas", cascade={"all"})
*/
private $datas;
public function __construct()
{
$this->datas = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add phrases
*
* #param Acme\DemoBundle\Entity\Datas $datas
*/
public function addPhrases(\Acme\DemoBundle\Entity\Datas $datas)
{
$this->datas[] = $datas;
}
/**
* Get datas
*
* #return Doctrine\Common\Collections\Collection
*/
public function getDatas()
{
return $this->datas;
}
?>
And these lines to the Datas.php
<?php
/**
* #ORM\ManyToOne(targetEntity="Users", inversedBy="users", cascade={"all"})
*/
protected $users;
/**
* Set users
*
* #param Acme\DemoBundle\Entity\Users $users
*/
public function setUsers(\Acme\DemoBundle\Entity\Users $users)
{
$this->users = $users;
}
/**
* Get users
*
* #return Acme\DemoBundle\Entity\Users
*/
public function getUsers()
{
return $this->users;
}
?>
Now getDatas() is working but inside of it is not. ($user->getDatas()->getPhrase();)
I am getting this error:
Call to undefined method Doctrine\ORM\PersistentCollection::getPhrase()
Conclusion: I got collection error because it returns a collection -of course-. Iterate (like foreach) it and you will access the data. (If you encounter a problem like this.)
If look at the documentation of #JoinColumn
This annotation is used in the context of relations in #ManyToOne, #OneToOne fields and in the Context of #JoinTable nested inside a #ManyToMany. This annotation is not required. If its not specified the attributes name and referencedColumnName are inferred from the table and primary key names.
So as you are using ManyToOne relation your relation definition would be,
/**
* #var Users
*
* #ORM\ManyToOne(targetEntity="Users")
* #ORM\JoinColumn(name="useridx", referencedColumnName="idx")
*/
private $useridx;
Edit:
If you want to get datas from user side then you have to create OneToMany relation. e.g
In Datas.php
/**
* #var Users
*
* #ORM\ManyToOne(targetEntity="Users", inversedBy = "datas")
* #ORM\JoinColumn(name="useridx", referencedColumnName="idx")
*/
private $useridx;
And in Users.php add following line,
/**
* #ORM\OneToMany(targetEntity="Datas", mappedBy="useridx", cascade={"persist"})
*/
protected $datas;
And then do a doctrine:generate:entities command. To do operations on relation check this doc entry.

Categories