Skip to main content

Events and responses

Work with events, listeners, response builders, response metadata, and pipelines.

In this guide

Introduction to events for developers

Events are a method of notifying different parts of Aero of tasks that have either been completed or need completing right after something has occurred. Events are only fired when a specified listener has been assigned to it and appropriately notified.

The events are always declared first in any Service Provider, while the listeners are wrapped within the event class. This is because we might need a number of listeners for a given event, each responsible for carrying out different tasks.

See our section on events for more information.

Example of an event:

<?php

namespace Aero\Account\Events;

use Aero\Account\Models\Customer;
use Aero\Events\ManagedEvent;

class CustomerUpdated extends ManagedEvent
{
public $customer;
public $customerName;
public $customerEmail;

public static $variables = [
'customer',
'customerName',
'customerEmail',
];

public function __construct(Customer $customer)
{
$this->customer = $customer;
$this->customerName = $customer->name;
$this->customerEmail = $customer->email;
}

public function getNotifiable()
{
return $this->customer;
}
}

Introduction to response builders

Response builders are Aero's unique solution to serving storefront pages. Based on the concept of pipelines used in Laravel middleware, developers can run code on all Aero pages without the need to register files, override routes, add custom namespaces or container binding.

See our section on response builders for more information.

Introduction to pipelines

Pipelines provide a convenient way to manipulate a given payload. This payload can be an object or a simple string.

See our section on pipelines for more information

How do I make custom mail notification events?

To create an event that shows up in the Mail Notifications part of the admin your event needs to extend Aero\Events\ManagedEvent and have Aero\Events\ManagedHandler as a registered listener.

To create an event you need to create a class that extends Aero\Events\ManagedEvent.

<?php

namespace Acme\MyModule\Events;

use Aero\Events\ManagedEvent;

class MyEvent extends ManagedEvent
{
//
}

To register Aero\Events\ManagedHandler as a listener of your event you need to add your event and the managed handler to a $listen array inside of a service provider.

<?php

namespace Acme\MyModule;

use Acme\MyModule\Events\MyEvent;
use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Events\ManagedHandler;

class ServiceProvider extends ModuleServiceProvider
{
protected $listen = [
MyEvent::class => [
ManagedHandler::class,
],
];

public function setup()
{
//
}
}

What are event listeners?

Event listeners, as the name suggests, listen to events that have been assigned to them. This means that listeners have to first be manually mapped for which events they should listen to.

Mappings for Event Listeners are declared in the appropriate Service Provider, depending on what part of the platform it affects - for example, it could be a Module Service Provider or the EventServiceProvider in the app directory of Aero.

How do I add a listener to an event?

<?php

namespace App\Providers;

use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;

class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
];

/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
parent::boot();

//
}
}

The listener is always nested within the event which it affects so that it can pass on the event to the listener’s handle method. Remember that the listener always goes within the array instantiating from the event class.

What are the events available?

Account

  • AddressCreated
  • AddressDeleted
  • AddessUpdated
  • CustomerManualylCreated
  • CustomerRegistered
  • CustomerUpdated
  • CustomerChanged
  • PasswordResetRequest

Cart

  • CartEmptied
  • CartItemAdded
  • CartItemRemoved
  • CartItemUpdated
  • OrderCanceled
  • OrderClosed
  • OrderComplete
  • OrderConfirmation
  • OrderDispatched
  • OrderItemBought
  • OrderOnHold
  • OrderPartiallyDispatched
  • OrderPartiallyReturned
  • OrderPlaced
  • OrderProcessing
  • OrderReturned
  • OrderStatusUpdated
  • OrderSuccessful
  • OrderUpdated

Catalog

  • AttributesDeleted
  • AttributesUpdated
  • CategoryDeleted
  • CategoryUpdated
  • ListingCreated
  • ListingsDeleted
  • ListingsUpdated
  • ListingUpdated
  • ManufacturerUpdated
  • ProductCreated
  • ProductDeleted
  • ProductUpdated
  • TagsUpdated

Content

  • BlockCreated
  • BlockDeleted
  • BlockUpdated

Fulfillment

  • FulfillmentDispatched

Payment

  • PaymentCanceled
  • PaymentCaptured
  • PaymentFailed
  • PaymentRefunded

Redirector

  • RedirectHit
  • RedirectNotFound

Store

  • FormSubmitted

Subscription

  • SubscriptionCanceled
  • SubscriptionCardExpired
  • SubscriptionCreated
  • SubscriptionFailed
  • SubscriptionOrderFailed
  • SubscriptionPaused
  • SubscriptionPaymentFailed
  • SubscriptionPaymentSuccess
  • SubscriptionShippingFailed
  • SubscriptionSuccessful
  • SubscriptionSuccessful
  • SubscriptionUnpaused
  • SubscriptionUpcoming

