【技術博客】 關於laravel5.1中文件上傳測試的若干嘗試

關於laravel5.1中文件上傳測試的若干嘗試

做者:ZGJphp

版本:v1.0前端

PM注:本人這兩天也正在嘗試解決這一問題,若有進展將及時更新這一博客laravel


在咱們的軟工第二階段中,我開始着手進行後端控制器的單元測試,然而一個階段下來,其中的一個涉及文件上傳的控制器方法始終沒能覆蓋徹底,這裏總結我在此測試上進行的若干嘗試(均未得到成功),先附上被測試控制器方法代碼:json

public function uploadPreparePdf()
{
 $exists = Auth::check() && ((Console::where('email', '=', Auth::user()->email)->get()->count()) > 0);
 $isAdmin = $exists;
 if (!$isAdmin) {
     return redirect('/index');
 }
 $data = ["status"=>"","message"=>""];

 if (Request::hasFile('prepare-pdf'))
 {
     $pdfFile = Request::file('prepare-pdf');
     $labID = $_POST['labID'];
     if (preg_match('/^pdf$/', $pdfFile->getClientOriginalExtension()) &&
             $pdfFile->getSize() < Config::get('phylab.maxUploadSize'))
     {
         $fname = $labID . '.pdf'; //TODO use lab id instead
         $pdfFile->move(Config::get('phylab.preparePath'), $fname);
         $data['status']=SUCCESS_MESSAGE;
         $data['message']="上傳成功";
     }
     else
     {
         $data["status"]=FAIL_MESSAGE;
         $data["message"] = "上傳失敗,文件格式或大小不符合要求!";
     }
 }
 else
 {
     $data["status"]=FAIL_MESSAGE;
     $data["message"] = "上傳失敗,沒有找到文件!";
 }

 return response()->json($data);
}

測試失敗的緣由是第二個if語句hasFile()的斷定始終爲false……也就是這一句:後端

if (Request::hasFile('prepare-pdf'))數組

另外附上前端代碼,這裏有一點須要注意的是,前端文件輸入的元素並未包含在form表單中,這也致使了其中一種測試方法的失敗,後面在嘗試2中會說起。框架

<div class="form-group">
<input type="file" id="input-prepare-pdf" name="prepare-pdf">
<p class="help-block">請務必先選擇一個實驗,文件類型限制爲PDF,大小不能超過5M</p>
</div>

<div class="modal-footer" style="margin-top: 0;padding-bottom: 0">
<div style="float:right">
 <button type="submit" id="btn-upload-preview" class="btn btn-large btn-danger">上傳</button>
</div>
</div>

如下開始詳細闡述我爲了解決該問題進行的若干思路和嘗試:ide

嘗試1:僞造存儲

經過查閱相關資料,我瞭解到laravel對於文件上傳測試有較爲便利的原生支持,可經過「僞造」磁盤和文件的方式進行文件上傳的測試,像這樣:函數

Storage::fake('avatars');
$response = $this->json('POST', '/avatar', [
'avatar' => UploadedFile::fake()->image('avatar.jpg')]);post

然而尷尬的是,這個原生支持僅針對laravel5.4以上的版本,而咱們的……則是上古版本5.1……

(要不是時間關係,我真的好想進行一次升級並重構,這5.1的版本實在是太老,無奈……)

這種最爲實用並且簡單的測試方法宣告失敗。

嘗試2:表單交互

經過查看laravel5.1的文檔,能夠發現文件上傳測試還能夠經過表單交互的方式進行,如這樣:

public function testPhotoCanBeUploaded()
{
$this->visit('/upload')
->name('File Name', 'name')
->attach($absolutePathToFile, 'photo')
->press('Upload')
->see('Upload Successful!');
}

文檔中對於attach和press函數的介紹是這樣的:

Method Description
$this->attach($pathToFile, $elementName) "Attach" a file to the form.
$this->press($buttonTextOrElementName) "Press" a button with the given text or name.

很淺顯,很少作解釋,這兩個函數在其餘測試中我亦使用過,然而在這裏進行測試時,會產生以下報錯:

%5CUsers%5C98239%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5C1558767803485

報錯行顯示在press函數,緣由是前端文件輸入並未包含在form表單中(見上文中的前端代碼),可是因爲對前端修改還須要牽扯到其餘的一些部分,況且我認爲爲了方便測試而修改代碼並非一個好習慣(我的理解)。

因此這一種嘗試也宣告失敗……

以上兩種測試是經過查閱資料和閱讀官方文檔能找到的兩種主流測試方法,未能成功,不甘失敗的我開始圍繞Request::hasFile()方法閱讀laravel5.1的底層代碼,但願經過其餘途徑解決該問題:

嘗試3:修改預約義全局變量$_FILE

爲何進行這一項嘗試,這就涉及到laravel請求生命週期的相關知識。

這裏僅闡述請求實例化的部分,laravel中請求實例化位於文件Illuminate\Http\Request.php中

/**
 * Create a new Illuminate HTTP request from server variables.
*
 * @return static
*/
public static function capture()
{
 static::enableHttpMethodParameterOverride();

 return static::createFromBase(SymfonyRequest::createFromGlobals());
}

而經過createFromBase方法的代碼(這裏就不貼了)又能發現laravel框架的請求實例是在Symfony請求實例的基礎上建立的。

對此,我又找到vendor\symfony\http-foundation\Request.php文件

其中關於請求的實例化有一句關鍵代碼:

