Laravel-admin 在 Laravels 執行的的一些問題解析——記一次因為莽而付出的代價

blankqwq發表於2019-09-07

初生牛犢不怕虎,直接莽上laravels

前因

peJ1oswJgm.jpg!large

我們公司一直都是採用 think3.2 ,雖然使用起來確實不錯,但是哪能有 laravel 舒服

於是我才用 laravel ,但是經理覺得後臺太慢了

LfN53diEDf.jpg!large

我做了一部分最佳化還是慢,我太難了,之前他讓我隨便選框架

tyio36wkkD.jpg!large 我就想到了大佬的 [laravels](https://github.com/hhxsv5/laravel-s) 心裡想laravels+laravel-admin豈不是美哉

結果一使用出大問題

發現問題,問題介紹

前端問題laravel-s已提供Cleaner,請參閱文件使用

  1. 第一個問題就是比較常見的,頂部的重新整理無限增多,如圖

laravel-admin在Laravels執行的的一些問題解析——記一次因為莽而付出的代價

  1. 第二個問題就是,刪除這個卻進入上一個進入的刪除路由,導致各種刪除失敗

laravel-admin在Laravels執行的的一些問題解析——記一次因為莽而付出的代價

  1. 匯出excel提示exit
  2. Pjax中介軟體提示exit

解決問題,提供思路

授人以魚不如授人以漁

我們先排查第一個問題,這個比較常見,其實就是我們的Admin例項並沒有被清除,導致無限新增navBar

GPi9iLMekg.png!large

然後在 admin/bootstrap.php中每次都呼叫一下

於是我想到第一個辦法,這個方法比較 憨批 ,因為當時認為這個例項重新建立應該挺麻煩,所以採用區域性清理的方式

解決問題之後測試刪除功能,又發現新的問題,也就是問題2

laravel-admin在Laravels執行的的一些問題解析——記一次因為莽而付出的代價

laravel-admin在Laravels執行的的一些問題解析——記一次因為莽而付出的代價

這個問題很明顯是例項沒清理乾淨,導致上一步生成的js下一步仍然存在,排除在控制器生成的可能,
所以初步判斷只有可能存在於\Encore\Admin\Admin例項中,因為這個例項是存在於容器中
所以直接從中尋找,發現原來是\Encore\Admin\Admin靜態變數

//因為是表單所以我們先進入 Encore\Admin\Form\Form 尋找Tool 發現renderDelete 
 protected function renderDelete()
 {
 $trans = [
  'delete_confirm' => trans('admin.delete_confirm'),
  'confirm' => trans('admin.confirm'),
  'cancel' => trans('admin.cancel'),
  'delete' => trans('admin.delete'),
 ];
    .
    .
    .

  Admin::script($script);

  return <<<HTML
<div class="btn-group pull-right" style="margin-right: 5px">
 <a href="javascript:void(0);" class="btn btn-sm btn-danger {$class}-delete" title="{$trans['delete']}">
 <i class="fa fa-trash"></i><span class="hidden-xs"> {$trans['delete']}</span>
 </a>\</div>
HTML;
 }

看到 Admin::script 這個原來是在Admin 下的 HasAssets


/**
 * @param string $script
 * @param bool   $deferred
 * @return array|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
 */
 public static function script($script = '', $deferred = false)
{
  if (!empty($script)) {
         if ($deferred) {
              return self::$deferredScript = array_merge(self::$deferredScript, (array) $script);
         }
          return self::$script = array_merge(self::$script, (array) $script);
     }
      $script = array_unique(array_merge(static::$script, static::$deferredScript));

  return view('admin::partials.script', compact('script'));
}

那麼解決起來就狠方便了 直接在bootstrap中初始化一下這些靜態變數即可,儘量少改原始碼

所以自己實現一個clean , laravel-s已提供Cleaner這裡僅供參考

        \Encore\Admin\Admin::$script=[];
        \Encore\Admin\Admin::$deferredScript=[];
        \Encore\Admin\Admin::$headerJs = [];
        \Encore\Admin\Admin::$manifestData = [];
        \Encore\Admin\Admin::$extensions = [];
        .
        .
        .
        $app->forgetInstance(\Encore\Admin\Admin::class);
        Facade::clearResolvedInstance(\Encore\Admin\Admin::class);

JvCwNzii7a.jpg!large 由此,`問題一`和`問題二`已經基本解決

下面就是解決exit問題了,採用丟擲異常的方式

Pjax中的exit

Export中

     //csv匯出操作
     $res = Response::stream(function () {
            $handle = fopen('php://output', 'w');
            $titles = [];
            $this->chunk(function ($records) use ($handle, &$titles) {
                if (empty($titles)) {
                    $titles = $this->getHeaderRowFromRecords($records);
                    // Add CSV headers
                    fputcsv($handle, $titles);
                }

                foreach ($records as $record) {
                    fputcsv($handle, $this->getFormattedRecord($record));
                }
            });
            // Close the output stream
            fclose($handle);
        }, 200, $headers);
        swoole_exit($res);
    //Pjax 修改後
    $next = function () use ($response) {
              return $response;
     };

  swoole_exit((new static())->handle(Request::capture(), $next));

    //swoole_exit實際是封裝的丟擲異常
    if (!function_exists('swoole_exit')){
      function swoole_exit($response)
         {
             throw new App\Exceptions\SwooleExitException($response);
         }
     }

我們需要修改異常處理Handler

 <?php

namespace App\Exceptions;

use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Response;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;

class Handler extends ExceptionHandler\
{
  /**
 * A list of the exception types that are not reported.\ *\ * @var array
 */ 
 protected $dontReport = [
  SwooleExitException::class
  ];


  /**
 * A list of the inputs that are never flashed for validation exceptions.\ *\ * @var array
 */
 protected $dontFlash = [
  'password',
  'password_confirmation',
 ];
  /**
 * Report or log an exception@param \Exception  $exception\
 * @return void\
 */
 public function report(Exception $exception)
 {
     parent::report($exception);
 }
  /**
 * Render an exception into an HTTP response.\ *\ * @param \Illuminate\Http\Request  $request
 * @param \Exception  $exception
 * @return \Illuminate\Http\Response
 */
 public function render($request, Exception $exception)
 {
     //判斷是否為我們的自定義異常
     if ($exception instanceof SwooleExitException) {
             //直接呼叫perpare
          return $exception->getResponse()->prepare($request);
     }
     return parent::render($request, $exception);
 }
}

自定義異常程式碼SwooleExitException

<?php

namespace App\Exceptions;

use Exception;
use Throwable;

class SwooleExitException extends Exception\
{
  protected $response;
  public function __construct($response,$message = "", $code = 0, Throwable $previous = null)
 {
 $this->response = $response;
  parent::__construct($message, $code, $previous);
 }
//獲取響應內容
public function getResponse(){
  return $this->response;
}

kGTCROO7Pe.jpg!large

如果你直接把exit去掉,那麼 匯出csv 會提示你 oops … ob_end_clean() 這個報錯

我們可以在Hhxsv5\LaravelS\Illuminate\Laravel中的 handleDynamic 加一個判斷ob_get_length()也可以不加,因為後期解決exit退出問題,也就不會有這種報錯

解決思路因為exit其實就是不執行後續的響應 ,所以我們想到異常就是執行到異常丟擲之前,於是我們可以定義一種特定的異常,來提前結束,用來代替exit ,至於 swoole/laravels 為什麼不能使用這些函式我就不在這贅述

使用異常,但是我們也得返回請求,但是我們的請求不能直接send掉,因為laravels需要使用swoole的方式返回,我們直接執行 send 是不起作用的,那我們就可以透過特定的異常返回我們所需要的響應,

1mCWY5nTni.jpg!large

public static function toResponse($request, $response)\
{
      if ($response instanceof Responsable) {
          $response = $response->toResponse($request);
     }
      if ($response instanceof PsrResponseInterface) {
          $response = (new HttpFoundationFactory)->createResponse($response);
     } elseif ($response instanceof Model && $response->wasRecentlyCreated) {
          $response = new JsonResponse($response, 201);
     } elseif (! $response instanceof SymfonyResponse &&
                      ($response instanceof Arrayable ||
                      $response instanceof Jsonable ||
                      $response instanceof ArrayObject ||
                      $response instanceof JsonSerializable ||
                      is_array($response))) {
                          $response = new JsonResponse($response);
     } elseif (! $response instanceof SymfonyResponse) {
          $response = new Response($response);
     }
     if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
              $response->setNotModified();
    }
    return $response->prepare($request);
}

Router::toResponse(); $exception->prepare() 兩種方式都可以,不過推薦使用後者

出現json格式異常/其他異常,主要是因為 其他擴充套件可能有替換Handler/自定義Handler沒有進行處理,需要額外注意

感謝z-song提供的laravel-admin優質擴充套件

感謝hhxsv5提供的laravel-s優質擴充套件

具體分析請等下次更新,我們把laravels大致走一遍,學習一下(?)

本作品採用《CC 協議》,轉載必須註明作者和本文連結
學習,沖沖衝~

相關文章