Doctrine attributes for 3 linked entities - symfony

I want to make a website that list albums, each albums have tracks, and in order to handle various artists albums, each track is made by an artist.
I'm new with symfony and I'm fighting with doctrine to find the best way to handle those 3 entities (album, track and artist).
Album (id, title, release_date, cover, etc...)
Track (id, id_album, id_artist, title, position, duration)
Artist (id, name, country, photo,...)
I was thinking about doing one-to-many/many-to-one relations or many-to-many with attributes, but I'm really not sure anymore what's the best way to link those 3 entities in doctrine.
At the end, I would like to make a homepage that looks like that :
[album_cover]
"album title"
artist
[album_cover]
"album title"
artist1, artist2
So from the album entity I need to get all the artists from all the album's tracks, I don't know yet how to do that.
So far I've done that, but I'm really not sure if it's the best way (with AlbumArtist = Track):
/**
* Album
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="AppBundle\Entity\AlbumRepository")
*/
class Album
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="AlbumArtist", mappedBy="album", cascade={"persist"})
*/
private $albumArtists;
/**
* #var string
*
* #ORM\Column(name="title", type="text")
*/
private $title;
/**
* Artist
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="AppBundle\Entity\ArtistRepository")
*/
class Artist
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="AlbumArtist", mappedBy="artist", cascade={"persist"})
*/
private $albumArtists;
/**
* #var string
*
* #ORM\Column(name="name", type="text")
*/
private $name;
/**
* AlbumArtist
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="AppBundle\Entity\AlbumArtistRepository")
*/
class AlbumArtist
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Album", inversedBy="albumArtists")
* #ORM\JoinColumn(name="album_id", referencedColumnName="id")
*/
private $album;
/**
* #ORM\ManyToOne(targetEntity="Artist", inversedBy="albumArtists")
* #ORM\JoinColumn(name="artist_id", referencedColumnName="id")
*/
private $artist;
/**
* #var string
*
* #ORM\Column(name="title", type="text")
*/
private $title;
Thanks for your help !

Your sequence is a bit strange in my opinion.
Lets make a few statements in human language:
An Album exist out of multiple tracks.
One Track exist out of one Song.
One Song can be composed by one or more Artists.
I use the entity Song because a song can be placed in multiple Albums and on a different track-number.
Gives us the those relations:
Album - oneToMany - Tracks (an Album have multiple tracks)
Tracks - ManyToOne - Song (a Song can be placed on multiple tracks)
Song - oneToMany - Artists (some songs are made by multiple artists)

Related

How to use Gedmo nested tree for storing multiple trees in one single table?

I have a table which store a tree for each username.
My entity looks like this:
/**
* Confsaves
* #Gedmo\Tree(type="nested")
* #ORM\Table(name="confsaves")
*#ORM\Entity(repositoryClass="Gedmo\Tree\Entity\Repository\NestedTreeRepository")
*/
class Confsaves
{
/**
* #var string
*
* #ORM\Column(name="Name", type="string", length=200, nullable=true)
*/
private $name;
/**
* #var string
*
* #Gedmo\TreeRoot
* #ORM\Column(name="Username", type="string", length=50, nullable=true)
*/
private $username;
/**
* #Gedmo\TreeParent
* #ORM\ManyToOne(targetEntity="Confsaves", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE", nullable=true)
*/
private $parent;
/**
* #ORM\OneToMany(targetEntity="Confsaves", mappedBy="parent")
* #ORM\OrderBy({"lft" = "ASC"})
*/
private $children;
/**
* #var integer
*
* #Gedmo\TreeLeft
* #ORM\Column(name="lft", type="integer", nullable=true)
*/
private $lft;
/**
* #var integer
*
* #Gedmo\TreeRight
* #ORM\Column(name="rght", type="integer", nullable=true)
*/
private $rght;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
I want build a different tree for each user.
In my controller I have created the repository like this:
$em = $this->getDoctrine()->getManager();
$repo = $em->getRepository('MyBundle:Confsaves');
How can I set the scope of the repository only on the user connected?
Is level necessary for build tree function?
I use an existing database that only have left, right and parent arguments.
Has been a long time, but for the record:
If you want a tree for each user, you must establish a relationship between your Confsaves and User entities, more specifically, between the root property of your Confsaves entity and your User one. Doctrine Extensions supports relationships on the root property of your tree since 2.4. Make sure you are using the right version. Cost me a day of debugging.
So, to relate your nested set tree to a user entity, just perform a ManyToOne in your User, and the inverse relationship in your Tree entity.

symfony2-easyadmin bundle blank screen error

i have a strange problem when trying to render entity in symfony2 easyadminBundle i got a blank screen without any errors,
when trying to list entity 'Ads' that has many-to-one relation-ship with entity 'pages' ,where page contains many ads., however if i modified the action parameter in the url to be &action=new instead of &action=list it shows the form but after saving it giving me the same blank screen!
My Pages Entity :
/**
* #var string
*
* #ORM\Column(name="page_name", type="string", length=30, nullable=true)
*/
private $pageName;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/*#ORM\OneToMany(targetEntity="Ads", mappedBy="pages")
*
*/
protected $ads;
public function __construct()
{
$this->ads = new ArrayCollection();
}
}
Ads Entity:
/**
* Ads
*
* #ORM\Table(name="ads")
* #ORM\Entity
*/
class Ads
{
/**
* #var integer
*
* #ORM\Column(name="page_id", type="integer", nullable=false)
*/
private $page_id ;
/**
* #var string
*
* #ORM\Column(name="ad_text", type="text", nullable=false)
*/
private $adText;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Pages", inversedBy="ads")
* #ORM\JoinColumn(name="page_id", referencedColumnName="id")
*/
private $page_ad;
}
Your configuration looks "not complicated", so it should work as expected. The "blank page" error is really strange. We render the data with try...catch to avoid any issue when we cannot access the information. Besides, we also catch the exceptions to display customized error pages.
Can you see any error message in your dev.log file?
You could also do the following. Create the simplest possible configuration for the list view of the Ads entity as follows:
easy_admin:
entities:
Ads:
list:
fields: ['id']
If this work, try adding more fields one by one until you find the error.

