Bidirectional relationship ManyToMany symfony - php

I need help for my problem.
When add the relationship ManyToMany to the php script this return when validate the relationship.
This is my scripts:
class Post implements ResourceInterface, TranslatableInterface
{
/**
* #var int
*/
private $id;
/**
* #var bool
*/
private $important;
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Tag", inversedBy="posts")
* #ORM\JoinTable(name="posts_tags",
* joinColumns={
* #ORM\JoinColumn(name="post_id", referencedColumnName="id", onDelete="CASCADE")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="tag_id", referencedColumnName="id")
* }
* )
*/
private $tags;
}
And
class Tag implements ResourceInterface, TranslatableInterface
{
use TranslatableTrait {
__construct as private initializeTranslationsCollection;
}
/**
* #var int
*/
private $id;
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Post",
mappedBy="tags")
*/
private $posts;
}
The error is:
Mapping
[FAIL] The entity-class AppBundle\Entity\Tag mapping is invalid: *
The field AppBundle\Entity\Tag#posts is on the inverse side of a bi-
directional relationship, but the specified mappedBy association on
the target-entity AppBundle\Entity\Post#tags does not contain the
required 'inversedBy="posts"' attribute.

Hello use symfony maker
. /bin/console make:entity Post
For the field name enter 'tags' and for the type enter 'relation' . Then give the related tags entity name. Shoud be Tag. Choose the manytomany relation and enter. Like this you should never have relation issue :-). Even if the entity exist already.

maybe the mapping of the tags attribute in the post entity should be like this:
class Post implements ResourceInterface, TranslatableInterface
{
/**
* #var int
*/
private $id;
/**
* #var bool
*/
private $important;
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Tag", inversedBy="posts", cascade={"persist"})
* #ORM\JoinTable(name="posts_tags",
* joinColumns={
* #ORM\JoinColumn(name="post_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="tag_id", referencedColumnName="id")
* }
* )
*/
private $tags;
}
I hope I've helped :)

Related

API Platform: Normalizing a Mapped Superclass only includes properties from the superclass, not the child class