$request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $server);

也就是說,這是經過PHP的全局變量建立一個新的請求實例,若是沒有laravel框架,PHP對於文件上傳以及測試能夠直接操做$_FILE全局變量是確定沒有問題的,laravel對其封裝在Request實例中,故我想到能否在發送post請求以前修改$_FILE,即這樣:

(多說起一點,$_FILE全局變量的結構是一個二維數組,每個元素有五個字段,具體可查閱相關PHP文檔)

$pdf_info = [
 'name' => public_path().'/prepare_pdf/phylab_test.pdf' ,
 'error' => 0 ,
 'type' => 'pdf' ,
 'size' => 100000000 ,
 'tmp_name' => public_path().'/prepare_pdf/phylab_test.pdf' ,
] ;
$_FILES["prepare-pdf"]["name"] = $pdf_info['name'] ;
$_FILES["prepare-pdf"]['error'] = $pdf_info['error'] ;
$_FILES["prepare-pdf"]['type'] = $pdf_info['type'] ;
$_FILES["prepare-pdf"]['tmp_name'] = $pdf_info['tmp_name'] ;
$_FILES["prepare-pdf"]['size'] = $pdf_info['size'] ;
//Request::capture() ;
//self::assertTrue(Request::hasFile('prepare-pdf')) ;
$this->post('/console/uploadPre')
   ->seeJson([

爲了驗證以前對於請求實例化代碼的理解,可見當中有兩句註釋代碼:

//Request::capture() ;
//self::assertTrue(Request::hasFile('prepare-pdf')) ;

這兩句代碼(取消註釋後)是能夠正常經過測試的。

遺憾的是,測試中提交post請求後,後端控制器方法中fasFile()的斷定依舊爲false。

這一種嘗試,失敗……

嘗試4:構造SplFileInfo類的實例

既然測試始終沒法覆蓋徹底的緣由是hasFile方法判斷爲false,那我就看看這一個函數是如何進行的,故我找到Request.php中hasFile方法的源碼:

public function hasFile($key)
{
 if (! is_array($files = $this->file($key))) {
     $files = [$files];
 }

 foreach ($files as $file) {
     if ($this->isValidFile($file)) {
         return true;
     }
 }

 return false;
}

首先我看到的是返回條件即$this->isValidFile($file)這一句:

在此找到isValidFile()這個方法:

protected function isValidFile($file)
{
 return $file instanceof SplFileInfo && $file->getPath() != '';
}

因此我想到了構造SplFileInfo類的實例並做爲post請求的數據發送一個post請求:

$pdf = new SplFileInfo('prepare_pdf/phylab_test.pdf') ;
$this->post('/console/uploadPre' , [
         'prepare-pdf' => $pdf ,
     ])->seeJson([

很遺憾,這一種嘗試依舊失敗……

嘗試5:構造UploadedFile類的實例

先說起一句,Request::file 方法返回來的對象是 Symfony\Component\HttpFoundation\File\UploadedFile 類的一個實例,這個類繼承了 PHP 的 SplFileInfo 類,固然進行這個嘗試更重要的緣由在後面:

在上一項嘗試中,我發現hasFile()方法在進行返回判斷以前,有這樣一句代碼:

$files = $this->file($key)

找到file()方法:

public function file($key = null, $default = null)
{
 return Arr::get($this->files->all(), $key, $default);
}

在返回調用Arr::get方法時第一個參數比較重要,即$this->files->all() ,

這裏又須要瞭解Request實例中的$files屬性,找到其源頭,位於文件vendor\symfony\http-foundation\Request.php中,而$file的初始化是這樣的:

$this->files = new FileBag($files);

傳入的參數即爲PHP中的$_FILE全局變量,這也就和我前面所說的請求實例化聯繫到了一塊兒,對此,我再次找到FileBag類的源碼,其中對於FileBag類的構造進行了多層調用,一層層找到其根源,有這樣一句:

parent::set($key, $this->convertFileInformation($value));

而經過對convertFileInformation()函數的解讀:

protected function convertFileInformation($file)
{
 if ($file instanceof UploadedFile) {
     return $file;
 }

 $file = $this->fixPhpFilesArray($file);
 if (is_array($file)) {

能夠發現,這個函數接收的參數有兩種,一種是UploadedFile實例,另外一種是一個文件信息的數組,並經過這個數組建立一個UploadedFile實例:

$file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'],             $file['size'], $file['error']);

**這也就和$_FILE全局變量的結構聯繫到了一塊兒。**

經過改變$_FILE的方式前面已經試過,失敗,那麼我能不能經過傳入UploadedFile實例的方式提交post請求,即這樣:

$pdf = new UploadedFile($pdf_info['tmp_name'], $pdf_info['name'], $pdf_info['type'], $pdf_info['size'], $pdf_info['error']);
$this->post('/console/uploadPre' , [
         'prepare-pdf' => $pdf ,
     ])->seeJson([

失望的是,這種嘗試,仍然失敗……

以上即是我針對laravel5.1文件上傳進行的幾項主要嘗試的過程,固然實際上還有一些看似就不靠譜的小嚐試(抱着幻想仍是要試一下),那些就不拿出來獻醜了哈哈…

雖然有一些綜合的緣由,可是終究仍是本身學術不精。若是有大佬知道解決的方案,或者是我以上部分的理解存在一些問題,歡迎指正,感激涕零!

相關文章
相關標籤/搜索