Symfony2: How to avoid duplicate entities that already exist in the database? - php

I have the entities "Student" and "Parents" in two forms, one embedded in another are Symfony2. I need to store data from both entities in different tables of the database, when a new student and his parents also adds added. But I need that parents are not duplicated in the database, so before adding have to check that there are none, add only in that case. I do not know how to do this in Symfony2. More information is another question I asked.
look the other question here
I hope someone can help me because I can not find a solution for this problem.

In order to have columns with unique entries you must use UniqueEntity Validation constraint Docs
Validates that a particular field (or fields) in a Doctrine entity is (are) unique. This is commonly used, for example, to prevent a new user to register using an email address that already exists in the system.
add #UniqueEntity("parent") above your entity name and unique=true on the field that you want to be unique
form the docs
// src/AppBundle/Entity/Author.php
namespace AppBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
// DON'T forget this use statement!!!
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity
* #UniqueEntity("email")
*/
class Author
{
/**
* #var string $email
*
* #ORM\Column(name="email", type="string", length=255, unique=true)
* #Assert\Email()
*/
protected $email;
// ...
}

Related

Doctrine ODM Unique constraint not validating property

I can't figure out why Doctrine's ODM Unique constraint isn't working for me.
Below is a Page Class with the property "title", that needs to be unique amongst all Pages.
namespace App\Document;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Bundle\MongoDBBundle\Validator\Constraints\Unique as MongoDBUnique;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use App\Repository\PageRepository;
use App\Document\Embeded\Section;
/**
* #ODM\Document(repositoryClass=PageRepository::class)
* #MongoDBUnique(fields="title")
*/
class Page
{
/** #ODM\Id() */
private $id;
/** #ODM\ReferenceOne(targetDocument=Site::class) */
private $site;
/** #ODM\Field(type="string")
* #ODM\UniqueIndex(order="asc")
*/
private $title;
// ...
Within the controller, $form->handleRequest($request) is called, followed by the query: if ($form->isSubmitted() && $form->isValid())
The form is always returned as valid. The ODM Unique constraint seems to be ignored. I've also tried to add a custom Validation Constraint and was met with the same issue.
Do I need to add any additional configuration to get this to work?
Symfony forms only validate the top-level object by design. In this case, the Page Class was attached to an embedded form.
Solution: add Symfony's #Valid() constraint to the property, within the top-level object.
/** #ODM\ReferenceOne(targetDocument="App\Document\Page", cascade={"persist"}, orphanRemoval=true)
* #ODM\Index
* #Assert\Valid()
*/
private $page;

Is there a way to handle duplicate enteries?

I am using the api-platform framework with a MySQL backend. I am getting errors when API clients use the POST end point to submit data if there is already an entry in the database.
Currently I am using a PRE_WRITE EventSubscriberInterface class to find the original database entry and delete it. However this seems incredable inefficient compared to a simple update action.
I am able to update the existing database entry, but then I'm unable to remove/stop the POST'd an item from being executed.
Is there a way around this? Ether to change the INSERT action to an ...ON DUPLICATE... or to simple stop the data the user post'd from being saved to the database?
You can also use the property "UniqueEntity" to set what's make your entity unique.
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity(repositoryClass="App\Repository\ArticleRepository")
* #ORM\HasLifecycleCallbacks()
* #UniqueEntity("slug")
*/
class Article
{
//...
/**
* #ORM\Column(type="string", length=255)
*/
private $slug;
}

Symfony doctrine lazy load properties

I have an entity that stores large files as blobs to the DB.
I would now like to get Symfony to never ever load these blobs unless I specifically request them via the appropriate getter.
In essence I want the same idea as lazy-loading relationships but for a string property.
What I have tried so far is to put all my other properties that hold the file meta data into a trait and then apply that trait to two entities.
namespace App\Entity\Traits;
use Doctrine\ORM\Mapping as ORM;
trait DocumentMetaData
{
/**
* #var int|null
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var \DateTime|null
*
* #ORM\Column(type="datetime")
*/
private $date_uploaded;
}
One entity has nothing to it but the trait...
namespace App\Entity;
use App\Entity\Traits\DocumentMetaData;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="documents")
* #ORM\Entity()
*/
class Document
{
use DocumentMetaData;
}
...the other has the added blob property:
namespace App\Entity;
use App\Entity\Traits\DocumentMetaData;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="documents")
* #ORM\Entity()
*/
class DocumentFile
{
use DocumentMetaData;
/**
* #var string|null
*
* #ORM\Column(type="blob")
*/
private $blob;
}
Now, if I don't want the blob to be loaded, for example for a listing of files, I simply use the entity that doesn't have the blob.
This approach sort of works but causes issues as I need to point both entities at the same table (see the class level ORM annotations).
Specifically, it makes doctrine freak out when running migrations:
The table with name 'myapp.documents' already exists.
That makes perfect sense and really I'm hoping that someone can point me to a nicer solution.
How can I tell doctrine not to load the blob unless its explicitly asked for?
So as per the comments on my question - the way to do this so that migrations do not break is to leverage doctrine's ability to lazy load relationships between tables.
Basically I had to go and create a new entity that only holds my giant blobs and then establish a one to one relationship between the original entity and the blob entity.
I then set that relationship to load EXTRA_LAZY and as a result I can now control when precisely the blobs of giant data should be loaded.
I don't think this is ideal in terms of normalising the DB design but it works a lot better than anything else so happy with that.

