Laravel 事件系統(一)、小試牛刀

river_bird發表於2018-09-03

回顧之前寫完的專案,突然發現laravel裡面各種奇技淫巧,自己並沒有使用到很多,甚至懷疑自己到底有沒有發揮laravel的真正“優雅”之處,於是結合文件,重新review了下程式碼,發現正如標題所說,確實有些地方可以重構,並且可以使用到laravel的優雅之道
首先貼一段我自己之前的程式碼;

 protected function editStatus($id, $status)
    {
        $route = Route::find($id);
        $this->beginTransaction();
        try {
            if ($route) {
                if ($status == 9) {
                    if ($route->status < 4 && $route->start_date >= date('Ymd', strtotime("-1 days"))) {
                        $route->cancel_date = date('Y-m-d H:i:s');
                    } else {
                        return array('status' => false, 'case' => 1);
                    }
                }
                if ($status == 5) {
                    if ($route->end_date > date('Ymd')) {
                        return array('status' => false, 'case' => 2);
                    }
                    if ($route->end_date > date('Ymd')) {
                        return array('status' => false, 'case' => 2);
                    }
                    $order = $this->select("select distinct(oa.order_id) from snb_order_activity oa join snb_order o on oa.order_id=o.id where oa.route_id={$route->id} and oa.activity_id={$route->activity_id} and o.pay_status in(1,3)");
                    $activity = DB::table('activity')->where('id', $route->activity_id)->first();
                    if ($order) {
                        $insertUsageArray = [];
                        $insertOutdoorsArray = [];
                        $settleAmount = 0;
                        foreach ($order as $value) {
                            $orderInfo = $this->findOne("select id,order_num,trade_num,total_amount,create_by,return_rate,pay_status,discount_amount from snb_order where id={$value['order_id']}");
                            $point = $activity->point;
                            $name = $activity->activity_name;
                            $levelPoint = $this->findOne("select integration_level_total from snb_app_user where id={$orderInfo['create_by']}");
                            $countPoint = $levelPoint['integration_level_total'] + $point;
                            $guestCount = DB::table('order_activity')->where('order_id', $orderInfo['id'])->count();
                            if ($orderInfo['pay_status'] == 3) {
                                $settleAmount += $route->org_price * $guestCount * (1 - $orderInfo['return_rate'] * 0.01);
                            } else {
                                $settleAmount += $route->org_price * $guestCount - $orderInfo['discount_amount'];
                            }
                            if ($countPoint) {
                                $levelName = LevelConfig::getLevelByIntegral($countPoint);
                                DB::table('app_user')->where('id', $orderInfo['create_by'])->update(
                                    array(
                                        'integration_level_total' => $countPoint,
                                        'level_name' => $levelName
                                    ));
                                $insertUsageArray[] = array(
                                    'app_user_id' => $orderInfo['create_by'],
                                    'order_id' => $orderInfo['id'],
                                    'order_num' => $orderInfo['order_num'],
                                    'trade_num' => $orderInfo['trade_num'],
                                    'integral_usage_amount' => $point,
                                    'integral_type' => 1,
                                    'remark' => '完成活動獎勵積分',
                                    'create_by' => $this->authHelpers()->getUserId(),
                                    'create_date' => date('Y-m-d H:i:s'),
                                    'update_by' => $this->authHelpers()->getUserId(),
                                    'update_date' => date('Y-m-d H:i:s')
                                );
                            }
                            $insertOutdoorsArray[] = array(
                                'user_id' => $orderInfo['create_by'],
                                'experience_cnt' => '完成活動' . $name,
                                'departure_date' => $route->start_date,
                                'create_by' => $this->authHelpers()->getUserId(),
                                'create_date' => date('Y-m-d H:i:s'),
                            );
                        }
                        $insertSettleArray = array(
                            'company_id' => $activity->company_id,
                            'route_id' => $route->id,
                            'settle_amount' => $settleAmount,
                            'settle_status' => 1,//待稽核
                        );
                        DB::table('integral_usage')->insert($insertUsageArray);
                        DB::table('outdoor_experience')->insert($insertOutdoorsArray);
                        $result = Settle::saveRecord('', $insertSettleArray);
                        if (!$result) {
                            $this->rollback();
                            return array('status' => false);
                        }
                        DB::table('order')->whereIn('id', array_column($order, 'id'))->update(array('status' => 3));
                    }
                }
                $route->status = $status;
                $route->save();
            }
        } catch (\Exception $e) {
            var_dump($e->getMessage());
            $this->rollback();
            return array('status' => false);
        }
        $this->commit();
        return array('status' => true);
    }

可以看到這個方法中有一段邏輯非常的麻煩,而且只是其中的情況下對應的邏輯,這樣的程式碼非常混亂,修改起來非常的麻煩,而且不符合laravel的優雅之說,利用php程式碼分析工具,可以看到此處file
提示覆雜度過高,因此這段程式碼有必要重構,這裡就利用到了laravel的事件系統;
首先在專案下
php artisan event:generate
生成相關事件的目錄,這時可以看到在專案下生成了Events和Listenesr兩個目錄;
首先我們在Events目錄下建立一個事件,名字叫做RouteEnd:

<?php
namespace App\Events;

