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.
I have a button. Every time the user clicks on it, the application will instantiate the UIImageView (imageview), assigning a unique number to its tag.
- (IBAction)buttonClicked:(id)sender {
imageview = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 300, 240)];
NSUInteger currentag = [self UniqueNum]; // Assigning a unique number to the tag variable
[self.view addSubview:imageview];
imageview.backgroundColor = [UIColor blueColor];
imageview.userInteractionEnabled = YES;
imageview.tag = currentag;
}
My goal is to get the tag number of the UIImageView copy that the user touches. With the following code, what I actually get is the tag number of the UIImageView copy that the application last created. How can I improve it so that the application will point to the tag number of the touched object?
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
UITouch *touch = [[event allTouches] anyObject];
if([touch view] == imageview) {
NSLog(#"Tag: %i",imageview.tag);
}
}
Thank you for your help.
I'd recommend using UIButtons with custom images here rather than UIImageViews if you need interaction.
Add this when you add the button:
[imageview addTarget:self action:#selector(someMethod:) forControlEvents:UIControlEventAllTouchEvents];
And then just cast the sender in someMethod (your declaration) to UIView to get its tag.
BTW, the method signature of someMethod goes something like this:
-(void) someMethod:(id)sender
You can optionally also add an event parameter, but that doesn't seem to be needed here.
I have a custom CollectionViewCell
#interface FBPicCell : UICollectionViewCell
#property (strong, nonatomic) IBOutlet UILabel *picby;
In my code in viewDidLoad I have
[self.fbpiccollectionView registerClass:[FBPicCell class] forCellWithReuseIdentifier:#"fbpiccell"];
and in cellForItemAtIndexPath function
FBPicCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"fbpiccell" forIndexPath:indexPath];
cell.backgroundColor = [UIColor whiteColor];
[cell.picby setText:#"Pavan"];
[cell.picby setTextColor:[UIColor redColor]];
But I don't see anything, just the white border. Not sure where i am messing things up. Any pointer would be helpful.
I had the same problem along with registring your call
register the nib as well as-
[collectionView registerNib:[UINib nibWithNibName:#"cellname" bundle:nil] forCellWithReuseIdentifier:#"cellname"];
This worked for me
Regards
I have a class with multiple Lists. Is the class design correct for gson to work?
My class:
Class Data
{
List<String> actor;
List<Integer> text_relevance;
List<String> title;
}
The json string is as follows ...
Is the class design for this json correct?
{
"data":
{
"actor":["Abercrombie, Ian","Baker, Dee Bradley","Burton, Corey",
"Eckstein, Ashley","Futterman, Nika","Kane, Tom",
"Lanter, Matt","Taber, Catherine","Taylor, James Arnold",
"Wood, Matthew"],
"text_relevance":["308"],
"title":["Star Wars: The Clone Wars"]
}
}
Yes, your code is correct. I deserialized it with this code:
Gson gson = new Gson();
Type type = new TypeToken<Map<String, Data>>() {}.getType();
Map<String, Data> map = gson.fromJson(json, type);
Data data = map.get("data");
Note that Gson will convert strings like "308" to the corresponding integer. This is generally used to permit very large values from losing precision. But it works out just fine for your case as well.
So i have two model classes:
class Dog(db.model):
dogName = StringProperty()
dogBreed = StringProperty()
class Cat(db.model):
catName = StringProperty()
catBreed = StringProperty()
and then i have a third model class to hold all the pictures
class Images(db.model):
imageReference = ReferenceProperty(*Animal*, collection_name = 'allImages')
imageURL = StringProperty()
Animal is either a Dog or a Cat. Obviously this does not compile.
Now my question is: Is there a way I can put Cat pictures in with Dog pictures? Or do I need to create more models like this:
class DogImages(db.model):
imageReference = ReferenceProperty(Dog, collection_name = 'allImages')
imageURL = StringProperty()
class CatImages(db.model):
imageReference = ReferenceProperty(Cat, collection_name = 'allImages')
imageURL = StringProperty()
You could use PolyModel:
class Animal(polymodel.PolyModel):
name = db.StringProperty()
breed = db.StringProperty()
class Dog(Animal):
pass
class Cat(Animal):
pass
Now you can have a ReferenceProperty that references Animals, and either Dogs or Cats will be permitted.
However, you don't have any properties that are specific to each type of animal - why not just have a regular Animal model, add a property indicating what species it is, and skip the separate models entirely?