Laravel 5.5 development learning notes

Time:2021-8-12

This blog post is used to sort out the notes of new features of laravel encountered in development.

1、 Clear cache command

php artisan optimize
php artisan cache:clear
PHP artisan config: clear // clear the configuration file cache
php artisan route:clear
php artisan view:clear

2、 Composer

modifycomposer.jsonYou need to reload with the following command:

composer dumpautoload

3、 Business

DB::transaction()Method will start a database transaction, and all SQL writes in the callback function will be included in the transaction. If the callback function throws an exception, the transaction will be rolled back automatically, otherwise the transaction will be committed. This method can help us save a lot of code.

//Start a database transaction
  $order = \DB::transaction(function() use ($user, $request){
     //Specific business
  });

4、 Exception handling

abnormalIt refers to abnormal events that occur during program operation, usually caused by external problems.
Exception handling is a common task in program development. How to handle exceptions gracefully reflects whether your program is rigorous enough to a certain extent.

We roughly divide anomalies into  User exception  and  System exceptionNext, we will explain it and code implementation.

1. Exceptions triggered by user’s wrong behavior

For example, the exception triggered when the user who has verified the mailbox in the previous chapter applies for activating the email again. For this kind of exception, we need to inform the user of the reason for triggering the exception.

We name this type of exceptionInvalidRequestException, you canmake:exceptionCommand to create:

$ php artisan make:exception InvalidRequestException

The newly created exception file is saved in theapp/Exceptions/Directory:

app/Exceptions/InvalidRequestException.php

<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Http\Request;

class InvalidRequestException extends Exception
{
    public function __construct(string $message = "", int $code = 400)
    {
        parent::__construct($message, $code);
    }

    public function render(Request $request)
    {
        if ($request->expectsJson()) {
            //The second parameter of the JSON () method is the HTTP return code
            return response()->json(['msg' => $this->message], $this->code);
        }

        return view('pages.error', ['msg' => $this->message]);
    }
}

Laravel supports definition in exception classes after 5.5render()Method, which will be called when the exception is triggeredrender()Method to output, we arerender()Judge if it isAJAXReturn on requestJSONFormat data, otherwise an error page will be returned.

Now create this error page:

$ touch resources/views/pages/error.blade.php

resources/views/pages/error.blade.php

@extends('layouts.app')
@Section ('title ',' error ')

@section('content')
<div class="panel panel-default">
    < div class = "panel heading" > error < / div >
    <div class="panel-body text-center">
        <h1>{{ $msg }}</h1>
        < a class = "BTN BTN primary" href = "{route ('root ')}}" > return to the home page</a>
    </div>
</div>
@endsection

When an exception is triggered, laravel will print the exception information and call stack to the log by default, such as:

Such exceptions are not caused by the problems of our system itself and will not affect the operation of our system. If a large number of such logs are printed into the log file, it will affect us to analyze the really problematic exceptions. Therefore, this behavior needs to be shielded.

Laravel has a built-in solution to mask the specified exception write log:

app/Exceptions/Handler.php

.
.
.
    protected $dontReport = [
        InvalidRequestException::class,
    ];
.
.
.

When an exception is triggered, laravel will check whether the type of the exception is defined in the $dontreport attribute. If so, it will not be printed to the log file.

2. System internal abnormality

For example, if the connection to the database fails, we need to tell the user what happened to such exceptions to a limited extent, but we can’t expose all the information to the user (for example, the information about the connection to the database fails will include the database address and account password). Therefore, we need to pass in two messages, one for the user to see, The other one is printed to the log for developers to see.

Create a newInternalExceptionClass:

$ php artisan make:exception InternalException

app/Exceptions/InternalException.php

<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Http\Request;

class InternalException extends Exception
{
    protected $msgForUser;

    public function __ Construct (string $message, string $msgforuser = 'system internal error', int $code = 500)
    {
        parent::__construct($message, $code);
        $this->msgForUser = $msgForUser;
    }

