I want to create new Entity SlideTranslation, and assign existed slide.
But every time entity had created without id of Slide. I can create SlideTranslation and than assign Slide to it, but it seems bad solution for me.
$slide = $em->getRepository('Model:Slide')->find($id);
if(isset($slide)) {
try {
$slideTranslation = new SlideTranslation();
$slideTranslation->setTranstable($slide);
$slideTranslation->setLocale('uk');
$slideTranslation->setAltText('Alt text');
$em->persist($slideTranslation);
$em->flush();
} catch (Exception $e) {
dump($e->getMessage());
}
}
Relations.
/**
* #ORM\ManyToOne(targetEntity="Model\Entity\Slide", inversedBy="tranlations")
* #ORM\JoinColumn(name="translatable_id", referencedColumnName="id")
*/
private $transtable;
I have tried method with getReference, but no result. Maybe I am breaking some patterns or principles and It's not possible in Doctrine2.
You will have to probably do it in the other way around
$slide = $em->getRepository('Model:Slide')->find($id);
$slideTranslation = new SlideTranslation();
$slideTranslation->setLocale('uk');
$slideTranslation->setAltText('Alt text');
$slide->addTranslation($slideTranslation);
$em->flush();
Then add cascade to the Slide entity, and you don't even need to persist the entity Translation
/**
* #ORM\OneToMany(targetEntity="Model\Entity\SlideTranslation", mappedBy="transtable", cascade={"persist", "remove"})
*/
private $translations;
Related
i have a error in my data and i get error out of range on a integer column and i try to prevent closed entity manager for proceeding work and for this purpose i reset manager in exception
public function renewDeliveryTime($delayReport) : void
{
try {
$this->delayReportRepository->updateRenewedDeliveryTimeAt($delayReport, 50000000);
}catch (\Exception $exception){
// out of range error
$this->managerRegistry->resetManager();
}
}
public function updateRenewedDeliveryTimeAt($delayReport,$delayDuration)
{
/**
* #var DelayReport $delayReport
*/
$delayReport->setDelayDuration($delayDuration);
$delayReport->setStatus(DelayReport::STATUS['DONE']);
$this->getEntityManager()->flush();
}
the problem is after i have another object and almost same operation in database but seems $this->getEntityManager()->flush() not work any more and nothing happens in database . it is related to $this->managerRegistry->resetManager()
public function enqueue($delayReport) : void
{
$this->pushInQueueReport($delayReport);
$this->delayReportRepository->updateStatus($delayReport, DelayReport::STATUS['IN_QUEUE']);
}
public function updateStatus($delayReport, $status)
{
/**
* #var DelayReport $delayReport
*/
$delayReport->setStatus($status);
$this->getEntityManager()->flush();
}
what is the problem and solution for this?
The problem with resetManager() is, that not all services wich has a reference to the entitymanager directly will be magically updated to have the new instance.
In updateStatus() Method you can easy check if your entity is Managed by the entity manager.
$uow = $this->getEntityManager()->getUnitOfWork();
if($uow->getEntityState($delayReport) !== UnitOfWork::STATE_MANAGED) {
// not managed
}
Dont know if a reassign can help here like $this->getEntityManager()->merge($delayReport).
BUT its really better to avoid a closed manager and validate your data before.
EDIT:
Not testet, if you will get the resetted EntityManager over the Registry. But its worth a try.
$entityManager = $managerRegistry->getManagerForClass(get_class($delayReport));
I've got a problem with my code. I am just trying to insert a simple set of data to my db, but doctrine insert my attribute (telVerifCode) as NULL.
I've dumped my data and figured out, that attribute (telVerifCode) has some value in it, but after I flush it is set to NULL.
This is my controller:
$user = $this->getUser();
if ($user->getTel() != $tel || $user->getTelCode() != $telCode) {
try {
$code = $this->sendTelehopneCode($user);
} catch (\Exception $e) {
//.......
}
// update user phone verifcation fields //
$user->setTelVerifCode($code);
$user->setLastTelVerificationCodeDate(new \DateTime());
$em->persist($user);
$em->flush();
}
My ORM Mapping:
/**
* #var string
*
* #ORM\Column(name="tel_verification_code", type="string", length=255, nullable=true)
*/
protected $telVerifCode;
/**
* #var \DateTime
*
* #ORM\Column(name="last_tel_verification_code_date", type="date", nullable=true)
*/
protected $lastTelVerificationCodeDate;
sendTelehopneCode function :
private function sendTelehopneCode($user)
{
$code = strval(rand(100000, 999999));
$tel = $user->getTelCode() . $user->getTel();
$msg = 'code:' . $code;
$twilio = $this->get('twilio.api');
try {
$message = $twilio->account->messages->sendMessage(
"+14*******", // Verified Outgoing Caller ID or Twilio number
$tel, // The phone number you wish to send a message to
$msg
);
} catch (\Services_Twilio_RestException $e) {
throw $e;
}
return $code;
}
Try clearing your doctrine caches, the code looks fine and cannot be the issue.
./bin/console doctrine:cache:clear-metadata
./bin/console doctrine:cache:clear-query
./bin/console doctrine:cache:clear-result
I solved the problem, I made a listener On preUpdate one that puts the value null, I completely forgotten it :(
Maybe your problem is due to a typo in your setter.
Are you sure your setter setTelVerifCode looks exactly like this?
public function setTelVerifCode($code)
{
$this->telVerifCode = $code;
}
so this is my prePersist on EventListener
public function prePersist(LifecycleEventArgs $args)
{
//the first entity will have the PMP, so we catch it and continue to skip this if after this
if ($this->pmp == null) {
$this->pmp = $args->getEntity()->getPmp();
}
$taxonomicClass = $args->getEntity();
if($taxonomicClass instanceof TaxonomicClass){
if(is_null($taxonomicClass->getId())){
//here it says that i have created a new entity, need to persist it via cascade={"persist"}
$taxonomicClass->setPmp($this->pmp);
}
}
}
that's fine, i had added the annotation on it:
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Pmp", cascade={"persist"})
* #ORM\JoinColumn(name="pmp_id", referencedColumnName="id", nullable=false)
**/
private $pmp;
and it saves everything from my hierarchy, even a new PMP, an object that already exist in the database!
what i want is that everything that im saving from my hierarchy needs to be related to the PMP that i passed, but when i set $taxonomicClass->setPmp($this->pmp); doctrine thinks that i created a new instance of PMP, since im not, i just want to this guy have an associaton with the PMP.
i tried put merge on the cascade option, but it only works with persist, how to make doctrine dont create a new instance, and instead use the one that i passed?
noticed my problem, i was assigning an attribute from memory, i should retrive him from database to doctrine understand.
public function prePersist(LifecycleEventArgs $args)
{
if ($this->pmp == null) {
$this->pmp = $args->getEntity()->getPmp();
}
$taxonomicClass = $args->getEntity();
if($taxonomicClass instanceof TaxonomicClass){
if(is_null($taxonomicClass->getId())){
//this solved the problem
$pmp = $args->getEntityManager()->getRepository("AppBundle:Pmp")->find($this->pmp->getId());
$taxonomicClass->setPmp($pmp);
}
}
}
i will keep in mind now that when a new entity is created, but it doesn't need to be saved, you must retrieve it from db, cascade={"persist"} wasn't even necessary
So I've this relations defined in my entities:
class Producto
{
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Norma", inversedBy="normasProducto", cascade={"persist"})
* #ORM\JoinTable(name="nomencladores.norma_producto", schema="nomencladores",
* joinColumns={#ORM\JoinColumn(name="producto_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="norma_id", referencedColumnName="id")}
* )
*/
protected $productoNormas;
}
class Norma
{
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Producto", mappedBy="productoNormas", cascade={"persist"})
*/
protected $normasProducto;
}
And I'm trying to check if a given pair producto_id-norma_id already exists for not try to insert it once again and I'm doing as follow:
$exists = $em->getRepository('AppBundle:Producto')->findOneByProductoNormas($productoId);
if ($exists) {
$status = 400;
} else {
try {
$producto->addProductoNormas($normaEntity);
$em->flush();
$response['success'] = true;
} catch (Exception $ex) {
$status = 400;
$response['error'] = $ex->getMessage();
}
}
But I'm getting this error:
Notice: Undefined index: joinColumns in
/var/www/html/project.dev/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
line 1665
Doing a research on Google and here on SO I found that possible it's a bug as point here or perhaps is not and we (me and the others who report the issue) are doing something wrong. I can't find where the issue or problem is so any advice or help will be fine for me and others. The only idea I have in mind is create a view at RDBMS and then create a entity for read it and check if record already exists, I have not other than this one, any ideas? Help? Working example code?
Actually you can use 'database_connection' service to check if such row exists:
$this->get('database_connection')
->fetchColumn('select count(id) as cnt
from <norma_producto_table>
where producto_id = ? and
norma_id = ?', array($producto_id, $norma_id));
That's really easier than trying to handle this with many to many relations methods. I would do that if I had to do what you need (and actually I'm doing so).
I don't know the definitive answer but i had the same problem and it had something to do with my entities annotations can't tell you exactly what though..
Here is a working example with photos and albums
class Photo
{
/**
* #ORM\ManyToMany(targetEntity="Acme\MyBundle\Entity\Album", inversedBy="photos")
* #ORM\JoinColumn(nullable=true)
*/
private $albums;
class Album
{
/**
* #ORM\ManyToMany(targetEntity="Acme\MyBundle\Entity\Photo", mappedBy="albums")
* #ORM\JoinColumn(nullable=true)
*/
private $photos;
In order to check the existence you could do, let's say you're searching for an idPic photo
$photo = $repository ... ->findOneBy($idPic)
if($photo->getAlbums()->contains($album))
I'm currently facing a challenge with SonataAdminBundle, one-to-many relationships and file uploads. I have an Entity called Client and one called ExchangeFile. One Client can have several ExchangeFiles, so we have a one-to-many relationship here. I'm using the VichUploaderBundle for file uploads.
This is the Client class:
/**
* #ORM\Table(name="client")
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks
*/
class Client extends BaseUser
{
// SNIP
/**
* #ORM\OneToMany(targetEntity="ExchangeFile", mappedBy="client", orphanRemoval=true, cascade={"persist", "remove"})
*/
protected $exchangeFiles;
// SNIP
}
and this is the ExchangeFile class:
/**
* #ORM\Table(name="exchange_file")
* #ORM\Entity
* #Vich\Uploadable
*/
class ExchangeFile
{
// SNIP
/**
* #Assert\File(
* maxSize="20M"
* )
* #Vich\UploadableField(mapping="exchange_file", fileNameProperty="fileName")
*/
protected $file;
/**
* #ORM\Column(name="file_name", type="string", nullable=true)
*/
protected $fileName;
/**
* #ORM\ManyToOne(targetEntity="Client", inversedBy="exchangeFiles")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
*/
protected $client;
// SNIP
}
In my ClientAdmin class, i added the exchangeFiles field the following way:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
// SNIP
->with('Files')
->add('exchangeFiles', 'sonata_type_collection', array('by_reference' => false), array(
'edit' => 'inline',
'inline' => 'table',
))
// SNIP
}
This allows for inline editing of various exchange files in the Client edit form. And it works well so far: .
The Problem
But there's one ceveat: When i hit the green "+" sign once (add a new exchange file form row), then select a file in my filesystem, then hit the "+" sign again (a new form row is appended via Ajax), select another file, and then hit "Update" (save the current Client), then the first file is not persisted. Only the second file can be found in the database and the file system.
As far as I could find out, this has the following reason: When the green "+" sign is clicked the second time, the current form is post to the web server, including the data currently in the form (Client and all exchange files). A new form is created and the request is bound into the form (this happens in the AdminHelper class located in Sonata\AdminBundle\Admin):
public function appendFormFieldElement(AdminInterface $admin, $subject, $elementId)
{
// retrieve the subject
$formBuilder = $admin->getFormBuilder();
$form = $formBuilder->getForm();
$form->setData($subject);
$form->bind($admin->getRequest()); // <-- here
// SNIP
}
So the entire form is bound, a form row is appended, the form is sent back to the browser and the entire form is overwritten by the new one. But since file inputs (<input type="file" />) cannot be pre-populated for security reasons, the first file is lost. The file is only stored on the filesystem when the entity is persisted (I think VichUploaderBundle uses Doctrine's prePersist for this), but this does not yet happen when a form field row is appended.
My first question is: How can i solve this problem, or which direction should i go? I would like the following use case to work: I want to create a new Client and I know I'll upload three files. I click "New Client", enter the Client data, hit the green "+" button once, select the first file. Then i hit the "+" sign again, and select the second file. Same for the third file. All three files should be persisted.
Second question: Why does Sonata Admin post the entire form when I only want to add a single form row in a one-to-many relationship? Is this really necessary? This means that if I have file inputs, all files present in the form are uploaded every time a new form row is added.
Thanks in advance for your help. If you need any details, let me know.
Further to my comment about SonataMediaBundle...
If you do go this route, then you'd want to create a new entity similar to the following:
/**
* #ORM\Table
* #ORM\Entity
*/
class ClientHasFile
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var Client $client
*
* #ORM\ManyToOne(targetEntity="Story", inversedBy="clientHasFiles")
*/
private $client;
/**
* #var Media $media
*
* #ORM\ManyToOne(targetEntity="Application\Sonata\MediaBundle\Entity\Media")
*/
private $media;
// SNIP
}
Then, in your Client entity:
class Client
{
// SNIP
/**
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\OneToMany(targetEntity="ClientHasFile", mappedBy="client", cascade={"persist", "remove"}, orphanRemoval=true)
*/
protected $clientHasFiles;
public function __construct()
{
$this->clientHasFiles = new ArrayCollection();
}
// SNIP
}
... and your ClientAdmin's configureFormFields:
protected function configureFormFields(FormMapper $form)
{
$form
// SNIP
->add('clientHasFiles', 'sonata_type_collection', array(
'required' => false,
'by_reference' => false,
'label' => 'Media items'
), array(
'edit' => 'inline',
'inline' => 'table'
)
)
;
}
... and last but not least, your ClientHasFileAdmin class:
class ClientHasFileAdmin extends Admin
{
/**
* #param \Sonata\AdminBundle\Form\FormMapper $form
*/
protected function configureFormFields(FormMapper $form)
{
$form
->add('media', 'sonata_type_model_list', array(), array(
'link_parameters' => array('context' => 'default')
))
;
}
/**
* {#inheritdoc}
*/
protected function configureListFields(ListMapper $list)
{
$list
->add('client')
->add('media')
;
}
}
I've figured out, that it could be possible to solve this problem by remembering the file inputs content before the AJAX call for adding a new row. It's a bit hacky, but it's working as I'm testing it right now.
We are able to override a template for editing - base_edit.html.twig. I've added my javascript to detect the click event on the add button and also a javascript after the row is added.
My sonata_type_collection field is called galleryImages.
The full script is here:
$(function(){
handleCollectionType('galleryImages');
});
function handleCollectionType(entityClass){
let clonedFileInputs = [];
let isButtonHandled = false;
let addButton = $('#field_actions_{{ admin.uniqid }}_' + entityClass + ' a.btn-success');
if(addButton.length > 0){
$('#field_actions_{{ admin.uniqid }}_' + entityClass + ' a.btn-success')[0].onclick = null;
$('#field_actions_{{ admin.uniqid }}_' + entityClass + ' a.btn-success').off('click').on('click', function(e){
if(!isButtonHandled){
e.preventDefault();
clonedFileInputs = cloneFileInputs(entityClass);
isButtonHandled = true;
return window['start_field_retrieve_{{ admin.uniqid }}_'+entityClass]($('#field_actions_{{ admin.uniqid }}_' + entityClass + ' a.btn-success')[0]);
}
});
$(document).on('sonata.add_element', '#field_container_{{ admin.uniqid }}_' + entityClass, function() {
refillFileInputs(clonedFileInputs);
isButtonHandled = false;
clonedFileInputs = [];
handleCollectionType(entityClass);
});
}
}
function cloneFileInputs(entityClass){
let clonedFileInputs = [];
let originalFileInputs = document.querySelectorAll('input[type="file"][id^="{{ admin.uniqid }}_' + entityClass + '"]');
for(let i = 0; i < originalFileInputs.length; i++){
clonedFileInputs.push(originalFileInputs[i].cloneNode(true));
}
return clonedFileInputs;
}
function refillFileInputs(clonedFileInputs){
for(let i = 0; i < clonedFileInputs.length; i++){
let originalFileInput = document.getElementById(clonedFileInputs[i].id);
originalFileInput.replaceWith(clonedFileInputs[i]);
}
}
I tried many different approaches and workaround and in the end I found out that the best solution in the one described here https://stackoverflow.com/a/25154867/4249725
You just have to hide all the unnecessary list/delete buttons around the file selection if they are not needed.
In all other cases with file selection directly inside the form you will face some other problems sooner or later - with form validation, form preview etc. In all these case input fields will be cleared.
So using media bundle and sonata_type_model_list is probably the safest option despite quite a lot of overhead.
I'm posting it in case someone is searching for the solution the way I was searching.
I've found also some java-script workaround for this exact problem. It worked basically changing names of file inputs when you hit "+" button and then reverting it back.
Still in this case you are still left with the problem of re-displaying the form if some validation fails etc. so I definitely suggest media bundle approach.