Appearance
Basket
In this chapter, we'll build a complete shopping basket feature from the ground up. This is a core part of any e-commerce application, and we'll see how various Livewire and Laravel features come together to create a seamless user experience.
We will cover:
- Adding and removing records from a session-based basket.
- Displaying basket contents and totals.
- Creating a mini-basket in the navigation bar that updates in real-time.
- Implementing a checkout process with a shipping information form.
- Storing the final order in the database and updating stock levels.
- Sending an order confirmation email to the customer and administrators.
Preparation
Create the Basket Component
First, let's create the main Livewire component that will handle the shopping basket page.
- Open your terminal in the project directory.
- Execute the following Artisan command to generate a new Livewire component named
Basket
:
bash
php artisan livewire:make Basket
1
This command creates two essential files:
app/Livewire/Basket.php
: The component class where all the logic for our basket will reside.resources/views/livewire/basket.blade.php
: The Blade view that will render the basket's user interface.
Add a New Route
To make our new Basket
component accessible via a URL, we need to add a route to it.
- Open the
routes/web.php
file. - Add a new route that points the
/basket
URL to ourBasket
component class.
php
Route::view('/', 'home')->name('home');
Route::get('shop', Shop::class)->name('shop');
Route::view('contact', 'contact')->name('contact');
Route::get('basket', Basket::class)->name('basket');
...
1
2
3
4
5
2
3
4
5
Create a Basket-log Component
During development, it's incredibly helpful to see what's happening inside our basket's session data. We'll create a dedicated debug component for this purpose.
- Create a new file inside the components folder:
resources/views/components/itf/basket-log.blade.php
- Paste the following code inside the component:
basket-log.blade.php code
This (debug) code is only visible when the APP_DEBUG
environment variable is set to true
(not in production) and can be removed later. It uses our Cart
helper to display the current state of the basket, including all records, keys, and totals.
php
@php
use App\Helpers\Cart;
@endphp
@props([
'record' => 15 // default value is 15
])
{{-- show this (debug) code only in development APP_ENV=local --}}
@env('local')
<div x-data="{ show: true }"
class="border p-4 bg-yellow-50 dark:bg-yellow-600 mt-8">
<p class="font-bold cursor-pointer" @click="show = !show">What's inside my basket?</p>
<div x-show="show" x-cloak>
@php
$detailedCartData = [
'Cart::getCart()' => Cart::getCart(),
'Cart::getRecords()' => Cart::getRecords(),
'Cart::getOneRecord(' . $record . ')' => Cart::getOneRecord((int)$record),
];
$inlineCartData = [
'Cart::getKeys()' => Cart::getKeys(),
'Cart::getTotalPrice()' => Cart::getTotalPrice(),
'Cart::getTotalQty()' => Cart::getTotalQty(),
];
@endphp
@foreach ($detailedCartData as $label => $data)
<hr class="my-4">
<p class="text-rose-800 dark:text-red-100 font-bold">{{ $label }}:</p>
<pre class="text-sm">@json($data, JSON_PRETTY_PRINT)</pre>
@endforeach
<hr class="my-4">
@foreach ($inlineCartData as $label => $data)
<p>
<span class="text-rose-800 dark:text-red-100 font-bold pr-2">{{ $label }}:</span>
@json($data, JSON_PRETTY_PRINT)
</p>
@endforeach
</div>
</div>
@endenv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Basic Scaffolding for the View
Let's set up the initial Blade view for our Basket
component.
- Open the
resources/views/livewire/basket.blade.php
file. - Replace the existing content with the following code. This sets the page title and includes our handy debugging components.
php
<div>
<x-slot:title>Your Shopping Basket</x-slot:title>
<x-slot:description>Your Shopping Basket</x-slot:description>
<x-itf.basket-log/>
<x-itf.livewire-log/>
</div>
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Create a MiniBasket Component
A common feature in online shops is a small indicator in the navigation bar showing the number of items in the cart. We'll create a MiniBasket
component for this. It will automatically update whenever the basket's content changes.
- Create the new component using the Artisan command:
bash
php artisan make:livewire Patrials/MiniBasket
1
Now, update the generated component files.
- app/Livewire/partials/MiniBasket.php.
- This component has a single purpose: to render the total quantity of items in the basket.
- The
#[On('basket-updated')]
attribute is the key to its magic. It tells Livewire to listen for a browser event namedbasket-updated
. - Whenever this event is dispatched from another component, the
render()
method ofMiniBasket
will be re-executed, fetching the latest count fromCart::getTotalQty()
and updating the view.
- resources/views/livewire/partials/mini-basket.blade.php.
- The view is very simple. It checks if the
$totalQty
is greater than zero. - If it is, it displays a small badge with the quantity. Otherwise, it renders nothing.
- The view is very simple. It checks if the
php
<?php
namespace App\Livewire\Patrials;
use Livewire\Attributes\On;
use Livewire\Component;
use App\Helpers\Cart;
class MiniBasket extends Component
{
#[On('basket-updated')]
public function render()
{
return view('livewire.patrials.mini-basket', ['totalQty' => Cart::getTotalQty()]);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Update the Navigation Bar
Now, let's integrate our new basket
route and the MiniBasket
component into the main navigation bar.
- Open the
resources/views/components/layouts/vinylshop/navbar.blade.php
file.- Find the "Basket" link and update it to use the named route.
- We will embed the
MiniBasket
component directly within the link, so the badge appears next to the text.
php
<flux:navlist variant="outline">
<flux:navlist.item icon="disc-3" href="{{ route('shop') }}">Shop</flux:navlist.item>
<flux:navlist.item icon="envelope" href="{{ route('contact') }}">Contact</flux:navlist.item>
<flux:navlist.item icon="shopping-cart" href="{{ route('basket') }}" class="!h-10">
Basket
@livewire('patrials.mini-basket')
</flux:navlist.item>
</flux:navlist>
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Add a Record to the Basket
With the preparation complete, let's implement the ability to add records to the basket from the main shop page.
Open app/Livewire/Shop.php and resources/views/livewire/shop.blade.php and add the following code:
- resources/views/livewire/shop.blade.php.
- We add a
wire:click
directive to the "Add to basket" button. - When clicked, it will call the
addToBasket
method in theShop
component, passing the ID of the specific record.
- We add a
- app/Livewire/Shop.php.
use Cart;
: We import our helper class to manage the basket session.use NotificationsTrait;
: We'll use this trait to show toast notifications.addToBasket(Record $record)
: This new public method handles the logic.Cart::add($record)
: The record is added to the user's session using our helper.$this->dispatch('basket-updated')
: A browser event is dispatched. TheMiniBasket
component will catch this and update its count.$this->toastSuccess(...)
: A success notification is displayed to the user, confirming the action.
php
<flux:button
wire:click="addToBasket({{ $record->id }})"
icon="shopping-bag" tooltip="Add to basket" variant="subtle"
class="cursor-pointer border border-zinc-200 dark:border-zinc-700"/>
1
2
3
4
2
3
4
Update the Basket Component
Now that we can add items, let's build out the main basket page to display and manage them. The page should allow users to adjust quantities, empty the basket, and eventually proceed to checkout.
The logic for the view is as follows:
- If the cart is empty, show a simple message.
- If the cart contains items:
- Display all items in a clear, responsive table.
- Provide controls to increase or decrease the quantity of each item.
- Show the total price for the entire basket.
- If the user is logged in, show a "Checkout" button.
- If the user is a guest, prompt them to log in or register to continue.
Add/Remove Items from the Basket
Let's implement the core functionality for managing item quantities and emptying the basket.
- app/Livewire/Basket.php
- We add three public methods:
decreaseQty
,increaseQty
, andemptyBasket
. - Each method uses the
Cart
helper to perform its action (delete
,add
, orempty
). - Critically, after modifying the cart, each method dispatches the
basket-updated
event. This ensures that both the main basket view and theMiniBasket
component will re-render with the latest data. - The
render()
method fetches all necessary data (records
,totalQty
,totalPrice
) from theCart
helper and passes it to the view.
- We add three public methods:
- resources/views/livewire/basket.blade.php
- The entire view is wrapped in an
if($totalQty === 0)
condition. - If the basket is empty, a simple alert is shown.
- If not, we display the basket contents. The
@guest
directive shows a login/register prompt to non-authenticated users. - A table iterates through the
$records
array, displaying details for each item. - The
+ 1
and- 1
buttons are wired to theincreaseQty
anddecreaseQty
methods, respectively. - Finally, the "Empty Your Basket" button is linked to the
emptyBasket
method, and the total price is displayed.
- The entire view is wrapped in an
Add a few records to the basket and check the result. Also test the +1
, -1
and Empty basket
buttons.
php
<?php
namespace App\Livewire;
use App\Helpers\Cart;
use App\Models\Record;
use Livewire\Component;
class Basket extends Component
{
// Decrease the quantity of a record in the basket
public function decreaseQty(Record $record)
{
Cart::delete($record);
$this->dispatch('basket-updated');
}
// Increase the quantity of a record in the basket
public function increaseQty(Record $record)
{
Cart::add($record);
$this->dispatch('basket-updated');
}
// Empty the basket
public function emptyBasket()
{
Cart::empty();
$this->dispatch('basket-updated');
}
public function render()
{
return view('livewire.basket', [
'records' => Cart::getRecords(),
'totalQty' => Cart::getTotalQty(),
'totalPrice' => Cart::getTotalPrice(),
]);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Result:
Check for Backorders
A crucial piece of business logic is handling cases where a user wants to order more items than we currently have in stock. We should allow this but clearly inform the user that these items are on backorder.
- app/Livewire/Basket.php
public $backorders = [];
: A new public property to hold information about backordered items.private function updateBackorders()
: This new method is the core of our backorder logic. It iterates through the items in the cart, compares the requested quantity (qty
) with the available quantity (stock
) from the database, and calculates any shortage. If a shortage exists, a descriptive string is added to the$backorders
array.- In the
render()
method, we now call$this->updateBackorders()
at the beginning. This ensures the backorder status is checked and updated on every component refresh.
- resources/views/livewire/basket.blade.php
- At the top of the view, we add a new section.
if(count($backorders) > 0)
: This condition checks if our backorders array contains any items.- If it does, an error-styled alert is displayed, explaining the situation and listing the specific records and quantities that are on backorder.
php
class Basket extends Component
{
public $backorders = [];
// Decrease the quantity of a record in the basket
public function decreaseQty(Record $record) { ... }
// Increase the quantity of a record in the basket
public function increaseQty(Record $record) { ... }
// Empty the basket
public function emptyBasket() { ... }
// Check for backorders
private function updateBackorders()
{
$this->backorders = [];
// Loop through the records in the basket and check if the qty is greater than the stock
foreach (Cart::getKeys() as $recordId) {
$cartRecord = Cart::getOneRecord($recordId);
$record = Record::findOrFail($recordId);
$shortage = $cartRecord['qty'] - $record->stock;
if ($shortage > 0) {
$this->backorders[] = "{$shortage} x {$record->artist} - {$record->title}";
}
}
}
function render()
{
$this->updateBackorders();
return view('livewire.basket', [
'records' => Cart::getRecords(),
'totalQty' => Cart::getTotalQty(),
'totalPrice' => Cart::getTotalPrice(),
]);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Checkout
The final step is the checkout process. We'll guide the user through providing shipping information, and then we'll process the order by saving it to the database and sending a confirmation email.
Create a Form Object for the Shipping Information
To keep our Basket
component clean, we'll encapsulate all the shipping form fields, validation, and logic into a dedicated Livewire Form Object.
- Create the form object with the Artisan command:
php artisan livewire:form ShippingForm
. - Replace the code in
app/Livewire/Forms/ShippingForm.php
with the following. It defines the properties for the shipping address and their corresponding validation rules.
php
<?php
namespace App\Livewire\Forms;
use Livewire\Attributes\Validate;
use Livewire\Form;
class ShippingForm extends Form
{
#[Validate('required')]
public $address = null;
#[Validate('required')]
public $city = null;
#[Validate('required|numeric')]
public $zip = null;
#[Validate('required')]
public $country = null;
public $notes = null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Create a Modal for the Shipping Information
We will use a modal to collect the shipping information without navigating the user away from the basket page.
- app/Livewire/Basket.php
- We introduce two new public properties:
$showModal
to control the modal's visibility, andpublic ShippingForm $form
to instantiate our new form object. showCheckoutModal()
: This method is triggered by the "Checkout" button. It resets the form object, clears any previous validation errors, and sets$showModal
totrue
.checkout()
: This method will handle the final order submission. For now, it validates the form data using$this->form->validate()
, hides the modal, and prepares for the next steps. We'll add the database and email logic here shortly.
- We introduce two new public properties:
- resources/views/livewire/basket.blade.php
- Checkout Button: Inside an
@auth
directive (so it's only visible to logged-in users), we add a "Checkout" button that callswire:click="showCheckoutModal()"
. - Checkout Modal: We add a
<flux:modal>
component, its visibility controlled bywire:model.self="showModal"
. Inside, we create a simple form with inputs for address, city, etc. Each input is bound to our form object usingwire:model.live="form.address"
. The final button in the modal callswire:click="checkout()"
.
- Checkout Button: Inside an
php
<?php
namespace App\Livewire;
use App\Helpers\Cart;
use App\Livewire\Forms\ShippingForm;
use App\Models\Record;
use App\Traits\NotificationsTrait;
use Livewire\Component;
class Basket extends Component
{
use NotificationsTrait;
public $backorders = [];
public $showModal = false;
public ShippingForm $form;
public function showCheckoutModal()
{
$this->form->reset();
$this->reset('backorders');
$this->resetErrorBag();
$this->showModal = true;
}
public function checkout()
{
// validate the form
$this->form->validate();
// hide the modal
$this->showModal = false;
// check if there are records in backorder
$this->updateBackorders();
// add the order to the database
// loop over the records in the basket and add them to the orderlines table
// update the stock
// send confirmation email to the user and to the administrators
// empty the cart
Cart::empty();
$this->dispatch('basket-updated');
// show a confirmation message
$this->toastSuccess('The records will be shipped as soon as possible.', [
'heading' => 'Thank you for your order!',
'position' => 'top-right',
'icon' => 'check-circle'
]);
}
// Decrease the quantity of a record in the basket
public function decreaseQty(Record $record) {... }
// Increase the quantity of a record in the basket
public function increaseQty(Record $record) {... }
// Empty the basket
public function emptyBasket() {... }
// Check for backorders
private function updateBackorders() { ... }
function render()
{
$this->updateBackorders();
return view('livewire.basket', [
'records' => Cart::getRecords(),
'totalQty' => Cart::getTotalQty(),
'totalPrice' => Cart::getTotalPrice(),
]);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
SPEEDUP TIPS
Testing the full checkout flow repeatedly can be tedious. To accelerate your development and debugging process, are a couple of handy tricks:
- Prevent the Cart from Emptying: To avoid re-adding records to your basket after every test order, temporarily comment out the
Cart::empty();
line in thecheckout()
method. - Pre-fill the Shipping Form: Instead of manually typing the shipping details each time, you can pre-populate the form with default data. Modify the
showCheckoutModal()
method to set these values automatically when the modal opens.
php
public function showCheckoutModal()
{
$this->form->reset();
$this->reset('backorders');
$this->resetErrorBag();
$this->showModal = true;
// for debugging only
$this->form->address = 'Kleinhoestraat 4';
$this->form->city = 'Geel';
$this->form->zip = '2440';
$this->form->country = 'Belgium';
$this->form->notes = "Please leave the package at the back door.\nThank you.";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
Add the Order to the Database
This is where the transaction becomes permanent. We'll take the information from the basket and the shipping form and create corresponding records in our orders
and orderlines
tables.
Let's break down the database logic. It's a two-part process:
- First, we insert a single new row into the
orders
table. This row will contain theuser_id
of the customer and thetotal_price
for the entire purchase. - After creating this order, we retrieve its newly generated
order_id
. - Finally, we loop through every record in the cart session. For each record, we create a new row in the
orderlines
table, using theorder_id
from the previous step to link it back to the main order.
The relationship looks like this, where one order can have many order lines:
Why duplicate data in Orderlines?
You'll notice the orderlines
table duplicates information like artist
and title
from the records
table. This is intentional and crucial for maintaining historical accuracy. If we only stored a record_id
, and later the record's price changed or it was deleted, the user's order history would become incorrect. By copying the data at the time of purchase, we create a permanent, unchangeable snapshot of the order.
In the example below: one order contains 3 different records (= 3 orderlines)
Update the checkout()
method in the Basket component:
- app/Livewire/Basket.php
- Inside the
checkout()
method, after validation, we perform the following steps:Order::create([...])
: A new row is created in theorders
table, storing theuser_id
and thetotal_price
from the cart. We capture the returnedOrder
model in the$order
variable.foreach (Cart::getRecords() as $record)
: We loop through every item in the cart.Orderline::create([...])
: For each item, a new row is created in theorderlines
table. We use the$order->id
we just created to link it to the parent order.- Stock Update: We find the original
Record
, decrease itsstock
by the quantity ordered, and save the change. A ternary operator ensures the stock never goes below zero.
- Don't forget to import the necessary models at the top of the file:
use App\Models\Order;
use App\Models\Orderline;
- Inside the
php
public function checkout()
{
// validate the form
$this->form->validate();
// hide the modal
$this->showModal = false;
// check if there are records in backorder
$this->updateBackorders();
// add the order to the database
$order = Order::create([
'user_id' => auth()->user()->id,
'total_price' => Cart::getTotalPrice(),
]);
// loop over the records in the basket and add them to the orderlines table
foreach (Cart::getRecords() as $record) {
Orderline::create([
'order_id' => $order->id,
'artist' => $record['artist'],
'title' => $record['title'],
'mb_id' => $record['mb_id'],
'total_price' => $record['price'],
'quantity' => $record['qty'],
]);
// update the stock
$updateQty = Record::findOrFail($record['id']);
$updateQty->stock > $record['qty'] ? $updateQty->stock -= $record['qty'] : $updateQty->stock = 0;
$updateQty->save();
}
// send confirmation email to the user and to the administrators
// empty the cart
Cart::empty();
$this->dispatch('basket-updated');
// show a confirmation message
$this->toastSuccess('The records will be shipped as soon as possible.', [
'heading' => 'Thank you for your order!',
'position' => 'top-right',
'icon' => 'check-circle'
]);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Confirmation Email
The final piece of the checkout process is to notify the customer and the shop administrators that a new order has been placed. We'll use Laravel's built-in mail capabilities for this.
First, create a new Mailable class: php artisan make:mail OrderConfirmation --markdown=emails.order-confirmation
- app/Livewire/Forms/ShippingForm.php
- We add a
sendEmail()
method to ourShippingForm
object. It's a good fit here since it uses the shipping details. - The method dynamically builds an HTML message string containing the order details, shipping address, and any backorder information.
- It then retrieves all admin users from the database.
- Finally, it uses the
Mail
facade to send theOrderConfirmation
mailable to the logged-in user, with the administrators CC'd. - Don't forget to import the necessary classes at the top of the file:
use App\Helpers\Cart;
use App\Mail\OrderConfirmation;
use App\Models\User;
use Mail;
- We add a
- app/Livewire/Basket.php
- Back in the
checkout()
method, we now call$this->form->sendEmail($this->backorders);
right before emptying the cart. This passes the backorder information to our new email-sending method.
- Back in the
php
<?php
namespace App\Livewire\Forms;
use App\Helpers\Cart;
use App\Mail\OrderConfirmation;
use App\Models\User;
use Livewire\Attributes\Validate;
use Livewire\Form;
use Mail;
class ShippingForm extends Form
{
#[Validate('required')]
public $address = null;
#[Validate('required')]
public $city = null;
#[Validate('required|numeric')]
public $zip = null;
#[Validate('required')]
public $country = null;
public $notes = null;
public function sendEmail($backorder)
{
$message = '<p>Thank you for your order.<br>The records will be delivered as soon as possible.</p>';
$message .= '<ul>';
foreach (Cart::getRecords() as $record) {
$message .= "<li>{$record['qty']} x {$record['artist']} - {$record['title']}</li>";
}
$message .= '</ul>';
$message .= "<p>Total price: € " . Cart::getTotalPrice() . "</p>";
$message .= '<p><b>Shipping address:</b><br>';
$message .= $this->address . '<br>';
$message .= $this->zip . ' ' . $this->city . '<br>';
$message .= $this->country . '</p>';
$message .= '<p><b>Notes:</b><br>';
$message .= $this->notes . '</p>';
if (count($backorder) > 0) {
$message .= '<p><b>Backorder:</b></p>';
$message .= '<ul>';
foreach ($backorder as $item) {
$message .= "<li>{$item}</li>";
}
$message .= '</ul>';
}
// Get all admins
$admins = User::where('admin', true)->select('name', 'email')->get();
$template = new OrderConfirmation([
'message' => $message,
]);
Mail::to(auth()->user())
->cc($admins)
->send($template);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
Now, let's configure the Mailable class and its view.
- app/Mail/OrderConfirmation.php
- We create a public property
$data
and accept an array in the constructor. This makes the data we passed from the form object available to the email's Blade view.
- We create a public property
- resources/views/emails/order-confirmation.blade.php
- This is the Markdown template for our email.
- It accesses the authenticated user's name and renders the HTML message we constructed. We use
{!! ... !!}
to ensure the HTML tags are rendered correctly and not escaped. - IMPORTANT: Do not indent the code in this file, as it can interfere with the Markdown parsing and break the email layout.
php
class OrderConfirmation extends Mailable
{
use Queueable, SerializesModels;
public $data;
/** Create a new message instance. ...*/
public function __construct($data)
{
$this->data = $data;
}
/** Get the message envelope. ...*/
public function envelope(): Envelope { ... }
/** Get the message content definition. ...*/
public function content(): Content { ... }
/** Get the attachments for the message. ...*/
public function attachments(): array { ... }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21