How do I create a new event listener?

To create a new listener class for one of our events, we can use Artisan to scaffold the listener from the project root directory:

php artisan make:listener UpdateDetails

After adding all the necessary functionality for our listener, we have to make Aero aware of it by adding it to a listen property, wrapped within an event like so:

protected $listen = [
Registered::class => [
UpdateDetails::class,
]
];

There is also an alternative, for events that should not be queued:

​​Event::listen(OrderPlaced::class, function ($event) {
$order = $event->order;
//...
});

Extending ManagedListener

In order to use a ManagedListener, we have to have an event extending ManagedEvent. This can be accomplished like so:

use Aero\Events\ManagedHandler;
protected $listen = [
FormSubmitted::class => [
ManagedHandler::class,
],
];

How do I create a new event?

To create a new event, we first have to scaffold the class using Artisan in the project root directory:

php artisan make:event Registered

The above command scaffolds a class into the app/Events and we can modify it accordingly to our needs.

After applying all the necessary functionality to the event, we have to also create a listener to listen to an event happening. In order for Aero to recognize the event, we have to specify it in a Service Provider of choice, for example, a module Service Provider:

protected $listen = [
Registered::class => [
// insert your listener here
]
];

Extending ManagedEvent

Aero’s ManagedEvent is an optional class made specifically for Aero events and is used mainly for mailing events. These are a special type of event as all the events that extend ManagedEvent are actions.

It’s important to note that the listener that listens to an event extending ManagedEvent, must also extend the ManagedListener class.

Variables

With ManagedEvent, we can create variables that can then be accessible in a mailing template. In essence, variables create helper text and allow the user to use data from the event itself. To define some variables, we have to create a property ‘variables’ in our event:

Adding variables
public static $variables = [
'product.slug',
'product.name',
'product.model',
'product.summary',
'product.description',
'product.thumbnail',
'product.heading',
'product.url',
'product.categories.*.name',
'product.categories.*.has_featured_image',
'product.categories.*.featured_image_file',
'product.manufacturer.name',
'product.manufacturer.has_logo',
'product.manufacturer.logo_file',
'product.attributes.*.name',
'product.attributes.*.image_file',
'product.images.*.image_file',
'product.all_images.*.image_file',
'product.variants.*.sku',
'product.variants.*.has_stock',
'product.highest_price',
'product.lowest_price',
'product.has_reductions',
];

The above variables will then be accessible in our mail template.

An alternative method of adding variables:

EventClass::addVariable(‘product.lowest_price’)

Or multiple, as an array:

EventClass::addVariables([‘product.manufacturer.name’, ‘product.lowest_price’])

How do I add a custom event to a Service Provider?

Laravel events provide an observer implementation, allowing you to listen and observe for specific tasks happening in your application. Adding a custom event to a ServiceProvider means that the application will listen to it as soon as it occurs. In terms of choosing the right ServiceProvider for the job, it breaks down into two options: a Module ServiceProvider, or any of the Providers listed under app/Providers of our shop’s root directory.

Event directory:

app/Events

Listener directory:

app/Listeners

The ServiceProvider class has a $listen property, to which we simply add on the Event that we wish to add, alongside its listener.

How do I extend responses?

Adding additional code to run on the response is as simple as calling the extend() method on the response builder class. This is typically done from within a Service Provider. If the code is unique to a particular store it could be placed in the boot method of the AppServiceProvider. If it is to be included as part of a module, then it can be added to the setup method of the module's Service Provider.

For example, if a module needs to send a variable foo to the product.twig view file for it to be outputted in the HTML, the ProductPage response builder class should be extended in the module's Service Provider as shown below:

<?php

namespace Acme\MyModule;

use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Store\Http\Responses\ProductPage;

class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
ProductPage::extend(function ($page) {
$page->setData('foo', 'bar');
});
}
}

When passing a Closure to the extend method, the $page argument is the response builder instance that will process the response.

For more advanced use cases, you can pass the response builder on for further processing in order to return the Illuminate\Http\Response instance. This is useful in situations when you may wish to modify the response before it is passed to the browser. A common use case for this is to add cookies to the response:

\Aero\Store\Http\Responses\ProductPage::extend(function ($page, $next) {
$response = $next($page);

$response->cookie('foo', 'bar');

return $response;
});

Alternatively, a fully qualified class name can be passed to the extend() method, which reduces bloat in the Service Provider and provides a better indication of what the code is responsible for. When doing so, the class must implement a handle() method:

<?php

namespace App\Http\Extensions;

class AddFooVariableToProductPage
{
public function handle($page)
{
$page->setData('foo', 'bar');
}
}
\Aero\Store\Http\Responses\ProductPage::extend(AddFooVariableToProductPage::class);

