今天遇到錯誤訊息耽誤了很久:
... foreign key constraint 'xxx_id_foreign' are incompatible.
錯誤訊息沒有詳細列出可能的錯誤,追了很久才知道有一些地方要注意。
More “Laravel Migration 出現 foreign key constraint in complete 注意事項” »
軟體開發和生活瑣事
今天遇到錯誤訊息耽誤了很久:
... foreign key constraint 'xxx_id_foreign' are incompatible.
錯誤訊息沒有詳細列出可能的錯誤,追了很久才知道有一些地方要注意。
More “Laravel Migration 出現 foreign key constraint in complete 注意事項” »
網路上多數都說需要 unique ID 的話用 uniqid() 產生即可,不過事實上這個方法產生的亂度不夠亂,特殊情空下是可以用猜的猜到:
foreach(range(1,10) as $n) {
echo uniqid() . "\n";
}
$ php uid.php 62283582750d2 62283582750de 62283582750df 62283582750e0 62283582750e1 62283582750e2 62283582750e3 62283582750e4 62283582750e5 62283582750e6
如果需要亂度夠大的 ID,官方建議使用 random_bytes() 等函式來實作:
foreach(range(1,10) as $n) {
echo bin2hex(random_bytes(8)) . "\n";
}
$ php uid.php 8deaa97f2cc4709d f2c556361d316f8d bcf97aa28e6b5a02 7453b298b1f748e5 6478265fb8da5c01 4981729191162649 80a7a2824818addf 622009f43ec32836 3b4d757e1894702c 4a254e226179e3b5
Laravel 和 Lumen 都有自己整理好的一套 unit test framework,因此若要加入 AspectMock 會需要對預設的 boostrap file 做一些調整。
整合時需要注意的事情:
/bootstrap/app.phpphpunit.xml 中的 bootstrap config,要改成自己新建立的 bootstrap filerequire 順序,不要讓 Laravel App 的資料被覆蓋掉 (另外也要注意 require 和 require_once 的差異)建立新的 bootstrap 設定檔 /tests/bootstrap.php:
<?php
// composer autoload
require_once __DIR__.'/../vendor/autoload.php';
// load Laravel bootstrap
$app = require_once __DIR__ . '/../bootstrap/app.php';
// create temporary folder for AspectMock
$tmpPath = '/tmp/aspectMock/';
if (!file_exists($tmpPath)) {
mkdir($tmpPath, 0777);
}
$kernel = \AspectMock\Kernel::getInstance();
$kernel->init([
'appDir' => __DIR__ . '/..',
'debug' => true,
'includePaths' => [
__DIR__ . '/../app',
__DIR__ . '/../vendor/laravel', // 如果需要 mock Model 則加上這行
],
'excludePaths' => __DIR__, // "/tests" 目錄不需要處理
'cacheDir' => $tmpPath,
]);
return $app; // 把 App 回傳給 createApplication()
接下來要調整原有的 phpunit.xml 設定檔,需要參考 AspectMock 的要求,來調整:
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="tests/bootstrap.php"
backupGlobals="false">
注意最後面二行,bootstrap 要改成剛剛新建立的檔案,backupGlobals 則要關閉。
最後,去調整 BaseTest.php,在 tearDown() 加上 AspectMock 清理設定的指令:
protected function tearDown(): void
{
test::clean();
}
另外在 createApplication() 這邊,要改用上面建立的 bootstrap file:
public function createApplication()
{
return require __DIR__ . '/bootstrap.php';
}
以上全部改好的話,執行 vendor/bin/phpunit 應該可以正常運作。
注意 require 和 require_once 的差異,Laravel 會在每次開始 unit test 之前,重新建立 $app,所以用 require_once 的話,就的設定清不掉
-$app = require_once __DIR__ . '/../bootstrap/app.php'; // Laravel bootstrap +$app = require __DIR__ . '/../bootstrap/app.php';
按照 Lumen 文件,在 unit test 中建立 HTTP request 要呼叫 $this->call(),但沒辦法自訂 header 內容。
細看實作 Laravel\Lumen\Testing\Concerns\MakesHttpRequests::call():
/**
* Call the given URI and return the Response.
*
* @param string $method
* @param string $uri
* @param array $parameters
* @param array $cookies
* @param array $files
* @param array $server
* @param string $content
* @return \Illuminate\Http\Response
*/
public function call($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
{
$this->currentUri = $this->prepareUrlForRequest($uri);
$symfonyRequest = SymfonyRequest::create(
$this->currentUri, $method, $parameters,
$cookies, $files, $server, $content
);
$this->app['request'] = LumenRequest::createFromBase($symfonyRequest);
return $this->response = TestResponse::fromBaseResponse(
$this->app->prepareResponse($this->app->handle($this->app['request']))
);
}
從 function parameter 和 return,大概就知道使用這個方法沒辦法塞 header 值。
但是若透過 SymfonyRequest 的話,則可以設定 header 內容:
$request = SymfonyRequest::create();
$request->headers->set('acccess_token', 'ooxx');
因此,若需要自訂 header,則必須 override 原本的 call() 方法。以下是自己改寫的 trait:
use Illuminate\Testing\TestResponse;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
trait ApiTokenRequestTrait
{
public function call($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null, $headers = [])
{
$symfonyRequest = SymfonyRequest::create(
$uri,
$method,
$parameters,
$cookies,
$files,
$server,
$content
);
foreach ($headers as $key => $value) {
$symfonyRequest->headers->set($key, $value);
}
$this->app['request'] = \Laravel\Lumen\Http\Request::createFromBase($symfonyRequest);
return TestResponse::fromBaseResponse(
$this->app->prepareResponse($this->app->handle($this->app['request']))
);
}
}
sitemap.xml 應該算是 SEO 中相當重要的一個檔案,告訴搜尋引擎到底網站中有哪些資源是公開瀏覽的,可惜 Laravel framework 並沒有支援這項功能,必須透過其他工具達成。
以下介紹二個目前看到比較欣賞的解決方法。