Entity containing other entities without having a table - symfony

I have an Entity ( Invoice ) which is purely for calculation purposes and has no table, that associates with two other entities having relations by tables. (Although there are so many other entities involved ).
class Row{
/**
* #var integer
*
* #ORM\Column(name="row_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="File")
* #ORM\JoinColumn(name="file_id", referencedColumnName="file_id")
*/
protected $file;
/**
* #var \DateTime
*
* #ORM\Column(name="date", type="date")
*/
private $date;
}
class File
{
/**
* #var integer
*
* #ORM\Column(name="file_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
}
class Invoice
{
/**
* #ORM\Id
* #ORM\Column(name="invoice_id", type="integer")
* #ORM\GeneratedValue
*/
protected $id = null;
/**
* #ORM\OneToMany(targetEntity="Row", mappedBy="row_id")
*/
protected $row;
/**
* #ORM\OneToMany(targetEntity="File", mappedBy="file_id")
*/
protected $file;
}
I want to be able to query for Invoices :
$sDate = //Some date
$this->getEntityManager()
->createQuery("SELECT Invoice, Row, File
FROM
ReportsEntitiesBundle:Invoice Invoice
LEFT JOIN
Row.row Row
LEFT JOIN
Row.file File
WHERE date=:date"
)
->setParaMeter(':date', $sDate)
->setFirstResult($iPage*$iLimit)
->setMaxResults($iLimit)
->getResult();
The questions :
# Doctrine tries to query the database, how can I prevent that and have it find the relevant entities?
# How can I relate the date ( which is in Row entity and cannot be in Invoice ) to the query?
Later this Invoice will become a part of another big entity for calculating/search purposes.
Thank you

Short Answer: You can't
Long Answer : You can't because an entity with #ORM annotations means its persisted to a database - querying that entity relates to querying a database table. Why not just create the table ?!?!?
You need somewhere to persist the association between file and row - a database table is a perfect place !!!!
Update
Just to clarify ... an Entity is just a standard class - it has properties and methods ... just like any other class - When you issue doctrine based commands it uses the annotations within the entities to configure the tables / columns / relationships etc if remove those you can use it however you like ... but you will need to populate the values to use it and you wont be able to use it in a Doctrine query and it obviously wont be persisted !

You can use a read-only entity. It's contents are backed by a view which you create manually in SQL.
PHP:
/** #ORM\Entity(readOnly =true) */
class InvoiceView
{ ...
SQL:
CREATE VIEW invoice_view AS (
SELECT ...

Related

Can't remove entities iterating over collection, but I can remove getting them directly from repository

My News entity is connected with Document entity through joiner NewsDocument entity with some extra fields: News -> NewsDocument <- Document
/**
* #ORM\Entity
* #ORM\Table(name="news_document")
* #UniqueEntity(
* fields={"news", "document"}
* )
*/
class NewsDocument
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Document", inversedBy="news")
* #ORM\JoinColumn(nullable=false)
*/
private $document;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\News", inversedBy="documents")
* #ORM\JoinColumn(nullable=false)
*/
private $news;
/**
* #ORM\Column(type="integer")
*/
private $position;
My News entity
/**
* #ORM\OneToMany(targetEntity="App\Entity\NewsDocument", mappedBy="news", cascade={"persist"})
* #ORM\OrderBy({"position" = "ASC"})
*/
private $documents;
public function getNewsDocuments()
{
return $this->documents;
}
When I tried to remove each NewsEntity iterating over documents field of my News entity, the NewsEntity wasn't removed from DB.
$news = $this->entityManager->getRepository(News::class)->find(26);
foreach($news->getNewsDocuments() as $newsDocument) {
$this->entityManager->remove($newsDocument);
}
$this->entityManager->remove($news);
$this->entityManager->flush();
News was deleted but NewsDocument not.
However the following code is able to remove both entities.
$news = $this->entityManager->getRepository(News::class)->find(26);
$newsDocument = $this->entityManager->getRepository(NewsDocument::class)->find(1);
$this->entityManager->remove($newsDocument);
$this->entityManager->remove($news);
$this->entityManager->flush();
I don't understand why first code isn't able to remove NewsDocument from DB.
I dumped $newsDocument variable in both cases and looked similar to each others.
I probably found a problem. My News entity uses SoftDeletable extension for Doctrine, but the joiner entity doesn't. I guess that extension tries to set a deleted_at flag to the relation, instead of deleting it permanently. This is why if I get joiner entity directly from repository works correctly.

Join-Table with metadata, composite key and one to many relationship

i am building a cart that can take items with specified versions.
I am using Symfony 2.4.3 and Doctrine 2
I have following code for three entities, Cart, CartItem and CartItemVersion.
Cart.php
// ----
/**
* #ORM\OneToMany(targetEntity="CartItem", mappedBy="cart")
*/
private $cartItems;
// ----
CartItem.php
// ----
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Cart")
*/
private $cart;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Item")
*/
private $item;
/**
* #ORM\OneToMany(targetEntity="CartItemVersion", mappedBy="cartItem")
*/
private $cartItemVersions;
// ----
CartItemVersion.php
// ----
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="CartItem")
*/
private $cartItem;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="ItemVersion")
*/
private $itemVersion;
// ----
While updating schema, i got this error.
[Doctrine\ORM\ORMException]
Column name `id` referenced for relation from CartItemVersion towards CartItem does not exist.
Then i gave name to the fields like cartItem_id in CartItemVersion.php and others.
Then updating schema returns,
[Doctrine\DBAL\DBALException]
An exception occurred while executing 'ALTER TABLE cart_item_version ADD CONSTRAINT FK_4D3EA2E02EA80FC1 FOREIGN KEY (cartItem_id) REFERENCES cart_item (cartItemVersion_id)':
SQLSTATE[HY000]: General error: 1005 Can't create table 'symfony.#sql-3d8_12a' (errno: 150)
I have referred Doctrine 2's documentation and followed Use Cases for OrderItem but it seems that this is something because of composite primary keys, but still giving proper names couldn't solve this issue.
Can anyone help?
In your mapping you have a bunch of issues which are corrected as below. You need define the referenced Column name and its own column name; furthermore, for those field which has mappedBy you need to define inversedBy, too.
Cart.php
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="CartItem", mappedBy="cart")
*/
private $cartItems;
CartItem.php
/**
* #ORM\ManyToOne(targetEntity="Cart", inversedBy="cartItems")
* #ORM\JoinColumn(name="cart_id", referencedColumnName="id")
*/
private $cart;
/**
* #ORM\ManyToOne(targetEntity="Item")
* #ORM\JoinColumn(name="item_id", referencedColumnName="id")
*/
private $item;
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="CartItemVersion", mappedBy="cartItem")
*/
private $cartItemVersions;
CartItemVersion.php
/**
* #ORM\ManyToOne(targetEntity="CartItem", inversedBy="cartItemVersions")
* #ORM\JoinColumn(name="cart_item_id", referencedColumnName="id")
*/
private $cartItem;
/**
* #ORM\ManyToOne(targetEntity="ItemVersion")
* #ORM\JoinColumn(name="item_version_id", referencedColumnName="id")
*/
private $itemVersion;
To get more info check Relationship Mapping Metadata Documentation
I think you have to make your entities slowly, step by step.
Begin with unidirectional and then add bidirectional when it is necessary...
By using association mapping documentation.
This answer could be a comment but can't post a comment because of my reputation...

