My field in the entity client:
/**
* #ORM\ManyToOne(targetEntity="App\Entity\ClientRestrictiveListValidationType")
* #ORM\JoinColumn(name="id_type_validation")
*/
private $validationType;
to save I must do the following:
$validationType = $this->entityManager->getRepository(ValidationType::class)->find($idValidationType);
$client->setValidationType($validationType);
but when looking for the type of validation, I am doing an extra query.
If I already have the $idValidationType I want to save, I don't want to do an extra query.
The entity is old and the properties is used for other services, for that I can't change the properties
Related
If entity A contains multiple entity B and has cascade:persist, how to reuse existing entities B when persisting ?
B entity has one primary key, an integer, and the id of the A parent. The only data it contains is the primary key.
Example:
A has 2 B entities, identified by their id, 14 and 23.
A.Bs = [{id=14, AId=A.id}, {id=23, AId=A.Id}]
Now if I modify this managed entity, to add a B entity to A, with id = 56.
A.Bs = [{id=14, AId=A.id}, {id=23, AId=A.Id}, {id=56}]
Relationships
Entity A
/**
* #var B[]|ArrayCollection
*
* #ORM\OneToMany(targetEntity="B", mappedBy="A", cascade={"persist", "remove"}, orphanRemoval=true)
* #Assert\Valid
*/
private $Bs;
Entity B
/**
* #var A
*
* #ORM\ManyToOne(targetEntity="A", inversedBy="Bs")
* #ORM\JoinColumn(name="A_id", referencedColumnName="A_id")
* #Assert\NotNull()
*/
private $A;
If I try to persist I get Integrity constraint violation, because Doctrine tries to persist the existing entities, that have id 14 and 23.
I understand this is expected behaviour, but how can I make it persist new entities, and reuse existing ones ?
More details:
If I get an existing entity A with $em->find($id) and directly use persist and flush, I will get UniqueConstraintException because it tries to persist the already persisted B entities.
Example code:
/** #var A $existingEntityA */
$existingEntityA = $this->getEntity($id);
$this->serializerFactory->getComplexEntityDeserializer()->deserialize(json_encode($editedEntityADataJson), A::class, 'json', ['object_to_populate' => $existingEntityA]);
$this->entityValidator->validateEntity($existingEntityA);
$this->_em->flush();
Example error : Integrity constraint violation: 1062 Duplicate entry '777111' for key 'PRIMARY'
If I understand your example properly - you're doing something like this:
$b = new B();
$b->setId(56);
$a->getB()->add($b);
and you having a row with primary key 56 into database table that is represented by B?
If my assumption is correct - it is wrong way to go. Reason is that Doctrine internally stores so called "identity map" that keeps track of all entities that either being fetched from database or persisted by calling EntityManager::persist(). Every entity that is scheduled for commit but not available into identity map is considered as "new" and scheduled for insertion. If row with same primary key is already available in database - you're receiving UniqueConstraintException.
Doctrine doesn't handle a case "let me look if there is an entity with such primary key in database" by itself because it will hurt performance significantly and is not needed in most cases. Each such test will result into database query, imagine if you will have thousands of such entities. Since Doctrine doesn't know business logic of your application - it will spend even more resources with attempts to guess optimal strategy so this is intentionally left out of scope.
Correct way for you would be to get your entity by itself before adding to collection:
$newB = $em->find(B::class, 56);
if ($newB) {
$a->getB()->add($newB);
}
In this case new entity will internally have "managed" status and will be correctly handled by Doctrine at a time of commit.
I have two separate entities that I want to link by a one-to-many relationship. But I want this relationship to be ordered, meaning every time I call on the first entity, the elements of the second entity come in a pre-ordered way. I cannot use 'order by' calls because this order has nothing to do with the fields of the second entity. I thought about having one field of the first entity be an array of entities, but I'm not sure how to accomplish that either..
EDIT
So far I have something like this:
/**
* #ORM\OneToMany(targetEntity="FusionDesign\BlogBundle\Entity\Element", mappedBy="page")
*/
private $elements;
and
/**
* #ORM\ManyToOne(targetEntity="FusionDesign\BlogBundle\Entity\Page", inversedBy="elements")
* #ORM\JoinColumn(name="page_id", referencedColumnName="id")
*/
private $page;
I'm aware that I can put "ORDER BY whatever ASC" somewhere in there but that orders according to a column in Element, and that's not what I need, because Element entities and Page entities would never be persisted at the same time, nor by the same process. What I want to do is constructing a basic CMS where the user could generate new pages. First choose the kind of elements a page could potentially have (like header image, banner, title, and so on) and persist Element entities with fields describing the html, routing and controller content according to those choices. Then, when a new page is created, give the user the choice to order those potential elements at will, and bind Element entities following an order that reflects the layout desired.
I thought about having something like this
/**
* #var array
*
* #ORM\Column(name="structure", type="array")
*/
private $structure;
Where the array stores Element entities but I have no idea how to do that.
You just need to define the orderBy attribute in doctrine's mapping configuration for the relation.
YAML Mapping Example:
'Example\Entity\Article':
# [..]
oneToMany:
images:
targetEntity: 'Example\Entity\Article\Image\ArticleImage'
mappedBy: 'article'
orderBy: # <--- here
position: 'ASC'
My issue is, I'm having trouble grasping DiscriminatorColumn and DiscriminatorMap in Doctrine's Class Inheritance.
I have a products entity that is considered the parent class / table.
There are several child entities that inherit the product entity. (models, parts, and options)
I feel like I should be able to use the primary key to link both tables... But how do I do that with DiscriminatorColumn?
Here is the general idea of what I want to happen...
Fetch all model objects from database while inheriting product parent entity
SELECT object
FROM parts_object parts
LEFT JOIN products_object po
ON parts.product_fk = po.product_id
Or... Fetch all part objects from database while inheriting product parent entity
SELECT object
FROM parts_object parts
LEFT JOIN products_object po
ON parts.product_fk = po.product_id
Ideally I want this done using Doctrine instead of some custom SQL.
Do I need to setup a "type" column for the parent table so each row defines whether it's a part, model, or option?
Doctrine inheritance docs
Okay, I'll try to explain this as simple as possible.
Let's start with DiscriminatorColumn
Discriminator column is basically, as it says, a column in your database. Its used to store, a key, if you like which helps to identify what kind of object you're currently querying, based on your DiscriminatorMap configuration.
DiscriminatorMap is the way you map each of those keys to an entity. You said you have the following
Product [parent]
Model [child of parent]
Part [child of parent]
Option [child of parent]
Then, your discriminator map should look something like this, for example:
#DiscriminatorMap({
"model" = "AppBundle\Entity\Model",
"Part" = "AppBundle\Entity\Part",
"Option" = "AppBundle\Entity\Option"
})
Always pay attention to your last definition in your mapping. The last line must end without a comma!
As of InheritanceType I would suggest you to use #InheritanceType("JOINED") because this will let you have single table for each of your child classes.
Every child class must extend your Product entity class, which is obviously the parent. Each child class must not define $id property, because of the inheritance mapping.
Then querying for records by specific type comes with the following query:
"SELECT product FROM AppBundle\Entity\Product product WHERE product INSTANCE OF AppBundle\Entity\Part"
The query will search only for records mapped to this entity only.
If you have any questions, don't hesitate to ask.
Edit as of new comment
-----------------------
A little bit more explanation. You do not need to create any extra property/column in your entity mappings. The moment you add this annotation #DiscriminatorColumn(name="discr", type="string") doctrine will create that column automatically for you. The column from this example would be named discr with type of VARCHAR.
I still don't understand what is used to join the tables. How does doctrine know to link the ids between the product and model
About this part. If you use #InheritanceType("JOINED") this would mean that your GeneratedValue ID would be set in your main entity - Product. Then each of the child entities that extend Product would automatically get the same ID, which is why you don't need to specify $id property in your child entities.
Lastly, how can you check which entity type you're currently viewing for example. Consider the following scenario, each of your child entities extends Product and we will perform a dummy search for a record:
$product = $entityManager->find('AppBundle:Product', 1); // example
Now, if you actually go and do a var_dump($product) you will notice something interesting. The object would be an instance of either Model,Part or Option because each of these entities are defined in your discriminator map and Doctrine automatically maps your records based on that.
Later, this can come handy in situations like this:
if( $product instanceof \AppBundle\Entity\Part ) {
// do something only if that record belongs to part.
}
If you want to use DiscriminatorMap for Doctrine, so you should use Doctrine, but not SQL.
Basic setup is:
/**
* #ORM\Table(name="product")
* #ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\ProductRepository")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="productType", type="string")
* #ORM\DiscriminatorMap({
* "Product" = "Product",
* "Model" = "Model",
* "Part" = "Part",
* "Option" = "Option",
* })
*/
class Product
{
...
}
MyApp\ProductBundle\Entity\Model
/**
* #ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\ModelRepository")
*/
class Model extends Product
{
}
MyApp\ProductBundle\Entity\Part
/**
* #ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\PartRepository")
*/
class Part extends Product
{
}
MyApp\ProductBundle\Entity\Option
/**
* #ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\OptionRepository")
*/
class Option extends Product
{
}
Then if you need to get all products at controller
$em = $this->getDoctrine()->getManager();
$repo = $em->getRepository("MyAppProductBundle:Product");
$products = $repo->findAll();
Then if you need select all models, just setup proper repository
$repo = $em->getRepository("MyAppProductBundle:Model");
$models = $repo->findAll();
I have a User entity in my Symfony2/Doctrine2 webapp. This user has an attribute last_updated to identify the latest time, anything has changed. I set this attribute to NOT NULL in my database. So far, so good.
I would consider it to be good practice to create a SQL trigger in the database, that sets this last_updated to NOW() on every INSERT or UPDATE. So you don't have to care about this in your application. So that's what I did, I implemented this trigger in my database.
But if I now create a user in my app
$user = new User();
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
I get an error message by Symfony:
An exception occurred while executing 'INSERT INTO User (username, ..., last_updated) VALUES (?, ..., ?)'
with params ["johndoe", ..., null]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'last_updated' cannot be null
The problem is clear: Symfony is trying to fire an INSERT-statement to the database with the parameter null for last_updated, which is not allowed - as this attribute may not be null.
I could quickly think of two workarounds:
One workaround would be to take the last_updated field out of the entity description. Then Symfony would not try to pass anything to the database for this column, and the trigger would set the appropriate value. But I don't think this is a good way, because as soon as I would try to update the db schema (doctrine:schema:update --force) I would loose my last_updated-column.
Another workaround: Simply do $user->setLastUpdated(new \DateTime()) before I persist() and flush(). But this would minimize the advantage of using a trigger on my database to avoid having to care about it in my application.
Is there any way to let Symfony/Doctrine know that there is a trigger running on my database? If not, (how) can I hook into Symfony/Doctrine to implement a proper workaround?
To quote a response to this question from a google group:
Database side code (such as Triggers and Functions) tend to break the benefits of developing software using an ORM like Propel or Doctrine as one of the biggest advantages of using ORM's is to be database agnostic. By having database side Triggers and Functions you are tying yourself to the database and therefore gain little to no benefit using an ORM. -GarethMc
https://groups.google.com/forum/#!topic/symfony-users/MH_ML9Dy0Rw
For this it is best to use the Life Cycle Callbacks as Faery suggests. One simple function will handle updating that field so that you dont have to worry about it if you decide to change databases in the future.
//In Your Entity File EX: SomeClass.php
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks()
*/
class SomeClass
{
....
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function prePersistPreUpdate()
{
$this->last_modified = new \DateTime();
}
}
See also references for lifecycle callbacks
Symfony reference
Doctrine reference
In your case you would add the lifecycle call back function and annotation to your User entity class. SomeClass is simply an example class showing that lifecycle callbacks are good for more than just your User entity.
Another (easier and more generalized) option would be to use the Timestampable Doctrine extension by Gedmo. In this way, you could simply annotate your entity fields to be timestamped on create or on update.
Example:
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
class MyEntity
{
...
/**
* #var \DateTime $lastUpdated
*
* #Gedmo\Timestampable(on="update")
* #ORM\Column(name="last_updated", type="datetime")
*/
private $lastUpdated;
...
}
https://packagist.org/packages/gedmo/doctrine-extensions
To simplify, two entities are defined: User and Comment. User can post many comments and every comment has only one user assigned, thus Comment entity has:
/**
* #var \Frontuser
*
* #ORM\ManyToOne(targetEntity="Frontuser")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="ownerUserID", referencedColumnName="id")
* })
*/
private $owneruserid;
However, when in action:
$orm = $this->getDoctrine()->getManager();
$repo = $orm->getRepository('CompDBBundle:Comment');
$repo->findBy(array('owneruserid' => $uid);
Error occured, that there's no such field like owneruserid.
How can I fetch all the user's comments then? The same happens to similar relations in my DB - looks likes you cannot run find() with foreign keys as parameters. I believe a function $user->getComments() should be automatically generated/recognised by Doctrine to allow efficient, quick access to related entities.
The example's simple but, what if there are more entities related to my User in the same way? Do I have to declare repositories for each and try to fetch them by it's owneruserid foreign keys?
Using doctrine, when you define a related entity it's type is the entity class (in this case FrontUser). Therefore firstly your related entity variable name is misleading. It should be e.g.
private $ownerUser;
Then, in order to do a findBy on a related entity field you must supply an entity instance e.g.
$orm = $this->getDoctrine()->getManager();
$userRepo = $orm->getRepository('CompDBBundle:FrontUser');
$user = $userRepo->findById($uid);
$commentRepo = $orm->getRepository('CompDBBundle:Comment');
$userComments = $commentRepo->findByOwnerUser($user);
If you don't have or want to retrieve the user entity you could use a DQL query with the 'uid' as a parameter instead.