Doctrine ORM order by annotations object fields - php

So its simple like this : I have the following code in some Entity
/**
* #var ServiceOffer
*
* #ORM\OneToMany(targetEntity="ServiceOffer", mappedBy="serviceProvider")
* #ORM\OrderBy({"service" = "desc"})
*/
private $offers;
What i need is to do something like this
/**
* #var ServiceOffer
*
* #ORM\OneToMany(targetEntity="ServiceOffer", mappedBy="serviceProvider")
* #ORM\OrderBy({"service.points" = "desc"})
*/
private $offers;
which is not working i don't want to do it through some function i wanted straight forward with annotations is there any way to do this ?
this is the points btw:
/**
* #var integer
*
* #ORM\Column(name="points", type="integer", nullable=true)
*/
private $points;

No. This is not possible. You should solve this creating a dql.
The DQL Snippet in OrderBy is only allowed to consist of unqualified, unquoted field names and of an optional ASC/DESC positional statement. Multiple Fields are separated by a comma (,). The referenced field names have to exist on the targetEntity class of the #ManyToMany or #OneToMany annotation.
More in doctrine2 documentation.
DQL Example
SELECT provider, offer, service
FROM \MyNamespace\Entity\ServiceProvider provider
INNER JOIN provider.offers offer
INNER JOIN offer.service service
WHERE
provider.id = 1
Notice, this code snippet provider, offer, service it's important to return here all entities or fields you will need so doctrine will load it for once, if it's possible. In other words, if you did'nt include any entity and call for it, doctrine will load it lazily.

Related

Doctrine: how to describe manyToOne relation with composite key and update cascade?

I have a table that has composite primary key: id + est_date. And it has an entity:
class Parent
{
/**
* #ORM\Id
*/
private $id;
/**
* #ORM\Id
*/
private int $estDate;
...
}
Now I need to create a related table and its entity.
class Child
{
...
/**
* don't know what to write here
*/
private $parentId;
/**
* don't know what to write here
*/
private int $parentEstDate;
...
}
How to discribe relation ManyToOne (many "Child" entities may relate to 1 "Parent")? And the second issue is - "estDate" of the "Parent" may change. How to specify cascade update in "Child"?
Please don't write that doctrine doesn't recomment to use composite keys. I know that.
on the child-entity you would refer to the parent entity the same way as with single columns, essentially. Starting with
annotation version:
/**
* #ORM\ManyToOne(targetEntity=Parent::class)
*/
private ?Parent $parent;
since the child is the owning side, you have to provide join columns, as you have noticed. There is a badly documented annotation JoinColumns that allows to define multiple join columns. (Note for those using the attribute syntax instead: you should be able to have multiple #[JoinColumn(...)], without the JoinColumns-Wrapper)
annotation version:
/**
* #ORM\ManyToOne(targetEntity=Parent::class)
* #ORM\JoinColumns({
* #ORM\JoinColumn("parent_id", referencedColumnName="id"),
* #ORM\JoinColumn("parent_est_date", referencedColumnName="est_date")
* })
*/
private ?Parent $parent;
If you want to add the inverse side as well, you always reference the object property, not the columns when using mappedBy/inversedBy.
Generally with doctrine-orm: Your class/object should not care about columns, only about php stuff, doctrine should handle the rest. The annotations tell doctrine, how this converts to columns. So not every column will get its own property in this case.

ResultSetMappingBuilder and name conflict in mapper

I've got entity A with two ManyToOne relations. And both join columns with same name. When I am creating query based on entity A with ResultSetMappingBuilder and setting this entity to method addRootEntityFromClassMetada it results in InvalidArgumentException The column 'created_by' conflicts with another column in the mapper..
Here is quick sample for entity A
/**
* #var B
* #ManyToOne(targetEntity="B")
* #JoinColumn(name="created_by", referencedColumnName="id")
*/
private $a;
/**
* #var C
* #ManyToOne(targetEntity="C")
* #JoinColumn(name="created_by", referencedColumnName="id")
*/
private $b;
How can I avoid to conflict? I tried almost everything that I found on stack. But there are not many problems with this.
Doctrine 2.6.1
Thanks for you answers,
Michal

Doctrine using join table to return results not working Symfony2

I'm having difficulties using join table values to fetch results via Doctrine. I'm currently programming a CRM for a client, which implements projects with tasks and users.
My structure is as follows:
user table - contains all users.
tasks table - contains a list of tasks in a project
user_task table - the join table that dictates which users can perform which tasks.
I have created a join table in my entity like so:
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Task", inversedBy="user")
* #ORM\JoinTable(name="user_task",
* joinColumns={
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="task_id", referencedColumnName="id")
* }
* )
*/
protected $task;
And in my task entity:
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\User", mappedBy="task")
* #ORM\JoinTable(name="user_task")
*/
private $user;
My problem is, that on the front end of the CRM, the admin will change a task from the drop down which then should poll the database to grab the list of users that are able to perform that task. But because the user_stage table is not an entity in its own right, I do not know how to incorporate it in my doctrine queryBuilder.
I tried using a native query, where $taskId is passed in from the Controller:
$qb = $this->createNativeQuery('SELECT user.name FROM user JOIN user_task ON user_task.user_id = user.id WHERE user_task.task_id = '.$taskId);
$result = $qb->getResult();
But I just get the following error:
Undefined method 'createNativeQuery'. The method name must start with
either findBy or findOneBy!
I rarely use Native Queries so I am not sure what I'm doing wrong here. Is there any way of being able to perform the query I need without using NativeQuery, and just somehow accessing the join table by referencing it from both the User and Task entities?
Thank you
Michael
Don't use createNativeQuery where under $this you have Repository object.
Create method in repository class and call it there.