EDIT: Created a repo with a simple use case to replicate the problem, and created an issue in the API Platform issue queue at https://github.com/api-platform/api-platform/issues/1648.
I have a SurveyData mapped superclass, which has multiple child classes that implement it. For this question, MonthlyData is a child of the SurveyData superclass.
I have a Submission API resource that has a OneToOne relationship to a SurveyData.
I am trying to normalize the SurveyData child entity (in this case, a MonthlyData entity) inline into my Submission entity when I retrieve it. I've set the normalizationContext in my Submission's ApiResource annotation, and set the #Groups annotations on the various properties I want to have inline.
It's mostly working, in that the properties of my SurveyData mapped superclass appear inline in my Submission entity, in addition to the #id and #type JSON-LD properties. However, properties from the actual child entity (MonthlyTotals) do not appear.
I can confirm that the object being passed into the normalizer is a fully populated MonthlyTotals object, but the output of the normalizer only contains the properties defined in the SurveyData mapped superclass.
I can confirm I am using the default core API platform JSON-LD normalizer.
Thank you in advance for any help!
Here is a Submission definition (minus 'use' statements and getters / setters for brevity:
<?php
/**
* #ApiResource(
* normalizationContext={"groups"={"submission"}},
* denormalizationContext={"groups"={"submission"}},
* itemOperations={
* "get"={
* "method"="GET",
* "access_control"="is_granted('view', object)",
* },
* "put", "patch", "delete",
* },
* )
*
* #ApiFilter(NumericFilter::class, properties={"patient.id"})
* #ApiFilter(OrderFilter::class, properties={"created", "status", "patient.chartID"}, arguments={"orderParameterName"="order"})
* #ApiFilter(SearchFilter::class, properties={"status": "exact", "patient.chartID": "exact"})
* #ORM\Entity(repositoryClass="App\Repository\SubmissionRepository")
* #DelphiAssert\SubmissionDataIsValid
*/
class Submission
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #Groups({"submission"})
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Facility")
* #ORM\JoinColumn(nullable=false)
* #Groups({"submission"})
*/
private $facility;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Patient", inversedBy="submissions")
* #Groups({"submission"})
*/
private $patient;
/**
* #ORM\Column(type="string", length=255)
* #Groups({"submission"})
*/
private $survey;
/**
* #ORM\Column(type="string", length=255, nullable=true)
* #Groups({"submission"})
*/
private $dateDetail;
/**
* #ORM\Column(type="datetime")
* #Assert\Type("\DateTimeInterface")
* #Groups({"submission"})
*/
private $created;
/**
* #ORM\Column(type="datetime")
* #Assert\Type("\DateTimeInterface")
* #Groups({"submission"})
*/
private $updated;
/**
* #ORM\Column(type="string", length=255)
* #Groups({"submission"})
*/
private $user;
/**
* #ORM\Column(type="string", length=255)
* #Groups({"submission"})
*/
private $status;
/**
* #ORM\OneToOne(targetEntity="App\Entity\SurveyData\SurveyData", inversedBy="submission", cascade={"persist", "remove"}, orphanRemoval=true, fetch="EAGER")
* #Groups({"submission"})
*/
private $surveyData;
Here's SurveyData (same conditions):
<?php
/**
* #ORM\Entity(repositoryClass="App\Repository\SurveyData\SurveyDataRepository")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap({
* "SurveyData" = "App\Entity\SurveyData\SurveyData",
* "RPCSEducationProcessMeasures" = "App\Entity\SurveyData\RPCS\EducationProcessMeasures",
* "RPCSMonthlyTotals" = "App\Entity\SurveyData\RPCS\MonthlyTotals"
* })
*/
class SurveyData
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #Groups("submission")
*/
private $id;
/**
* #ORM\OneToOne(targetEntity="App\Entity\Submission", mappedBy="surveyData", cascade={"persist", "remove"}, fetch="EAGER")
*/
private $submission;
and here's MonthlyData:
/**
* #ApiResource(
* normalizationContext={"groups"={"submission"}},
* denormalizationContext={"groups"={"submission"}}
* )
* #ORM\Entity(repositoryClass="App\Repository\SurveyData\RPCS\MonthlyTotalsRepository")
*/
class MonthlyTotals extends SurveyData
{
/**
* #ORM\Column(type="integer")
* #Groups("submission")
*/
private $num_deliveries;
/**
* #ORM\Column(type="integer")
* #Groups("submission")
*/
private $num_cesarean;
/**
* #ORM\Column(type="integer")
* #Groups("submission")
*/
private $num_epidural_anesthesia;
Might be #Groups({"submission"}) v. #Groups("submission").
Change all to #Groups({"submission"}).
It's hard to verify with current documentation, as it's now documented with PHP8 attributes. I've only used the YAML configuration, so not 100% sure.

Doctrine - OneToOne relationship not working with extends MappedSuperclass

I try to use #MappedSuperclass. It works well for simple variable (int, string...) and OneToMany/ManyToOne relationship. But OneToOne relationship doesn't work.
I have two MappedSuperclass with OneToOne relationship :
_SiteUser
/**
* #MappedSuperclass _SiteUser
*
* #ORM\Entity(repositoryClass="_SiteModule\_Repository\_SiteUserRepository")
* #ORM\Table(name="site_users")
*/
class _SiteUser
{
/**
* #var int
* #Groups("id")
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
* #Groups({"username"})
*
* #ORM\Column(name="username", type="string", length=255, unique=true)
*/
protected $username;
/**
* #var string
* #Groups({"password"})
*
* #ORM\Column(name="password", type="string", length=255)
*/
protected $password;
/**
* #var _SiteUserTo
*
* #ORM\OneToOne(targetEntity="_SiteModule\_Entity\_SiteUserTo", mappedBy="user")
* #Gedmo\Versioned()
*/
protected $user_to;
_SiteUserTo
/**
* #MappedSuperclass _SiteUserTo
*
* #ORM\Entity(repositoryClass="_SiteModule\_Repository\_SiteUserToRepository")
* #ORM\Table(name="users_to")
*/
class _SiteUserTo
{
/**
* #var int
* #Groups("id")
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var _SiteUser
*
* #ORM\OneToOne(targetEntity="_SiteModule\_Entity\_SiteUser", inversedBy="user_to")
* #JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
And this is my 2 class who extends these MappedSuperclass :
SiteUser
/**
* SiteUser
*
* #ORM\Entity(repositoryClass="SiteModule\Repository\SiteUserRepository")
* #ORM\Table(name="site_users")
*/
class SiteUser extends _SiteUser
{
}
SiteUserTo
/**
* SiteUserTo
*
* #ORM\Entity(repositoryClass="SiteModule\Repository\SiteUserToRepository")
* #ORM\Table(name="users_to")
*/
class SiteUserTo extends _SiteUserTo
{
}
When I generate entities from my MappedSuperclass (_SiteUser and _SiteUserTo), I got well a table named "users_to" with id and user_id. But when I generate entities from my others classes (SiteUser and SiteUserTo), it creates the table "users_to" with only id field. I don't know why...
If I update my SiteUser Class like this :
/**
* Class SiteUser
*
* #ORM\Entity(repositoryClass="SiteModule\Repository\SiteUserRepository")
* #ORM\Table(name="site_users")
*/
class SiteUser extends _SiteUser
{
/**
* #var boolean
*
* #ORM\Column(name="test", type="boolean")
*/
protected $test;
/**
* #var SiteUserTo
*
* #ORM\OneToOne(targetEntity="SiteModule\Entity\SiteUserTo", mappedBy="user")
*/
protected $user_to;
}
And SiteUserTo like this :
/**
* Class SiteUserTo
*
* #ORM\Entity(repositoryClass="SiteModule\Repository\SiteUserToRepository")
* #ORM\Table(name="users_to")
*/
class SiteUserTo extends _SiteUserTo
{
/**
* #var boolean
*
* #ORM\Column(name="test", type="boolean")
*/
protected $test;
/**
* #var SiteUser
*
* #ORM\OneToOne(targetEntity="SiteModule\Entity\SiteUser", inversedBy="user_to")
* #JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
}
I got the same problem, no field user_id in table users_to. But the field "test" is well created in the table site_users and in the table users_to...
A mapped superclass cannot be an entity, it is not query-able and
persistent relationships defined by a mapped superclass must be
unidirectional (with an owning side only). This means that One-To-Many
associations are not possible on a mapped superclass at all.
Furthermore Many-To-Many associations are only possible if the mapped
superclass is only used in exactly one entity at the moment. For
further support of inheritance, the single or joined table inheritance
features have to be used.
Simply said you can not have #MappedSuperClass and #ORM\Entity annotations at the same time, hence the unexpected results
https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/inheritance-mapping.html

Doctrine :: Relation ManyToOne will not work

I got a ParentClass like this
/** #ORM\MappedSuperclass */
class BaseValue
{
/**
* #var int
*
* #ORM\Id
* #ORM\Column(name="id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var field
* #ORM\OneToMany(targetEntity="Field", mappedBy="value", cascade="all")
*/
protected $field;
/**
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* #param int $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* #return field
*/
public function getField()
{
return $this->field;
}
/**
* #param field $field
*/
public function setField($field)
{
$this->field = $field;
}
}
And a child like this
* #ORM\Entity
* #ORM\Table(name="integers")
*/
class Integer extends BaseValue
{
/**
* #var integer
*
* #ORM\Column(name="value", type="integer", nullable=true)
*/
protected $value;
/**
* #return string
*/
public function getValue()
{
return $this->value;
}
/**
* #param string $value
*/
public function setValue($value)
{
$this->value = $value;
}
}
Now I would like to relate the child in another class like this
* #ORM\Entity
* #ORM\Table(name="fields")
*/
class Field
{
/**
* #var int
*
* #ORM\Id
* #ORM\Column(name="id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var
* #ORM\ManyToOne(targetEntity="BaseValue", mappedBy="field", cascade="all")
* #ORM\JoinColumn(name="vid", referencedColumnName="id")
*/
protected $value; // but it does not work
It always gets me the following error:
[Doctrine\Common\Annotations\AnnotationException]
[Creation Error] The annotation #ORM\ManyToOne declared on property zmpim\Entity\Field::$value does not have a property named "mappedBy". Available properties: targetEntity, cascade, fetch, inversedBy
Both got mappedby,.. so the error seems to be senseless
Update:
A field has got values and labels. The values get inherited from BaseValue into IntegerValue, StringValue and later others...
My OneToMany Relation is a parent class of inheritance.
like this, now:
/** #ORM\MappedSuperclass */
class BaseValue
{
/**
* #var int
*
* #ORM\Id
* #ORM\Column(name="id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var Field
* #ORM\OneToMany(targetEntity="Field", mappedBy="field", cascade="persist", orphanRemoval=true )
*/
protected $field;
And this is my ManyToOne:
/**
*
* #ORM\Entity
* #ORM\Table(name="fields")
*/
class Field
{
/**
* #var int
*
* #ORM\Id
* #ORM\Column(name="id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var int|null
* #ORM\ManyToOne(targetEntity="BaseValue", inversedBy="value")
* #ORM\JoinColumn(name="vid", referencedColumnName="id", onDelete="CASCADE")
*/
protected $value;
It still give me an error, but now it is:
[Doctrine\ORM\ORMException]
Column name id referenced for relation from zmpim\Entity\Field towards zmpim\Entity\BaseValue does not exist.
Your Entity Field is the inversed side of your mapping so instead of using MappedBy declaration you have to use this
/**
* Inversed side
* #var int|null
* #ORM\ManyToOne(targetEntity="BaseValue", inversedBy="field")
* #ORM\JoinColumn(name="[your_name]", referencedColumnName="[id]", onDelete="CASCADE")
*/
protected $value;
To understand well inversedSide and MappedBy attributes you can read this:
Doctrine inverse and owning side
After reading again you are aware of your relationnal problem between the two of your entities, but if you declare ManyToOne annotation, you have to set inversedBy attribute or you'll get an error. And this is what you have.
You can't declare ManyToOne annotation with mappedBy attribute because it does not exist and throw an exception by Doctrine.
To resume :
ManyToOne association =>
* #ORM\ManyToOne(targetEntity="[yourEntity]", inversedBy="[Field]")
Be careful this side require the declaration of this :
* #ORM\JoinColumn(name="[your_name]", referencedColumnName="[id]", onDelete="CASCADE")
OneToMany =>
* #ORM\OneToMany(targetEntity="[An_Entity]",
* mappedBy="[Field]", cascade={"persist"}, orphanRemoval=true)
EDIT from your answer :
Your mapping is still incorrect, your data in InversedBy And mappedBy need to be switched.

Cascade deletion does not trigger PostRemove

My entities are as follows:
a Portfolio has many Series [OneToMany-ManyToOne]
a Serie has many Assets [OneToMany-ManyToOne]
a Serie can have an Asset (as a thumbnail) [OneToOne]
I also used the cascade={"remove"} property in order to remove the Series and Assets if the corresponding Portfolio has been deleted.
Serie
/**
* Serie
*
* #ORM\Table(name="serie")
* #ORM\HasLifecycleCallbacks
*/
class Serie
{
/**
* #var \Portfolio
*
* #ORM\ManyToOne(targetEntity="Portfolio", inversedBy="series")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="portfolio", referencedColumnName="id")
* })
*/
private $portfolio;
/**
* #var \Image
*
* #ORM\OneToOne(targetEntity="Image")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="preview", referencedColumnName="id", nullable=true, onDelete="SET NULL")
* })
*/
private $preview;
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\OneToMany(targetEntity="Asset", mappedBy="serie", cascade={"remove"})
**/
private $assets;
/**
* #ORM\PostRemove
*/
public function removeDirectory(){
rmdir(...);
}
Asset
/**
* #ORM\Table(name="asset")
* #ORM\HasLifecycleCallbacks
*/
class Asset
{
/**
* #var \Serie
*
* #ORM\ManyToOne(targetEntity="Serie", inversedBy="assets")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="serie", referencedColumnName="id")
* })
*/
protected $serie;
/**
* #ORM\PostRemove()
*/
public function removeFile()
{
unlink($this->filename);
}
I put the cascade={"remove"} property in Asset's side of the relationship because the other way around would result in a foreign key constraint error.
The postRemove method in Serie is called but not the one in Asset. Am I doing something wrong, is there a way to fix it?

Doctrine 2 multiple mappedBy?

I've got a problem setting up the Doctrine mapping correctly.
I have a CashRegister Entity which has a bin location and a return bin location. Both locations are from the same Type (BinLocation Entity).
Outgoing from CashRegister, CashRegister->getBinLocations() and CashRegister->getReturnBinLocations() are working fine, but how can I achieve that BinLocation->getCashRegisters() returns all CashRegister Entities that are referenced (binLocation + returnBinLocation)?
/**
* CashRegister
*
* #ORM\Table(name="cash_registers")
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class CashRegister
{
...
/**
* #var BinLocation
*
* #ORM\ManyToOne(targetEntity="BinLocation", inversedBy="cashRegisters")
* #ORM\JoinColumn(name="bin_location_id", referencedColumnName="id")
*/
private $binLocation;
/**
* #var BinLocation
*
* #ORM\ManyToOne(targetEntity="BinLocation", inversedBy="cashRegisters")
* #ORM\JoinColumn(name="return_bin_location_id", referencedColumnName="id")
*/
private $returnBinLocation;
/**
* #return BinLocation
*/
public function getBinLocation()
{
return $this->binLocation;
}
/**
* #return BinLocation
*/
public function getReturnBinLocation()
{
return $this->returnBinLocation;
}
...
}
/**
* BinLocation
*
* #ORM\Table(name="bin_locations")
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class BinLocation
{
...
/**
* #var CashRegister[]
*
* #ORM\OneToMany(targetEntity="CashRegister", mappedBy="binLocation") <= Here is the problem, in this case mappedBy need to be an array [binLocation, returnBinLocation]
*/
private $cashRegisters;
/**
* #return CashRegister[]
*/
public function getCashRegisters()
{
return $this->cashRegisters;
}
...
}
The simple answer is that you cannot. mappedBy accepts only one argument.
The solution to achieve what you want is however simple. Create a second property in BinLocation called: cashRegisters2 as follows:
/**
* #var CashRegister[]
*
* #ORM\OneToMany(targetEntity="CashRegister", mappedBy="binLocation")
*/
private $cashRegisters;
/**
* #var CashRegister[]
*
* #ORM\OneToMany(targetEntity="CashRegister", mappedBy="binLocation")
*/
private $cashRegisters2;
Then merge the Collections in your getCashRegisters method.
/**
* #return CashRegister[]
*/
public function getCashRegisters()
{
return new ArrayCollection(
array_merge($cashRegisters->toArray(), $cashRegisters2->toArray())
);
}
Also change your CashRegister mappings accordingly:
/**
* #var BinLocation
*
* #ORM\ManyToOne(targetEntity="BinLocation", inversedBy="cashRegisters")
* #ORM\JoinColumn(name="bin_location_id", referencedColumnName="id")
*/
private $binLocation;
/**
* #var BinLocation
*
* #ORM\ManyToOne(targetEntity="BinLocation", inversedBy="cashRegisters2")
* #ORM\JoinColumn(name="return_bin_location_id", referencedColumnName="id")
*/
private $returnBinLocation;
Note: I did not test the code. This example is to server guide only.
Note2: The ArrayCollection merge was inspired from here: https://stackoverflow.com/a/16871539/2853903
I have also searched for solutions and made a patch for Doctrine so you can have custom_attributes linked to a variety of entity types.
Doctrine 2.6: https://github.com/danielbeeke/doctrine2/commit/2d8530176b872cb490c5c88b8c8e17d8d0091388
Doctrine 2.7: https://github.com/danielbeeke/doctrine2/commit/5bde696848ea9fe7035fadc4d46baa4c0d51f3a2
/**
* #Entity
* #Table(name="product")
* #HasLifecycleCallbacks
**/
class Product {
/**
* One Product has Many Attributes.
*
* #OneToMany(
* targetEntity="CustomAttribute",
* mappedBy="EntityId",
* mappedByType={
* "field": "EntityType",
* "value": "product"
* }
* )
*
* #var $CustomAttributes ArrayCollection
*/
protected $CustomAttributes;
}
/**
* #Entity
* #Table(name="custom_attribute")
* #HasLifecycleCallbacks
**/
class CustomAttribute_entity {
/** #Id #Column(type="integer") #GeneratedValue */
protected $Id;
/**
* Many Attributes have One Entity of the EntityType provided.
* #ManyToOne(targetEntity="Product", inversedBy="CustomAttributes")
* #JoinColumn(name="EntityId", referencedColumnName="Id")
*/
protected $EntityId;
/** #Column(type="string") */
protected $EntityType;
/** #Column(type="string") */
protected $Type;
/** #Column(type="string") */
protected $Value;
}

Categories