api platfrom many to many relationship is not working - php

I'm using api-platform in my symfony project
I have many to many relation between entity companyUser and a surveyReceiver entity. I have a join table and I want to add a record to it while updating a companyUser. in fact I want to add a existing surveyReceiver to companyUser object. but instead of adding an existing one it tries to create a new one and then add it to the companyUser object
there is an example below
companyUser entity:
#[ORM\ManyToMany(targetEntity: SurveyReceiver::class, inversedBy: "members",cascade: ['persist'])]
#[ORM\JoinTable(name: "membership")]
#[ORM\JoinColumn(name: 'company_user_id',referencedColumnName: "id")]
#[ORM\InverseJoinColumn(name: "survey_receiver_id",referencedColumnName: "id")]
private $surveyReceivers
SurveyReceiver entity:
#[ORM\ManyToMany(targetEntity: CompanyUser::class, mappedBy: "surveyReceivers")]
private $members;
this is my put request.
PUT:company-users/890
{
"name": "xxx,
"surveyReceivers":[
{
"survey_receiver_id" : 6
}
]
}
it will create a record in join table with company_user_id :
company_user_id
survey_receiver_id
890
349
which is wrong. survey_receiver_id should be 6 as it was in request
I would be grateful it you help me to see where I did a mistake or miss sth.

Try it using PATCH method instead of PUT with Content-Type header set to application/merge-patch+json

Related

PHP Doctrine ORM: Use Subclass in an inheritance model as an attribute of another entity

I'm using Doctrine (ORM Version 2.10) in my PHP Symfony Project.
Entity Structure
I have two major Entities: An Article and a Tag.
An Article has multiple Tags and a Tag is used by multiple Articles.
I have implemented an unidirectional many-to-many association from Article to Tag and vice-versa which is stored in Article.tags or Tag.articles respectively.
My Tag is a superclass and has many subclasses depending on the type of the Tag: There is a Subclass called Category, Keyword and more. I'm using single-table-inheritance and cannot use another inheritance model.
Article.php:
#[ORM\Table("article")]
#[ORM\Entity]
class Article
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: "integer")]
protected int $id;
#[ORM\Column(type: "string")]
protected string $title;
// [...]
#[ORM\ManyToMany(Tag::class, inversedBy: "articles")]
#[ORM\JoinTable("tagged_with")]
#[ORM\JoinColumn("article")]
#[ORM\InverseJoinColumn("tag")]
private $tags;
}
Tag.php:
#[ORM\Entity]
#[ORM\InheritanceType("SINGLE_TABLE")]
#[ORM\DiscriminatorColumn("type", "integer")]
#[ORM\Table("tag")]
#[ORM\DiscriminatorMap([Category::class, Keyword::class]]
class Tag
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: "integer")]
protected int $id;
#[ORM\Column(type: "string")]
protected string $value;
#[ORM\ManyToMany(Article::class, "tags")]
protected $articles;
}
Example subclass Category.php:
#[ORM\Entity]
class Category extends Tag
{
// [...]
}
The Problem
While the many-to-many association from Article to Tag works fine, I now want a many-to-many association from my subclasses, lets say Category to Article as an class-attribute, for example Category.articles and Article.categories.
Is this possible?
If yes, how would I acomplish this?
I've tried the same annotations as Article.tags for the new Article.categories, but this doesn't work.
If no, is there another way to do this?
I thought about using the Criteria-Model in Doctrine Collections in a new getter, but I can't find a way to filter for the discriminator-column, as I can't add it to Tag as an attribute (Tag.type for example).
What did work was filtering the Tags-Collection with the filter-Method and instanceof in Article.php:
#[ORM\Table("article")]
#[ORM\Entity]
class Article
{
// [...]
#[ORM\ManyToMany(Tag::class, inversedBy: "articles")]
#[ORM\JoinTable("tagged_with")]
#[ORM\JoinColumn("article")]
#[ORM\InverseJoinColumn("tag")]
private $tags;
// [...]
// New Function:
public function getCategories(): ArrayCollection
{
return $this->tags->filter(fn($tag) => $tag instanceof Category);
}
}
I don't like this approach, as it only works in php and not on the database (performance), and I'd much rather prefer a class-attribute for the categories over a custom getter.
So, is there another way to do this?

Cannot assign <IdClass> to property of type <EntityClass>

