Undefined index: inverseJoinColumns while trying to define ManyToMany relationship between two entities - symfony

I have two entities: User and Company and the relationship between them is n:m. In my User.php entity I have this code:
/**
* #ORM\ManyToMany(targetEntity="PL\CompanyBundle\Entity\Company", mappedBy="users", cascade={"all"})
*/
protected $companies;
public function __construct() {
$this->companies = new \Doctrine\Common\Collections\ArrayCollection();
}
public function setCompanies(\PL\CompanyBundle\Entity\Company $companies) {
$this->companies = $companies;
}
public function getCompanies() {
return $this->companies;
}
And in my Company.php entity I have this other code:
/**
* #ORM\ManyToMany(targetEntity="Application\Sonata\UserBundle\Entity\User", mappedBy="companies")
*/
protected $users;
public function __construct() {
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
}
But I got this error:
ContextErrorException: Notice: Undefined index: inverseJoinColumns in
/var/www/html/apps/portal_de_logistica/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
line 1041
What is wrong in the mapping?
EDIT Refactor code
Following instructions from #ahmed-siouani I made the following changes:
User.php
/**
* #ORM\ManyToMany(targetEntity="PL\CompanyBundle\Entity\Company", inversedBy="users")
* #ORM\JoinTable(name="fos_user_user_has_company",
* JoinColumns={#ORM\JoinColumn(name="fos_user_user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="company_id", referencedColumnName="id")}
* )
*/
protected $companies;
where fos_user_user_has_company is the table added for the n:m relationship.
Company.php
/**
* #ORM\ManyToMany(targetEntity="Application\Sonata\UserBundle\Entity\User", mappedBy="companies")
*/
protected $users;
And now the error is:
AnnotationException: [Creation Error] The annotation #ORM\JoinTable
declared on property
Application\Sonata\UserBundle\Entity\User::$companies does not have a
property named "JoinColumns". Available properties: name, schema,
joinColumns, inverseJoinColumns
Any?

