Doctrine 2.1 Persist entity in preUpdate lifeCycleCallback - orm

I'm struggling with the following, in a entity class I have a preUpdate lifeCycleCallback which has to persist a new entity before it flushes the changes for a auditTrail.
In preRemove and prePersist this works perfectly but in preUpdate nothing happends. If I call flush myself it goes in a recursive loop.
According to the Google groups for doctrine-user putting it in onFlush should be a option but in that event I can't access the old values of the entity to save this old values in a new other entity for the audittrail.
Some small example what i'm trying to archive:
<?php
/**
* #Entity
* #HasLifeCycleCallbacks
*/
class someEntity {
... annotations ...
/**
* #PreUpdate
*/
public function addAuditTrail() {
$em = \Zend_Registry::get('doctrine')->getEntityManager();
$entity = new AuditTrail();
$entity->action = 'update';
$entity->someField = $this->someField;
$em->persist($entity); //this just doesn't do anything :-(
}
}
?>
It's not real code, just something to illustrate you what I want. I also tried something like this:
$em->getUnitOfWork()->computeChangeSet($em->getClassMetaData(get_class($entity)), $entity);
Which should work according to this topic: http://groups.google.com/group/doctrine-user/browse_thread/thread/bd9195f04857dcd4
If I call the flush again but that causes Apache to crash because of some infinite loop.
Anyone who got ideas for me? Thanks!

You should never use the entitymanager inside your entities. If you would like to add audit trails, you should map the "SomeEntity" entity to the "AuditTrail" entity and do something like
/**
* #PreUpdate
*/
public function addAuditTrail() {
$entity = new AuditTrail();
$entity->action = 'update';
$entity->someField = $this->someField;
$this->autitTrail->add($entity);
}
If you set the cascade option on the mapping, it will get persisted when you persist "SomeEntity".

I had the same problem in the preUpdate method of an EventListener. I solved this by storing the new entity in a property and moving the new persist() and flush() calls to the postUpdate method.
class someEntity {
... annotations ...
protected $store;
/**
* #PreUpdate
*/
public function addAuditTrail() {
//$em = \Zend_Registry::get('doctrine')->getEntityManager();
$entity = new AuditTrail();
$entity->action = 'update';
$entity->someField = $this->someField;
// replaces $em->persist($entity);
$this->store = $entity;
}
/**
* #PostUpdate
*/
public function saveAuditTrail() {
$em = \Zend_Registry::get('doctrine')->getEntityManager();
$em->persist($this->store);
$em->flush();
}
}

entitymanager->persist() will not work inside the preUpdate method.
Instead of that, you can save the AuditTrail data to session and after the flush of the 'SomeEntity', take the data from session and perform entitymanager->persist(...) and entitymanager->flush()

Related

Yii2 Event not run after login

I created a Behavior which contains function. This function should be afterLogon of User (yii/web/User::EVENT_AFTER_LOGIN).
But this function never will be triggered unfortunatelly.
I have a Behaviour class for the user model:
class UserBehavior extends Behavior
{
/**
* #inheritdoc
* #param \yii\base\Component $owner
*/
public function attach($owner)
{
parent::attach($owner);
$owner->on(\yii\web\User::EVENT_AFTER_LOGIN, [$this, 'updateLoginInformation']);
}
/**
* Update login information data:
* - login ip address
* - login time
*/
public function updateLoginInformation()
{
/** #var \common\models\User $owner */
$owner = $this->owner;
$owner->logged_in_ip = Yii::$app->request->getUserIP();
$owner->logged_in_at = time();
$owner->save();
}
}
I declared the events and the attach too.
But this events never be run after login...
I attached this behavior to the user model:
/**
* #inheritdoc
*/
public function behaviors()
{
return [
TimestampBehavior::className(),
UserBehavior::className()
];
}
If I know well the the EVENT_AFTER_LOGIN will be triggered automatically by the Yii framework, this is the reason why I do not trigger it again.
And I do not where is the problem, because the updageLoginInformatin never called.
I usually use any logic I want in a model inside the proper action that calls it (IE: actionLogin). But I like your approach.
I just made a test here and the correct way to call the event is something like this:
$user = \Yii::$app->user;
$user->on($user::EVENT_AFTER_LOGIN, [$this, 'updateLoginInformation']);
I didn't create a behavior class, I just added this lines in my init(), but the logic is probably the same as yours.

Tx_Extbase_Domain_Repository_FrontendUserRepository->findAll() not working in typo3 4.5.30?

