Magento - customer_save_after always fired twice - php

I am using the customer_save_after event in magento, and all is working fine apart from 1 annoying thing - it is always fired twice.
There are no other modules rewriting this and I can find no other reason for this happening. When I look through all of the events getting fired at this time and this event is definately getting fired twice.
Anyone explain this?
I am writing a web service that hooks into this and its turning out to be quite inefficient to duplicate things.

I've noticed this double-save behaviour too. The way to prevent issue with your observer is to set a flag in the request that can be checked e.g.
if(Mage::registry('customer_save_observer_executed')){
return $this; //this method has already been executed once in this request (see comment below)
}
...execute arbitrary code here....
/* Customer Addresses seem to call the before_save event twice,
* so we need to set a variable so we only process it once, otherwise we get duplicates
*/
Mage::register('customer_save_observer_executed',true);

I ran into this as well and did a stack trace in the observer for each method, and can tell you at least ONE reason why it fires twice (there may be others):
When a new user creates an account, createPostAction() runs when the form is submitted. This action does a save() on the customer.
THEN, after the customer has been created, setCustomerAsLoggedIn() is called by createPostAction(). This in turn calls setCustomer(), which has this little bit of code:
if ((!$customer->isConfirmationRequired()) && $customer->getConfirmation()) {
$customer->setConfirmation(null)->save(); // here is the second save
$customer->setIsJustConfirmed(true);
}
Those are the two save()s which dispatch the save event. I only know this for sure for account creation in Magento 1.5. I doubt if it gets fired twice when creating users in the Admin area, or when a user edit's their information... but I don't know for sure.
I hope this helps!

Be careful with Jonathans solution, 'customer_save_observer_executed' stays in the session, so event will not be fired again in the browser session. So it's generally a bad idea, because it will not allow to register two or more customers in a row(actually, it will, but events will not be fired)
I suggest the following solution:
public function customerRegister(Varien_Event_Observer $observer)
{
$customer = $observer->getEvent()->getCustomer();
if (!$customer->getId())
return $this;
if(Mage::registry('customer_save_observer_executed_'.$customer->getId()))
return $this;
//your code goes here
Mage::register('customer_save_observer_executed_'.$customer->getId(),true);
}

I used a static var:
private static $_handleCustomerFirstSearchCounter = 1;
public function Savest($observer) {
if (self::$_handleCustomerFirstSearchCounter > 1) {
return $this;
}
$customerData = Mage::getSingleton('customer/session')->getCustomer();
$model = Mage::getModel('customerst/customerst')
->setQueryText(Mage::app()->getRequest()->getParam('q'))
->setCustomerId($customerData->getId())
->setCustomerName($customerData->getName())
->save();
self::$_handleCustomerFirstSearchCounter++;
}

The difference between these 2 events is one of them can't get customer info, while the other can. So the solution is
public function email_CustomerRegister(Varien_Event_Observer $observer){
$customer = Mage::getSingleton('customer/session')->getCustomer();
$customer_email = $customer->getEmail();
if(empty($customer_email)){
return;
}
// do something
}

Related

DDD - how to deal with get-or-create logic in Application Layer?