Doctrine2 relation for non Id column

I'm building a simple web-service using Symfony 3, Doctrine 2.5 and stuck at ORM relations described below in simplified structure.
I have an Action entity containing many actions with ManyToOne relation...
class Action
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer
*
* #ORM\ManyToOne(targetEntity="\AppBundle\Entity\Status")
* #ORM\JoinColumn(referencedColumnName="code", nullable=false)
*/
private $status;
and the Status Entity with a few statuses.
class Status
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(type="integer", unique=true)
*/
private $code;
I cannot get proper way to set referencedColumnName="code" column (not 'Id' as usual) for Action entity.
Configured this way repo throws wxception at persist moment with "Notice: Undefined index: code";
I guess that it is mappedBy or inversedBy annotation parameter... but can't figure out "how".
Unfortunately it's not supported in Doctrine (reference).
You may edit your Status entity like this (ensure that code is set before persist):
class Status
{
/**
* #ORM\Column(name="code", type="integer", unique=true)
* #ORM\Id
*/
private $code;
}
If autoincremented field is your requirement you can take a look on this answer for possible solutions.
Just thought I'd add you can still use the non-primary keys as many to many, by using the entity itself as the join table. This will work but you still need to set your relationship keys correctly.
Example:
/**
* #ORM\Entity
*/
class Car {
/**
* #var integer
*
* #ORM\Column(name="id", type="bigint", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #ORM\Column(name="registration_code", type="text", length=128, nullable=false)
* #var string
*/
public $registrationCode;
/**
* #var \Doctrine\Common\Collections\Collection
* #ORM\ManyToMany(targetEntity="Registration", mappedBy="Cars")
* #ORM\JoinTable(name="car",
* joinColumns={#ORM\JoinColumn(name="id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="registration_code", referencedColumnName="registration_code")}
* )
*/
public $Registrations;
public function __construct() {
$this->Cars = new ArrayCollection();
}
}
/**
* #ORM\Entity
*/
class Registration {
/**
* #var integer
*
* #ORM\Column(name="id", type="bigint", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #ORM\Column(name="registration_code", type="text", length=128, nullable=false)
* #var string
*/
public $registrationCode;
/**
* #var ArrayCollection
* #ORM\ManyToMany(targetEntity="Car", mappedBy="Registrations")
* #ORM\JoinTable(name="car",
* joinColumns={#ORM\JoinColumn(name="registration_code", referencedColumnName="registration_code")},
* inverseJoinColumns={#ORM\JoinColumn(name="id", referencedColumnName="id")}
* )
*/
public $Cars;
public function __construct() {
$this->Cars = new ArrayCollection();
}
}
The upside is that it works fine as a workaround.
Keep in mind a few things:
it's a collection not a single instance;
column has to be managed manually on your end;
you must set up constraints correctly (indexes, keys, etc);
check your queries still perform!

