symfony relation from one entity to many entities from differents tables - database

I have a symfony app with a Comment entity, and I would like to link it, with a relation property, to many entities. But by many entities, I don't mean many objects, but many classes.
A comment can be about different things in my app : a Drawing entity, a Texte entity, a Compo entity, or a Photo entity. Each entity correspond to a type of artwork, each one with differents properties and different pages. And each one can be rated and commented.
The problem is : when I wanna create the relation property in my Comment entity, I got to indicate just ONE entity. I want that some comments are about a drawing entity, some are about text etc.
I see a solution : create one entity comment by entity to be link, but my app would have very many entities, and my code would be so duplicated, which is not so good for future changes etc.
Is there a way to link one entity to many differents types of entity ?
Thank you.
EDIT 4 : ALL PREVIOUS EDITS BELOW ARE POINTLESS !
I let it so beginners can see some of my code, to have an inheritance mapping example detailed (I which I could have it when I tried first, I didn't understood everything) but I just realised the stupid mistake I made :
* #DiscriminatorMap({"comment" = "Comment", "comment" = "CommentDrawing", "commentphoto" = "CommentPhoto", "commenttexte" = "CommentTexte"})
I forgot "commentcompo" = "CommentCompo"
I wrote "comment" = "Comment" AND "comment" = "CommentDrawing" (both referenced by "comment")
That's why I had to put "comment" as discriminator instead of "commentdrawing".
Sorry for that stupid mistake, So much code lines I didn't notice it.
And for those who also use inheritance mapping for first time and want informations, I'm now trying to fill comments with forms. If you want to do the same (you probably will if you came here for an answer), I think this is the solution :
https://symfony.com/doc/current/form/inherit_data_option.html
EDIT :
Thanks to the answer of Jakumi below, I used inheritance mapping. I chosen the first solution : the one table solution. But I tried, I have no error message, but this didn't work to me, and after hours I still dont understand what I did wrong.
My Comment class (the topmost class of my comments hierarchy, which has CommentDrawing, CommentCompo, CommentPhoto, CommentTexte as children) (Comment is abstract because I had an error telling me that indicated me to put it abstract to solve the error) :
<?php
namespace App\Entity;
use App\Entity\Comment;
use App\Entity\CommentPhoto;
use App\Entity\CommentTexte;
use App\Entity\CommentDrawing;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\InheritanceType;
use Doctrine\ORM\Mapping\DiscriminatorMap;
use Doctrine\ORM\Mapping\DiscriminatorColumn;
/**
* #ORM\Entity(repositoryClass="App\Repository\CommentRepository")
* #InheritanceType("SINGLE_TABLE")
* #DiscriminatorColumn(name="discriminator", type="string")
* #DiscriminatorMap({"comment" = "Comment", "comment" = "CommentDrawing", "commentphoto" = "CommentPhoto", "commenttexte" = "CommentTexte"})
*/
abstract class Comment
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="datetime")
*/
private $createdAt;
/**
* #ORM\Column(type="text")
*/
private $content;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="comments")
* #ORM\JoinColumn(nullable=false)
*/
private $author;
public function getId(): ?int
{
return $this->id;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(string $content): self
{
$this->content = $content;
return $this;
}
public function getAuthor(): ?User
{
return $this->author;
}
public function setAuthor(?User $author): self
{
$this->author = $author;
return $this;
}
}
And here is my CommentDrawing entity (I created the other CommentSomething classes but the only one I tried to use now is CommentDrawing) :
<?php
namespace App\Entity;
use App\Entity\Comment;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\CommentDrawingRepository")
*/
class CommentDrawing extends Comment
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Dessin", inversedBy="commentsDrawing")
*/
private $drawing;
public function getId(): ?int
{
return $this->id;
}
public function getDrawing(): ?Dessin
{
return $this->drawing;
}
public function setDrawing(?Dessin $drawing): self
{
$this->drawing = $drawing;
return $this;
}
}
And here is the Dessin entity it refers to (dessin is drawing in french, I forgot to name it with english name when I created it) this is not a child class of Comment, this is the subject of the CommentDrawing (linked by ManyToOne), which is itself the child class of Comment :
<?php
namespace App\Entity;
use Cocur\Slugify\Slugify;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity(repositoryClass="App\Repository\DessinRepository")
* #ORM\HasLifecycleCallbacks
* #UniqueEntity(
* fields = {"nom"},
* message = "Un autre dessin contient le même nom. Merci de le changer. Vérifiez aussi que vous avez entré un slug unique, ou vide. Si le slug est en double, ça engendrera un bug.")
* #UniqueEntity(
* fields = {"url"},
* message = "Un autre dessin contient la même url, vous vous apprêtez à poster deux fois le même dessin. Merci de changer l'url.")
* )
*/
class Dessin
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
// Some code I hidden because it's useless to show
/**
* #ORM\Column(type="string", length=255)
*/
private $slug;
// Some code I hidden because it's useless to show
/**
* #ORM\OneToMany(targetEntity="App\Entity\CommentDrawing", mappedBy="drawing")
*/
private $commentsDrawing;
public function __construct()
{
// Some code I hidden
$this->commentsDrawing = new ArrayCollection();
}
// Some code I hidden
public function getId(): ?int
{
return $this->id;
}
// Some code I hidden
public function getSlug(): ?string
{
return $this->slug;
}
public function setSlug(string $slug): self
{
$this->slug = $slug;
return $this;
}
// Some code I hidden
/**
* #return Collection|CommentDrawing[]
*/
public function getCommentsDrawing(): Collection
{
return $this->commentsDrawing;
}
public function addCommentsDrawing(CommentDrawing $commentsDrawing): self
{
if (!$this->commentsDrawing->contains($commentsDrawing)) {
$this->commentsDrawing[] = $commentsDrawing;
$commentsDrawing->setDrawing($this);
}
return $this;
}
public function removeCommentsDrawing(CommentDrawing $commentsDrawing): self
{
if ($this->commentsDrawing->contains($commentsDrawing)) {
$this->commentsDrawing->removeElement($commentsDrawing);
// set the owning side to null (unless already changed)
if ($commentsDrawing->getDrawing() === $this) {
$commentsDrawing->setDrawing(null);
}
}
return $this;
}
}
To verify if this worked correctly, I manually created a comment in the database with phpmyadmin :
Then I tried to show the content of the drawing comment above in a page, using the drawing var corresponding to the drawing which is the subject of my comment. Nothing happened. So I directly tried to dump it in a Controller, impossible to get the comment.
To get the comment, I used the $commentsDrawing property of Drawing entity (you can see it in the code above). Here is the code I used to dump the drawing var that should contain a comment (I put the dump() function in the show function, that is call with the drawing slug as parameter, founded in the URL. I'm sure the show() function works correctly and show the good drawing, because I tested it before). It is the DrawingController :
<?php
namespace App\Controller;
use App\Entity\Dessin;
use App\Form\DrawingType;
use App\Entity\CategorieDessin;
use App\Service\PaginationService;
use App\Repository\DessinRepository;
use App\Repository\CategorieDessinRepository;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class DrawingsController extends AbstractController
{
// I hide the rest of the code because it's useless
/**
* show a drawing
*
* #Route("/dessin/{slug}", name="drawing_show")
*
* #return Response
*/
public function show(Dessin $drawing)
{
dump($drawing);
die();
return $this->render('drawings/show.html.twig', [
'drawing' => $drawing
]);
}
}
What the dump() shows :
As you can see, in commentsdrawing > collection, no elements.
If I do the same without the dump(), I got the drawing, no error, but no comments neither.
I really don't see what I did wrong... Could someone please help ?
EDIT 2 :
When I do this :
/**
* show a drawing
*
* #Route("/dessin/{slug}", name="drawing_show")
*
* #return Response
*/
public function show(Dessin $drawing, CommentDrawingRepository $repo)
{
$comments = $repo->findAll();
dump($comments);
die();
return $this->render('drawings/show.html.twig', [
'drawing' => $drawing
]);
}
I get an empty array
EDIT 3 :
Well, I added a new CommentDrawing directly from my DrawingController :
/**
* show a drawing
*
* #Route("/dessin/{slug}", name="drawing_show")
*
* #return Response
*/
public function show(Dessin $drawing, CommentDrawingRepository $repo, DessinRepository $repoDrawing, UserRepository $repoUser, ObjectManager $manager)
{
$drawing = $repoDrawing->findAll()[0];
$user = $repoUser->findAll()[0];
$comment = new CommentDrawing();
$comment->setCreatedAt(new \DateTime())
->setContent("un test")
->setAuthor($user)
->setDrawing($drawing);
$manager->persist($comment);
$manager->flush();
$comments = $repo->findAll();
dump($comments);
return $this->render('drawings/show.html.twig', [
'drawing' => $drawing
]);
}
And that worked. The comment is registred in the database, the dump shows a comment, and the comment appear in my page.
I tried to understand why the comment added with phpmyadmin didn't work, and what a surprise : the difference between both comments is that the one added with phpmyadmin has commentdrawing as dicriminator value, and the one added by doctrine has just comment as value... Which is the value for the abstract class ! I thought the discriminator value was usefull to tell doctrine what column to consider, I don't understand anymore... But well, problem solved. Thank you for your help !
note : the second one is the one which worked... Why ?

There is nothing that says you can't have multiple ManyToOne relationships within single entity. So it may not be as elegant and clean as inheritance mapping as suggested by Jakumi, but it works with little effort. I've done this in a project along the lines of what you describe with a single Comment entity that references multiple other entitys at the same time. For my Comment class, I have other entitys (Admission and Referral) that each point to multiple Comments, but Admission and Referral are otherwise nothing alike and so would not make sense for me to have each of them extend some abstract class (as suggested by Arleigh Hix in the comments to your question). My reasons for doing this are not relevant to your project, but it works well for me.
Comment class:
/**
* #ORM\Entity
*/
class Comment
{
/**
* #ORM\Column(type="text")
*/
private $comment;
/**
* #ORM\ManyToOne(
* targetEntity="App\Entity\Referral",
* inversedBy="comments"
* );
*/
private $referral;
/**
* #ORM\ManyToOne(
* targetEntity="App\Entity\Admission",
* inversedBy="comments"
* );
*/
private $admission;
// getters and setters omitted
}
Then of course my Admission entity (and very similarly my Referral entity) have the other side of the relationship:
/**
* #ORM\Entity
*/
class Admission
{
public function __construct()
{
$this->comments = new ArrayCollection();
}
/**
* #ORM\OneToMany(targetEntity="Comment",
* mappedBy="admission",
* )
*/
private $comments;
}

the association itself is most likely the wrong place to do this abstraction. Anyway, what you're probably somewhat looking for is inheritance mapping.
For reference:
https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/inheritance-mapping.html
one table solution:
One option would be, to do single table inheritance. Parent class gets a method "getSubject()" that the child classes override and holds userid + rating + comment text. Child classes are DrawingComment etc. and each of them has a one-to-one association to the comment's subject.
Advantages: simple, clear semantics, can distinguish by classname (or extra function/property) if need be, referential integrity is stable, somewhat easy to do stats over, searchable regardless of comment subject type
Disadvantages: discriminator-column is somewhat awkward to use and slightly reduces speed.
many tables solution:
Leave inheritance mapping, make abstract class (+ interface?) or trait (+interface) for the common code. store each comment type in its own class/entity/table. When you don't care about the type of a comment, you can still use interface functions to handle it cleanly.
advantages: same, as before - except for searchability, no discriminator column, since each comment type has its own table
disadvantages: searchability is slightly reduced
Ultimately it's a question of taste or habit. And it depends on the common and distinct fields of different comment types. if - apart from the subject - they all share the same fields, I would do single table inheritance. the more they differ, the more I'd tend towards the many tables solution.

Related

Empty object variable and empty ArrayCollection onetomany symfony

I got three classes:
ProjectType
Phase
ProjectTypePhase (This is to create a seperate join table to make sure ProjectType and Phase gets linked with an id for ordering)
ProjectType
/**
* #OneToMany(targetEntity="ProjectTypePhase", mappedBy="project_type")
*/
private $projectTypePhases;
public function __construct()
{
$this->projectTypePhases = new ArrayCollection();
}
/**
* #return mixed
*/
public function getProjectTypePhases()
{
return $this->projectTypePhases;
}
Phase
/**
* #OneToMany(targetEntity="ProjectTypePhase", mappedBy="phase")
*/
private $projectTypePhases;
public function __construct()
{
$this->projectTypePhases = new ArrayCollection();
}
/**
* #return mixed
*/
public function getProjectTypePhases()
{
return $this->projectTypePhases;
}
ProjectTypePhase
/**
* #ManyToOne(targetEntity="ProjectType", inversedBy="project_type_phase")
* #JoinColumn(name="project_type_id", referencedColumnName="id")
*/
private $projectType;
/**
* #ManyToOne(targetEntity="Phase", inversedBy="project_type_phase")
* #JoinColumn(name="phase_id", referencedColumnName="id")
*/
private $phase;
public function __construct($projectType, $phase)
{
$this->projectType = $projectType;
$this->phase = $phase;
}
I filled the database through MySQL workbench since they are only id entries (correct?). Anyway, whenever I try to do $projectType->getProjectTypePhases();` it returns an empty collection. Also when I try this:
$repository = $this->getDoctrine()->getRepository('AppBundle:ProjectTypePhase');
$projectPhases = $repository->findAll();
I get all the entries, but somehow the variable name for instance of the projecttype and phase entity is null even though they are filled in the database. The corresponding keys are correct and names are filled. What goes wrong? And is there some approach that needs to be done what I am missing? The thing I am trying to accomplish is:
I had a Many to Many relationship which worked fine between ProjectType and Phase. However since there is no id for that table things didn't get in the order I intended to. I had searched for a solution and that was a seperate entity which could handle the relationship between ProjectType and Phase and add additional columns. Is this the correct way?
Modify ProjectTypePhase as follows
/**
* #ManyToOne(targetEntity="ProjectType", inversedBy="projectTypePhases")
* #JoinColumn(name="project_type_id", referencedColumnName="id")
*/
private $project_type;
/**
* #ManyToOne(targetEntity="Phase", inversedBy="projectTypePhases")
* #JoinColumn(name="phase_id", referencedColumnName="id")
*/
private $phase;
You must make names match in order to make the things works. However it's pretty strange that doctrine does not warn you

Saving Many to Many relationship to database in Symfony2

In my Symfony2 project I have two related entities: Users and Favorites. They have a many-to-many relationship.
My application works as follows:
In my Twig-page I have an few items with a button 'Add to Favorites'. When you click the button my controller saves the item_id in the Favorites column. But then I want to save
the user who added the item to his favorites and here my application fails.
The User and the favorite exist but the joincolumn between Users and Favorites remains empty.
I also don't receive any kind of errors.
Here is my code:
Entity Users
class Users implements AdvancedUserInterface
{
/**
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Favorites", inversedBy="user", cascade={"persist"})
* #ORM\JoinTable(name="user_has_favorite",
* joinColumns={
* #ORM\JoinColumn(name="user_id", referencedColumnName="user_id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="favorite_id", referencedColumnName="favorite_id")
* }
* )
*/
private $favorite;
public function __construct()
{
$this->favorite = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addFavorite(\Geo\CityTroopersBundle\Entity\Favorites $favorite)
{
$this->favorite[] = $favorite;
return $this;
}
...
Entity Favorites
class Favorites
{
/**
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Users", mappedBy="favorite", cascade={"persist"})
*/
private $user;
public function __construct()
{
$this->user = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addUser(\Geo\CityTroopersBundle\Entity\Users $user)
{
$this->user[] = $user;
return $this;
}
My controller
public function showNewsAction()
{
$request = $this->get('request');
$itemId=$request->request->get('itemId');
if($itemId != NULL)
{
//MAKE NEW FAVORITE AND ADD TO DATABASE LINKED WITH ITEM
$favorite = new Favorites();
$favorite->setItemId($itemId);
//LINK FAVORITE ID WITH USER ID IN JOINCOLUMN
$userId = 6;
$em = $this->getDoctrine()->getEntityManager();
$user = $em->getRepository('GeoCityTroopersBundle:Users')->find($userId);
$favorite->addUser($user);
$em->persist($favorite);
//I TRIED THIS TOO, BUT IT FAILED
/*$user->addFavorite($favorite);
$em->persist($user);*/
$em->flush();
You were close there. For doctrine many-to-many relationships, you need to call both add methods
$favorite->addUser($user);
$user->addFavorite($favorite);
$em->persist($favorite);
$em->persist($user);
$em->flush();
This should do the trick. In the docs they do this, but don't mention it too explicitly. Not sure why either because lots of people run into this (myself included).
As explained here, only the owning side is responsible for the connection management :
http://doctrine-orm.readthedocs.org/en/latest/reference/association-mapping.html#owning-and-inverse-side-on-a-manytomany-association
So only
$user->addFavorite($favorite);
should persist, and not
$favorite->addUser($user);
Like indicated by Cedric, adding a record for a many to many relation is done only in one direction and it depends on how you defined the relation: adding can be done only by the parent entity of the relation, so in your case you must use:
$user->addFavorite($favorite);
In your line :
#ORM\JoinColumn(name="favorite_id", referencedColumnName="favorite_id")
The
name="favorite_id"
part refers to the columns in the join table, whereas the
referencedColumnName="favorite_id"
refers to the id in the favorite table which usualy is simply "id". You should try :
/**
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Favorites", inversedBy="user", cascade={"persist"})
* #ORM\JoinTable(name="user_has_favorite",
* joinColumns={
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="favorite_id", referencedColumnName="id")
* }
* )
*/
private $favorite;

ReflectionException: Class ArrayCollection does not exist

I am trying to serialize entities for mobile digest. I have this Entity class:
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Entity\User as BaseUser;
/**
* xxx\xxx\Entity\User
*
* #ORM\Table()
* #ORM\Entity()
* #ORM\Entity(repositoryClass="xxx\xxx\Entity\UserRepository")
*/
class User extends BaseUser
{
/**
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\OneToMany(targetEntity="\xxx\xxx\Entity\Music", mappedBy="user")
*/
protected $musics;
/**
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\OneToMany(targetEntity="\xxx\xxx\Entity\Message", mappedBy="user")
*/
protected $messages;
/**
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\OneToMany(targetEntity="\xxx\xxx\Entity\Location", mappedBy="user")
*/
protected $locations;
public function __construct()
{
parent::__construct();
$this->musics = new ArrayCollection();
$this->messages = new ArrayCollection();
$this->locations = new ArrayCollection();
}
}
Now when I call this line in my DefaultController.php:
$user = $this->getUser();
$em = $this->getDoctrine()->getManager();
$array = $em->getRepository('xxxBundle:User')
->findLatest();
$serializer = $this->get('serializer');
$response = $serializer->serialize($array, 'json'); //THIS LINE THROWS EXCEPTION
I have use Doctrine\Common\Collections\ArrayCollection; in DefaultController.php, but it seems the error is coming from inside JMSSerializerBundle.
What have I tried thusfar
I have tried defining the Doctrine annotations to start with a \, but that didn't help
I have cleared my cache a million times
I have searched for similar exceptions, but they all seem to be caused by a typo and I've checked for typos for the last 48 hours and I can't find one.
The classes were autogenerated with app/console.
Take a look at this issue on GitHub: https://github.com/schmittjoh/JMSSerializerBundle/issues/123
This solution works!
I am using JMSSerializerBundle and in Serialized Entity i have ManyToOne relation. I used property $product and of course setter and getter for that. If serializer try to get Product I got this same message i thnk because it don't understand how to convert related Entity to string/int. I adding Accessor with custom method getProductId and inside this method return
$this->product->getId()
JMS\Serializer\Annotation as Serializer
#Serializer\Accessor(getter="getProductId")
In OneToMany relation, in custom get method I return ArrayCollection as Array $this->statuses->toArray()
You can also think of coonverter for relation entity, but I haven't tried (no time)

Is there a way to add created_by and modified_by similar to how created and modified works in cakePHP?

I'm wondering if there's a way to add created_by and modified_by similar to how created and modified work in CakePHP?
I like the fact that cake recognizes those fields and takes care of them automatically, regardless of the model, without me having to implement them at all. I would like to add a similar feature using the current user id (in my application, there is always a userid, even if it sometimes may be 0).
I assume the starting place is before_save() in app_model?
--
Also, is there any way for me to get cake to recognize this as a foreign key to the user table automatically (similar to how it recognizes user_id), or will I have to add the has/belongs to relationship manually? I ask because this will go on most of my models, so I'd prefer to reduce redundant code.
Thank you!
For the first part of your question, I use this Behavior code to do exactly what you are looking for:
class UserLinkBehavior extends ModelBehavior
{
/**
* The string to use to retrieve the user id from CakeSession
*
* #var string
*/
var $user_id_key = 'Auth.User.id';
function setup(&$model, $settings)
{
if(isset($settings['user_id_key']))
{
$this->user_id_key = $settings['user_id_key'];
}
}
function beforeSave(&$model)
{
App::uses('CakeSession', 'Model/Datasource');
$logged_user_id = CakeSession::read($this->user_id_key);
if(isset($logged_user_id))
{
$this->set_user_on_current_model($model, $logged_user_id);
}
return true;
}
/**
* Set the created_by and modified_by user id on the current model
*
* #param Model $model
* #param int $logged_user_id
* #return void
*/
private function set_user_on_current_model(&$model, $logged_user_id)
{
if(isset($logged_user_id))
{
/*
* Id is not set -> it is a creation
*/
if($model->hasField('created_by') && (!isset($model->data[$model->alias]['id']) || empty($model->data[$model->alias]['id'])))
{
if(!isset($model->data[$model->alias]['created_by']))
{
$model->data[$model->alias]['created_by'] = $logged_user_id;
/*
* If the save is called with a whitelist, add 'created_by' to the whitelist
* in order to have this field saved as well
*/
if(!empty($model->whitelist) && !in_array('created_by', $model->whitelist))
{
$model->whitelist[] = 'created_by';
}
}
}
/*
* Id is set -> it is an update
*/
if($model->hasField('modified_by') && isset($model->data[$model->alias]['id']) && !empty($model->data[$model->alias]['id']))
{
$model->data[$model->alias]['modified_by'] = $logged_user_id;
/*
* If the save is called with a whitelist, add 'modified_by' to the whitelist
* in order to have this field saved as well
*/
if(!empty($model->whitelist) && !in_array('modified_by', $model->whitelist))
{
$model->whitelist[] = 'modified_by';
}
}
}
}
}
Then just declare it in your Model or your AppModel
var $actsAs = array('UserLink');
For the second part of your question, you could probably add a beforeFind() callback to the behavior and use the model->bindModel() function to link the model having the created_by and modified_by fields with a User model. Personaly I prefere to declare these links in each model manually when I need them.
It cannot be like the created and modified field but you can add this fields in the controller method wherever you want.
eg. in the add method you can add like follows.
$this->request->data['ModelName']['created_by'] = $this->Auth->user['userid'];
I found this one and working good for me its easy to implement and understand
<?php
App::uses('Model', 'Model');
class AppModel extends Model {
//get current logged-in user
public function getCurrentUser() {
App::uses('CakeSession', 'Model/Datasource');
$Session = new CakeSession();
$user = $Session->read('Auth.User');
return $user['id'];
}
//populate created_by and modified_by
public function beforeSave($options = array()) {
parent::beforeSave($options);
//find all fields from table created_by/modified_by exists
$fields = array_keys($this->getColumnTypes());
//get modal name to feed in data in appropriate array key
$modal = array_keys($this->data);
$modal = $modal[0];
//add created_by value
if(in_array('created_by', $fields) && !isset($this->data[$modal]['id'])){
//correct this line
$this->data[$modal]['created_by'] = $this->getCurrentUser()==null?-1:$this->getCurrentUser();
return true;
}elseif(in_array('modified_by', $fields)){
$this->data[$modal]['modified_by'] = $this->getCurrentUser()==null?-1:$this->getCurrentUser();
return true;
}
return true;
}
}

How to change and entity type in Doctrine2 CTI Inheritance

How (if possible at all) do you change the entity type with Doctrine2, using it's Class Table Inheritance?
Let's say I have a Person parent class type and two inherited types Employe and Client. My system allows to create a Person and specify it's type - that's fairly easy to implement - but I'd also like to be able to change the person from an Employe to a Client, while maintaining the Person-level information (it's id and other associated records).
Is there a simple way to do this with Doctrine2?
I was looking for this behaviour yesterday also.
In the end, after speaking with people in #doctrine on freenode, I was told that it is not possible.
If you want to do this, then you have to go through this:
Upgrading a User
Grab the Person Entity.
Update the discrimator column so that it is no longer a 'person' and change it to 'employee'
Create a corresponding row inyour Employee table for this inheritance.
Removing Inheritance
Likewise if you want to remove inheritance, you have to..
Grab the Person Entity.
Update the discrimnator column so that it is no longer an 'employee' and change it to a 'person'.
Delete the corresponding row in your Employee table. (Yes you have to delete it, just change the discrimator coumn is not sufficient).
This might be 7 months late, but it is at least the correct answer for anything else looking to suport such a feature.
PHP doesn't have support for object casting, so Doctrine doesn't support it. To workaround the problem I write this static method into parent classes:
public static function castToMe($obj) {
$class = get_called_class();
$newObj = New $class();
foreach (get_class_vars(get_class($newObj)) as $property => $value) {
if (method_exists($obj, 'get' . ucfirst($property)) && method_exists($newObj, 'set' . ucfirst($property))) {
$newObj->{'set' . ucfirst($property)}($obj->{'get' . ucfirst($property)}());
}
}
return $newObj;
}
You can create this method in class Person and use it to cast from Employe to Client and viceversa:
$employe = New Employe();
$client = Client::castToMe($employe);
Now, if you want, you can remove the $employe entity.
You could do something like this though:
This Trait can be used on your Repository class:
namespace App\Doctrine\Repository;
trait DiscriminatorTrait
{
abstract public function getClassMetadata();
abstract public function getEntityManager();
private function updateDiscriminatorColumn($id, $class)
{
$classMetadata = $this->getClassMetadata();
if (!in_array($class, $classMetadata->discriminatorMap)) {
throw new \Exception("invalid discriminator class: " . $class);
}
$identifier = $classMetadata->fieldMappings[$classMetadata->identifier[0]]["columnName"];
$column = $classMetadata->discriminatorColumn["fieldName"];
$value = array_search($class, $classMetadata->discriminatorMap);
$connection = $this->getEntityManager()->getConnection();
$connection->update(
$classMetadata->table["name"],
[$column => $value],
[$identifier => $id]
);
}
}
There still might be some extra work you need to put in, like clearing values in fields that are only present on one of your sub-classes
In Doctrine2, when you have your parent entity class, Person set as:
/**
* #Entity
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="discr", type="string")
* #DiscriminatorMap({"person" = "Person", "employee" = "Employee", , "client" = "Client"})
*/
class Person
{
// ...
}
and sub classes such as Client set as:
/** #Entity */
class Client extends Person
{
// ...
}
when you instantiate Person as:
$person = new Person();
Doctrine2 checks your #DiscriminatorMap statement (above) for a corresponding mapping to Person and when found, creates a string value in the table column set in #DiscriminatorColumn above.
So when you decide to have an instance of Client as:
$client = new Client();
Following these principles, Doctrine2 will create an instance for you as long as you have declared the parameters in the #DiscriminatorMap. Also an entry will be made on the Person table, in the discr column to reflect that type of entity class that has just been instantiated.
Hope that helps. It's all in the documentation though
i use this method
trait DiscriminatorTrait
{
// ...
public function updateDiscriminatorColumn($id, $class)
{
// ... other code here
$connection->update(
"Person", // <-- just there i put my parent class
[$column => $value],
[$identifier => $id]
);
}
}
and i use call like this after :
$this->em->getRepository(Client::class)->updateDiscriminatorColumn($cCenter->getId(), Employe::class);
$this->em->close();
// I update the data directly without going through doctrine otherwise it will create a new Person
try {
$query = "
INSERT INTO Employe (id, /* ... other fields */)
VALUES ({$callCenter->getId()}, /* ... other fields */)
";
$results = $this->connection->executeQuery($query)->execute();
} catch (\Exception $exception) {
echo $exception->getMessage().PHP_EOL;
}
$this->em->close();
// i restart the connection
/** #var EntityManagerInterface $entityManager */
$entityManager = $this->em;
if ($this->em->isOpen() === false) {
$this->em = $entityManager->create(
$this->em->getConnection(),
$this->em->getConfiguration(),
$this->em->getEventManager()
);
}
// and there a get Employer en update him
$employe = $this->em->getRepository(Employe::class)->find($id);
$employe->setFirstname($callCenter->getFirstName());
// other code
And it is work for me

Resources