How to update row with unique column in Doctrine - symfony

I have a row with sku column that must be unique. But when I try to update this row it doesn't happen because of this. How to avoid this with Doctrine ? Or I should write custom logic ?
if (!$this->uniqueSkuSpecification->isSatisfiedBy($request->getSku())) {
return ErrorResponse::fromCustomError(
'Product sku must be unique',
'sku',
Response::HTTP_UNPROCESSABLE_ENTITY
);
}
The way I am populating the data from $request (its DTO)
public function updateFields(UpdateProductCommand $request): Product
{
$reflection = new \ReflectionClass($this);
foreach ($reflection->getProperties() as $property) {
$field = ucfirst($property->getName());
$getter = "get{$field}";
$setter = "set{$field}";
if (method_exists($this, $setter) && !empty($request->$getter())) {
$this->$setter($request->$getter());
}
}
return $this;
}
EDIT:
I am saving/updating this way:
$this->entityManager->persist($product);
$this->entityManager->flush();

Related

ManyToOne - why it create duplicate foreign object / Traget Entity

This is my database, as you can see, I have two Many To One relation and the owner of relationship is the table ESA.
For this web app, I use Symfony 4 and doctrine as ORM, and MySQL 5.7.24, PHP 7.2 .
(https://imgur.com/oCzzs2a)
The process is :
I upload as csv
Create a row in database table filesupload
with mention of this csv
Read the csv
Import each row of csv into the ESA table and set id_filesupload field with the filesupload object
I try to use the :
$entityManager->merge();
Working greate but only when id_filesupload has been already set in the table ESA.
It's doesn't create duplicate filesupload row with same value.
It duplicate my filesupload everytime I flush.
I have try to no pass the object filesupload to the import function but only the id, and get the object by the id.. the result is the same.
In a other hand, the exactly the same process for Department and it doesn't create duplicate entries into Department table.
Part of my Entity ESA
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Esa
*
* #ORM\Table(name="esa")
* #ORM\Entity(repositoryClass="App\Repository\EsaRepository")
*/
class Esa
{
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Department", inversedBy="Esa")
* #ORM\JoinColumn(name="department_id", referencedColumnName="id", nullable=true)
*/
private $department;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Filesupload", inversedBy="Esa")
* #ORM\JoinColumn(name="filesupload_id", referencedColumnName="id", nullable=true)
*/
private $filesupload;
public function getDepartment(): ?Department
{
return $this->department;
}
public function setDepartment(?Department $department): self
{
$this->department = $department;
return $this;
}
public function getFilesupload(): ?Filesupload
{
return $this->filesupload;
}
public function setFilesupload(?Filesupload $filesupload): self
{
$this->filesupload = $filesupload;
return $this;
}
}
Part of my Controller ESA Upload the CSV (Process step 1 + 2)
/**
* #Route("/Aqueduct/UploadData", name="Aqueduct_Upload")
*/
public function UploadData(Request $request)
{
$entityManager = $this->getDoctrine()->getManager();
$form = $this->createForm(FilesuploadType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$FilesuploadFile = $form['UploaderESA']->getData();
// this condition is needed because the 'ESA csv' field is not required
// so the CSV file must be processed only when a file is uploaded
if ($FilesuploadFile) {
$originalFilename = pathinfo($FilesuploadFile->getClientOriginalName(), PATHINFO_FILENAME);
// this is needed to safely include the file name as part of the URL
$safeFilename = transliterator_transliterate('Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $originalFilename);
//$newFilename = $safeFilename.'-'.uniqid().'.'.$brochureFile->guessExtension();
//force csv
$newFilename = $safeFilename.'-'.uniqid().'.csv';
// Move the file to the directory where csv are stored
try {
//get the new param of moving file
$FilesuploadFile=$FilesuploadFile->move(
$this->getParameter('uploads_directory'),
$newFilename
);
// create and set this Fileupload
$FileUpload = new Filesupload();
$FileUpload
->setType("ESA")
->setFilename($newFilename);
// save the uploaded filename to database
$entityManager->persist($FileUpload);
$entityManager->flush();
$entityManager->clear();
} catch (FileException $e) {
// ... handle exception if something happens during file upload
}
}
$this->ImportESA($FilesuploadFile, $FileUpload);
}
else{
return $this->render('Aqueduct/import.html.twig', [ 'form' => $form->createView()]);
}
}
Part of my Controller ESA Upload the CSV (Process step 3 + 4)
public function ImportESA($FilesuploadFile, $FileUpload)
{
$batchSize = 80;
$i=0;
$entityManager = $this->getDoctrine()->getManager();
$repositoryESA = $this->getDoctrine()->getRepository(Esa::class);
$csv = Reader::createFromPath($FilesuploadFile->getRealPath(), 'r');
//param the header of the array
$csv->setDelimiter(';');
$csv->setEnclosure('"');
$csv->setHeaderOffset(0);
/*$csv->setEncodingFrom('iso-8859-15');*/
$records = $csv->getRecords();
foreach ($records as $offset => $record) {
//Remove matu and degree and class split
$classLetter = $this->RemoveMatuTag($this->AllLettersBeforeNumb($record["Classe - Nom"]));
$department = $this->GetDepartmentByClasseName($classLetter);
++$i;
$EsaRecord = new Esa();
$EsaRecord
->setDepartment($department)
->setConcatenate($Concatenate)
->setFilesupload($FileUpload)
;
$entityManager->persist($EsaRecord);
if (($i % $batchSize) === 0) {
$entityManager->flush();
$message = 'Done';
$entityManager->clear(); // Detaches all objects from Doctrine!
}
}
}
}
$entityManager->flush();
$entityManager->clear(); // Detaches all objects from Doctrine!
return $this->redirect($this->generateUrl('Aqueduct_TransformData'));
}
How I get the department
public function AllLettersBeforeNumb($var)
{
return preg_replace("/\d.*/","$2",$var);
}
public function RemoveMatuTag($var)
{
return str_replace(" MATU", "",$var);
}
public function GetDepartmentByClasseName($var)
{
$repository = $this->getDoctrine()->getRepository(Education::class);
$education = $repository->findOneBy(['Shorten' => $var]);
$department = NULL;
if ($education != NULL) {
$department = $education->getDepartment();
}
if (! $department){
$repository = $this->getDoctrine()->getRepository(Department::class);
$department = $repository->find(0);
}
return $department;
}
As my understanding I don't want to : cascade={"persist"} cause it will create an filesupload row for each row in my CSV (ESA table).
I expect the have only 1 filesupload row for all my new esa row.
But actual I have 1 filesupload for each packet of 80 lines, cause of $entityManager->flush();
I have 17160 row in my csv.
It's cause I unset the $FileUpload when I call the clear() method of the entity manager.
My bad...