Eager loading of related entity in Symfony 2

There are three entities: Customer, Messages, Attachments.
The relationship between these entities is straight forward: A customer can have many messages and a message can have many attachments. Both relations are "one-to-many".
I told doctrine to be lazy when loading the messages for the Customer entity. So $customer->getMessages() results in an additional SQL statement. That's fine.
But I also defined an "EAGER" loading for the attachments for the Message entity.
Now I would have expected that the messages I get by calling $customer->getMessages() are already loaded with all their attachments. But $message->getAttachments() still causes one SQL statement per message.
Is this behavior expected?
Just for reference, excepts from my classes:
Customer.php
class Customer
{
/**
* #ORM\OneToMany(targetEntity="Message", mappedBy="customer")
* #ORM\OrderBy({"createdOn" = "DESC"})
*/
private $messages;
Message.php
class Message
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Customer", inversedBy="messages")
* #ORM\JoinColumn(name="customer_id", referencedColumnName="id")
**/
private $customer;
/**
* #ORM\OneToMany(targetEntity="Attachment", mappedBy="message", fetch="EAGER")
**/
private $attachments;
Attachment.php:
class Attachment
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Message", inversedBy="attachments")
* #ORM\JoinColumn(name="message_id", referencedColumnName="id")
**/
private $message;
It sounds like expected behavior to me. The doctrine documentation seems to imply that eager fetching is only one level deep.
According to the docs:
Whenever you query for an entity that has persistent associations and
these associations are mapped as EAGER, they will automatically be
loaded together with the entity being queried and is thus immediately
available to your application.
http://doctrine-orm.readthedocs.org/en/latest/reference/working-with-objects.html#by-eager-loading
The entity being queried in your case is customer and customer has eager on messages so messages are populated. Messages, however are not the object being queried, so attachments do not get loaded.
the correct code example might be like the following:
NOTE: fetch message with lazy loading, i.e. get messages with additional query proactively; as long as the messages are fetched from database, the corresponding attachments referenced to each message will be loaded automatically.
Customer
class Customer
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Message", mappedBy="customer", fetch="LAZY")
* #ORM\OrderBy({"createdOn" = "DESC"})
*/
private $messages;
Message
class Message
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Customer", inversedBy="messages")
* #ORM\JoinColumn(name="customer_id", referencedColumnName="id")
*/
private $customer;
/**
* #ORM\OneToMany(targetEntity="Attchment", mappedBy="message", fetch="EAGER")
*/
private $attachments;
Attachment
class Attachment
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToONe(targetEntity="Message", inversedBy="attachments")
* #ORM\JoinColumn(name="message_id", referencedColumnName="id")
*/
private $message;

Doctrine2 How to check if there is the related record in

I have a bidirectional many-to-many between theese two entities:
Position
/**
* Position
*
* #ORM\Table(name="applypie_position")
* #ORM\Entity(repositoryClass="Applypie\Bundle\PositionBundle\Entity\PositionRepository")
*/
class Position
{
const IS_ACTIVE = true;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="Applypie\Bundle\UserBundle\Entity\Applicant", mappedBy="bookmarks")
*/
private $bookmarkedApplicants;
Applicant
/**
* Applicant
*
* #ORM\Table(name="applypie_applicant")
* #ORM\Entity
*/
class Applicant
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="Applypie\Bundle\PositionBundle\Entity\Position", inversedBy="bookmarkedApplicants")
* #ORM\JoinTable(name="applypie_user_job_bookmarks",
* joinColumns={#ORM\JoinColumn(name="applicant_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="position_id", referencedColumnName="id")}
* )
*/
private $bookmarks;
My Problem is: In an PositionControllers Action which easily shows an position by ID i need to know if there the current Applicant whichs wants to see the position has an bookmark for the current position.
I first thought of get all the Bookmarks with $applicant->getBookmarks() and run in within a forearch, checking all the applicants bookmarks against the current position, but i think there must be an easier way?
thank you
If you want to stay object oriented you can do it this way:
class Applicant
{
// fields and ORM annotations
public function hasBookmark(Bookmark $bookmark) {
return $this->bookmarks->contains($bookmark);
}
class MyController
{
public function testAction() {
$applicant = $this->getUser(); // or however you fetch the applicant object
$bookmark = $bookmarkRepository->find($bookmarkId); // again, however you get the bookmark object
// #var boolean $applicantHasBookmark
$applicantHasBookmark = $applicant->hasBookmark($bookmark);
// other controller code
}

Resources