I am trying to run a simple query off of the Tx_Extbase_Domain_Repository_FrontendUserRepository. I cannot get anything to work except findByUid(), not even findAll().
In my controller I have this code which seems to work:
/**
* #var Tx_Extbase_Domain_Repository_FrontendUserRepository
*/
protected $userRepository;
/**
* Inject the user repository
* #param Tx_Extbase_Domain_Repository_FrontendUserRepository $userRepository
* #return void */
public function injectFrontendUserRepository(Tx_Extbase_Domain_Repository_FrontendUserRepository $userRepository) {
$this->userRepository = $userRepository;
}
/**
* action create
*
* #param Tx_BpsCoupons_Domain_Model_Coupon $newCoupon
* #return void
*/
public function createAction(Tx_BpsCoupons_Domain_Model_Coupon $newCoupon) {
...... some code .....
$user = $this->userRepository->findByUid(($GLOBALS['TSFE']->fe_user->user[uid]));
$newCoupon->setCreator($user);
...... some code .....
}
but in another function I want to look up a user not by uid but by a fe_users column called vipnumber (an int column) so I tried
/**
* check to see if there is already a user with this vip number in the database
* #param string $vip
* #return bool
*/
public function isVipValid($vip) {
echo "<br/>" . __FUNCTION__ . __LINE__ . "<br/>";
echo "<br/>".$vip."<br/>";
//$ret = $this->userRepository->findByUid(15); //this works!! but
$query = $this->userRepository->createQuery();
$query->matching($query->equals('vip',$vip) );
$ret = $query->execute(); //no luck
.................
and neither does this
$ret = $this->userRepository->findAll();
How can one work but not the others? In my setup I already put
config.tx_extbase.persistence.classes.Tx_Extbase_Domain_Model_FrontendUser.mapping.recordType >
which seems to be necessary for the fiondByUid to work, is i t preventing the other from working?
I am using typo3 v 4.5.30 with extbase 1.3
Thanks
If $this->userRepository->findByUid(15); works, there is no reason why $this->userRepository->findAll(); should not. However $this->userRepository->findAll(); returns not a single Object but a collection of all objects, so you have to iterate over them.
If you add a column to the fe_users, you have to add it to TCA and to your extbase model (you need a getter and a setter), too! After that you can call findByProperty($property) in your repository. In your case that would be
$user = $this->userRepository->findByVipnumber($vip);
This will return all UserObjects that have $vip set as their Vipnumber. If you just want to check if that $vip is already in use, you can call
$user = $this->userRepository->countByVipnumber($vip);
instead. Which obviously returns the number of Users that have this $vip;
You never use $query = $this->createQuery(); outside your Repository.
To add the property to the fronenduser Model you create your own model Classes/Domain/Model/FronendUser.php:
class Tx_MyExt_Domain_Model_FrontendUser extends Tx_Extbase_Domain_Model_FrontendUser {
/**
* #var string/integer
*/
protected $vipnumber;
}
Add a getter and a setter. Now you create your own FrontendUserRepository and extend the extbase one like you did with the model. You use this repository in your Controller. Now you're almost there: Tell Extbase via typoscript, that your model is using the fe_users table and everything should work:
config.tx_extbase {
persistence{
Tx_MyExt_Domain_Model_FrontendUser{
mapping {
tableName = fe_users
}
}
}
}
To disable storagePids in your repository in general, you can use this code inside your repository:
/**
* sets query settings repository-wide
*
* #return void
*/
public function initializeObject() {
$querySettings = $this->objectManager->create('Tx_Extbase_Persistence_Typo3QuerySettings');
$querySettings->setRespectStoragePage(FALSE);
$this->setDefaultQuerySettings($querySettings);
}
After this, your Querys will work for all PIDs.
I didn't have the opportunity to work with frontend users yet, so I don't know if the following applies in this case:
In a custom table I stumbled uppon the fact, that extbase repositories automatically have a look at the pids stored in each entry and check it against a set storage pid (possibly also the current pid if not set). Searching for a uid usually means you have a specific dataset in mind so automatic checks for other values could logically be ignored which would support your experiences. I'd try to set the storage pid for your extension to the place the frontend users are stored in ts-setup:
plugin.[replace_with_extkey].persistence.storagePid = [replace_with_pid]

The class 'Doctrine\ORM\PersistentCollection' was not found in the chain configured namespaces

I am using in project Zend Framework 2 with Doctrine 2 ORM.
I am trying to persist a many to many relation. I followed th documentation described here (manytomany).
While trying to persist data: $em->persist($form->getData()); I got the error:
"The class 'Doctrine\ORM\PersistentCollection' was not found in the chain configured namespaces".
Any suggestions ?
To be more clear I added below some code:
First I annotated entities like documentation said, for many to many relation:
/**
* #ORM\ManyToMany(targetEntity="\User\Entity\Client", mappedBy="reportSettings")
*/
private $client;
public function __construct() {
$this->client = new ArrayCollection();
}
and
/**
* #ORM\ManyToMany(targetEntity="\Statistics\Entity\ReportSettings", inversedBy="client")
* #ORM\JoinTable(name="report_client_settings",
* joinColumns={#ORM\JoinColumn(name="client_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="report_setting_id", referencedColumnName="id")})
*/
private $reportSettings;
public function __construct() {
$this->reportSettings = new ArrayCollection();
}
And in the controller
$form = new UpdateReportSettingsForm();
$form->bind($reportSettings);
$request = new Request();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
$data = $form->getData();
$em->persist($data); // here I got the error - The class 'Doctrine\ORM\PersistentCollection' was not found in the chain configured namespaces
$em->flush();
}
I also use in the form DoctrineModule\Form\Element\ObjectMultiCheckbox.
An simple var_dump($data) - return a persistent collection.
The error appear because the form was not defined correctly. The right way how to do a many to many relation i founded here - http://laundry.unixslayer.pl/2013/zf2-quest-zendform-many-to-many/
Have you added this piece of code in the beginning of your entity where you are mapping
use Doctrine\Common\Collections\ArrayCollection;