    public function render(Request $request)
    {
        if ($request->expectsJson()) {
            return response()->json(['msg' => $this->msgForUser], $this->code);
        }

        return view('pages.error', ['msg' => $this->msgForUser]);
    }
}

The first parameter of this exception constructor is the original exception information, such as failure to connect to the database. The second parameter is the information displayed to the user. Generally speaking, you only need to tell the user the internal error of the system, because whether it is failure to connect to MySQL or redis is the same to the user, that is, the system is unavailable, Users can’t solve any problems based on this information.

use

Next, we will replace the exception in the previous verification mailbox function with the exception we just defined.

app/Http/Controllers/EmailVerificationController.php

use App\Exceptions\InvalidRequestException;
.
.
.
    public function verify(Request $request)
    {

        $email = $request->input('email');
        $token = $request->input('token');
        if (!$email || !$token) {
            Throw new invalidrequestexception ('incorrect verification link ');
        }
        if ($token != Cache::get('email_verification_'.$email)) {
            Throw new invalidrequestexception ('validation link is incorrect or expired ');
        }
        if (!$user = User::where('email', $email)->first()) {
            Throw new invalidrequestexception ('user does not exist ');
        }
        .
        .
        .
    }
    public function send(Request $request)
    {
        $user = $request->user();
        if ($user->email_verified) {
            Throw new invalidrequestexception ('you have verified the mailbox ');
        }
        .
        .
        .
    }

5、 Delayed task

Laravel provides the delayed job function to solve the problem that shopping carts occupy inventory for a long time. When our system triggers a delayed task, laravel will use the current time plus the delay time of the task to calculate the timestamp that the task should be executed, and then serialize the timestamp and task information into the queue. Laravel’s queue processor will constantly query and execute the tasks in the queue that meet the expected execution time equal to or earlier than the current time.

1. Create task

We passmake:jobCommand to create a task:

$ php artisan make:job CloseOrder

The created task class is saved inapp/JobsDirectory, now edit the task class just created:

app/Jobs/CloseOrder.php

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Models\Order;

//Represents that this class needs to be put into the queue for execution, rather than executed immediately when triggered
class CloseOrder implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $order;

    public function __construct(Order $order, $delay)
    {
        $this->order = $order;
        //Set the delay time. The parameter of the delay () method represents the number of seconds after execution
        $this->delay($delay);
    }

    //Define the specific execution logic of this task class
    //The handle () method is called when the queue processor takes a task from the queue
    public function handle()
    {
        //Judge whether the corresponding order has been paid
        //If the payment has been made, you do not need to close the order and exit directly
        if ($this->order->paid_at) {
            return;
        }
        //Execute SQL through transactions
        \DB::transaction(function() {
            //Mark the closed field of the order as true to close the order
            $this->order->update(['closed' => true]);
            //Cycle through the SKUs in the order and add the quantity in the order back to the SKU inventory
            foreach ($this->order->items as $item) {
                $item->productSku->addStock($item->amount);
            }
        });
    }
}

2. Trigger task

Next, we need to trigger this task after creating the order:

app/Http/Controllers/OrdersController.php

use App\Jobs\CloseOrder;
    .
    .
    .
    public function store(Request $request)
    {
        .
        .
        .
        $this->dispatch(new CloseOrder($order, config('app.order_ttl')));

        return $order;
    }

The delay time of the second parameter of the closeorder constructor is read from the configuration file. To facilitate our test, set this value to 30 seconds:

config/app.php

'order_ttl' => 30,

3. Test

By default, it is generated by laravel.envThe file sets the queue driver tosync(synchronization). In the synchronization mode, the delayed task will be executed immediately, so you need to change the queue driver toredis

.env

QUEUE_DRIVER=redis

To use redis as a queue driver, we also need to introducepredis/predisThis bag

$ composer require predis/predis

Next, start the queue processor:

$ php artisan queue:work

Laravel 5.5 development learning notes

6、 Permission control

For the sake of safety, we only allow the creator of the order to see the corresponding order information. This requirement can be passed throughAuthorization policy class(Policy).