use App\Models\Route;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\SerializesModels;

class RouteEnd
{
    use Dispatchable, InteractsWithSockets,SerializesModels;
    public $route;

    public function __construct(Route $route)
    {
        $this->route = $route;
    }

}

在初始化方法中,引數中接收了一個Eloquent 模型;並且設定該成員變數為接收的這個值;主要是為了傳遞這個變數到監聽器中,接下來編寫監聽器,名字為RouteEndListener;

class RouteEndListener
{
    public function handle(RouteEnd $event){

        $route = $event->route;
        $order = DB::select("select distinct(oa.order_id) from snb_order_activity oa join snb_order o on oa.order_id=o.id where oa.route_id={$route->id} and oa.activity_id={$route->activity_id} and o.pay_status in(1,3)");
        $activity = DB::table('activity')->where('id', $route->activity_id)->first();
        if ($order) {
            $insertUsageArray = [];
            $insertOutdoorsArray = [];
            $settleAmount = 0;
            foreach ($order as $value) {
                $orderInfo = $this->findOne("select id,order_num,trade_num,total_amount,create_by,return_rate,pay_status,discount_amount from snb_order where id={$value['order_id']}");
                $point = $activity->point;
                $name = $activity->activity_name;
                $levelPoint = $this->findOne("select integration_level_total from snb_app_user where id={$orderInfo['create_by']}");
                $countPoint = $levelPoint['integration_level_total'] + $point;
                $guestCount = DB::table('order_activity')->where('order_id', $orderInfo['id'])->count();
                if ($orderInfo['pay_status'] == 3) {
                    $settleAmount += $route->org_price * $guestCount * (1 - $orderInfo['return_rate'] * 0.01);
                } else {
                    $settleAmount += $route->org_price * $guestCount - $orderInfo['discount_amount'];
                }
                if ($countPoint) {
                    $levelName = LevelConfig::getLevelByIntegral($countPoint);
                    DB::table('app_user')->where('id', $orderInfo['create_by'])->update(
                        array(
                            'integration_level_total' => $countPoint,
                            'level_name' => $levelName
                        ));
                    $insertUsageArray[] = array(
                        'app_user_id' => $orderInfo['create_by'],
                        'order_id' => $orderInfo['id'],
                        'order_num' => $orderInfo['order_num'],
                        'trade_num' => $orderInfo['trade_num'],
                        'integral_usage_amount' => $point,
                        'integral_type' => 1,
                        'remark' => '完成活動獎勵積分',
                        'create_by' => $this->authHelpers()->getUserId(),
                        'create_date' => date('Y-m-d H:i:s'),
                        'update_by' => $this->authHelpers()->getUserId(),
                        'update_date' => date('Y-m-d H:i:s')
                    );
                }
                $insertOutdoorsArray[] = array(
                    'user_id' => $orderInfo['create_by'],
                    'experience_cnt' => '完成活動' . $name,
                    'departure_date' => $route->start_date,
                    'create_by' => $this->authHelpers()->getUserId(),
                    'create_date' => date('Y-m-d H:i:s'),
                );
            }
            $insertSettleArray = array(
                'company_id' => $activity->company_id,
                'route_id' => $route->id,
                'settle_amount' => $settleAmount,
                'settle_status' => 1,//待稽核
            );
            DB::table('integral_usage')->insert($insertUsageArray);
            DB::table('outdoor_experience')->insert($insertOutdoorsArray);
            $result = Settle::saveRecord('', $insertSettleArray);
            if (!$result) {
               throw new \Exception('儲存失敗',2000);
            }
            DB::table('order')->whereIn('id', array_column($order, 'id'))->update(array('status' => 3));
        }
    }

}

可以看到這個類主要實現了handle方法,而我把之前非常混亂的一段程式碼移到了這裡,從而之前的程式碼,變成

 protected function editStatus($id, $status)
    {
        $route = Route::find($id);
        $this->beginTransaction();
        try {
            if ($route) {
                if ($status == 9) {
                    if ($route->status < 4 && $route->start_date >= date('Ymd', strtotime("-1 days"))) {
                        $route->cancel_date = date('Y-m-d H:i:s');
                    } else {
                        return array('status' => false, 'case' => 1);
                    }
                }
                if ($status == 5) {
                 if ($route->end_date > date('Ymd')) {
                        return array('status' => false, 'case' => 2);
                    }
                                        event(new RouteEnd($route));
                }
                $route->status = $status;
                $route->save();
            }
        } catch (\Exception $e) {
            var_dump($e->getMessage());
            $this->rollback();
            return array('status' => false);
        }
        $this->commit();
        return array('status' => true);
    }

可以看到程式碼縮減了一段,而且這段程式碼也實現了它的唯一職權,修改狀態,而不用去做其他相關邏輯的事情,符合單一職責的設計模式,程式碼中 event(new RouteEnd($route));實現的功能是分發事件,簡單一點來講就是呼叫相應的處理方法,另外還有一點,要把事件對應的監聽器寫到EventServiceProvider下的$listen變數中去,這樣才能生效,否則會報錯,格式如下

protected $listen = [
        'App\Events\RouteEnd'=>[
            'App\Listeners\RouteEndListener'
        ]
    ];

相關文章