Is it possible to do joins in Doctrine with the Criteria functionality?

public function findActiveEvents($start, $end)
{
$expr = Criteria::expr();
$criteria = Criteria::create();
$criteria->where(
$expr->andX($expr->gte('start', $start), $expr->lte('end', $end)
));
return $this->matching($criteria);
}
So let's say my event entity has a category and category has many events, how would I filter these?
If you want to get collection of inactive events on category object you could use criteria class
class Category{
protected $events; // (oneToMany)
// ...
protected getEvents() { // default method
return $this->events;
}
protected getActiveEvents() {
$expr = Criteria::expr();
$criteria = Criteria::create();
$criteria->where(
$expr->andX($expr->gte('start', $start), $expr->lte('end', $end)
));
return $this->events->matching($criteria);
}
}
How filter data inside entity object in Symfony 2 and Doctrine

Retrieve session value and pass to an entity attribute

I want to retrieve a quantity for each item that I store in session and store it in database.
How do I retrieve the quantity in session and passed to my quantity attribute of my article entity during database persistence?
For example for this article:
(id 4, quantity 2).
I would store 2 in the quantity attribute of my article entity.
I tried :
$article->setQuantity($session->get('panier'));
I have this error:
An exception occurred while executing 'INSERT INTO article ....... {"4": "2"}
Notice: Array to string conversion
/**
* #Route("/payment", name="payment")
*/
public function paymentAction(Request $request)
{
$session = $request->getSession();
$produits = $this->getDoctrine()->getManager()->getRepository('AppBundle:Stock')->findArray(array_keys($session->get('panier')));
$commande = $session->get('commande');
var_dump($session->get('panier'));
if ($request->isMethod('POST')) {
$token = $request->get('stripeToken');
\Stripe\Stripe::setApiKey($this->getParameter("private_key"));
\Stripe\Charge::create(array(
"amount" => $commande->getTotal() * 100,
"currency" => "EUR",
"source" => $token,
"description" => ""
));
foreach ($produits as $produit) {
$article = new Article();
$article->setTitle($produit->getStock()->getTitle());
$article->setContent($produit->getStock()->getContent());
//problem here
$article->setQuantity($session->get('panier'));
//
$article->setPrice($produit->getPrice());
$commande->addArticle($article);
$em = $this->getDoctrine()->getManager();
$em->persist($commande);
$em->flush();
}
return $this->redirectToRoute('confirmation');
}
return $this->render(':default:payment.html.twig', array(
'commande' => $commande,
'panier' => $session->get('panier'),
'produits' => $produits,
'public_key' => $this->getParameter("public_key"),
));
}
Add article in session :
/**
* #Route("/shop/add/{id}", name="add_article")
*
*/
public function addArticlelAction(Request $request, $id)
{
$session = $request->getSession();
if (!$session->has('panier'))
$session->set('panier', array());
$panier = $session->get('panier');
if (array_key_exists($id, $panier)) {
if ($request->query->get('qte') != null)
$panier[$id] = $request->query->get('qte');
} else {
if ($request->query->get('qte') != null)
$panier[$id] = $request->query->get('qte');
else
$panier[$id] = 1;
}
$session->set('panier', $panier);
return $this->redirectToRoute('panier');
}
UPDATE:
If $id in addArticlelAction is the product id then:
foreach ($produits as $produit) {
$article = new Article();
$article->setTitle($produit->getStock()->getTitle());
$article->setContent($produit->getStock()->getContent());
//problem here
$article->setQuantity($session->get('panier')[$produit->getId()]);
//
$article->setPrice($produit->getPrice());
$commande->addArticle($article);
$em = $this->getDoctrine()->getManager();
$em->persist($commande);
$em->flush();
}
should work, because for the moment you have two products (product1 who has id 1 and product 4 who has id 4). When you call /shop/add/{id}, you are adding to $session->get('panier')[1] and $session->get('panier')[4] the quantities. So, when you're in foreach (to store in DB), you need to access index 1 and index 4 ($produit->getId())

