Doctrine 2 - ManyToOne association not working - symfony

I need a hand with the following code.
I have this two clases:
Class Expert{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(name="id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="username", type="string", length=255)
* #Assert\NotBlank()
*/
protected $username;
/**
* #ORM\Column(name="email", type="string", length=255, unique=true)
* #Assert\NotBlank()
*/
protected $email;
/**
* #ORM\Column(name="password", type="string", length=40)
* #Assert\NotBlank()
*/
protected $password;
}
Class Job{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="titulo", type="string", length=255)
* #Assert\NotBlank()
*/
protected $title;
/**
* #ORM\Column(name="description", type="text")
* #Assert\NotBlank()
*/
protected $description;
/**
* #ORM\ManyToOne(targetEntity="Expert")
* #ORM\JoinColumn(name="expert_id", referencedColumnName="id")
*/
protected $assigned_expert;
}
And this custom Repository:
class JobRepository extends EntityRepository
{
public function getTechnicianFinishedJobs($id)
{
$Q = $this->getQueryBuilder('j')
->where('j.expert = :expert_id')
->setParameter('expert_id', $id)
->getQuery()
try{
return $q->getResult();
}catch(NoResultException $e){
return false;
}
}
}
When I run this I get the following error:
[Semantical Error] line 0, col 68 near 'expert = :e': Error: Class Job has no field or association named expert
The idea is that one expert can be assigned to many jobs and one job can be assigned to one expert . The job needs to know who's the designated expert but not the other way around, so that's why I use a ManyToOne unidirectional association.
I tried changing the repository to ->where('j.expert_id = :expert_id') and other combinations with no avail.
Can somebody tell me what I'm doing wrong?
Thanks in advance.

If 'j' is your job table, you can't use j.expert, because this is (as far as I can tell) no attribute of your table/entity. You named the field 'expert_id'.
I guess it should be:
$Q = $this->getQueryBuilder('j')
->where('j.assigned_expert = :expert_id')
->setParameter('expert_id', $id)
->getQuery()
#alvk4: He explained why he didn't use bidirectional association. What you suggested, forgive me if I'm wrong, is bidirectional association.

You miss something on your annotation see an working example:
/**
* Fluency\Bundle\DesktopBundle\Entity\Application
*
* #ORM\Table(
* name="desktop.applications",
* uniqueConstraints={
* #ORM\UniqueConstraint(name="applications_jsid_key", columns={"jsid"}),
* #ORM\UniqueConstraint(name="applications_type_key", columns={"type"}),
* #ORM\UniqueConstraint(name="applications_classname_key", columns={"classname"})
* }
* )
* #ORM\Entity(repositoryClass="Fluency\Bundle\DesktopBundle\Entity\Repository\ApplicationRepository")
*/
class Application
{
...
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Fluency\Bundle\DesktopBundle\Entity\ApplicationFile", mappedBy="application", cascade={"persist"})
* #ORM\JoinTable(name="desktop.application_files",
* joinColumns={
* #ORM\JoinColumn(name="idapplication", referencedColumnName="idapplication")
* }
* )
*/
private $files;
...
}
/**
* Fluency\Bundle\DesktopBundle\Entity\ApplicationFile
*
* #ORM\Table(name="desktop.application_files")
* #ORM\Entity
*/
class ApplicationFile
{
...
/**
* #var \Fluency\Bundle\DesktopBundle\Entity\Application
*
* #ORM\ManyToOne(targetEntity="Application", inversedBy="files")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="idapplication", referencedColumnName="idapplication", onDelete="CASCADE")
* })
*/
private $application;
...
}
See a working example of DQL on my repository class:
...
public function getApplicationFilesByJsid($jsid)
{
if(empty($jsid) OR !$jsid OR !is_string($jsid))
{
throw new \Psr\Log\InvalidArgumentException();
}
$query = $this->getEntityManager()->createQueryBuilder()
->select('a, af, m, ft')
->from($this->getEntityName(), 'a')
->innerJoin('a.files', 'af')
->innerJoin('a.module', 'm')
->innerJoin('af.filetype', 'ft')
->where('a.active = 1 AND a.jsid = :jsid')
->setParameter('jsid', $jsid)
->orderBy('af.id', 'ASC')
->getQuery();
$applicationFiles = $query->getSingleResult(\Doctrine\ORM\Query::HYDRATE_ARRAY);
return $applicationFiles;
}
...
#enigma: Yes is bidirectional, but your DQL its'n right, would be j.assigned_expert, but anyway the Expert is owning side of relationship, also he needs set mappedBy=assigned_expert on annotation.

Related