adoptmake:policyCommand to create an authorization policy class:

$ php artisan make:policy OrderPolicy

app/Policies/OrderPolicy.php

<?php

namespace App\Policies;

use App\Models\Order;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class OrderPolicy
{
    use HandlesAuthorization;

    public function own(User $user, Order $order)
    {
        return $order->user_id == $user->id;
    }
}

Then inAuthServiceProviderRegister this policy in:

app/Providers/AuthServiceProvider.php

use App\Models\Order;
use App\Policies\OrderPolicy;
.
.
.
    protected $policies = [
        UserAddress::class => UserAddressPolicy::class,
        Order::class       => OrderPolicy::class,
    ];

Finally in[email protected]()Medium check permission:

appHttp/Controllers/OrdersController.php

public function show(Order $order, Request $request)
    {
        //Permission verification
        $this->authorize('own', $order);
        return view('orders.show', ['order' => $order->load(['items.productSku', 'items.product'])]);
    }

7、 Encapsulate business code

Generally, the business is relatively simple at the beginning of the project. We all write the business logic in the controller, but with the increase of time,We will find that we write a lot of business code containing complex logic in the controller, which is a bad habit. In this way, with the increase of demand, our controller will soon become bloated。 If we want to develop the app side in the future, these codes may need to be repeated in API’s controller. If there are business logic adjustments, we need to modify two or more places, which is obviously unreasonable. So we need toLogical complexityYesBusiness codePackage.

Here we will use it in the projectServicePattern to encapsulate the code. Shopping cart logic, placed inCartServiceClass, place the business logic code of the order inOrderServiceInside.

Take the order of e-commerce project as an example:

1. Shopping cart

First create aCartServiceClass:

$ mkdir -p app/Services && touch app/Services/CartService.php

app/Services/CartService.php

<?php

namespace App\Services;

use Auth;
use App\Models\CartItem;

class CartService
{
    public function get()
    {
        return Auth::user()->cartItems()->with(['productSku.product'])->get();
    }

    public function add($skuId, $amount)
    {
        $user = Auth::user();
        //Query whether the item is already in the shopping cart from the database
        if ($item = $user->cartItems()->where('product_sku_id', $skuId)->first()) {
            //If it exists, the product quantity is directly superimposed
            $item->update([
                'amount' => $item->amount + $amount,
            ]);
        } else {
            //Otherwise, create a new cart record
            $item = new CartItem(['amount' => $amount]);
            $item->user()->associate($user);
            $item->productSku()->associate($skuId);
            $item->save();
        }

        return $item;
    }

    public function remove($skuIds)
    {
        //You can pass a single ID or an ID array
        if (!is_array($skuIds)) {
            $skuIds = [$skuIds];
        }
        Auth::user()->cartItems()->whereIn('product_sku_id', $skuIds)->delete();
    }
}

Next we’re going to modify itCartController, change it to call theCartServiceClass:

app/Http/Controllers/CartController.php

<?php

namespace App\Http\Controllers;

use App\Models\ProductSku;
use Illuminate\Http\Request;
use App\Http\Requests\AddCartRequest;
use App\Services\CartService;

class CartController extends Controller
{
    protected $cartService;

    //Use the automatic parsing function of laravel to inject the cartservice class
    public function __construct(CartService $cartService)
    {
        $this->cartService = $cartService;
    }

    public function index(Request $request)
    {
        // select * from product_skus where id in (xxxx)
        $cartItems = $this->cartService->get();

        $addresses = $request->user()->addresses()->orderBy('last_used_at', 'desc')->get();

        return view('cart.index', ['cartItems' => $cartItems, 'addresses' => $addresses]);
    }

    public function add(AddCartRequest $request)
    {
        $this->cartService->add($request->input('sku_id'), $request->input('amount'));

        return [];
    }



    public function remove(ProductSku $sku, Request $request)
    {
        $this->cartService->remove($sku->id);
        return [];
    }
}

