I was working on an app and had everything set up nicely. I have an Alert entity which takes the following form.
/**
* Alert
*
* #ORM\Table(name="alert")
* #ORM\Entity(repositoryClass="Nick\AlertBundle\Repository\AlertRepository")
*
*/
class Alert
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="search_command", type="string", length=256, nullable=false)
*/
private $searchCommand;
/**
* #var string
*
* #ORM\Column(name="is_connecting", type="string", length=20, nullable=false)
*/
private $isConnecting;
/**
* #var \DateTime
*
* #ORM\Column(name="last_updated", type="datetime", nullable=false)
*/
private $lastUpdated;
/**
* #var boolean
*
* #ORM\Column(name="is_deleted", type="boolean", nullable=false)
*/
private $isDeleted;
/**
* #var string
*
* #ORM\Column(name="alert_status", type="string", length=11, nullable=false)
*/
private $alertStatus;
/**
* #ORM\OneToMany(targetEntity="Nick\AlertBundle\Entity\BookingClass", mappedBy="availabilityAlert")
*/
private $bookingClass;
/**
* #ORM\OneToMany(targetEntity="Nick\AlertBundle\Entity\Pseudos", mappedBy="availabilityAlert")
*/
private $pseudo;
/**
* #ORM\OneToMany(targetEntity="Nick\AlertBundle\Entity\FlightNumbers", mappedBy="availabilityAlert")
*/
private $flightNumbers;
/**
* #ORM\OneToMany(targetEntity="Nick\AlertBundle\Entity\Availability", mappedBy="availabilityAlert")
*/
private $availability;
/**
* Constructor
*/
public function __construct()
{
$this->bookingClass = new ArrayCollection();
$this->pseudo = new ArrayCollection();
$this->flightNumbers = new ArrayCollection();
$this->availability = new ArrayCollection();
}
//other methods
}
So I had this app working, but now I have decided to add a log in system. Each Alert should now be associated to a user - a user can have none to many alerts.
So I have created my user class and set up all the security. The entity looks like
/**
* User
*
* #ORM\Table(name="user_table")
* #ORM\Entity()
*
*/
class User implements UserInterface
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="username", type="string", length=255, nullable=false)
*/
private $username;
/**
* #var string
*
* #ORM\Column(name="password", type="string", length=255, nullable=false)
*/
private $password;
//other functions
}
So whats the best way to associate Alerts to a User? Should I set it up like I do the with the other Entities in my Alert class? Should I add an Alert variable within the user class which is an Array Collection of Alerts?
Really just looking for a bit of advice how to best handle this.
Thanks
Yes you should add one to many relation ship in user entity and link with alert entity and in alert entity point back to user entity in many to one way
class User
{
/**
* #ORM\OneToMany(targetEntity="Alert", mappedBy="user")
*/
protected $alerts;
public function __construct()
{
$this->alerts= new ArrayCollection();
}
//... other getters amd setters
}
class Alert
{
// ...
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="alerts")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
}
Reference
I dont really see here any other solutions besides using ManyToOne association in the Alert entity or ManyToMany in case one Alert has to be associated with more then 1 user.
Should I add an Alert variable within the user class which is an Array Collection of Alerts?
Heck, why not? That way you can easily get users with joined alerts.
Related
I have #Embeddable class for id:
/**
* #ORM\Embeddable()
*/
final class Id
{
/**
* #ORM\Id()
* #ORM\Column(type="string")
*/
private $id;
//getters, setters, etc
}
And two classes that have #ManyToOne relationship:
/**
* #ORM\Entity()
*/
final class Gist
{
/**
* #var Id
*
* #ORM\Id()
* #ORM\Embedded(class="App\Entity\Id")
*/
private $id;
/**
* #var User
*
* #ORM\ManyToOne(targetEntity="App\Entity\User")
*/
private $user;
//getters, setters, etc
}
And User class:
/**
* #ORM\Entity()
*/
final class User implements UserInterface
{
/**
* #var Id
*
* #ORM\Embedded(class="App\Entity\Id")
*
*/
private $id;
//getters, setters, etc
}
When I trying to create schema I got error: No mapping found for field 'id' on class 'App\Entity\User'.. What I'm doing wrong?
There no solution. https://github.com/doctrine/doctrine2/issues/7094.
Support of identifiers within #Embeddable is not really there yet, sorry.
I suggest not to use #Embeddable on id field. Cause it won't work. Declare your id field normally and it should work.
/**
* #ORM\Entity()
*/
class Gist
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var User
*
* #ORM\ManyToOne(targetEntity="App\Entity\User")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
//getters, setters, etc
}
/**
* #ORM\Entity()
*/
class User implements UserInterface
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
//getters, setters, etc
}
Also your Many-To-One relation is bad, see: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#many-to-one-unidirectional
And never declare entities as final classes, see: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/architecture.html#entities
I'm working on Symfony 2.7. I have to create a mail client to retrieve and send mails from the mail server (IMAP protocol); to do this, I used the php-imap classes, with a bundle (included with composer). But I'm not sure about the way I should use them : Do I extend the classes to represent my Mail and Mailbox objects, or should I create new classes from scratch ?
I don't want to manipulate IMAP straight from my controllers, I think it would be too long to process. Is that right?
Is it a good idea to create a "watcher" (periodic command executed by cron) to parse new mails every 2 minutes or so, create new mails entity from them, and send the waiting ones?
Could I do that while extending the php-imap classes? This way I would use one class only? But wouldn't that be too heavy to store for the database ?
What's the correct way to fetch only new mails ? Do I have to use a specific function, like imap_check, or do I do that by a search criteria (like date from the last check) ? I tried with criteria "NEW", but that was unsuccessful.
Also, the mailboxes I have to parse are quite heavy. I tried to make a search in one of them with "ALL" criteria, but it's really long to process ! Am I doing it right ? Do I just have to be patient ?
Here's what I did for the "watcher" function :
use PhpImap\Mailbox as ImapMailbox;
class GetNewMailsCommand extends ContainerAwareCommand
{
$em = $this->getContainer()->get('doctrine')->getEntityManager();
$mailboxes = $em->getRepository('MIPMailBundle:MailBox')->findAllActive();
foreach ($mailboxes as $mailbox){
$imapBox = new ImapMailbox('{'.$mailbox->getServer().':143/notls/norsh/novalidate-cert}INBOX', $mailbox->getAdress(), $mailbox->getPassword());
if ($mailbox->getMails() == null || empty($mailbox->getMails())){
$mailsIds = $imapBox->searchMailbox('ALL');
if(!$mailsIds) {
$output->writeln($mailbox->getAdress() . " is empty");
}
} else {
$mailsIds = $imapBox->searchMailbox('NEW');
if(!$mailsIds) {
$output->writeln("No new mail for " . $mailbox->getAdress());
}
}
foreach ($mailsIds as $mailId){
$imapMail = $imapBox->getMail($mailId);
$mail = new Mail($mailbox, false);
$mail->setSubject($imapMail->subject);
$mail->setSender($imapMail->fromAddress);
$mail->setCc($imapMail->cc);
$mail->setBcc($imapMail->bcc);
$mail->setToString($imapMail->toString);
$mail->setContent($imapMail->textPlain);
$mail->setDate(new \DateTime($imapMail->date));
foreach ($imapMail->to as $toAddress){
$mail->addRecipient($toAddress);
}
$em->persist($mail);
}
}
$em->flush();
And here's my entities :
class MailBox
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var
*
* #ORM\OneToOne(targetEntity="\MIP\CRMBundle\Entity\Agency", inversedBy="mailBox", cascade={"persist"})
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=true)
*/
private $user;
/**
* #var Agency
*
* #ORM\ManyToMany(targetEntity="\MIP\CRMBundle\Entity\Agency", inversedBy="mailBoxShared")
* #ORM\JoinTable(name="mailbox_shared")
*/
private $sharedTo;
/**
* #var
*
* #ORM\OneToMany(targetEntity="Mail", mappedBy="mailBox")
*/
private $mails;
/**
* #var boolean
*
* #ORM\Column(name="active", type="boolean")
*/
private $active;
/**
* #var string
*
* #ORM\Column(name="adress", type="string", length=255)
*/
private $adress;
/**
* #var string
*
* #ORM\Column(name="server", type="string", length=255)
*/
private $server;
/**
* #var string
*
* #ORM\Column(name="password", type="string", length=255)
*/
private $password;
/**
* #var integer
*
* #ORM\Column(name="port", type="integer")
*/
private $port;
/**
* MailBox constructor.
*/
public function __construct()
{
$this->sharedTo = new ArrayCollection();
$this->mails = new ArrayCollection();
}
class Mail
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="sender", type="string", length=255)
*/
private $sender;
/**
* #var array
*
* #ORM\Column(name="recipients", type="json_array", nullable=true)
*/
private $recipients;
/**
* #var string
*
* #ORM\Column(name="toString", type="string", nullable=true)
*/
private $toString;
/**
* #var array
*
* #ORM\Column(name="cc", type="json_array", nullable=true)
*/
private $cc;
/**
* #var array
*
* #ORM\Column(name="bcc", type="json_array", nullable=true)
*/
private $bcc;
/**
* #var string
*
* #ORM\Column(name="subject", type="string", length=255, nullable=true)
*/
private $subject;
/**
* #var \DateTime
*
* #ORM\Column(name="date", type="datetime")
*/
private $date;
/**
* #var string
*
* #ORM\Column(name="content", type="text", nullable=true)
*/
private $content;
/**
* #var
*
* #ORM\OneToMany(targetEntity="MIP\CRMBundle\Entity\File", mappedBy="mail", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="file_id", referencedColumnName="id", nullable=true)
*/
protected $files;
/**
* #var ArrayCollection
*/
private $attached;
/**
* #var MailBox
* #ORM\ManyToOne(targetEntity="MailBox", inversedBy="mails")
* #ORM\JoinColumn(name="mailBox_id", referencedColumnName="id")
*/
private $mailBox;
/**
* #var LabelSticker
*
* #ORM\ManyToMany(targetEntity="\MIP\MailBundle\Entity\LabelSticker", mappedBy="mails")
*/
private $labels;
/**
* #var boolean
*/
private $readed;
/**
* #var boolean
*/
private $sent;
/**
* Constructor
* #param MailBox $mailbox
* #param boolean $readed
*/
public function __construct($mailbox, $readed)
{
$this->files = new ArrayCollection();
$this->date = new \DateTime('now');
$this->mailBox = $mailbox;
$this->readed = $readed;
}
Thanks for your help !
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!
Very very weird. I have used this method from doctrine hundreds of times. I have a simple controller that takes an id as parameter. The query that Doctrine generates is wrong and crash.
/**
* #Security("has_role('ROLE_ADMIN')")
* #return Response
*/
public function editSellerAction($id)
{
$em = $this->getDoctrine()->getManager();
$seller = $em->getRepository('SiteUserBundle:Seller')->find($id);
// ...
$form = $this->createForm(new SellerType(), $seller, array(
'method' => 'POST'
));
// ...
}
The query generated is the following
[2/2] DBALException: An exception occurred while executing 'SELECT t1.id AS id2, t1.username AS username3, t1.password AS password4, t1.firstname AS firstname5, t1.lastname AS lastname6 FROM seller t1 WHERE t0.id = ? LIMIT 1' with params ["2"]:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 't0.id' in 'where clause' +
The error thrown makes sense because it's looking at "WHERE t0.id" when it should be looking at "WHERE t1.id". I tried the query with t1 using phpmyadmin and it works.
Any idea what might cause this issue?
/**
* Seller have access to their customer and are able to RW access to the customers
*
* #ORM\Table("seller")
* #ORM\Entity
* #author Michael Villeneuve
*/
class Seller extends User
{
/**
* #var array
*
* #ORM\OneToMany(targetEntity="Customer", mappedBy="seller", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="seller_id", referencedColumnName="id")
**/
protected $customers;
/**
* #var string
*
* #ORM\Column(name="firstname", type="string", length=255, nullable=false)
*/
protected $firstname;
/**
* #var string
*
* #ORM\Column(name="lastname", type="string", length=255, nullable=false)
*/
protected $lastname;
// Other attributes and only getters/setter
/**
*
* #ORM\Entity
*/
class User implements UserInterface
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=255, unique=true)
*/
private $username;
/**
* #ORM\Column(type="string", length=64)
*/
private $password;
I have 3 entities that extends the User (customer, admin and seller).
Updated link: https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/inheritance-mapping.html
Read up a bit on mapped super classes: http://docs.doctrine-project.org/en/latest/reference/inheritance-mapping.html. Basically, your abstract base user class cannot itself be an entity.
So take the #ORM\Entity line out of your User class. That is where the table 0 (t0) is coming from.
You have 2 options:
The first one is to create an abstract User entity and inherit all values from it. This is useful if you have many entities with the same behaviour. I e.g. like to create a BaseEntity with a ID field and some basic methods. All entities can extend this one and automatically have an ID. Cerad explained in his answer how this is done.
The second option are so called discriminator fields. Basically they allow you to have one User table and sub-tables for every extended entity. You can read about them in the official docs.
Which one you end up using is probably case dependent.
Try to add id field to the Seller entity instead of User
/**
* Seller have access to their customer and are able to RW access to the customers
*
* #ORM\Table("seller")
* #ORM\Entity
*/
class Seller extends User
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var array
*
* #ORM\OneToMany(targetEntity="Customer", mappedBy="seller", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="seller_id", referencedColumnName="id")
**/
protected $customers;
/**
* #var string
*
* #ORM\Column(name="firstname", type="string", length=255, nullable=false)
*/
protected $firstname;
/**
* #var string
*
* #ORM\Column(name="lastname", type="string", length=255, nullable=false)
*/
protected $lastname;
// Other attributes and only getters/setter
/**
*
* #ORM\Entity
* #author Michael Villeneuve<michael#panierdachat.com>
*/
class User implements UserInterface
{
/**
* #ORM\Column(type="string", length=255, unique=true)
*/
private $username;
/**
* #ORM\Column(type="string", length=64)
*/
private $password;
this is my entity:
/**
* #ORM\Table(name="Animal")
* #ORM\HasLifecycleCallbacks
*/
class Animal {
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var localizedcontent $lctitle
*
* #ORM\ManyToOne(targetEntity="localizedcontent",fetch="EAGER", cascade={"persist"})
* #ORM\JoinColumn(name="lcTitle", referencedColumnName="pkId", nullable=false)
*/
private $lctitle;
/**
* #var localizedcontent $lcdescription
*
* #ORM\ManyToOne(targetEntity="localizedcontent",fetch="EAGER", cascade={"persist"})
* #ORM\JoinColumn(name="lcDescription", referencedColumnName="pkId", nullable=false)
*/
private $lcdescription;
/**
* #ORM\PostLoad
*/
public function postLoad(){
$lct = $this->lctitle;
$lcd = $this->lcdescription;
}
This is my dql:
SELECT a,lct FROM Animal JOIN e.lctitle lct WHERE a.id=:id
When i'm starting xdebug, it tells me that lcdescription is a proxy object and lctitle doesn't exists. I don't know why.
I think the postLoad event is too early because the localizedcontent isn't loaded at this moment, right? Is there an other listener for reading the value of lctitle in relation to the Animal Object?
Thanks
Doctrine always returns proxies. These classes inherit from the entity-classes. It might help if you declare your relations protected instead of private.
/**
* #var localizedcontent $lctitle
*
* #ORM\ManyToOne(targetEntity="localizedcontent",fetch="EAGER", cascade={"persist"})
* #ORM\JoinColumn(name="lcTitle", referencedColumnName="pkId", nullable=false)
*/
protected $lctitle;
or you could write a getter and call this one in your post-load function
public function getLctitle() {
return $this->lctitle;
}
public function getLcdescription() {
return $this->lcdescription;
}
/**
* #ORM\PostLoad
*/
public function postLoad(){
$lct = $this->getLctitle();
$lcd = $this->getLcdescription();
}