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"})
Related
I using symfony 3.4 and doctrineORM 2.5, I have an entity called Project which contains a OneToMany relationship to other entity called Opportunity
So, in the form edit UI of Project entity I have a multiselect choices of opportunities, the user can select or deselect any opportunity in this list.
When I submit the form to ProjectController--->editAction doctrine update automatically opportunities's ArrayCollection with the new selected values, that's good :), but after persist Project object doctrine does not remove that user is deselected from the multi selection list.
class Project{
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="Module\CRMBundle\Entity\Opportunity", mappedBy="project")
*/
private $opportunities;
}
class Opportunity{
/**
* #var Project
* #ORM\ManyToOne(targetEntity="Module\CRMBundle\Entity\Project", inversedBy="opportunities")
* #ORM\JoinColumn(name="project_code", referencedColumnName="code")
*/
private $project;
}
My solution is to reset the relationship everytime somebody edits Project like this :
public function editAction(Request $request, Project $project){
$form = $this->createForm('Module\CRMBundle\Form\ProjectType', $project);
$form->handleRequest($request);
$em = $this->getDoctrine()->getManager();
//Get all old opportunities of this project and set their project to null
$oldOpportunities = $em->getRepository('CRMBundle:Opportunity')->findBy(array(
'project' => $project
));
foreach ($oldOpportunities as $opportunity){
$opportunity->setProject(null);
$em->persist($opportunity);
}
//If request is comming we set this project to whole opportunities
if ($request->isMethod('POST')) {
foreach ($project->getOpportunities() as $opportunity){
$opportunity->setProject($project);
}
}
$em->persist($project);
$em->flush();
}
This solution work very well, but there is a best way to update this relationship because sometimes I have a very lot Opportunities from the owning side (Project), I think that very expensive query that doctrine will excute
I have a relation in Doctrine2 #ORM\OneToMany, suposing that i have table school and student, in the entity school i have the #ORM\OneToMany column students,
and i also have a virtual deletion column deleted_at, so every student that has the deleted_at different of null is a deleted student that is supposed not to appear in the column #ORM\OneToMany $students. How can i make this filter?
/**
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\OneToMany(targetEntity="App\Oceano\Entities\Student",
* mappedBy="cartCore",
* cascade={"all"}
* )
*/
private $students;
So, when i call for school students, it is retrieving also the deleted ones.
$schoolObj->getStudents();
Any Solution using annotation or some clean change?
You practically described Laravel's soft deleting feature. So, if you use it, you do not need to do anything and soft deleted students will not appear. You just need to add Illuminate\Database\Eloquent\SoftDeletes trait to the Student model.
If you're using some own functionality, create a local scope in the Student model:
public function scopeNotDeleted($query)
{
return $query->whereNull('deleted_at');
}
And use it:
Student::notDeleted()->get();
Or:
$school->students()->notDeleted()->get();
You can use Criteria filter class in your entity to students collection which are not deleted
protected getStudents() {
$criteria = \Doctrine\Common\Collections\Criteria::create()
->where(\Doctrine\Common\Collections\Criteria::expr()->eq('deleted_at', null));
return $this->students->matching($criteria);
}
To get deleted students you could write it like
protected getDeletedStudents() {
$criteria = \Doctrine\Common\Collections\Criteria::create()
->where(\Doctrine\Common\Collections\Criteria::expr()->neq('deleted_at', null));
return $this->students->matching($criteria);
}
How filter data inside entity object in Symfony 2 and Doctrine
We've recently switched to a new permissions system on a project I am working on.
I have completed integration for this, eloquent relationships and all, when the requirements of the integration changed a little.
The permission system integrates with all of our systems across our infrastructure, and when it comes to referencing users from the Laravel project, the value in the permissions system is slightly different; in that it is prefixed with user-.
For example, a user with the username james in my users table is referenced as user-james in the permissions system table.
Is there any way to specify the value the eloquent relationship should look at?
I could just add a column to the users table to store the primary key of this user as it exists in the permissions table, but I wanted to see if there was a way to do this with eloquent.
If we consider relation is one - one we can do something like below:
First extend BelongsTo relation and change condition on where clause:
class CustomBelongsTo extends BelongsTo
{
/**
* #inheritDoc
*/
public function addConstraints()
{
if (static::$constraints) {
// For belongs to relationships, which are essentially the inverse of has one
// or has many relationships, we need to actually query on the primary key
// of the related models matching on the foreign key that's on a parent.
$table = $this->related->getTable();
$this->query->where($table.'.'.$this->otherKey, '=', 'user-'.$this->parent->{$this->foreignKey});
}
}
}
Then override belongsTo method on your model to use this custom relation.
class User extends Model {
protected $table = 'users';
public function permissions(){
return $this->belongsTo(Permission:class, 'username');
}
public function belongsTo($related, $foreignKey = null, $otherKey = null, $relation = null)
{
// If no relation name was given, we will use this debug backtrace to extract
// the calling method's name and use that as the relationship name as most
// of the time this will be what we desire to use for the relationships.
if (is_null($relation)) {
list($current, $caller) = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$relation = $caller['function'];
}
// If no foreign key was supplied, we can use a backtrace to guess the proper
// foreign key name by using the name of the relationship function, which
// when combined with an "_id" should conventionally match the columns.
if (is_null($foreignKey)) {
$foreignKey = Str::snake($relation).'_id';
}
$instance = new $related;
// Once we have the foreign key names, we'll just create a new Eloquent query
// for the related models and returns the relationship instance which will
// actually be responsible for retrieving and hydrating every relations.
$query = $instance->newQuery();
$otherKey = $otherKey ?: $instance->getKeyName();
return new CustomBelongsTo($query, $this, $foreignKey, $otherKey, $relation);
}
}
I hope this help.
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
I have 3 "main" entities : TypeA and TypeB linked to User by a ManyToOne relation.
I have 2 "secondary" entities : UserTypeA and UserTypeB, which contain the attributes of the ManyToOne relations (for example, the comment a user has assigned to a product of type A). These two entities and their repository are similar (except that one is linked to TypeA and the other to TypeB).
Here is a part of my code :
public function typea_commentAction(TypeA $typea)
{
$user = $this->getUser();
$userTypeA = $this->getDoctrine()
->getManager()
->getRepository('GamUserBundle:UserTypeA')
->getComment($user, $typea);
//...
}
public function typeb_commentAction(TypeB $typeb)
{
$user = $this->getUser();
$userTypeB = $this->getDoctrine()
->getManager()
->getRepository('GamUserBundle:UserTypeB')
->getComment($user, $typeb);
//...
}
As you can see, I need to duplicate each action to make them work with each entity. Is there any way to combine these actions ? Same question about the secondary entities and their repositories.
Thanks.
Create a service class that performs the logic and takes say, the user type as a parameter.