How do I search by properties of entity which do not have #Column anotation in Doctrine2?

/**
* #ORM\Entity
*/
class Order extends BaseEntity
{
// this is trait for #Id
use Identifier;
/**
* #ORM\Column(type="integer")
*/
protected $costPerUnit;
/**
* #ORM\Column(type="integer")
*/
protected $numberOfUnits;
// i want to search by this property
protected $totalCost;
public function getTotalCost()
{
return $this->numberOfUnits * $this->costPerUnit;
}
}
I have an entity like this and I'd like to be able to do for example
$orderRepository->findOneByTotalCost('999')
$orderRepository->findBy(['totalCost' => '400']);
Is this possible in Doctrine2? Or would I go about it differently?
Like I said in my comments, it's likely you're wrestling with an issue that shouldn't have occurred in the first place. Still, having a SUM value mapped to a property is possible using doctrine, in a variety of ways: Check the aggregate field docs to find out which would solve your problem best.
To my eyes, is that you're using entities as more than what they really are: Entities represent records in a database, Doctrine is a DBAL. Searching data using entities (or repositories) is querying the database. You could solve the problem by adding custom methods to your entity manager or a custom repository class that'll query all of the data required to compute the totalCost value for all entities, and return only those you need. Alternatively, use the connection from your DBAL to query for the id's you're after (for example), then use those values to get to the actual entities. Or, like I said before: use aggregate fields.
The problems you have with the findOneByTotalCost and findBy examples you show is that the first requires you to write a method Called findOneByTotalCost yourself. The problem with your use of findBy is simply that your argument is malformed: the array should be associative: use the mapped column names as keys, and the values are what you want to query for:
$repo->findBy(
['totalCost' => 400]
);
is what you're looking for, not ['totalCost', 400]. As for the entity itself, you'll need to add an annotation:
Yes it is, judging by your use of #ORM\Entity annotations in the doc-blocks, this ought to do it:
/**
* #ORM\Column(type="string", length=255)
*/
protected $regioun = 'Spain';
The update the table, and you'll be able to:
$entities = $repo->findBy(
['region' => 'Spain']
);
Don't forget that this code represents a table in a DB: you can search on any of the fields, but use indexes, which you can do by adding annotations at the top of your class definition:
/**
* #ORM\Table(name="tblname", indexes={
* #ORM\Index(name="region", columns={"region"})
* })
*/
class Foo
{}
As ever: in DB's, indexes matter
You should write a method findOneByTotalCost on your entity repository, something like:
public function findOneByTotalCost ($queryParams){
$query = 'select o
from <yourEntity> o
where o.numberOfUnits * o.costPerUnit = :myParam';
$dql = $this->getEntityManager()->createQuery($query);
$dql->setParameter('myParam', $queryParams);
return $dql ->execute();
}
Them, $orderRepository->findOneByTotalCost('999') should work.

Symfony Association Mapping OneToOne and OneToMany to Same Entity