Integrity constraint violation in Magento custom module

I have a similar probelm to Integrity constraint violation creating Product in Magento (unanswered) but I am creating a custom Observer that hooks into the catalog_product_save_after event - based on this tutorial: http://fishpig.co.uk/blog/custom-tabs-magento-product-admin.html
However whenever a new product is saved I get this error:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '22-1' for key 'UNQ_CATALOGINVENTORY_STOCK_ITEM_PRODUCT_ID_STOCK_ID'
The config.xml looks like this:
<adminhtml>
<events>
<catalog_product_save_after>
<observers>
<a1web_save_product_data>
<type>singleton</type>
<class>metricimperial/observer</class>
<method>saveProductData</method>
</a1web_save_product_data>
</observers>
</catalog_product_save_after>
</events>
</adminhtml>
The outline of the class is like this:
<?php
class A1web_MetricImperialConverter_Model_Observer
{
/**
* Flag to stop observer executing more than once
*
* #var static bool
*/
static protected $_singletonFlag = false;
* #param Varien_Event_Observer $observer
*/
public function saveProductData(Varien_Event_Observer $observer)
{
if (!self::$_singletonFlag) {
self::$_singletonFlag = true;
$product = $observer->getEvent()->getProduct();
//Custom updates made to product object here
$product->save();
}
catch (Exception $e) {
Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
}
}
}
/**
* Retrieve the product model
*
* #return Mage_Catalog_Model_Product $product
*/
public function getProduct()
{
return Mage::registry('product');
}
/**
* Shortcut to getRequest
*
*/
protected function _getRequest()
{
return Mage::app()->getRequest();
}
}
The product is saved correctly with the custom product data I'm adding - and once the product is saved the error does not occur on subsequent saves of the same product. It is just when the product is first created the error occurs.
Thanks in advance
Instead of using $product->save() try using the resource model, a la $product->getResource()->save($product).
The reason being $product->save() will re-trigger all save events, hence running whatever is saving the cataloginventory_stock and throwing the error.
In this case, I'd recommend not using the catalog_product_save_after event. Instead, try using catalog_product_prepare_save which is fired after the POST data is applied to the product, but before ->save() is called. That way you don't have to mess with with saving or that ugly $_singletonFlag!
Also, with catalog_product_prepare_save you get the HTTP Request object inside the Observer Event. No need for Mage::app()->getRequest(). Woot!

Doctrine2 ArrayCollection

Ok, I have a User entity as follows
<?php
class User
{
/**
* #var integer
* #Id
* #Column(type="integer")
* #GeneratedValue
*/
protected $id;
/**
* #var \Application\Entity\Url[]
* #OneToMany(targetEntity="Url", mappedBy="user", cascade={"persist", "remove"})
*/
protected $urls;
public function __construct()
{
$this->urls = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addUrl($url)
{
// This is where I have a problem
}
}
Now, what I want to do is check if the User has already the $url in the $urls ArrayCollection before persisting the $url.
Now some of the examples I found says we should do something like
if (!$this->getUrls()->contains($url)) {
// add url
}
but this doesn't work as this compares the element values. As the $url doesn't have id value yet, this will always fail and $url will be dublicated.
So I'd really appreciate if someone could explain how I can add an element to the ArrayCollection without persisting it and avoiding the duplication?
Edit
I have managed to achive this via
$p = function ($key, $element) use ($url)
{
if ($element->getUrlHash() == $url->getUrlHash()) {
return true;
} else {
return false;
}
};
But doesn't this still load all urls and then performs the check? I don't think this is efficient as there might be thousands of urls per user.
This is not yet possible in a "domain driven" way, ie. just using objects. You should execute a query to check for the existance:
SELECT count(u.id) FROM User u WHERE ?1 IN u.urls AND u.id = ?2
With Doctrine 2.1 this will be possible using a combination of two new features:
Extra Lazy Collections
#IndexBy for collections, so you would define #OneToMany(targetEntity="Url", indexBy="location")
ExtraLazy Collection Support for index by using ->contains().
Points 1 and 2 are already implemented in Doctrine 2 master, but 3 is still missing.
You should try using the exists method on the collection and manually compare values.