I have the following entity graph:
#[ORM\Entity]
class Professional
{
#[ORM\Id]
#[ORM\Column(type: 'professional_id')]
#[ORM\GeneratedValue(strategy: 'NONE')]
private ProfessionalId $id;
#[ORM\OneToOne(targetEntity: ProfessionalCatchmentArea::class, mappedBy: 'professional', cascade: ['all'])]
private ?ProfessionalCatchmentArea $catchmentArea = null;
}
#[ORM\Entity]
class ProfessionalCatchmentArea
{
#[ORM\Id]
#[ORM\OneToOne(targetEntity: Professional::class, inversedBy: 'catchmentArea')]
#[ORM\JoinColumn(nullable: false)]
private Professional $professional;
#[ORM\OneToMany(targetEntity: ProfessionalCatchmentAreaZone::class, mappedBy: 'catchmentArea', orphanRemoval: true, cascade: ['all'])]
private Collection $zones;
}
#[ORM\Entity]
class ProfessionalCatchmentAreaZone
{
#[ORM\Id]
#[ORM\Column(type: 'uuid')]
#[ORM\GeneratedValue(strategy: 'NONE')]
private Uuid $id;
#[ORM\ManyToOne(targetEntity: ProfessionalCatchmentArea::class, inversedBy: 'zones')]
#[ORM\JoinColumn(name: 'professional_id', referencedColumnName: 'professional_id', nullable: false)]
private ProfessionalCatchmentArea $catchmentArea;
}
Note: ProfessionalId is a custom ID class containing a UUID.
As you can see, Professional has a one-to-one relationship with ProfessionalCatchmentArea, which in turn has a one-to-many relationship with ProfessionalCatchmentAreaZone.
Due to the one-to-one, ProfessionalCatchmentArea shares its primary key with Professional.
Therefore, the JoinColumn for the many-to-one relationship from ProfessionalCatchmentAreaZone to ProfessionalCatchmentArea is based on the professional_id column.
For some reason, even though Doctrine does not complain with this mapping (bin/console doctrine:schema:validate is OK), and even though traversal from Professional to $catchmentArea to $zones works fine, attempting to load the ProfessionalCatchmentAreaZone entities directly from the EntityRepository fails:
$entityManager->getRepository(ProfessionalCatchmentAreaZone::class)->findAll();
Cannot assign App\Entity\ProfessionalId to property App\Entity\ProfessionalCatchmentArea::$professional of type App\Entity\Professional
Any idea why?
Is this mapping one-to-one => one-to-many, while sharing the primary key in the one-to-one relationship, not supported? Is this a Doctrine bug?
I've got a few ideas:
Based on the information provided, I'm inclined to believe this is an issue with the object inheritance and the mapped type for the data type of Professional->$id.
Given that ProfessionalId extends the UUID, the only valid class datatype for ProfessionalId is the class ProfessionalId.
If the data type was UUID then the values that are compatible with UUID would be an instance of UUID or ProfessionalId (because it INHERITS UUID).
Check that the Datatype is registered for that name:
<?php
// In your bootstrap file
\Doctrine\DBAL\Types\Type::addType('professional_id', ProfessionalId::class);
?>
Ensure that the ProfessionalId type inherits the annotations from the UUID type as seen here:
https://stackoverflow.com/a/40442063/5779200

Symfony2 creating and persisting entity relationships