Note that this code will only evaluate when Aero routes a request to the particular response builder, i.e. code that is set to run on the ProductPage will not be processed on a request for the Homepage.

How do I access the properties of the response builder?

All response builders hold properties that were assigned to them upon creation from the request. These properties are typically the arguments that would be passed to a conventional Laravel controller and subsequently used in the code within the controller.

For example on the ProductPage, the Aero\Catalog\Models\Product model for the requested URL can be accessed as follows:

\Aero\Store\Http\Responses\ProductPage::extend(function ($page) {
$product = $page->product;

// do things with the $product model
});

How do I pass data to a response?

The most common task performed when extending responses, is setting additional data. Typically, for HTML responses that render from a view, this involves sending data to the view to be used as a variable. If the response returns JSON, it may be adding additional key/value pairs.

For example, if you needed to obtain customer reviews for a given product and output them on the product page, you could do the following, which passes a product to a 3rd party reviews API and then adds the reviews it returns to the response data, which is then passed to the product.twig view file ready to be loop around and outputted.

\Aero\Store\Http\Responses\ProductPage::extend(function ($page) {
$reviews = \Acme\ReviewsAPI::forProduct($page->product);

$page->setData('reviews', $reviews);
});

Another example is getting the latest 5 blog posts from a blog module and sending them to the homepage to be displayed:

\Aero\Store\Http\Responses\Homepage::extend(function ($page) {
$posts = \Acme\BlogPosts::latest()->limit(5)->get();

$page->setData('posts', $posts);
});

You can obtain the existing data set against the response builder using the getData() method and remove data using the removeData() method.

How do I change the response view?

For responses that return HTML, a Twig view file is used. Whilst these are originally defined, the view can be changed. For example, to change the product page view based on product data:

\Aero\Store\Http\Responses\ProductPage::extend(function ($page) {
if ($view = $page->product->additional('view')) {
$page->setView($view);
}
});

Setting the view to null will result in a JSON response.

You can get the current view for a response using $page->getView().

How do I set the response status code?

There may be situations where the HTTP response status needs to be altered. By default, the returned status is 200, however, this can be changed by passing the new status code to the setStatus() method:

\Aero\Store\Http\Responses\ProductPage::extend(function ($page) {
$page->setStatus(403);
});

How do I redirect a response?

Sometimes you may wish to redirect the user to another location, either to an external site or within the store. To do so, pass the URL, route(), or an Illuminate\Http\RedirectResponse instance to the setRedirect() method. For example, if you stored the age of customers (computed from their provided date of birth), and needed to prevent access to those under the age of 18:

\Aero\Store\Http\Responses\ProductPage::extend(function ($page) {
if ($page->request->customer()->age < 18) {
$page->setRedirect('/denied');
}
});

As extension code is evaluated in the order it's added to the response builder, code that runs after this step may override the redirect. To force a redirect and prevent execution of the code that follows in the pipeline, you should return an instance of the redirect() helper:

\Aero\Store\Http\Responses\ProductPage::extend(function ($page) {
if (! $page->product->canBeViewedByIp($page->request->ip())) {
return redirect('/denied');
}
});

How do I set response headers?

When extending the response builder, you can set HTTP headers to be sent with the response:

\Aero\Store\Http\Responses\ProductPage::extend(function ($page) {
$page->setHeader('foo', 'bar');
});

How do I attach middlewear to a response builder?

Since Aero does not expose the underlying routes and controllers, middleware can be attached directly to the response builder. This allows for code to be executed before the main pipeline is run.

For example, to restrict all products from being accessible to guest visitors, you could register the auth middleware to the ProductPage response:

\Aero\Store\Http\Responses\ProductPage::middleware('auth');

When assigning middleware, you can also pass the fully qualified class name:

\Aero\Store\Http\Responses\ProductPage::middleware(\App\Http\Middleware\CheckAge::class);

Alternatively, you can provide a Closure. For example, middleware could be added to the homepage to set a tracking cookie from the referrer:

\Aero\Store\Http\Responses\Homepage::middleware(function ($request, $next) {
return tap($next($request), function ($response) use ($request) {
$response->cookie('referer', $request->header('Referer'));
});
});

Refer to the Laravel documentation for more information on middleware.

What are the available responses from the response builder?

Core

| Class

|

Description

|

View

| | --- | --- | --- | |

Homepage

|

The homepage of the store.

|

homepage.twig

| |

ProductPage

|

The product page providing information on a particular product.

|

product.twig

| |

ListingsPage

|

The listings page which displays available products for the particular criteria defined against the URL

|

listings.twig

| |

ListingsJson

|

The JSON response of listings for the particular criteria defined against the URL.