You may need to specify the joinColumns and the inverseJoinColumns when defining thejoinTable. For a bidirectional many-to-many definition is would be something like,
class User
{
// ...
/**
* Bidirectional - Many users have Many companies (OWNING SIDE)
*
* #ManyToMany(targetEntity="Company", inversedBy="users")
* #JoinTable(name="users_companies",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="company_id", referencedColumnName="id")}
* )
**/
private $companies;
public function __construct() {
$this->companies = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
}
While your Company class should be defined as follow,
class Company
{
// ...
/**
* Bidirectional (INVERSE SIDE)
*
* #ManyToMany(targetEntity="User", mappedBy="companies")
*/
private $users;

My fault was that both sides of the ManyToMany had been defined with mappedBy, but only one side should have been using mappedBy and the other side should have used inversedBy (this is normally defined at main entity, that controls the collection).

In addition to #Ahmed solution take care of typos since I made one and for that the second error I got. See my annotation said:
/**
* #ORM\ManyToMany(targetEntity="PL\CompanyBundle\Entity\Company", inversedBy="users")
* #ORM\JoinTable(name="fos_user_user_has_company",
* JoinColumns={#ORM\JoinColumn(name="fos_user_user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="company_id", referencedColumnName="id")}
* )
*/
protected $companies;
But right one is:
/**
* #ORM\ManyToMany(targetEntity="PL\CompanyBundle\Entity\Company", inversedBy="users")
* #ORM\JoinTable(name="fos_user_user_has_company",
* joinColumns={#ORM\JoinColumn(name="fos_user_user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="company_id", referencedColumnName="id")}
* )
*/
protected $companies;
Differences are in this line:
joinColumns={#ORM\JoinColumn(name="fos_user_user_id", referencedColumnName="id")},

Related

Api platform, react admin - create new Entity with relation

I am afraid I might have ran into some sort of XY problem...
I have an entity "Asset" with related "AssetType" entity (One AssetType can have many Asset entities)
When creating new entity with POST method, the request fails with "SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'type_id' cannot be null"
Data posted from react-admin (POST to /api/assets route):
{
"data":{
"type":"assets",
"attributes":{
"name":"asdf",
"description":"LoraWAN enabled sensor"
},
"relationships":{
"asset_type":{
"data":{
"id":"/api/asset_types/a71b47b8-b9fb-11ea-b4d5-e6b986f12daf",
"type":"asset_types"
}
}
}
}
}
I understand that there is data lost somewhere doing deserialization of object, but cannot figure out where. Also I have identical set of entities (Gateway and Location where each Location can have multiple Gateways) and the creation of new entities work as expected...
New to Symfony & api-platform, any help appreciated.
Asset entity is set tup to be visible in api-platform:
/**
* #ApiResource(
* collectionOperations={"get", "post"},
* itemOperations={"get", "put", "delete"},
* normalizationContext={"groups"={"read"}},
* denormalizationContext={"groups"={"write"}}
* )
* #ORM\Entity(repositoryClass="App\Repository\AssetRepository")
*/
class Asset
{
/**
* #ORM\Id
* #ORM\Column(type="uuid_binary_ordered_time", nullable=false, unique=true)
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class="Ramsey\Uuid\Doctrine\UuidOrderedTimeGenerator")
* #Groups({"read"})
*/
private $uuid;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\AssetType", inversedBy="assets", cascade={"persist"})
* #ORM\JoinColumn(name="type_id", referencedColumnName="uuid", nullable=false)
*
* #Groups({"read", "write"})
*/
private $assetType;
}
AssetType entity:
/**
* #ApiResource(
* normalizationContext={"groups"={"read"}},
* denormalizationContext={"groups"={"write"}}
* )
* #ORM\Entity(repositoryClass="App\Repository\AssetTypeRepository")
*/
class AssetType
{
/**
* #ORM\Id()
* #ORM\Column(name="uuid", type="uuid_binary_ordered_time", nullable=false, unique=true)
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class="Ramsey\Uuid\Doctrine\UuidOrderedTimeGenerator")
* #Groups({"read", "write"})
*/
private $uuid;
/**
* #ORM\Column(type="string", length=255, nullable=true)
* #Groups({"read", "write"})
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Asset", mappedBy="assetType")
*/
private $assets;
public function __construct()
{
$this->assets = new ArrayCollection();
}
public function getUuid()
{
return $this->uuid;
}
public function setUuid($uuid): self
{
$this->uuid = $uuid;
return $this;
}
public function getAssets(): Collection
{
return $this->assets;
}
public function addAsset(Asset $asset): self
{
...
}
public function removeAsset(Asset $asset): self
{
...
}
In case anyone sumbles across similar problem - the reason for missing values was property naming and its normalization.
Relationship data posted contains key "asset_type" which needs to be converted to camelCase "assetType" in react-admin's dataProvider (that's the approach I took).

Symfony OneToMany with associative array : new row inserted instead of update

I have to internationalize an app and particularly an entity called Program. To do so, I created an other entity ProgramIntl which contains a "locale" attribute (en_GB, fr_FR, etc) and strings which must be internationalized. I want the programIntl attribute in Program to be an associative array (with locale as key).
We have an API to read/write programs. GET and POST works fine but when I want to update data (PUT), the programIntl is not updated: an insert query is launched (and fails because of the unique constraint, but that's not the question).
Here is the code:
In Program.php:
/**
* #var
*
* #ORM\OneToMany(targetEntity="ProgramIntl", mappedBy="program", cascade={"persist", "remove", "merge"}, indexBy="locale", fetch="EAGER")
* #ORM\JoinColumn(nullable=false, onDelete="cascade")
* #Groups({"program_read", "program_write"})
*/
private $programIntl;
public function addProgramIntl($programIntl)
{
$this->programIntl[$programIntl->getLocale()] = $programIntl;
$programIntl->setProgram($this);
return $this;
}
public function setProgramIntl($programIntls)
{
$this->programIntl->clear();
foreach ($programIntls as $locale => $programIntl) {
$programIntl->setLocale($locale);
$this->addProgramIntl($programIntl);
}
}
public function getProgramIntl()
{
return $this->programIntl;
}
In ProgramIntl.php:
/**
* #ORM\Entity(repositoryClass="App\Repository\ProgramIntlRepository")
* #ORM\Table(name="program_intl",uniqueConstraints={#ORM\UniqueConstraint(name="program_intl_unique", columns={"program_id", "locale"})})
*/
class ProgramIntl
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #Groups({"program_read", "program_write"})
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Program", inversedBy="programIntl")
* #ORM\JoinColumn(nullable=false)
*/
private $program;
/**
* #ORM\Column(type="string", length=5, options={"fixed" = true})
*/
private $locale;
/**
* #ORM\Column(type="string", length=64)
* #Assert\NotBlank()
* #Groups({"program_read", "program_write"})
*/
private $some_attr;
/* ... */
}
Any idea of what could be the reason of the "insert" instead of "update" ?
Thanks
I forgot to mention that we use api-platform.
But I found the solution myself. In case anyone is interested, adding the following annotation to classes Program and ProgramIntl solved the problem:
/* #ApiResource(attributes={
* "normalization_context"={"groups"={"program_read", "program_write"}},
* "denormalization_context"={"groups"={"program_read", "program_write"}}
* }) */

symfony2 - add value to protected object

How can I set the protected object user? After filling the form i have to add user object with current user data (for example like saving comments). I tried something like that:
if ($form->isValid()) {
$comment = $form->getData();
$comment->user = $this->contextSecurity->getToken()->getUser();
$this->model->save($comment);
}
And i've got this error
FatalErrorException: Error: Cannot access protected property AppBundle\Entity\Comment::$user in /home/AppBundle/Controller/CommentsController.php line 184
Here is my Comment entity:
class Comment
{
/**
* Id.
*
* #ORM\Id
* #ORM\Column(
* type="integer",
* nullable=false,
* options={
* "unsigned" = true
* }
* )
* #ORM\GeneratedValue(strategy="IDENTITY")
*
* #var integer $id
*/
private $id;
/**
* Content.
*
* #ORM\Column(
* name="content",
* type="string",
* length=250,
* nullable=false
* )
* #Assert\NotBlank(groups={"c-default"})
* #Assert\Length(min=3, max=250, groups={"c-default"})
*
* #var string $content
*/
private $content;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="comments")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
*/
protected $user;
I'm using Symfony2.3. Any help will be appreciated.
You can't modify protected properties from outside of the object. You need a public property or a setter for that.
class Comment
{
// ...
public function setUser(User $user)
{
$this->user = $user;
}
}
And in a controller you can write:
$comment->setUser($this->getUser());
This question is not related to Symfony2, at first you should read about php types, especially about objects. read here and then here
You should understand how Visibility works. After that you will understand that access to protected/private properties of the object is only available from the object itself, so you need to create public method
setUser($user) {
$this->user = $user;
}
I always use protected, If i want edit variable or take the value, I use the getter and setter:
public function setUser($user) {
$this->user = $user;
}
public function getUser(){
return $this->user;
}

Error mapping doctrine on OneToOne bidirectional relation

I'm trying to create an entity that is connected with another entity 1:1. The whole point is that Equip has to have an Estadi, and just one. I can update the schema correctly, the database is okay, but on the webpage debugger appears a mapping error.
The association AppBundle\Entity\Equip#estadi refers to the inverse
side field AppBundle\Entity\Estadi#nom which is not defined as
association.
The association AppBundle\Entity\Equip#estadi refers to the inverse
side field AppBundle\Entity\Estadi#nom which does not exist
This is entity Estadi:
/**
* #ORM\Entity
* #ORM\Table(name="estadis")
*/
class Estadi{
/**
* #ORM\Column(type="string",length=30)
* #ORM\OneToOne(targetEntity="Equip",mappedBy="estadi",cascade={"persist"})
* #ORM\Id
*/
protected $nom;
/**
* #ORM\Column(type="integer")
*/
protected $aforament;
/**
* #ORM\Column(type="integer")
*/
protected $num_portes;
/**
* #ORM\Column(type="string",length=50)
*/
protected $direccio;
/**
* #ORM\Column(type="string", length=4)
*/
protected $any_construccio;
/**
* #ORM\Column(type="string", length=30)
*/
protected $nom_aficio;
}
This is Entity Equip:
/**
* #ORM\Entity
* #ORM\Table(name="equips")
*/
class Equip{
/**
* #ORM\Column(type="string",length=30)
* #ORM\Id
*/
protected $nom;
/**
* #ORM\Column(type="integer")
*/
protected $punts_lliga;
/**
* #ORM\Column(type="integer")
*/
protected $num_jugadors;
/**
* #ORM\OneToOne(targetEntity="Estadi",inversedBy="nom")
* #ORM\JoinColumn(name="nom_estadi",referencedColumnName="nom",onDelete="SET NULL")
*/
protected $estadi;
/**
* #ORM\OneToOne(targetEntity="Entrenador",inversedBy="nom")
* #ORM\JoinColumn(name="nom_entrenador",referencedColumnName="nom",onDelete="SET NULL")
*/
protected $entrenador;
/**
* #ORM\ManyToOne(targetEntity="Lliga",inversedBy="equips")
* #ORM\JoinColumn(name="nom_lliga",referencedColumnName="nom",onDelete="SET NULL")
*/
protected $lliga;
/**
* #ORM\OneToMany(targetEntity="Jugador",mappedBy="nom_equip")
*/
protected $jugadors;
public function __construct(){
$this->jugadors = new ArrayCollection();
}
}
When you define the #OneToOne annotation, it should not be on your primary key. Either the owning entity should contain a single association (unidirectional), or each entity should contain an association to the other - as entities, not connected to a primary key.
Your Equip mapping should instead look like this:
/**
* #ORM\OneToOne(targetEntity="Estadi", inversedBy="equip")
* #ORM\JoinColumn(name="nom_estadi", referencedColumnName="nom")
*/
protected $estadi;
public function setEstadi(Estadi $estadi)
{
$this->estadi = $estadi;
return $this;
}
public function getEstadi()
{
return $this->estadi;
}
Your Estadi mapping should instead look like this:
/**
* #OneToOne(targetEntity="Equip", mappedBy="estadi")
*/
protected $equip;
public function getEquip(Equip $equip)
{
return $this->equip;
}
I removed the cascade and onDelete, because if you're handling everything through Doctrine it should handle that for you automatically, but you still might have a use for them. I also only put the setter on the owning entity from the way you described, but you could put it back on your Estadi entity as well - that's up to you.

Symfony2/Doctrine2 Inheritance

I'm attempting to accomplish BASIC inheritance in Doctrine 2, but I'm running into several major issues. Such a task should not be so complicated. Let's get down to business...
I have three classes, BaseFoodType, Drink, and Snack. My BaseFoodType has the following class definition:
/** #ORM\MappedSuperclass */
class BaseFoodType {
/**
* #ORM\Column(type="integer", length=7)
*/
public $budget = 0;
}
Which follows the instructions for inheritance on the doctrine website: http://docs.doctrine-project.org/en/2.0.x/reference/inheritance-mapping.html
Here is what the sub-classes look like prior to generating my entities:
namespace MySite\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* MySite\MainBundle\Entity\EventDrink
*
* #ORM\Table(name="drink")
* #ORM\Entity
*/
class Drink extends BaseFoodType {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="integer", length=5, nullable=true)
*/
public $people_count;
}
Both Drink, and Snack inherit from this base class but I'm running into numerous issues when attempting to build my entities using the doctrine:generate:entities command. First, Symfony inserts a private "budget" property into each subclass, along with getters and setters (THIS DEFEATS THE PURPOSE INHERITANCE)
/**
* #var integer
*/
private $budget;
/**
* Set budget
*
* #param integer $budget
*/
public function setBudget($budget)
{
$this->budget = $budget;
return $this;
}
/**
* Get budget
*
* #return integer
*/
public function getBudget()
{
return $this->budget;
}
Second, I'm getting a fatal error:
Fatal error: Access level to MySite\MainBundle\Entity\Drink::$budget
must be public (as in class MySite\MainBundle\Entity\BaseFoodType) in
C:\xampp\htdocs\MySite\src\MySite\MainBundle\Entity\Drink.php on line
197
I could probably make the generated properties public and be on my way, but again, that defeats the purpose of inheritance!
Thanks in advance for any insight.
Doctrine provides the means to specify the visibility of generated fields. Either protected or private. The default is private.
The problem is that the Symfony command that invokes Doctrine offers no way to change this.
Creating your own subclass of the standard Symfony command will allow you more control over the generation process. This might help you along.
namespace Foo\Bundle\FooBundle\Command;
use Doctrine\Bundle\DoctrineBundle\Command as DC;
use Doctrine\ORM\Tools\EntityGenerator;
class GenerateEntitiesDoctrineCommand extends DC\GenerateEntitiesDoctrineCommand
{
protected function configure()
{
parent::configure();
$this->setName('foo:generate:entities');
}
/**
* get a doctrine entity generator
*
* #return EntityGenerator
*/
protected function getEntityGenerator()
{
$entityGenerator = new EntityGenerator();
$entityGenerator->setGenerateAnnotations(true);
$entityGenerator->setGenerateStubMethods(true);
$entityGenerator->setRegenerateEntityIfExists(false);
$entityGenerator->setUpdateEntityIfExists(true);
$entityGenerator->setNumSpaces(4);
$entityGenerator->setAnnotationPrefix('ORM\\');
$entityGenerator->setFieldVisibility($entityGenerator::FIELD_VISIBLE_PROTECTED);
return $entityGenerator;
}
}
This does two things. It sets the property visibility to protected. This prevents php errors.
$entityGenerator->setFieldVisibility($entityGenerator::FIELD_VISIBLE_PROTECTED);
It also copies the annotations from mapped super class into the entity class.
$entityGenerator->setGenerateAnnotations(true);
Here's some example code where properties are inherited from a base class and their visibility and annotations copy correctly into the inheriting class
/** #ORM\MappedSuperclass */
class DataSuper {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Campaign", inversedBy="data")
* #ORM\JoinColumn(name="campaign_id", referencedColumnName="id")
* #Exclude
*/
protected $campaign;
/**
* #ORM\Column(type="text", nullable=true, name="data")
*/
protected $data;
/**
* #ORM\Column(type="datetime")
*/
protected $createdDate;
}
/**
* #ORM\Entity(repositoryClass="Foo\Bundle\FooBundle\Entity\DataRepository")
* #ORM\Table(name="data")
* #ExclusionPolicy("none")
*/
class Data extends DataSuper
{
}
After generation the Data class looks like:
class Data extends DataSuper
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", precision=0, scale=0, nullable=false, unique=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="data", type="text", precision=0, scale=0, nullable=true, unique=false)
*/
protected $data;
/**
* #var \DateTime
*
* #ORM\Column(name="createdDate", type="datetime", precision=0, scale=0, nullable=false, unique=false)
*/
protected $createdDate;
/**
* #var \Foo\Bundle\FooBundle\Entity\Campaign
*
* #ORM\ManyToOne(targetEntity="Foo\Bundle\FooBundle\Entity\Campaign", inversedBy="data")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="campaign_id", referencedColumnName="id", nullable=true)
* })
*/
protected $campaign;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set data
*
* #param string $data
* #return Data
*/
public function setData($data)
{
$this->data = $data;
return $this;
}
/**
* Get data
*
* #return string
*/
public function getData()
{
return $this->data;
}
/**
* Set createdDate
*
* #param \DateTime $createdDate
* #return Data
*/
public function setCreatedDate($createdDate)
{
$this->createdDate = $createdDate;
return $this;
}
/**
* Get createdDate
*
* #return \DateTime
*/
public function getCreatedDate()
{
return $this->createdDate;
}
/**
* Set campaign
*
* #param \Foo\Bundle\FooBundle\Entity\Campaign $campaign
* #return Data
*/
public function setCampaign(\Foo\Bundle\FooBundle\Entity\Campaign $campaign = null)
{
$this->campaign = $campaign;
return $this;
}
/**
* Get campaign
*
* #return \Foo\Bundle\FooBundle\Entity\Campaign
*/
public function getCampaign()
{
return $this->campaign;
}
}
And the table structure is correct once you do:
php app/console doctrine:schema:update --force
The exception is being thrown because BaseFoodType::budget is a public property and doctrine:generate:entities created a private property in your Drink / Snack classes extending BaseFoodType ( which is not correct but the way the command works by now ).
Property visibility in a subclass can only be the same level or more liberate ( private -> protected -> public ) but never more restrictive.
doctrine:generate:entities did not take superclass's public property into account when generating the getters/setters as the implementation with a public property is non-standard.
Therefore you will have to adjust the generated class manually.
I recommend using private/protected properties combined with getters & setters.

Resources