Here, we use the automatic parsing function of the laravel container. When laravel initializes the controller class, it will check the constructor parameters of the class. In this example, laravel will automatically create a cartservice object and pass it to the cartcontroller as a construction parameter.

2. Order

Original order controller

app/Http/Controllers/OrdersController.php

<?php

namespace App\Http\Controllers;

use App\Http\Requests\OrderRequest;
use App\Models\ProductSku;
use App\Models\UserAddress;
use App\Models\Order;
use Carbon\Carbon;
use Illuminate\Http\Request;
use App\Exceptions\InvalidRequestException;
use App\Jobs\CloseOrder;
use App\Services\CartService;

class OrdersController extends Controller
{
    public function show(Order $order, Request $request)
    {

        //Permission verification
        $this->authorize('own', $order);

        //The load () method here is somewhat similar to the with () preloading method described in the previous chapter, which is called delayed preloading
        //The difference is that load () is called on the queried model, while with () is called on the ORM query constructor.

        return view('orders.show', ['order' => $order->load(['items.productSku', 'items.product'])]);
    }

    public function index(Request $request)
    {
        $orders = Order::query()
            //Preload using the with method to avoid the N + 1 problem
            ->with(['items.product', 'items.productSku'])
            ->where('user_id', $request->user()->id)
            ->orderBy('created_at', 'desc')
            ->paginate();

        return view('orders.index', ['orders' => $orders]);
    }

    //Use the automatic parsing function of laravel to inject the cartservice class
    public function store(OrderRequest $request, CartService $cartService)
    {
        $user = $request->user();

        //Start a database transaction
        $order = \DB::transaction(function() use ($user, $request){
            $address = UserAddress::find($request->input('address_id'));

            //Update the last use time of this address
            $address->update(['last_used_at' => Carbon::now()]);

            //Create an order
            $order   = new Order([
                'address' = > [// put the address information into the order
                    'address'       => $address->full_address,
                    'zip'           => $address->zip,
                    'contact_name'  => $address->contact_name,
                    'contact_phone' => $address->contact_phone,
                ],
                'remark'       => $request->input('remark'),
                'total_amount' => 0,
            ]);

            //The order is associated with the current user
            $order->user()->associate($user);

            //Write to database
            $order->save();

            $totalAmount = 0;
            $items       = $request->input('items');
            //Traverse SKUs submitted by users
            foreach ($items as $data) {
                $sku  = ProductSku::find($data['sku_id']);
                //Create an orderitem and directly associate it with the current order
                $item = $order->items()->make([
                    'amount' => $data['amount'],
                    'price'  => $sku->price,
                ]);
                $item->product()->associate($sku->product_id);
                $item->productSku()->associate($sku);
                $item->save();
                $totalAmount += $sku->price * $data['amount'];

                //Inventory reduction
                if ($sku->decreaseStock($data['amount']) <= 0) {
                    Throw new invalidrequestexception ('the inventory of this commodity is insufficient ');
                }
            }

            //Update total order amount
            $order->update(['total_amount' => $totalAmount]);

            //Remove the ordered item from the shopping cart
            $skuIds = collect($request->input('items'))->pluck('sku_id');
            // $user->cartItems()->whereIn('product_sku_id', $skuIds)->delete();
            $cartService->remove($skuIds);

            return $order;

        });

        $this->dispatch(new CloseOrder($order, config('app.order_ttl')));

        return $order;
    }
}

Encapsulating service classes

Create firstOrderServiceClass:

$ touch app/Services/OrderService.php

app/Services/OrderService.php

<?php

namespace App\Services;

use App\Models\User;
use App\Models\UserAddress;
use App\Models\Order;
use App\Models\ProductSku;
use App\Exceptions\InvalidRequestException;
use App\Jobs\CloseOrder;
use Carbon\Carbon;

