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

blankqwq發表於2019-09-07

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

前因

1240

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

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

1240

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

1240

我就想到了大佬的 laravels 心裡想laravels+laravel-admin豈不是美哉,畢竟有swoole 以前學過一些,覺得應該可以把持住

結果一使用出大問題

發現問題,問題介紹

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

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

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

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

  1. 匯出excel提示exit

4.Pjax中介軟體提示exit

目前比較真實的問題就是這麼一些,具體其他問題請等待我後續的發現

解決問題,提供思路

授人以魚不如授人以漁

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

1240

然後在 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'),
 ];
  $class = uniqid();

  $script = <<<SCRIPT

$('.{$class}-delete').unbind('click').click(function() {

 swal({ title: "{$trans['delete_confirm']}",
 type: "warning",\ showCancelButton: true, confirmButtonColor: "#DD6B55",\ confirmButtonText: "{$trans['confirm']}",
 showLoaderOnConfirm: true,\ cancelButtonText: "{$trans['cancel']}",\
 preConfirm: function() {\ return new Promise(function(resolve) {\ $.ajax({\ method: 'post',\ url: '{$this->getDeletePath()}',
 data: {\ _method:'delete',\_token:LA.token,\ },\ success: function (data) {\ $.pjax({container:'#pjax-container', url: '{$this->getListPath()}' });

 resolve(data);\ }\ });\ });\ }\ }).then(function(result) {\ var data = result.value;\ if (typeof data === 'object') {\ if (data.status) {\ swal(data.message, '', 'success');\ } else {\ swal(data.message, '', 'error');\ }\ }\ });\});\

SCRIPT;

  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,之前憨批的方法我也就換成更加粗暴的方式,最終還是清理掉Admin在容器中的例項

        \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);

1240

由此,問題一和問題二已經基本解決,菜鳥也想學習大佬的操作,想要更加優雅一點

下面就是解決exit問題了,中間複雜心裡鬥爭,最後只能採用丟擲異常的方式

Pjax中的exit 在56行

Export中 這裡就很多,我就不一一指出

 //匯出操作
     $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) {
          return Router::toResponse($request, $exception->getResponse());
     }
 return parent::render($request, $exception);
 }
}
<?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;
}

1240

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

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

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

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

1240

至於Router::toResponse();其實我還沒有分析,只是猜想是返回響應

結果是猜想正確,具體分析請等下次更新,我們把laravels大致走一遍,學習一下

laravels可以獲取到最終響應然後返回,目的就達到了

學習,沖沖衝~

相關文章