Symfony2: Custom identifier in Sonata entities

I have an entity with a custom id (i.e. UUID) generated on __construct function.
namespace AppBundle\Entity;
use Rhumsaa\Uuid\Uuid;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Person
{
/**
* #ORM\Id
* #ORM\Column(type="string")
*/
private $id;
/**
* #ORM\Column(type="string")
*/
private $name;
public function __construct()
{
$this->id = Uuid::uuid4()->toString();
}
This entity is used in sonata and also in other part of the project. I need this entity to have id before persisting and flushing it, so I can not use a an auto-increment.
So, the problem is sonata don't let me create entities because it takes the create option as and edit on executing because that entity already has an id, but this entity does not exists at this moment, so it fails.
The problem isn't the library for generating UUID, any value for 'id' fails.
Anyone know how to solve it? Another similar approach to solve the problem?
You shouldn't set your id in the constructor, but rather use the prePersist Doctrine event to alter your entity before persisting it for the first time.
You may use annotations to do so, see the Doctrine Documentation on prePersist.
The issue with setting the id in the constructor is that you may override it when you're retrieving it from the database, in which case it will be incorrect.

Doctrine 2 and Many-to-many link table with an extra field

(Sorry for my incoherent question: I tried to answer some questions as I was writing this post, but here it is:)
I'm trying to create a database model with a many-to-many relationship inside a link table, but which also has a value per link, in this case a stock-keeping table. (this is a basic example for more problems I'm having, but I thought I'd just test it with this before I would continue).
I've used exportmwb to generate the two Entities Store and Product for this simple example, both are displayed below.
However, the problem now is that I can't figure out how to access the stock.amount value (signed int, as it can be negative) using Doctrine. Also, when I try to create the tables using doctrine's orm:schema-tool:create function
This yielded only two Entities and three tables, one as a link table without values and two data tables, as many-to-many relationships aren't entities themselves so I can only have Product and Store as an entity.
So, logically, I tried changing my database model to have stock as a separate table with relationships to store and product. I also rewrote the fieldnames just to be able to exclude that as a source of the problem:
Then what I found was that I still didn't get a Stock entity... and the database itself didn't have an 'amount'-field.
I really needed to be able to bind these stores and products together in a stock table (among other things)... so just adding the stock on the product itself isn't an option.
root#hdev:/var/www/test/library# php doctrine.php orm:info
Found 2 mapped entities:
[OK] Entity\Product
[OK] Entity\Store
And when I create the database, it still doesn't give me the right fields in the stock table:
So, looking up some things here, I found out that many-to-many connections aren't entities and thus cannot have values. So I tried changing it to a separate table with relationships to the others, but it still didn't work.
What am I doing wrong here?
A Many-To-Many association with additional values is not a Many-To-Many, but is indeed a new entity, since it now has an identifier (the two relations to the connected entities) and values.
That's also the reason why Many-To-Many associations are so rare: you tend to store additional properties in them, such as sorting, amount, etc.
What you probably need is something like following (I made both relations bidirectional, consider making at least one of them uni-directional):
Product:
namespace Entity;
use Doctrine\ORM\Mapping as ORM;
/** #ORM\Table(name="product") #ORM\Entity() */
class Product
{
/** #ORM\Id() #ORM\Column(type="integer") */
protected $id;
/** ORM\Column(name="product_name", type="string", length=50, nullable=false) */
protected $name;
/** #ORM\OneToMany(targetEntity="Entity\Stock", mappedBy="product") */
protected $stockProducts;
}
Store:
namespace Entity;
use Doctrine\ORM\Mapping as ORM;
/** #ORM\Table(name="store") #ORM\Entity() */
class Store
{
/** #ORM\Id() #ORM\Column(type="integer") */
protected $id;
/** ORM\Column(name="store_name", type="string", length=50, nullable=false) */
protected $name;
/** #ORM\OneToMany(targetEntity="Entity\Stock", mappedBy="store") */
protected $stockProducts;
}
Stock:
namespace Entity;
use Doctrine\ORM\Mapping as ORM;
/** #ORM\Table(name="stock") #ORM\Entity() */
class Stock
{
/** ORM\Column(type="integer") */
protected $amount;
/**
* #ORM\Id()
* #ORM\ManyToOne(targetEntity="Entity\Store", inversedBy="stockProducts")
* #ORM\JoinColumn(name="store_id", referencedColumnName="id", nullable=false)
*/
protected $store;
/**
* #ORM\Id()
* #ORM\ManyToOne(targetEntity="Entity\Product", inversedBy="stockProducts")
* #ORM\JoinColumn(name="product_id", referencedColumnName="id", nullable=false)
*/
protected $product;
}
Doctrine handles many-to-many relationships just fine.
The problem that you're having is that you don't need a simple ManyToMany association, because associations can't have "extra" data.
Your middle (stock) table, since it contains more than product_id and store_id, needs its own entity to model that extra data.
So you really want three classes of entity:
Product
StockLevel
Store
and two associations:
Product oneToMany StockLevel
Store oneToMany StockLevel

Categories