class OrderService
{
    public function store(User $user, UserAddress $address, $remark, $items)
    {
        //Start a database transaction
        $order = \DB::transaction(function () use ($user, $address, $remark, $items) {
            //Update the last use time of this address
            $address->update(['last_used_at' => Carbon::now()]);
            //Create an order
            $order   = new Order([
                'address' = > [// put the address information into the order
                    'address'       => $address->full_address,
                    'zip'           => $address->zip,
                    'contact_name'  => $address->contact_name,
                    'contact_phone' => $address->contact_phone,
                ],
                'remark'       => $remark,
                'total_amount' => 0,
            ]);
            //The order is associated with the current user
            $order->user()->associate($user);
            //Write to database
            $order->save();

            $totalAmount = 0;
            //Traverse SKUs submitted by users
            foreach ($items as $data) {
                $sku  = ProductSku::find($data['sku_id']);
                //Create an orderitem and directly associate it with the current order
                $item = $order->items()->make([
                    'amount' => $data['amount'],
                    'price'  => $sku->price,
                ]);
                $item->product()->associate($sku->product_id);
                $item->productSku()->associate($sku);
                $item->save();
                $totalAmount += $sku->price * $data['amount'];
                if ($sku->decreaseStock($data['amount']) <= 0) {
                    Throw new invalidrequestexception ('the inventory of this commodity is insufficient ');
                }
            }
            //Update total order amount
            $order->update(['total_amount' => $totalAmount]);

            //Remove the ordered item from the shopping cart
            $skuIds = collect($items)->pluck('sku_id')->all();
            app(CartService::class)->remove($skuIds);

            return $order;
        });

        //Here we use the dispatch function directly
        dispatch(new CloseOrder($order, config('app.order_ttl')));

        return $order;
    }
}

Most of the code here is fromOrdersControllerThere are only a few changes to be noted:

  • 1. The $user and $address variables are changed to be obtained from parameters.We must pay attention to one thing when packaging functions$request cannot appear outside the controller and middleware. According to the principle of single responsibility, the task of obtaining data should be completed by the controller. The encapsulated class only needs to focus on the implementation of business logic
  • 2. The calling method of cartservice is changed to throughapp()Function is created because the store () method is called manually and cannot be injected through the automatic parsing of the laravel container. It must not be used when calling encapsulated libraries in our codenewKeyword to initialize, butIt should be initialized through the container of laravel, because the constructor of cartservice class may change in the later development process, such as injecting other classes. If we use new to initialize, we need to modify every place where this class is called; Using app () or automatic parsing and injection, laravel will automatically help us deal with these dependencies.
  • 3. Previously in the controller$this->dispatch()Method to trigger the task class, but there is no such method in our encapsulated class, so the task class that closes the order is changed todispatch()Auxiliary function to trigger.

Modified controller

app/Http/Controllers/OrdersController.php

<?php

namespace App\Http\Controllers;

use App\Http\Requests\OrderRequest;
use App\Models\UserAddress;
use App\Models\Order;
use Illuminate\Http\Request;
use App\Services\OrderService;

class OrdersController extends Controller
{
    .
    .
    .
    public function store(OrderRequest $request, OrderService $orderService)
    {
        $user    = $request->user();
        $address = UserAddress::find($request->input('address_id'));

        return $orderService->store($user, $address, $request->input('remark'), $request->input('items'));
    }
}

3. About service mode

The service mode writes the business logic of PHP into the service class of corresponding responsibility to solve the problem of bloated controller。 And meetSOLIDAccording to the single responsibility principle, cartservice is responsible for the logic of shopping cart, not cartcontroller. The controller is the dispatching center, and the coding logic is clearer. Later, if we have API or other requirements that will use the shopping cart function, we can also directly use cartservice, and the code reusability will be greatly increased. In addition, the service can make use of the dependency injection mechanism provided by laravel, which greatly improves the testability of part of the service code, and the better the robustness of the program.

8、 Container

containerIs an important concept in modern PHP development. Laravel is built on the basis of container. We will inject the payment operation class instance into the container, which can be directly passed in future codeapp('alipay')To get the corresponding instance without having to recreate it every time.