Insert in Doctrine preUpdate event and cancel update

In my event I check if start date is changed if is, then I want to insert new row, and cancel update
if ($args->hasChangedField('startAt')) {
// cancel update
// insert new entity
}
I have did something like this
public function preFlush(PreFlushEventArgs $args)
{
$em = $args->getEntityManager();
/** #var UnitOfWork $unitOfWork */
$unitOfWork = $em->getUnitOfWork();
$identityMap = $unitOfWork->getIdentityMap();
if (0 === count($identityMap)) {
return;
}
foreach ($identityMap as $entities) {
foreach ($entities as $entity) {
if ($entity instanceof Settelment) {
$original = $unitOfWork->getOriginalEntityData($entity);
if ($entity->getStartAt() !== $original['startAt']) {
$em->detach($entity); // remove entity
}
}
}
}
}
But now I remove entity and its ok but I dont define here new object and persist it, and I got extra row in database (this is what I want) but how does it work ?? why i get extra row if I didnt create entity.

SonataAdminBundle Exporter issue with mapped entities

There is a standard feature in sonata-admin-bundle to export data using exporter; But how to make export current entity AND mapped ManyToOne entity with it?
Basically what I want, is to download exactly same data as defined in ListFields.
UPD: In docs, there is only todo
UPD2: I've found one solution, but I do not think it is the best one:
/**
* Add some fields from mapped entities; the simplest way;
* #return array
*/
public function getExportFields() {
$fieldsArray = $this->getModelManager()->getExportFields($this->getClass());
//here we add some magic :)
$fieldsArray[] = 'user.superData';
$fieldsArray[] = 'user.megaData';
return $fieldsArray;
}
I created own source iterator inherited from DoctrineORMQuerySourceIterator.
If value in method getValue is array or instance of Traversable i call method getValue recursive to get value for each "Many" entity:
protected function getValue($value)
{
//if value is array or collection, creates string
if (is_array($value) or $value instanceof \Traversable) {
$result = [];
foreach ($value as $item) {
$result[] = $this->getValue($item);
}
$value = implode(',', $result);
//formated datetime output
} elseif ($value instanceof \DateTime) {
$value = $this->dateFormater->format($value);
} elseif (is_object($value)) {
$value = (string) $value;
}
return $value;
}
In your admin class you must override method getDataSourceIterator to return your own iterator.
This
$this->getModelManager()->getExportFields($this->getClass());
returns all entity items. Better practice is to create explicit list of exported items in method getExportFields()
public function getExportFields()
{
return [
$this->getTranslator()->trans('item1_label_text') => 'entityItem1',
$this->getTranslator()->trans('item2_label_text') => 'entityItem2.subItem',
//subItem after dot is specific value from related entity
....
Key in array is used for export table headers (here is traslated).

Resources