Hello I try to create an entity when another linked entity is created via a postPersist method but I find myself making this error someone knows why? I can not find the reason.
In ClientAdmin.php like the Sonata Documentation advice to do. Sonata Doc
public function postPersist($client)
{
if ($client instanceof Client )
{
$money = new Money();
$money->setClient($client);
$money->setSurname($client->getSurname());
$money->setFirstname($client->getFirstname());
}
}
Client.php :
/**
* #ORM\OneToOne(targetEntity="Money", mappedBy="client", cascade={"persist", "remove"})
*/
protected $money;
/**
* Set money
*
* #param \AppBundle\Entity\Money $money
*
* #return Client
*/
public function setMoney(\AppBundle\Entity\Money $money )
{
$this->money = $money;
}
/**
* Get money
*
* #return \AppBundle\Entity\Money
*/
public function getMoney()
{
return $this->money;
}
The error :
Solution :
Working but nothing is create is the table "Money" so i'm supposed it because I don't persist and flush it but I can't do it in it . :/
Working on Symfony 3.3 with SonataAdmin 3.19
Thanks in advance !
Edit : Solution found :
public function postPersist($client)
{
$em = $this->getConfigurationPool()->getContainer()->get('doctrine.orm.entity_manager');
if ($client instanceof Client )
{
$money = new Money();
$money->setClient($client);
$money->setSurname($client->getSurname());
$money->setFirstname($client->getFirstname());
$em->persist($money);
$em->flush();
}
}
}
your code is totally wrong.
$this->setMoney(new Money()); }
this means you call setMoney method of the class ClientAdminController(which is $this)
but ClientAdminController does not have the method setMoney(Money). You have to call it on a Client instance.
Related
In my admin I have a OneToMany defined as it:
/**
* #ORM\OneToMany(targetEntity="Module", mappedBy="sequence", cascade={"persist", "remove"})
*/
private $modules;
And the inversed side:
/**
* #ORM\ManyToOne(targetEntity="ModuleSequence", inversedBy="modules", cascade={"persist"}, fetch="LAZY")
* #ORM\JoinColumn(name="sequence_id", referencedColumnName="id")
*/
protected $sequence;
In my admin class I defined the 'modules' field as it:
->add('modules', 'sonata_type_collection',array(
'by_reference' => false
))
Finally in the ModuleSequence Entity here's the addModule method:
/**
* Add modules
*
* #param \AppBundle\Entity\Module $module
* #return ModuleSequence
*/
public function addModule(\AppBundle\Entity\Module $module)
{
$module->setSequence($this);
$this->modules[] = $module;
return $this;
}
I have the "add" button, I get the modal, I fill it and validate. The Ajax request is sent into the profiler but no new row appear.
The 'sequence_id' is not set in the database and I don't know why... Any idea please?
When I use the 'inline' & 'table' options, the id is well set.
Had the same issue and solved it with overriding the prePersist and preUpdate methods and then persist the associations.
public function prePersist($object)
{
$this->persistBlocks($object);
$content->mergeNewTranslations();
}
public function preUpdate($object)
{
$this->prePersist($object);
}
private function persistModules($object)
{
$em = $this->getConfigurationPool()->getContainer()->get('doctrine.orm.entity_manager');
foreach($object->getModules() as $module)
{
$module->setObject($object); // change Object to the name of your Entity!!
$em->persist($module);
}
}
After a long discussion on Sonata Admin GitHub here:
https://github.com/sonata-project/SonataAdminBundle/issues/4236
Problem partially solved...
I have a lifecycle event. As soon as an order is created the prePersist lifecycle event add a few more details to the order before it is persisted to the database.
This is my prePersist event class;
<?php
namespace Qi\Bss\BaseBundle\Lib\PurchaseModule;
use Qi\Bss\BaseBundle\Entity\Business\PmodOrder;
use Doctrine\ORM\Event\LifecycleEventArgs;
/**
* Listener class
* Handles events related to list prices
*/
class OrderUserListener
{
/**
* Service container
* #var type
*/
private $serviceContainer;
/**
* Performs tasks before destruction
* #ORM\PrePersist
*/
public function prePersist(LifecycleEventArgs $args)
{
$order = $args->getEntity();
if ($order instanceof PmodOrder) {
$user = $this->serviceContainer->get('security.token_storage')->getToken()->getUser();
if ($user) {
$order->setCreatedBy($user);
$order->setCreatedAt(new \DateTime(date('Y-m-d H:i:s')));
$order->setDepartment($user->getDepartment());
$order->setStatus(PmodOrder::STATUS_AWAITING_APPROVAL);
$this->serviceContainer->get('bss.pmod.order_logger')->log($order, 'Order Created');
}
}
}
/**
* Sets the sales order exporter object
* #param type $serviceContainer
*/
public function setServiceContainer($serviceContainer)
{
$this->serviceContainer = $serviceContainer;
}
}
It works perfectly but this part $this->serviceContainer->get('bss.pmod.order_logger')->log($order, 'Order Created'); doesn't want to work. I try to call a service inside it. I know the service works perfectly inside my controllers, but here I get an error;
A new entity was found through the relationship
'Qi\Bss\BaseBundle\Entity\Business\PmodLog#order' that was not
configured to cascade persist operations for entity: Nuwe Test vir
logger. To solve this issue: Either explicitly call
EntityManager#persist() on this unknown entity or configure cascade
persist this association in the mapping for example
#ManyToOne(..,cascade={"persist"}).
This is how my OrderLogger service class looks like;
<?php
namespace Qi\Bss\BaseBundle\Lib\PurchaseModule;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Doctrine\ORM\EntityManager;
use Qi\Bss\BaseBundle\Entity\Business\PmodLog;
/**
* Class AppLogger. Purchase Module logger.
* #package FcConnectBundle\Lib
*/
class OrderLogger {
private $em;
private $tokenStorage;
/**
* Constructor.
*
* #param EntityManager $em
* #param TokenStorage $securityTokenStorage
*/
public function __construct(EntityManager $em, TokenStorage $securityTokenStorage)
{
$this->em = $em;
$this->tokenStorage = $securityTokenStorage;
}
/**
* Log an order action.
*
* #param string $text
*/
public function log($order, $action)
{
$logRecord = new PmodLog();
if (is_object($this->tokenStorage->getToken())) {
$user = $this->tokenStorage->getToken()->getUser();
if (is_object($user)) {
$logRecord->setUser($user);
}
}
$logRecord->setOrder($order);
$logRecord->setAction($action);
$logRecord->setTime(new \DateTime());
$this->em->persist($logRecord);
$this->em->flush();
}
}
I have already tried changing the persist in my log to merge, but that also doesn't work. Can somebody please help and explain what I do wrong?
This is not the best architecture, but it will work:
On prePersist add all messages to some kind of private variable (like $logMessages), and add another event
/**
* #param PostFlushEventArgs $args
*/
public function postFlush(PostFlushEventArgs $args)
{
$logMessages = $this->logMessages;
$this->logMessages = array(); //clean to avoid double logging
if (!empty($logMessages)) {
foreach ($logMessages as $message) {
$this->serviceContainer->get('bss.pmod.order_logger')->log($message);
}
}
}
I fixed the problem by adding a postPersist and call the logger in there instead of inside my prePersist;
/**
* Performs tasks before destruction
* #ORM\PostPersist
*/
public function postPersist(LifecycleEventArgs $args)
{
$order = $args->getEntity();
if ($order instanceof PmodOrder) {
$this->serviceContainer->get('bss.pmod.order_logger')->log($order, 'Order Created');
}
}
Because what I think is happening is that the logger tries to be executed but the order in the logger doesn't yet exists as it is not yet persisted. This way makes more sense to me, and I think this is the easiest fix. I could be wrong though, any comments and other opinions on my answer are welcome.
I have very interesting question about PHPUnit data providers.
protected $controller;
protected function setUp()
{
$this->controller = new ProductController();
}
/**
* #covers ProductsController::createItem
* #dataProvider getTestDataProvider
* #param number $name
*/
public function testCreateItem($name)
{
$prod = $this->controller->createItem($name);
$id = $prod->getId;
$this->assertInternalType('int', $id);
$this->assertInstanceOf('Product', $prod);
}
/**
* #covers ProductsController::getItemInfo
* #depends testCreateItem
* #param number $id
*/
public function testGetItemInfo($id)
{
$info = $this->controller->getItemInfo($id);
$this->assertArrayHasKey('id',$info);
$this->assertEquals($id, $info['id']);
}
I use getTestDataProvider to get test data from CSV file. Then testCreateItem create 10 new products from CSV rows.
How can I create an array of $id of new products and use it as Data provider for testGetItemInfo? I can't store it in SESSION or file because provider functions run's before SetUp.
Maybe someone has already faced a similar problem?
I have only idea with static field (maybe not the best, but if someone has better I'll look).
private static $ids;
/**
* #dataProvider some
*/
public function testT1($id)
{
self::$ids[] = $id;
}
/**
* #depends testT1
*/
public function testT2()
{
var_dump(self::$ids);
}
public function some()
{
return [
[1],
[2],
[3]
];
}
You must remember that field is visible in all class so if you want use another data set you must nullify this field.
Context
I need to hold an entity into a session using Doctrine 2.3 (with PHP 5.4), and I'm having a problem once the $_SESSION variable is set.
Code
I have the following classes:
Persistente
Superclass for holding information about persistent classes.
/**
* #MappedSuperclass
*/
abstract class Persistente
{
public function __construct()
{}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
/**
* #Id
* #GeneratedValue
* #Column(type="integer")
*/
protected $id;
}
Persona
Holds basic information about a person.
/**
* #Entity
* #AttributeOverrides({
* #AttributeOverride(name="id",
* column=#Column(
* name="Persona_Id",
* type="integer"
* )
* )
* })
*/
class Persona extends Persistente
{
...
public function getContInformacion()
{
return $this->contInformacion;
}
public function setContInformacion(ContenedorInformacion $contInformacion)
{
$this->contInformacion = $contInformacion;
}
...
/**
* #OneToOne(targetEntity="ContenedorInformacion", cascade={"all"} )
* #JoinColumn(name="ContInfo_Id", referencedColumnName="ContInfo_Id")
*/
private $contInformacion;
}
ContenedorInformacion
Class that contains information about the person, which can be dynamically added to the object depending on some validation rules.
/**
* #Entity
* #AttributeOverrides({
* #AttributeOverride(name="id",
* column=#Column(
* name="ContInfo_Id",
* type="integer"
* )
* )
* })
*/
class ContenedorInformacion extends Persistente
{
...
/**
* #OneToMany(targetEntity="UnidadInformacion", mappedBy="contInformacion", cascade={"all"}, indexBy="clave")
*/
private $unidadesInformacion;
/**
* #OneToMany(targetEntity="Rol", mappedBy="contInformacion", cascade={"all"}, indexBy="clave")
*/
private $roles;
}
Issue
Whenever I add Persona to a session, the following code gets executed:
public function login(Persona $t)
{
if ($this->autorizar($t) === false) {
return false;
}
$dao = new DAOManejadorMsSql();
$daoPersona = $dao->fabricarDAO("\Codesin\Colegios\Personas\Persona");
$t = $this->buscarPersona($t);
$daoPersona->soltar($t);
$dao->cerrar();
$_SESSION['usuario'] = $t;
if ($t->getContInformacion()->existeRol('SYSADMIN') === true) {
return 'SYSADMIN';
}
}
soltar() executes the detach() method from the EntityManager, effectively leaving the entity unmanaged. However, the ContenedorInformacion object inside Persona is a proxy generated by Doctrine instead of the wanted object. Why does this happen? Thank you beforehand.
EDIT: This is the error.
Warning: require(C:\xampp\htdocs/Zeus/lib/vendor/DoctrineProxies/__CG__/Codesin/Colegios/Personas/ContenedorInformacion.php): failed to open stream: No such file or directory in C:\xampp\htdocs\Zeus\Common\Utils\autoload.php on line 8
Fatal error: require(): Failed opening required 'C:\xampp\htdocs/Zeus/lib/vendor/DoctrineProxies/__CG__/Codesin/Colegios/Personas/ContenedorInformacion.php' (include_path='.;C:\xampp\php\PEAR') in C:\xampp\htdocs\Zeus\Common\Utils\autoload.php on line 8
I had to use a very crude approach.
I figured out the following: given I'm not going to reattach the information immediately, I remade another ContenedorInformacion which contains the exact same information than the proxy. And given the ArrayCollections aren't using proxies but rather the whole objects, I did this.
public function login(Persona $t)
{
if ($this->autorizar($t) === false) {
return false;
}
$dao = new DAOManejadorMsSql();
$daoPersona = $dao->fabricarDAO("\Codesin\Colegios\Personas\Persona");
$t = $this->buscarPersona($t);
$daoPersona->soltar($t);
$dao->cerrar();
/***** NEW LINES START HERE *****/
$contInfo = new ContenedorInformacion();
$contInfo->setId($t->getContInformacion()->getId());
$contInfo->setUnidadesInformacion(new ArrayCollection($t->getContInformacion()->getUnidadesInformacion()->toArray()));
$contInfo->setRoles(new ArrayCollection($t->getContInformacion()->getRoles()->toArray()));
$t->setContInformacion($contInfo);
/***** NEW LINES END HERE *****/
$_SESSION['usuario'] = $t;
if ($t->getContInformacion()->existeRol('SYSADMIN') === true) {
return 'SYSADMIN';
}
}
It's quite dirty, but it works like a charm.
I have searched, and there is a lot of questions wiith the same problem, but none of them solves to my issue.
I have an Entity, here is it's code:
/*
* #ORM\HasLifecycleCallbacks
*/
class MyEntity
{
// some preoperties here...
/**
* #ORM\Column(type="text", nullable=true)
* #Assert\MaxLength(limit="500")
*/
private $delivery = null;
/**
* #var $deliveryOn bool
*
* Virtual field used for $delivery property organization
*/
private $deliveryOn = false;
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preSetDelivery()
{
if ($this->deliveryOn == false)
$this->delivery = null;
}
/**
* #ORM\PostLoad()
*/
public function loadDeliveryOn()
{
if ($this->delivery != null)
$this->deliveryOn = true;
}
}
loadDeliveryOn method perfectly works all the time. But the preSetDelivery fired only when I persist the entity to the database for the first time. I want it to be called when object is updated too, but it doesn't work. And I have no any idea why.
My edit controller:
public function editAction($id)
{
// some code here...
// ...
$request = $this->getRequest();
if ($request->isMethod('POST'))
{
$form->bind($request);
if ($form->isValid())
{
$em->flush();
}
}
}
From official docs concerning preUpdate:
Changes to fields of the passed entities are not recognized by the
flush operation anymore, use the computed change-set passed to the
event to modify primitive field values.
If you have access to UnitOfWork maybe there is a way to recompute change-set as there is in onFlush?
if you use inheritance - for example #ORM\InheritanceType("SINGLE_TABLE")
you need to add #ORM\MappedSuperclass in the parent class
PreUpdate only fires if there are actual changes on the entity. If you don't change anything in the form, there will be no changes on the entity, and no preUpdate listeners will be called.