|

-

| |

SearchPage

|

The search results for a particular search term.

|

search.twig

| |

SearchJson

|

The JSON response of search results for a particular search term.

|

-

| |

CartPage

|

The cart summary page, where customers can see an overview of the items in their cart and update quantities.

|

cart.twig

| |

CartItemAdd

|

Actions to take when an item is added to the cart.

|

-

| |

CartItemUpdate

|

Actions to take when an item is updated in the cart.

|

-

| |

CartEmpty

|

Actions to take when the cart is emptied.

|

-

| |

CartDiscountCodeApply

|

Actions to take when a discount code is applied to the cart.

|

-

| |

CartDiscountCodeRemove

|

Actions to take when a discount code is removed from the cart.

| | |

InformationPage

|

A static page used to display information, for example the About Us or Terms & Conditions.

|

information-page.twig

| |

Error404Page

|

The 404 page that displays when a page does not exist.

|

errors/404.twig

| |

FormSubmit

|

Actions to take when a form is submitted.

|

-

| |

AccountOverviewPage

|

A static page used to display account overview information.

|

account/account-overview.twig

|

Admin

Catalog

| Class

|

Description

|

View

| | --- | --- | --- | |

AdminDashboardPage

|

Used to display the admin dashboard.

|

admin/dashboard.blade.php

| |

AdminCategoryCreatePage

|

Used to display the category creation page in the admin.

|

admin/catalog/categories/new.blade.php

| |

AdminCategoryEditPage

|

Used to display the category edit page in the admin

|

admin/catalog/categories/edit.blade.php

| |

AdminCategoryStore

|

Actions for creating a new category.

|

-

| |

AdminCategoryUpdate

|

Actions for updating an existing category.

|

-

| |

AdminCollectionCreatePage

|

Used to display the collection creation page in the admin.

|

admin/catalog/collections/index.blade.php

| |

AdminCollectionEditPage

|

Used to display the collection edit page in the admin.

|

admin/catalog/collection/edit.blade.php

| |

AdminCollectionStore

|

Actions for creating a new collection.

|

-

| |

AdminCollectionUpdate

|

Actions for updating an existing collection.

|

-

| |

AdminManufacturerCreatePage

|

Used to display the manufacturer creation page in the admin.

|

admin/catalog/manufacturers/new.blade.php

| |

AdminProductCreatePage

|

Used to display the product creation page in the admin.

|

admin/catalog/products/new.blade.php

| |

AdminProductStore

|

Action for creating a new product.

|

-

| |

AdminProductUpdate

|

Actions for updating an existing product.

|

-

|

Discounts

| Class

|

Description

|

View

| | --- | --- | --- | |

AdminDiscountCreatePage

|

Used to display the discount creation page in the admin.

| | |

AdminDiscountEditPage

|

Used to display the discount edit page in the admin.

|

admin/discounts/edit.blade.php

| |

AdminDiscountStore

|

Actions for creating a new discount.

|

-

| |

AdminDiscountUpdate

|

Actions for updating an existing discount.

|

-

|

Orders

| Class

|

Description

|

View

| | --- | --- | --- | |

AdminOrderViewPage

|

Used to display the order view page in the admin.

|

admin/orders/view.blade.php

|

What are the available pipelines

Class

Description

Payload

CartItemBuilder

The process that occurs when adding an item to the cart. Extending this class allows for manipulation of the cart item before it is saved to the cart.

Aero\Cart\CartItem $item

ContentForHead

The HTML content that will be inserted into the <head> tag of the page.

String $content

ContentForBody

The HTML content that will be inserted just before the closing </body> tag of the page.

String $content

RobotsTxt

The robots.txt file. For more information, click here.

Illuminate\Support\Collection $content

How do I extend pipelines?

The payload data can be altered using the extend() method. This is typically done from within a Service Provider. If the code is unique to a particular store it could be placed in the boot() method of the AppServiceProvider. If it is to be included as part of a module, then it can be added to the setup method of the module's Service Provider.

For example, if a module needs to add an option to items to flag them as requiring gift wrap, the CartItemBuilder pipeline class should be extended in the module's ServiceProvider as shown below:

<?php

namespace Acme\MyModule;

use Aero\Cart\CartItemOption;
use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Store\Pipelines\CartItemBuilder;

class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
CartItemBuilder::extend(function ($item) {
if (request()->has('gift_wrap')) {
$option = CartItemOption::create('Gift wrap');

$item->addOption($option);
}
});
}
}

To adjust a payload which is a simple string, ensure that the payload variable is passed by reference so that it can be directly modified:

\Aero\Store\Pipelines\ContentForHead::extend(function (&$content) {
$content .= "";

// or ...

$content .= view('my_module::script-snippet');
});