In this example, we introduce a third-party payment libraryyansongda/payThe container can then be used to call the instance code directly.

1. Import payment Library

yansongda/payThis library encapsulates the interface between Alipay and WeChat. Through this library, we do not need to pay attention to the interface differences of different payment platforms, use the same methods and parameters to complete the payment function, and save development time.

First passcomposerIntroduce this package:

$ composer require yansongda/pay

Configuration parameters:
Create a new profile to save the parameters required for payment:
config/pay.php

<?php

return [
    'alipay' => [
        'app_id'         => '',
        'ali_public_key' => '',
        'private_key'    => '',
        'log'            => [
            'file' => storage_path('logs/alipay.log'),
        ],
    ],

    'wechat' => [
        'app_id'      => '',
        'mch_id'      => '',
        'key'         => '',
        'cert_client' => '',
        'cert_key'    => '',
        'log'         => [
            'file' => storage_path('logs/wechat_pay.log'),
        ],
    ],
];

2. Container creation

We usuallyAppServiceProviderYesregister()Method to inject an instance into the container:

app/Providers/AppServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Monolog\Logger;
use Yansongda\Pay\Pay;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //Inject a singleton object named Alipay into the service container
        $this->app->singleton('alipay', function () {
            $config = config('pay.alipay');

            // $config['notify_url'] = route('payment.alipay.notify');
            $config['notify_url'] = 'http://requestbin.leo108.com/1nj6jt11';
            $config['return_url'] = route('payment.alipay.return');

            //Judge whether the current project operation environment is online
            if (app()->environment() !== 'production') {
                $config['mode']         = 'dev';
                $config['log']['level'] = Logger::DEBUG;
            } else {
                $config['log']['level'] = Logger::WARNING;
            }
            // call Yansongda\Pay to create a Alipay payment object.
            return Pay::alipay($config);
        });

        $this->app->singleton('wechat_pay', function () {
            $config = config('pay.wechat');
            if (app()->environment() !== 'production') {
                $config['log']['level'] = Logger::DEBUG;
            } else {
                $config['log']['level'] = Logger::WARNING;
            }
            //Call yansongda \ pay to create a wechat payment object
            return Pay::wechat($config);
        });
    }
}

Code parsing:

  • $this->app->singleton()Inject a into the service containerSingleton object, when the object is fetched from the container for the first time, the callback function will be called to generate the corresponding object and save it to the container, and then the object in the container will be returned directly.
  • app()->environment()Get the currently running environment, and the online environment will return to production. For Alipay, if the project running environment is not an online environment, the development mode is enabled, and the log level is set to DEBUG. Since wechat payment has no development mode, only the log level is set to debug.

3. Testing

Next, let’s test the instance just injected into the container and enter Tinker:

> php artisan tinker

Then enter them separatelyapp('alipay')andapp('wechat_pay')

Laravel 5.5 development learning notes

You can see that it’s OK.

9、 Event and listener

Laravel’seventIt provides a simple observer implementation, which can subscribe to and listen to various events in the application. The event classes are saved in the app / events directory, and the listeners of these events are saved in the app / listeners directory.These directories are automatically created only when you use the artisan command to generate events and listeners

Event mechanism is a good applicationDecoupling modeBecause an event can have multiple independent listeners.
For example, in our order system, after payment, we need to increase the sales volume of the goods in the order. For example, we need to send an email to users to inform them that the order has been paid successfully.

Increasing sales of goods and sending emails will not affect the payment status of orders,Even if these two operations fail, it will not affect our subsequent business processes, we usually useAsynchronous eventTo solve it.

1. Create payment success event

php artisan make:event OrderPaid

app/Events/OrderPaid.php

<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use App\Models\Order;

class OrderPaid
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    protected $order;

    public function __construct(Order $order)
    {
        $this->order = $order;
    }

    public function getOrder()
    {
        return $this->order;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('channel-name');
    }
}

The event itself does not need to be logical. It only needs to contain relevant information. In our scenario, only one order object is required

