Magento: Observer method getCustomer() is NULL - magento

Okay so I have an observer that is observing the controller_action_postdispatch_customer_account_createpost action. My issue is that in the method I try to do the following:
public function customerSaveAfter($observer)
{
/** #var Mage_Customer_Model_Customer $customer */
$customer = $observer->getEvent()->getCustomer();
}
No matter what I do $customer is NULL. There is another extension that is called right before this and it uses that method exactly the same way and comes up with a customer. Please help.

The customer object is blank because the controller_action_postdispatch_customer_account_createpost event is a controller action event, and has nothing to do with the customer object. That event is issued in the following code
#File: app/code/core/Mage/Core/Controller/Varien/Action.php
public function postDispatch()
{
if ($this->getFlag('', self::FLAG_NO_POST_DISPATCH)) {
return;
}
Mage::dispatchEvent(
'controller_action_postdispatch_'.$this->getFullActionName(),
array('controller_action'=>$this)
);
Mage::dispatchEvent(
'controller_action_postdispatch_'.$this->getRequest()->getRouteName(),
array('controller_action'=>$this)
);
Mage::dispatchEvent('controller_action_postdispatch', array('controller_action'=>$this));
}
Specifically, the
Mage::dispatchEvent(
'controller_action_postdispatch_'.$this->getRequest()->getRouteName(),
array('controller_action'=>$this)
);
bit. ($this->getRequest()->getRouteName() returns customer_account_createpost). Notice that
array('controller_action'=>$this)
is passed into the event dispatch — this means you could access the controller object from your observer with the following
$observer->getControllerAction();
$observer->getData('controller_action');
You can also get a list of data keys variable to an observer with
var_dump(
array_keys($observer->getData())
);
The "other extension" (by which I assume you mean another extension's observer object) is probably listening to a different event, one that passes in a customer object to the event. For example, consider the customer_login event.
#File: app/code/core/Customer/Model/Session.php
public function setCustomerAsLoggedIn($customer)
{
$this->setCustomer($customer);
Mage::dispatchEvent('customer_login', array('customer'=>$customer));
return $this;
}
Here the event dispatch includes a customer object
array('customer'=>$customer)
which means the customer will be available in your observer.

Related

Attaching many to many relations while still binding to created event

So I've run into this issue a few times and now I've decided that I want to find a better solution.
For examples sake, I have two models, Order & Product. There is a many to many relation so that an order can have multiple products and a product can of course have multiple orders. Table structure looks like the below -
orders
id
more fields...
products
id
more fields...
product_orders
order_id
product_id
So when an order is created I run the following -
$order = Order::create($request->validated())
$order->products()->attach([1,2,3,4...]);
So this creates an order and attaches the relevant products to it.
However, I want to use an observer, to determine when the order is created and send out and perform related tasks off the back (send an order confirmation email, etc.) The problem being, at the time the order created observer is triggered, the products aren't yet attached.
Is there any way to do the above, establishing all the many to many relationships and creating the order at the same time so I can access linked products within the Order created observer?
Use case 1
AJAX call hits PUT /api/order which in turn calls Order::place() method. Once an order is created, an email is sent to the customer who placed the order. Now I could just put an event dispatch within this method that in turn triggers the email send but this just feels a bit hacky.
public static function place (SubmitOrderRequest $request)
{
$order = Order::create($request->validated());
$order->products()->attach($request->input('products'));
return $order;
}
Use case 2
I'm feature testing to make sure that an email is sent when an order is created. Now, this test passes (and email sends work), but it's unable to output the linked products at this point in execution.
/**
* #test
**/
public function an_email_is_sent_on_order_creation()
{
Mail::fake();
factory(Order::class)->create();
Mail::assertSent(OrderCreatedMailable::class);
}
Thanks,
Chris.
I think the solution to your problem could be transaction events as provided by this package from fntneves.
Personally, I stumbled upon the idea of transactional events for another reason. I had the issue that my business logic required the execution of some queued jobs after a specific entity had been created. Because my entities got created in batches within a transaction, it was possible that an event was fired (and the corresponding event listener was queued), although the transaction was rolled back because of an error shortly after. The result were queued listeners that always failed.
Your scenario seems comparable to me as you don't want to execute your event listeners immediately due to missing data which is only attached after the model was actually created. For this reason, I suggest wrapping your order creation and all other tasks that manipulate the order within a transaction. Combined with the usage of said package, you can then fire the model created event as the actual event listener will only be called after the transaction has been committed. The code for all this basically comes down to what you already described:
DB::transaction(function() {
$order = Order::create($request->validated());
$order->products()->attach($request->input('products'));
});
In your model, you'd simply define an OrderCreated event or use an observer as suggested in the other answer:
class Order
{
protected $dispatchesEvents = [
'created' => OrderCreated::class,
];
}
class OrderCreated implements TransactionalEvent
{
public $order;
/**
* Create a new event instance.
*
* #param \App\Order $order
* #return void
*/
public function __construct(Order $order)
{
$this->order = $order;
}
}
You can redefine boot method in your model, if product ids is static
class Order extends Eloquent {
protected static function boot() {
parent::boot();
static::saving(function ($user) {
$this->products()->attach([1,2,3,4...]);
});
}
}
Or use observers
class OrderObserver
{
public function created($model)
{
//
}
}
And register this
class EventServiceProvider extends ServiceProvider
{
public function boot(DispatcherContract $events)
{
parent::boot($events);
Order::observe(new OrderObserver());
}
}

Trouble with multiple model observers in Laravel

I'm stuck on a weird issue. It feels like in Laravel, you're not allowed to have multiple model observers listening to the same event. In my case:
Parent Model
class MyParent extends Eloquent {
private static function boot()
{
parent::boot();
$called_class = get_called_class();
$called_class::creating(function($model) {
doSomethingInParent();
return true;
}
}
}
Child Model
class MyChild extends myParent {
private static function boot()
{
parent::boot();
MyChild::creating(function($model) {
doSomethingInChild();
return true;
}
}
}
In the above example, if I do:
$instance = MyChild::create();
... the line doSomethingInChild() will not fire. doSomethingInParent(), does.
If I move parent::boot() within the child after MyChild::creating(), however, it does work. (I didn't confirm whether doSomethingInParent() fires, but I'm presuming it doesn't)
Can Laravel have multiple events registered to Model::creating()?
This one is tricky. Short version: Remove your return values from you handlers and both events will fire. Long version follows.
First, I'm going to assume you meant to type MyParent (not myParent), that you meant your boot methods to be protected, and not private, and that you included a final ) in your create method calls. Otherwise your code doesn't run. :)
However, the problem you describe is real. The reason for it is certain Eloquent events are considered "halting" events. That is, for some events, if any non-null value is returned from the event handlers (be it a closure or PHP callback), the event will stop propagating. You can see this in the dispatcher
#File: vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php
public function fire($event, $payload = array(), $halt = false)
{
}
See that third parameter $halt? Later on, while the dispatcher is calling event listeners
#File: vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php
foreach ($this->getListeners($event) as $listener)
{
$response = call_user_func_array($listener, $payload);
// If a response is returned from the listener and event halting is enabled
// we will just return this response, and not call the rest of the event
// listeners. Otherwise we will add the response on the response list.
if ( ! is_null($response) && $halt)
{
array_pop($this->firing);
return $response;
}
//...
If halt is true and the callback returned anything that's not null (true, false, a sclaer value, an array, an object), the fire method short circuits with a return $response, and the events stop propagating. This is above and beyond that standard "return false to stop event propagation". Some events have halting built in.
So, which Model events halt? If you look at the definition of fireModelEvent in the base eloquent model class (Laravel aliases this as Eloquent)
#File: vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
protected function fireModelEvent($event, $halt = true)
{
//...
}
You can see a model's events default to halting. So, if we look through the model for firing events, we see the events that do halt are
#File: vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
$this->fireModelEvent('deleting')
$this->fireModelEvent('saving')
$this->fireModelEvent('updating')
$this->fireModelEvent('creating')
and events that don't halt are
#File: vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
$this->fireModelEvent('booting', false);
$this->fireModelEvent('booted', false);
$this->fireModelEvent('deleted', false);
$this->fireModelEvent('saved', false);
$this->fireModelEvent('updated', false);
$this->fireModelEvent('created', false);
As you can see, creating is a halting event, which is why returning any value, even true, halted the event and your second listener didn't fire. Halting events are typically used when the Model class wants to do something with the return value from an event. Specifically for creating
#File: vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
protected function performInsert(Builder $query)
{
if ($this->fireModelEvent('creating') === false) return false;
//...
if you return false, (not null) from your callback, Laravel will actually skip performing the INSERT. Again, this is different behavior from the standard stop event propagation by returning false. In the case of these four model events, returning false will also cancel the action they're listening for.
Remove the return values (or return null) and you'll be good to go.

Are cake events handled asynchronously?

At the moment I don't have any queuing functionality in my Cakephp aplication. I will need that in the near future. An upload will result in a batchjob that uses external API with usage limitations, so it would be best if it was handeled in a seperate threat with a queue.
I don't have any experience with this, so I'm going to try a different, but easier, example.
User actions result in e-mails being send. At the moment, the loading of the page is delayed by the (rather long) time it takes the server to send the e-mail. I'd like to use the Event system to fix this. (I am aware I can also do this using this the afterRender function, or dispatch it to a shellTask, but that way I don't learn anything)
From the example page:http://book.cakephp.org/2.0/en/core-libraries/events.html
I've found this example:
// Cart/Model/Order.php
App::uses('CakeEvent', 'Event');
class Order extends AppModel {
public function place($order) {
if ($this->save($order)) {
$this->Cart->remove($order);
$this->getEventManager()->dispatch(new CakeEvent('Model.Order.afterPlace', $this, array(
'order' => $order
)));
return true;
}
return false;
}
}
Let's say the function was called by a controller action:
public function place_order() {
$result = $this->Order->place($this->request->data);
$this->set('result', $result);
}
Now my question... Will the corresponding view be rendered after all the dispatched events completes? or will the Model function just trigger the event and then forget about it?
The last option seems more logical to me (which also resembles the mentioned jQuery functionality in the article)
The problem is that If this were true, I don't understand the later example:
In the example about using results:
// Using the event result
public function place($order) {
$event = new CakeEvent('Model.Order.beforePlace', $this, array('order' => $order));
$this->getEventManager()->dispatch($event);
if (!empty($event->result['order'])) {
$order = $event->result['order'];
}
if ($this->Order->save($order)) {
// ...
}
// ...
}
if the event was just triggered (and then forgot about) there is no way you can asume it has modified the passed event object on the next line of code!
I would like to use cake as much as possible, but I'm not sure if I can get my desired background behavior without shellTasks and external queue. Any tips about these Cake Events?
Cake Events are triggered synchronously. When an event is triggered, all available listeners are called, before proceeding with other instructions.
You can imagine it on your second example as:
public function place($order) {
$event = new CakeEvent('Model.Order.beforePlace', $this, array('order' => $order));
$this->getEventManager()->dispatch($event); // -> all listeners are called at this point
// ... here you can assume your $event was modified
if (!empty($event->result['order'])) {
$order = $event->result['order'];
}
if ($this->Order->save($order)) {
// ...
}
// ...
}

Observe customer account verification event in Magento

Is there a way to catch the event when the customer verifies it's account? I need this feature to enable user's access to other integrated subsystem
Since confirmAction() doesnt seem to fire any events in
/app/code/core/Mage/Customer/controllers/AccountController.php
You could do either
Overriding Frontend Core Controllers to create you own event using Mage::dispatchEvent() or add code directly to confirmAction in AccountController.php
Use #Pavel Novitsky answer but you may need to check that you are on the confirm account controller or check for the changing of email verification flag, because this event will trigger every time a customer information is change/updated
eg
public function myObserver(Varien_Event_Observer $observer)
{
if(Mage::app()->getRequest()->getControllerName() == '....account_confirm'){
$customer = $observer->getCustomer();
....
}
}
Every model has standard load_before, load_after, save_before, save_after, etc. events. Look at the Mage_Core_Model_Abstract to get the list of all predefined events.
For customers you can use customer_save_after event. In observer check original data vs new data:
public function myObserver(Varien_Event_Observer $observer)
{
$customer = $observer->getCustomer();
$orig_active_flag = $custoner->getOrigData('is_active');
$new_active_flag = $customer->getData('is_active');
// do something here …
return $this;
}
Even you can create your own event after customer vefication using below code.
Mage::dispatchEvent('Yuor_Unique_Event_Name', array());
Now using this event you can do anything you want.

Accessing model through Varien_Event_Observer

I have a custom observer in Magento 1.6.2.0 that is called when a CMS page is saved or deleted (events cms_page_delete_before/cms_page_save_before). I have verified (using Mage::log()) that the observer is working, however when I try the following:
public function getCmsUrl(Varien_Event_Observer $observer)
{
$url = $observer->getEvent()->getPage()->getIdentifier();
return $url;
}
I get nothing returned (rather than "about-us" or "enable-cookies" or whatever URL path the CMS page has). The following code, however, works perfectly fine:
public function getProductUrl(Varien_Event_Observer $observer)
{
$baseUrl = $observer->getEvent()->getProduct()->getBaseUrl();
return $baseUrl;
}
Can someone let me know what the correct way of accessing a CMS page is when passed via an observer?
Thanks in advance for any help/tips/pointers :-)
The events cms_page_delete_before and cms_page_save_before are fired in Mage_Core_Model_Abstract. This it how it looks like in the beforeSave function:
Mage::dispatchEvent($this->_eventPrefix.'_save_before', $this->_getEventData());
As you can see, it uses a variable _eventPrefix to construct the event key. In the CMS page model, this is set to cms_page.
Also notice the part $this->_getEventData(). This is how the model, in this case the CMS page, is passed to the observer:
protected function _getEventData()
{
return array(
'data_object' => $this,
$this->_eventObject => $this,
);
}
As you can see, the object has two names, data_object and a name defined in a variable, _eventObject. In the product model, the name is set to product, but in the CMS page model, the variable is missing. Apparently the Magento team forgot to put this in, and as a result, the default name from the core model is used:
protected $_eventObject = 'object';
That means you can get the CMS page in your observer by using getObject:
public function myObserver(Varien_Event_Observer $observer)
{
$page = $observer->getEvent()->getObject();
}

Resources