I have two entities Skin and Email. I want Email to be a part of the Skin entity, however I can't use the console to update schema automatically right now, and that is probably why I can't get the relationship to work.
So I want to store all the Emails in the Email entity, and I want to assign an email to a desired Skin. So the relationship should me OneToOne.
I am not an expert in MySQL relationships so this is what I made by myself:
In my main Skin class I created a field in which I want to store the emails id:
/**
*
* #ORM\OneToOne(targetEntity="MediaparkLt\UserBundle\Entity\Email", inversedBy="skin")
* #ORM\JoinColumn(name="email_id", referencedColumnName="id")
*/
protected $email_id;
In the Email class I created the skin field in which I want to show the skins Id:
/**
*
* #ORM\OneToOne(targetEntity="MediaparkLt\SkinBundle\Entity\Skin", mappedBy="email_id")
* #ORM\JoinColumn(name="skin", referencedColumnName="id")
*/
protected $skin;
Now in my CMS I create the new email like this:
public function saveAction(Request $request) {
$item = new Email();
$type = new EmailType($this->container->getParameter("langs"));
$form = $this->createForm($type, $item);
$form->handleRequest($request);
$em = $this->getEntityManager();
if ($form->isValid()) {
$this->upload($form, $item);
$em->persist($item);
$em->flush();
return $this->redirect($this->generateUrl('cms_skin_email_list', array('skin_id' => $item->getId())));
}
return array('form' => $form->createView(), 'item' => $item);
}
And this is the form:
public function buildForm(FormBuilderInterface $builder, array $option) {
$builder->add('title', 'text', array('label' => 'cms.Title'));
$builder->add('registration_content', 'textarea', array('label' => 'cms.registration.content'));
}
Now in mysql I create the relationship like this:
ALTER TABLE `skin` ADD CONSTRAINT `emailId` FOREIGN KEY (`email_id`) REFERENCES `lottery`.`email`(`id`) ON DELETE RESTRICT ON UPDATE RESTRICT;
Now when I create a new email I still get NULL on both entitites.
When you create two entities with a one-to-one relationship, both entities need to be persisted either explicitly or by using cascade persist on one side of the relationship. You also need to explicitly set both sides of the relationship.
Doctrine - Working with Associations - Transitive persistence / Cascade Operations
States:
Even if you persist a new User that contains our new Comment this code
would fail if you removed the call to
EntityManager#persist($myFirstComment). Doctrine 2 does not cascade
the persist operation to all nested entities that are new as well.
Doctrine - Working with Associations - Establishing Associations
States:
In the case of bi-directional associations you have to update the
fields on both sides
With no cascade persist you need something like this:
$skin = new Skin();
$email = new Email();
$skin->setEmail($email);
$email->setSkin($skin);
$em->persist($email);
$em->persist($skin);
$em->flush();
With cascade persist on the Skin side of the relationship you can omit $em->persist($skin). Note if you cascade persist you would usually also cascade remove:
* #ORM\OneToOne(targetEntity="MediaparkLt\UserBundle\Entity\Email", inversedBy="skin", cascade={"persist", "remove"})

Deleting record from many to many table doctrine symfony 2

I can add record to many to many table tag_post successfully but I'm not able to remove any records from tag_post. Please bear in mind I only want to remove records from tag_post not the record from table post itself.
I have 3 tables post,tag and tag_post. tables tag_post contains relation between post and tag. the fields in table tag_post are :
tag_id
post_id
Mapping file for post:
oneToMany:
tagPostAssociations:
targetEntity: Mockizart\Bundle\BlogBundle\Entity\MockblogTagPost
mappedBy: "post"
cascade: ["persist","remove"]
Mapping file for tag:
oneToMany:
tagPostAssociations:
targetEntity: Mockizart\Bundle\BlogBundle\Entity\MockblogTagPost
mappedBy: "tag"
cascade: ["persist","remove"]
Mapping file for tag_post:
manyToOne:
post:
associationKey: true
targetEntity: Mockizart\Bundle\BlogBundle\Entity\MockblogPost
inversedBy: "tagPostAssociations"
tag:
targetEntity: Mockizart\Bundle\BlogBundle\Entity\MockblogTag
inversedBy: "tagPostAssociations"
My code for test:
$post = $this->getDoctrine()->getManager()->find('MockizartBlogBundle:MockblogPost',6);
$b = $this->getDoctrine()->getManager()->find('MockizartBlogBundle:MockblogTagPost',['tagId' => 20,'postId' => 6]);
$post->removeTagPostAssociation($b);
$this->getDoctrine()->getManager()->persist($post);
$this->getDoctrine()->getManager()->flush();
My tag_post entity:
public function __construct($tag, $post)
{
$this->tagId = $tag->getId();
$this->postId = $post->getId();
$this->post = $post;
$this->tag = $tag;
}
My post entity:
public $tagPostAssociations;
public function __construct() {
$this->tagPostAssociations = new ArrayCollection();
}
public function addTagPostAssociation(MockblogTagPost $tagPostAssociations)
{
$newTag = $tagPostAssociations;
$this->newTags[$newTag->getTagId().$newTag->getPostId()] = $newTag;
$hasTagPost = $this->hasTagPost($newTag);
if (!$hasTagPost) {
$this->tagPostAssociations[] = $tagPostAssociations;
}
return $this;
}
public function removeTagPostAssociation(MockblogTagPost $tagPost)
{
$this->tagPostAssociations->removeElement($tagPost);
return $this;
}
public function getTagPostAssociations()
{
return $this->tagPostAssociations;
}
I only post codes that I think related to the case. if you want to see more code, please let me know.
I can't test your setup, however I believe you've mixed up your understanding of the cascade attribute with Doctrine. See, if you did something like:
$post = $this->getDoctrine()->getManager()->find('MockizartBlogBundle:MockblogPost',6);
$this->getDoctrine()->getManager()->remove($post);
$this->getDoctrine()->getManager()->flush();
Then Doctrine would recognize that you are deleting the $post in question and would cascade operations to all of the associations that have the "remove" cascade rule defined, which would delete the tag_post entry in question. However you're not deleting your $post, so you must manually remove the tag_post after taking it out of your object:
$post = $this->getDoctrine()->getManager()->find('MockizartBlogBundle:MockblogPost',6);
$b = $this->getDoctrine()->getManager()->find('MockizartBlogBundle:MockblogTagPost',['tagId' => 20,'postId' => 6]);
$post->removeTagPostAssociation($b);
// This may need to come after the persist, haven't tested
$this->getDoctrine()->getManager()->remove($b);
$this->getDoctrine()->getManager()->persist($post);
$this->getDoctrine()->getManager()->flush();
This is obviously cumbersome if you're constantly removing entries, so perhaps an event listener could automate this process for you.
One other thing to note is that you may be able to get away with just removing the tag_post and not touching your post or tag objects at all:
$b = $this->getDoctrine()->getManager()->find('MockizartBlogBundle:MockblogTagPost',['tagId' => 20,'postId' => 6]);
$this->getDoctrine()->getManager()->remove($b);
$this->getDoctrine()->getManager()->flush();
Reason being is that all of the foreign reference columns "live" in the tag_post table, meaning there would be no integrity violations by simply removing a ManyToMany reference. Only thing is any loaded post or tag object would have out-of-date tagPostAssociations