Next, we trigger this event in the server callback of successful payment:
app/Http/Controllers/PaymentController.php

use App\Events\OrderPaid;
.
.
.
    public function alipayNotify()
    {
        .
        .
        .
        $this->afterPaid($order);

        return app('alipay')->success();
    }

    protected function afterPaid(Order $order)
    {
        event(new OrderPaid($order));
    }

2. Create listener

We hope that after the order is paid, the corresponding commodity sales will increase accordingly, so we create a listener to update the commodity sales:

> php artisan make:listener UpdateProductSoldCount --event=OrderPaid

app/Listeners/UpdateProductSoldCount.php

<?php

namespace App\Listeners;

use App\Events\OrderPaid;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Models\OrderItem;

//Implements shouldqueue means that this listener executes asynchronously
class UpdateProductSoldCount implements ShouldQueue
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *Laravel will execute the listener's handle method by default, and the triggered event will be used as the parameter of the handle method
     * @param  OrderPaid  $event
     * @return void
     */
    public function handle(OrderPaid $event)
    {
        //Get the corresponding order from the event object
        $order = $event->getOrder();

        //Loop through the items of the order
        foreach($order->items as $item)
        {
            $product = $item->product;

            //Calculate the sales volume of corresponding goods
            $soldCount = OrderItem::query()
                ->where('product_id', $product->id)
                ->whereHas('order', function ($query) {
                    $query->whereNotNull('paid_ at');  //  The associated order status is paid
                })->sum('amount');

            //Update product sales
            $product->update([
                'sold_count' => $soldCount,
            ]);
        }
    }
}

3. Correlate events and listeners

Don’t forget toEventServiceProviderAssociate events with listeners:

app/Providers/EventServiceProvider.php

<?php

namespace App\Providers;

use App\Listeners\RegisteredListener;
use Illuminate\Auth\Events\Registered;
use Illuminate\Support\Facades\Event;

use App\Events\OrderPaid;
use App\Listeners\UpdateProductSoldCount;
use App\Listeners\SendOrderPaidMail;

use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [

        //After the listener is created, you need to associate the event with the listener in the eventserviceprovider to take effect
        // @url https://laravel-china.org/courses/laravel-shop/1584/verification-mailbox-below
        Registered::class => [
            RegisteredListener::class,
        ],

        OrderPaid::class => [
            UpdateProductSoldCount::class,
            SendOrderPaidMail::class,
        ],
    ];

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

        //
    }
}

4. Testing

Since the event listeners we defined are asynchronous, we need to start the queue processor before testing:

> php artisan queue:work

Find an order that has been paid successfully from the database and record its ID:

Laravel 5.5 development learning notes

Then enter Tinker in the terminal:

php artisan tinker

The event of successful order payment is triggered in Tinker. The order corresponding to the event is the one we just found in the database:

>>> event(new App\Events\OrderPaid(App\Models\Order::find(16)))

Laravel 5.5 development learning notes

At this time, you can see the output in the window for starting queue processing:

Laravel 5.5 development learning notes

You can see that the event listener that updates the inventory is already executing in the queue.

10、 MySQL command to export data

Because this is a one-time job, there is no need to write code to deal with import and export, so we choose to use it directlymysqldumpThis command-line program is suitable for exporting data in the database in terms of cost:

mysqldump -t laravel-shop admin_menu admin_permissions admin_role_menu admin_role_permissions admin_role_users admin_roles admin_user_permissions admin_users > database/admin.sql

Command resolution:

  • -The T option means that the data table structure will not be exported. The structure of these tables will be created through laravel’s migration migration file;
  • Laravel shop represents the name of the database to be exported, followed by the list of tables to be exported;
  • Database / admin.sql saves the exported contents to the database / admin.sql file.

In the homestead environment, we do not need an account password to execute MySQL related commands, because homestead has been configured for us. When executing the MySQL command online, you need to specify the account password through the – U and – P parameters on the command line, such as mysqldump – uroot – p123456 laravel shop > database / admin.sql

Laravel 5.5 development learning notes