I have a View entity that represents the primary page record, and then I have an associated entity called ViewVersion which stores multiple versions of the entity as it's changed over time. The View entity sets the current "Published" ViewVersion in the VersionId field. This makes for a simple OneToOne association. But in some contexts I will also want to get all the versions associated with this View entity, e.g. if I want to allow the user to review older versions and revert back. So I will need another mapping which is a OneToMany. The first viewVersion will map to the active "published" version, and the second viewVersions will show all the versions.
Entity Definitions
/**
* #ORM\Entity
* #ORM\Table(name="view")
* #ORM\Entity(repositoryClass="Gutensite\CmsBundle\Entity\View\ViewRepository")
*/
class View extends Entity\Base {
/**
* #ORM\OneToOne(targetEntity="\Gutensite\CmsBundle\Entity\View\ViewVersion", inversedBy="view", cascade={"persist", "remove"}, orphanRemoval=true)
* #ORM\JoinColumn(name="versionId", referencedColumnName="id")
*/
protected $viewVersion;
/**
* #ORM\Column(type="integer", nullable=true)
*/
protected $versionId = NULL;
/**
* #ORM\OneToMany(targetEntity="\Gutensite\CmsBundle\Entity\View\ViewVersion", mappedBy="viewAll", cascade={"persist", "remove"}, orphanRemoval=true)
*/
protected $viewVersions;
}
/**
* #ORM\Entity
* #ORM\Table(name="view_version")
* #ORM\Entity(repositoryClass="Gutensite\CmsBundle\Entity\View\ViewVersionRepository")
*/
class ViewVersion extends Entity\Base {
/**
* #ORM\OneToOne(targetEntity="\Gutensite\CmsBundle\Entity\View\View", mappedBy="viewVersion", cascade={"persist"})
*/
protected $view;
/**
* #ORM\ManyToOne(targetEntity="\Gutensite\CmsBundle\Entity\View\View", inversedBy="viewVersions")
* #ORM\JoinColumn(name="viewId", referencedColumnName="id")
*/
protected $viewAll;
/**
* The primary view entity that this version belongs to.
* #ORM\Column(type="integer", nullable=true)
*/
protected $viewId;
}
This "works" but is it recommended to have two associations with the same entity like this? Or is this a really bad idea?
The ViewVersion entity will reference a single View entity in both cases, but the mapped associations need two separate variables, e.g. View and ViewAll. I'm not exactly sure how the internals work for the association, and how the reference variable with the mapping is used.
Alternatively, I could get rid of the OneToOne association, and just set a ViewRepository function to get the current published version based on the versionId (just like the old mapped entity used to do with the getVersion()). That would work, but is it more internal overhead, because it would make two queries... or will Doctrine be smart enough to optimize this, just like it did with the getVersion().
NOTE:
These other answers are not complete.
References:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/working-with-associations.html
http://doctrine-orm.readthedocs.org/en/2.0.x/reference/association-mapping.html#one-to-many-bidirectional
Typically, I have found the best approach is to solve this in a different way.
One common pattern I have seen before is you use a single table to hold all records, and have an 'active' flag.
If your query to select the active one works like so:
SELECT * FROM table WHERE active = true ORDER BY updated_at DESC LIMIT 1;
Then enabling a new one becomes as simple as:
UPDATE table SET active = 1, updated_at = '<timestamp>' WHERE id = <new id>;
UPDATE table SET active = 0, updated_at = '<timestamp>' WHERE id = <old id>;
Your new page will be active as soon as the first query hits, and your second query will avoid any sort of weirdness as that row will already be no longer active.
If you have other models that depend on a consistent ID to reference, then another route which also maintains some sanity would be to have one table for the active entries (in whole, not in part) and then a second table with additional metadata to track versions.
The latter approach could be nicely handled via Doctrine's inheritance system (http://docs.doctrine-project.org/en/2.0.x/reference/inheritance-mapping.html) which would let you define the base View class, and then for the "ViewRevision" model, extend View and add a "Revised on" type timestamp.
Per the advice from #jmather I've decided this model is "okay", because I need a single View entity that other entities can access (e.g. Routing urls that point to a single View, i.e. "page").
I've changed the OneToOne relationship for View to be unidirectional only, because the ViewVersion already has an association back to the View via the other OneToMany (so it doesn't need two paths back).
This allows me to keep a simple method for $view->getPublished() handy and seems more logical.
/**
* #ORM\Entity
* #ORM\Table(name="view")
*/
class View extends Entity\Base {
/**
* This is a OneToOne Unidirectional association, just so that we can get the
* current published version easily, based on the publishedId.
* #ORM\OneToOne(targetEntity="\Gutensite\CmsBundle\Entity\View\TestVersion")
* #ORM\JoinColumn(name="publishedId", referencedColumnName="id")
*/
protected $published;
/**
* #ORM\Column(type="integer", nullable=true)
*/
protected $publishedId = NULL;
/**
* This is the regular OneToMany Bi-Directional Association, for all the versions.
* #ORM\OneToMany(targetEntity="\Gutensite\CmsBundle\Entity\View\ViewVersion", mappedBy="view", cascade={"persist", "remove"}, orphanRemoval=true)
*/
protected $versions;
}
/**
* #ORM\Entity
* #ORM\Table(name="view_version")
*/
class ViewVersion extends Entity\Base {
/**
* #ORM\ManyToOne(targetEntity="\Gutensite\CmsBundle\Entity\View\View", inversedBy="versions")
* #ORM\JoinColumn(name="viewId", referencedColumnName="id")
*/
protected $view;
/**
* The primary view entity that this version belongs to.
* #ORM\Column(type="integer", nullable=true)
*/
protected $viewId;
}
However, I've discovered that as long as the $view->publishedId is set the view can't be deleted from the database because of foreign key constraints (even though it's uni-directional). So I have to break that foreign key link before removing. I think that's fine. I posted details about that here: Overlapping Entity Association causing Database Foreign Key Constraint Errors when Removing Entity

Categories