Symfony + Doctrine - UnitOfWork commit order

I have a form that shows entity:
class Event
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
*
* #ORM\OneToMany(targetEntity="EventAttendee", mappedBy="event", cascade={"all"})
*/
private $attendees;
}
and a collection within it:
class EventAttendee
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
*
* #ORM\ManyToOne(targetEntity="Event", inversedBy="attendees")
* #ORM\JoinColumn(name="event_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $event;
/**
*
* #ORM\OneToOne(targetEntity="Employee")
* #ORM\JoinColumn(name="employee_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $employee;
}
If I delete an employee from the collection and add it again, I'm getting integrity constraint violation. This is because Doctrine's UnitOfWork first executes Inserts and then Deletes. Therefore, when it inserts a new record db still has the old one with the same employee.
Doctrine2 developers did not provide any working solution for Symfony2 users (here is the thread: http://www.doctrine-project.org/jira/browse/DDC-601).
And thus, I'm asking the question here: is it anyhow possible to avoid this issue?
EDIT:
My current workaround is:
find all not-persisted colletion items ready to insert
remove them from the collection and save to a variable
remove all the items that were really deleted in the form
call flush()
add all the items for insert back to the collection
call flush()
This works for me, however doesn't look good. Maybe someone has a better solution.

Symfony2 Doctrine2 column being set to null

I have a system that took form information detailing a project, added it to a project table and is meant to add an entry into an assigned projects table to associate user with project (point of this is allowing multiple users for each project). Anyway I got this working without foreign keys, struggled to add them but eventually got them.
Unfortunately this additional has caused this error 'SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'projectId' cannot be null' whenever something is added to the assignedProjects table.
So my question is, have I missed something in my codes?
The code to add a new row to assignedProjects:
$assignedProject = new AssignedProjects();
$assignedProject->setProjectId($project->getId());
$assignedProject->setUserId($user[0]['id']);
$em = $this->getDoctrine()->getEntityManager();
$em->persist($assignedProject);
$em->flush();
The code for the assignProjects entity:
class AssignedProjects
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer $projectId
*
* #ORM\Column(name="projectId", type="integer")
*/
private $projectId;
/**
* #ORM\ManyToOne(targetEntity="Projects", inversedBy="assignment")
* #ORM\JoinColumn(name="projectId", referencedColumnName="id")
*/
private $project;
/**
* #var integer $UserId
*
* #ORM\Column(name="userId", type="integer")
*/
private $userId;
/**
* #ORM\ManyToOne(targetEntity="Dev\UserBundle\Entity\User", inversedBy="assignment")
* #ORM\JoinColumn(name="userId", referencedColumnName="id")
*/
private $user;
(followed by the usual getters and setters)
and the project tables entity is:
class Projects
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $projectName
*
* #ORM\Column(name="projectName", type="string", length=255)
*/
private $projectName;
/**
* #ORM\OneToMany(targetEntity="AssignedProjects", mappedBy="project")
*/
protected $assignment;
Any help would be much appreciated!
Either you use the ProjectId and UserId columns and manage the relationship manually (not recommended) or you use the doctrine relationships(recommended), but don´t do both things. If you go for the second option, don´t include the projectId and userId columns, they are automatically created for you by doctrine. So, your AssignedProjects class should be:
class AssignedProjects {
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id * #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Projects", inversedBy="assignment")
* #ORM\JoinColumn(name="projectId", referencedColumnName="id")
*/
private $project;
/**
* #ORM\ManyToOne(targetEntity="Dev\UserBundle\Entity\User", inversedBy="assignment")
* #ORM\JoinColumn(name="userId", referencedColumnName="id")
*/
private $user;
and in your controller you would do:
$assignedProject = new AssignedProjects();
$assignedProject->setProject($project);
$assignedProject->setUser($user[0]);
$em = $this->getDoctrine()->getEntityManager();
$em->persist($assignedProject);
$em->flush();
Note that I am setting the Project and User fields, not the ids
By the way, unless you need to save extra data about this project assignement (things like the date or similar), you can declare a direct ManyToMany relationship between User and Project and do away with this class, Doctrine would generate the needed table by itself
With Doctrine2, you don't have to declare the foreign key (projectId) but only the association (project). So you can delete $projectId property, as well as setProjectId ans getProjectId methods. Same fix for $user...
Instead, you will use setProject like that :
$assignedProject = new AssignedProjects();
$assignedProject->setProject($project);
$assignedProject->setUser($user[0]);
$em = $this->getDoctrine()->getEntityManager();
$em->persist($assignedProject);
$em->flush();
Have a look to Doctrine2 documentation, it will help you, for sure !
http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/association-mapping.html

Stop related entities to be queried in doctrine2 while using QueryBuilder

I am having two entities User and Profile with one to one relationship.
$qb = $this->getDoctrine()->getEntityManager()->createQueryBuilder();
$qb->add('select', 'u')
->add('from', '\Acme\TestBundle\Entity\User u')
->add('orderBy', 'u.id DESC');
$query = $qb->getQuery();
$customer = $query->execute();
When i Check the number of queries in Symfony profiler I could see n number for queries triggered on Profile table for n users in User table. Is there any way where I can stop the querying of the Profile table.
Please let me know if there is better way of implementing it.
Thanks in advance
Added Entity Classes
class User
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $email
*
* #ORM\Column(name="email", type="string", length=255)
*/
private $email;
/**
* #var Acme\TestBundle\Entity\Profile
*
* #ORM\OneToOne(targetEntity="Acme\TestBundle\Entity\Profile", mappedBy="user")
*/
private $profile;
}
class Profile
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer $user_id
*
* #ORM\Column(name="user_id", type="integer")
*/
private $user_id;
/**
* #var string $user_name
*
* #ORM\Column(name="user_name", type="string", length=100)
*/
private $user_name;
/**
* #var Acme\TestBundle\Entity\User
*
* #ORM\OneToOne(targetEntity="Acme\TestBundle\Entity\User", inversedBy="profile")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
}
Response from the mysql log
120110 15:14:29 89 Connect root#localhost on test
89 Query SET NAMES UTF8
89 Query SELECT c0_.id AS id0, c0_.email AS email1, c0_.password AS password2, c0_.is_demo_user AS is_demo_user3, c0_.status AS status4, c0_.current_service AS current_service5, c0_.registration_mode AS registration_mode6, c0_.verification_code AS verification_code7, c0_.account_type AS account_type8, c0_.activated_date AS activated_date9, c0_.status_updated_at AS status_updated_at10, c0_.created_at AS created_at11, c0_.updated_at AS updated_at12 FROM user c0_ WHERE c0_.id = 1 ORDER BY c0_.email ASC
89 Query SELECT t0.id AS id1, t0.user_id AS user_id2, t0.user_name AS user_name3, t0.age AS age4, t0.created_at AS created_at5, t0.updated_at AS updated_at6, t0.user_id AS user_id7 FROM profile t0 WHERE t0.user_id = '1'
89 Quit
Your answer is here!!
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html
"In many cases associations between entities can get pretty large. Even in a simple scenario like a blog. where posts can be commented, you always have to assume that a post draws hundrets of comments. In Doctrine 2.0 if you accessed an association it would always get loaded completly into memory. This can lead to pretty serious performance problems, if your associations contain several hundrets or thousands of entities.
With Doctrine 2.1 a feature called Extra Lazy is introduced for associations. Associations are marked as Lazy by default, which means the whole collection object for an association is populated the first time its accessed. If you mark an association as extra lazy the following methods on collections can be called without triggering a full load of the collection:"
<?php
namespace Doctrine\Tests\Models\CMS;
/**
* #Entity
*/
class CmsGroup
{
/**
* #ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY")
*/
public $users;
}
It's a bit late, but it might help others out there!

Resources