I have an DailyReport Entity in my Domain Layer. There are some fields in this object:
reportId
userId
date
tasks - Collection of things that user did in given day;
mood - how does the user felt during the whole day;
Also, there are some methods in my Application Service:
DailyReportService::addTaskToDailyReport
DailyReportService::setUserMoodInDailyReport
The thing is that both of these methods require DailyReport to be created earlier or created during function execution. How to deal with this situation?
I have found 2 solutions:
1 Create new DailyReport object before method dispatching, and after that pass reportId to them:
//PHP, simplified
public function __invoke() {
$taskData = getTaskData();
/** #var $dailyReport DailyReport|null **/
$dailyReport = $dailyReportRepository->getOneByDateAndUser('1234-12-12', $user);
//there were no report created today, create new one
if($dailyReport === null) {
$dailyReport = new DailyReport('1234-12-12', $user);
$dailyReportRepository->store($dailyReport);
}
$result = $dailyReportService->addTaskToDailyReport($taskData, $dailyReport->reportId);
//[...]
}
This one requires to put a more business logic to my Controller which i want to avoid.
2: Verify in method that DailyReport exists, and create new one if needed:
//my controller method
public function __invoke() {
$taskData = getTaskData();
$result = $dailyReportService->addTaskToDailyReport($taskData, '1234-12-12', $user);
//[...]
}
//in my service:
public function addTaskToDailyReport($taskData, $date, $user) {
//Ensure that daily report for given day and user exists:
/** #var $dailyReport DailyReport|null **/
$dailyReport = $dailyReportRepository->getOneByDateAndUser();
//there were no report created today, create new one
if($dailyReport === null) {
$dailyReport = new DailyReport($date, $user);
$dailyReportRepository->store($dailyReport);
}
//perform rest of domain logic here
}
This one reduces complexity of my UI layer and does not expose business logic above the Application Layer.
Maybe these example is more CRUD-ish than DDD, but i wanted to expose one of my use-case in simpler way.
Which solution should be used when in these case? Is there any better way to handle get-or-create logic in DDD?
EDIT 2020-03-05 16:21:
a 3 example, this is what i am talking about in my first comment to Savvas Answer:
//a method that listens to new requests
public function onKernelRequest() {
//assume that user is logged in
$dailyReportService->ensureThereIsAUserReportForGivenDay(
$userObject,
$currentDateObject
);
}
// in my dailyReportService:
public function ensureThereIsAUserReportForGivenDay($user, $date) {
$report = getReportFromDB();
if($report === null) {
$report = createNewReport();
storeNewReport();
}
return $report;
}
//in my controllers
public function __invoke() {
$taskData = getTaskData();
//addTaskToDailyReport() only adds the data to summary, does not creates a new one
$result = $dailyReportService->addTaskToDailyReport($taskData, '1234-12-12', $user);
//[...]
}
This will be executed only when user will log in for the first time/user were logged in yesterday but this is his first request during the new day.
There will be less complexity in my business logic, i do not need to constantly checking in services/controllers if there is a report created because this has been executed
previously in the day.
I'm not sure if this is the answer you want to hear, but basically I think you're dealing with accidental complexity, and you're trying to solve the wrong problem.
Before continuing I'd strongly suggest you consider the following questions:
What happens if someone submits the same report twice
What happens if someone submits a report two different times, but in the second one, it's slightly different?
What is the impact of actually storing the same report from the same person twice?
The answers to the above questions should guide your decision.
IMPORTANT: Also, please note that both of your methods above have a small window where two concurrent requests to store the rerport would succeed.
From personal experience I would suggest:
If having duplicates isn't that big a problem (for example you may have a script that you run manually or automatically every so often that clears duplicates), then follow your option 1. It's not that bad, and for human scale errors should work OK.
If duplicates are somewhat of a problem, have a process that runs asynchronously after reports are submited, and tries to find duplicates. Then deal with them according to how your domain experts want (for example maybe duplicates are deleted, if one is newer either the old is deleted or flagged for human decision)
If this is part of an invariant-level constraint in the business (although I highly doubt it given that we're speaking about reports), and at no point in time should there ever be two reports, then there should be an aggregate in place to enforce this. Maybe this is UserMonthlyReport or whatever, and you can enforce this during runtime. Of course this is more complicated and potentially a lot more work, but if there is a business case for an invariant, then this is what you should do. (again, I doubt it's needed for reports, but I write it here in the care reports were used as an example, or for future readers).

How to efficiently fire an event each time a different page is accessed

im working on an application from my companies previous developer and im at the point now where the boss has asked me to log which user has accessed what page. So I've set up some events and listeners to get the job done as i have been doing for all the other logs but there is a problem.
If i was to fire an event the way I've been doing it up till now id have to re-write the code to fire an event for every separate method that returns a page, but right now this is the only viable option i see because then i can customise the information that i put into the event log (I need the ability to customise information that's being pushed to the event / listener). Example Below
public function showCreateAccount()
{
// NEW EVENT HERE
//I need the account types for this page and the status types
$account_types = AccountController::getTypes();
$status_types = StatusController::getTypes();
$timezone_types = TimezoneController::getTypes();
$currency_types = CurrencyController::getTypes();
return view('create.account')->with('account_types', $account_types)
->with('status_types', $status_types)
->with('currency_types', $currency_types)
->with('timezone_types', $timezone_types);
}
public function showCreateRevenue()
{
// NEW EVENT HERE...
$revenue_types = RevenueController::getTypes();
return view('create.income')->with('revenue_types', $revenue_types);
}
public function showCreateIncomeWithID($id)
{
// NEW EVENT HERE
return view('create.income')->with('account_id', $id);
}
public function showCreateExpense()
{
// NEW EVENT HERE, ETC ETC
$expense_types = ExpenseController::getTypes();
return view('create.outcome')->with('expense_types', $expense_types);
}
Now this will work fine i assume but what im worried about is that this method isnt "good practice" surely there must be an alternative to writing a new event in each and every function whilst still being able to pass relevant information about what page has been accessed?
If i am wrong please correct me if not your answers are welcome

race conditions with php sessions and ajax request