Use php-imap classes to parse new mails from server, with Symfony 2.7

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 !

Symfony Doctrine update many to one

I have two entities:
1)
/**
* Post
*
* #ORM\Table(name="article")
* #ORM\Entity(repositoryClass="AppBundle\Repository\PostRepository")
*/
class Post
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
...
/**
* #var string
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Image", cascade={"persist"}, fetch="EAGER")
* #ORM\JoinColumn(name="image", referencedColumnName="id", nullable=true)
*/
private $image;
}
and 2)
/**
* Image
*
* #ORM\Table(name="image")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ImageRepository")
*/
class Image
{
/**
* #var int
*
* #ORM\Column(name="id", type="string", length=40)
* #ORM\Id
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="small", type="blob", nullable=true)
*/
private $small;
/**
* #var string
*
* #ORM\Column(name="medium", type="blob", nullable=true)
*/
private $medium;
/**
* #var string
*
* #ORM\Column(name="large", type="blob", nullable=true)
*/
private $large;
...
}
Both entities have getters and setters.
The controller sets the post to update
$requestFile = $request->files->get('image');
$em = $this->getDoctrine()->getManager();
$record = $em->getRepository('AppBundle:Post')->find($id);
if (!$record) {
throw $this->createNotFoundException(
'No product found for id '.$record
);
}
$record->setTitle($request->request->get('title'));
$record->constructAlias($request->request->get('title'));
...
if ($request->files->get('image')) {
// check if image already exists
$imgGeneratedId = sha1_file($request->files->get('image')->getPathName());
$imageRepo = $this->getDoctrine()->getRepository('AppBundle:Image');
$isPresent = $imageRepo->find($imgGeneratedId);
$image = $isPresent;
if (!$isPresent) {
$image = new Image();
$image->setId($imgGeneratedId);
$image->setLarge(file_get_contents($request->files->get('image')->getPathName()));
$mediumImage =
...
$em->persist($image);
}
$record->setImage($em->getReference('AppBundle:Image', $image->getId()));
}
$em->persist($record);
$em->flush();
Please ignore any syntax errors or other kinds; there are none.
The problem is that even if image already exists it is trying to create one which causes error in database. So, in order to avoid this I used getReference, but this causes other issues.
So, how can I force doctrine, not to insert an image if already exists and just set the article image with the image id?
Thanks.

Self JOIN query with Symfony entity

Here's my table :
My Category entity (without getter/setter):
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="slug", type="string", length=255, nullable=true)
*/
private $slug;
/**
* #var int
* #Gedmo\TreeLeft
* #ORM\Column(name="lft", type="integer")
*/
private $lft;
/**
* #var int
* #Gedmo\TreeLevel
* #ORM\Column(name="lvl", type="integer")
*/
private $lvl;
/**
* #var int
* #Gedmo\TreeRight
* #ORM\Column(name="rgt", type="integer")
*/
private $rgt;
/**
* #Gedmo\TreeRoot
* #ORM\ManyToOne(targetEntity="Category")
* #ORM\JoinColumn(name="root", referencedColumnName="id", onDelete="CASCADE")
*/
private $root;
/**
* #Gedmo\TreeParent
* #ORM\ManyToOne(targetEntity="Category", inversedBy="children")
* #ORM\JoinColumn(name="parent", referencedColumnName="id", onDelete="CASCADE")
*/
private $parent;
/**
* #ORM\OneToMany(targetEntity="Category", mappedBy="parent")
* #ORM\OrderBy({"lft" = "ASC"})
*/
private $children;
In my controller, if I do this :
$category= $this->container->get('app.category.manager')->getCategoryBySlug($category_slug);
$root = $term->getRoot();
Doctrine execute 2 queries, one for the category itself, and one for the root of the category. I would like to create my own repository function to join the 2 entities in one query. I've tried so many things with the query builder, now I'm completly lost.
First, create a custom Repository class for your entity.
Then, paste the a method like the following into:
class CategoryRepository extends EntityRepository
{
public function getRootByCategorySlug($slug)
{
return $this->getEntityManager()
->createQueryBuilder()
->from('YourBundle:Category', 'c')
->where('c.slug = :slug')
->setParameter('slug', $slug)
->leftJoin('c.root', 'r') // Join the association
->select('r') // Fetch the association only
->getQuery()
->getResult()
;
}
}
And use it like follows:
$repo = $this->getDoctrine()->getManager()->getRepository('YourBundle:Category');
$rootCategory = $repo->getRootByCategorySlug('your_slug');

Doctrine query crashing

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;

doctrine2 attribute doesn't exist

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();
}

Resources