Repository Methods From Entity In ArrayCollection

So, we have two entities. One with repository and another is not. When we trying to get the data from another table we will get the ArrayCollection data. Question is how to call this entity repository methods? Is it real?
Example:
$system = $this
->getDoctrine()
->getEntityManager()
->getRepository('SomeBundle:FirstEntity')
->findOneByColumnID($id);
$den = $system->getDataFromSecondTable(); // ArrayCollection of SecondEntity
And then i want to use some kind of:
$den[0]->functionFromSecondEntityRepository();
So, method "functionFromSecondEntityRepository" is in Repository of class SecondEntity and i can't call it - error on undefined method call "functionFromSecondEntityRepository".
So how can i do it in right way?
You didnt provide too many details so I will make some example up here.
Let's say you have an Entity FriendsList and a One-to-Many relationship with Entity Friend.
$List = $this->getDoctrine()
->getEntityManager()
->getRepository('SomeBundle:FriendsList')
->find($id);
// The list you pulled in by ID can now be used
$List->getId();
foreach($List->getFriends() as $Friend)
{
// Each friend will be output here, you have access
// to the Friend methods now for each.
$Friend->getId();
$Friend->getFirstName();
$Friend->getLastName();
$Friend->getDOB();
$Friend->getFavoriteColor();
}
By default when you create relationships a method to acquire the collection is created, in this example getFriends which returns an array of Entities. After you generate the entities look at your Entity Model to see which methods are available. By default one is created for each property in your entity and additional ones for Collections.
SomeCool/Bundle/Entity/FriendsList
Somecool/Bundle/Entity/Friend
The following is what a one-to-many relationship would look like if you use YAML configuration.
SomeCool\Bundle\Entity\FriendsList:
type: entity
table: null
oneToMany:
friend:
targetEntity: Friend
mappedBy: friendslist
cascade: ["persist"]
SomeCool/Bundle/Entity/Friend
manytoOne:
friends:
targetEntity: FriendsList
mappedBy: friend
cascade: ["persist"]
Accessing a Repository
YAML Configuration (services.yml)
somebundle.bundle.model.friends:
class: SomeBundle/Bundle/Model/Friends
arguments: [#doctrine.orm.entity_manager]
On the Controller
$friendsModel = $this->get('somebundle.bundle.model.friends');
$Friends = $friendsModel->findByFirstName('Bobby');
foreach($Friends as $Friend)
{
$Friend->getLastName();
}
Repository methods are not available in Entities. You would need a function in your AnotherEntity to grab the ArrayCollection. IE:
class FirstEntity {
public function getAnotherEntity()
{
return $this->anotherEntity;
}
}
class AnotherEntity
{
public function getArrayCollection()
{
return $this->myArrayCollection;
}
}
$firstEntity->getAnotherEntity()->getArrayCollection();
Another option would be to get the AnotherEntity's repository based on results from first:
$system = $this
->getDoctrine()
->getEntityManager()
->getRepository('SomeBundle:SomeEntity')
->findOneByColumnID($id);
$anotherEntity = $system->getAnotherEntity();
$anotherEntityResult = $this->getDoctrine()
->getRepository(get_class($anotherEntity))
->functionFromAnotherEntityRepository($anotherEntity->getId());
If using the second solution, I'd make sure that $anotherEntity is not null before attempting to retrieve the repository.

Categories