I have a shopping cart I built myself that uses session variables to maintain the state of cart across requests. I have an increment and decrement button that allows users to increase or decrease the quantity of a product in their cart. this happens via an Ajax request. The cart class operates by restoring the cart out of the session when constructed, and saving the cart back to the session when destructed.
<?php
class Cart {
/**
* constructor
*/
public function __construct(){
//restore cart
$this->restore();
}
/**
* destructor
*/
public function __destruct(){
//save cart
$this->save();
}
/**
* restore
*/
public function restore(){
//retrieve session info
if(Session::has('cart')){
//get cart
$session = Session::get('cart');
//assign session info
$this->data = ($session['data']);
$this->rates = $session['rates'];
$this->lines = $session['lines'];
}
}
/**
* save
*/
public function save(){
Session::put('cart', $this->forSession());
}
the problem I'm running into is a race condition with multiple Ajax requests. the user can hit the button many times, sending multiple Ajax requests. each request is therefore pulling the current state of the session, doing the operation, and then saving it. the problem is the previous transaction is not necessarily completed and saved when it restores the cart. my first fix was to make any subsequent Ajax requests cancel the previous one, to both cut down on unnecessary (immediately overwritten) requests, and also to help avoid this race condition. while it seemed to help, it still acts quirky. So my next thought was to attack it at the source, namely the cart class itself. my idea was to implement some type of 'locking' to prevent the cart from being accessed until a previous operation was completed. The idea looked something like this.
<?php
/**
* is cart locked
*/
public function isCartLocked(){
if(Session::get('cartLock') === 1){
sleep(1);
$this->isCartLocked();
}
}
public function restore(){
Session::put('cartLock', 0);
//check if cart is locked
$this->isCartLocked();
//lock cart
Session::put('cartLock', 1);
...
}
public function save(){
//unlock the cart
Session::put('cartLock', 0);
...
}
now the first question is, SHOULD I be doing something like this, with the locking? And then, if so, is this a decent way to handle it?
After my first attempt at it, the problem I seem to be running into is the destructor is not necessarily called all the time, which is causing my cart to stay locked, and eventually causing a timeout error.
Thanks for any help!
I'd think that you would actually want to debounce the function call to the AJAX request so that you're not concerning yourself with locking/unlocking or cancelling previous requests - just make one once you're relatively sure they're done adjusting the quantity.
I would recommend this jQuery plugin for throttling or debouncing JavaScript function calls (though jQuery actually isn't required to use this, it's just accessible through the jQuery namespace if available), assuming that's how you're making your AJAX request.
I would have to say that if you are running into race conditions then you have some issues in regards to your application design, and these sorts of issues may haunt your application for a while since concurrent accesses of the session will be smashing your session store relatively frequently
The easiest way to do something like this in my opinion would be with a database table and instead of updating a field one would be adding a row for each user event ( so a increment action would insert , , , 1. And a decrement would insert a row of -1 )
This way you can simply do a sum operation to tally your counts, and when the users checkout process is complete you can clear up the entire order in the cart table .

Symfony2 count visited sites per user

I'm trying to count how many pages a user visited during his login.
So I created a Service which is listening to the Kernel::TERMINATE Event that shall increase a counter everytime a user goes to another site within my system.
class ActivityListener implements EventSubscriberInterface {
private $doctrine;
private $session;
public function __construct(Registry $doctrine, Session $session){
$this->doctrine = $doctrine;
$this->session = $session;
}
public static function getSubscribedEvents(){
return array(
KernelEvents::TERMINATE => 'increasePageCount'
);
}
public function increasePageCount(){
$em = $this->doctrine->getManager();
$log = $em->getRepository('Bundle:UserLog')->findBy(array('sessionId' => $this->session->getId()));
if($log){
$log = $log[0];
$log->increasePageCounter(); //Function in the entity that increases the count
$em->persist($log);
$em->flush();
}
}
}
The code will be called, which is fine, but up to 10 times per pagechange, which is obviously way to much!
I also tried the Kernel::REQUEST Event, but with the same result.
What could I so that it just increases the PageCounter only once per new Page Request? (e.g. The user goes from the Billing Overview to the details of one certain bill whatsoever)
Kernel::VIEW is fired when the controller action response is returned. If you want to just track users getting from one view to another, it seems to be your best bet.
See https://github.com/symfony/HttpKernel/blob/master/HttpKernel.php#L150
Of course this is also fired if you just return a JSON Response in some ajax call or a redirect you might do. So all responses are fired. Not just renders. You might be able to filter it through the request though.
Also I would really suggest not doing user tracking inside Symfony itself. Tools like Piwik have a nice API where you can create good profiles for users and track them. There is also a Symfony Bundle for Piwik, not sure if it is still maintained or even works

Call new php function when order is complete in Magento

I have a magento site for ecommerce. When an order is placed, I need to call another function I've created in a new php file and pass the order skus, quantities and shipping address to. I'm extremely comfortable with php, but Magento is an entirely new beast for me.
Does anyone know how to call a function when an order is placed? Even just the name of the event would be helpful.
I haven't used it personally, but sales_order_place_after sounds like it might be what you're looking for. It's used in this way in this Inchoo article, which also involves doing some things as soon as an order is placed.
Here's a page on the Magento wiki about setting up an event observer, which really is just a little XML to tell Magento to run some code when that event is dispatched, and the code you want to run.
you can try sales_order_place_before and sales_order_place_after
if you are interested in the events fired, a common approach is to temporary add
Mage::log($name); in the Mage.php (app/Mage.php) like this
public static function dispatchEvent($name, array $data = array())
{
Mage::log($name);
Varien_Profiler::start('DISPATCH EVENT:'.$name);
$result = self::app()->dispatchEvent($name, $data);
Varien_Profiler::stop('DISPATCH EVENT:'.$name);
return $result;
}
this will log any event fired during a page view or action to the var/log/system.log, if you enabled logging in the backend System->Configuration>Developer->Log Settings

Categories