概要
L4.2で稼働しているシステムを5.0にアップグレードした際のトラブル事例集。
基本的には Upgrading To 5.0 From 4.2 に従えば良いのだけど、メジャーアップデートだけあって細々いくつか問題が起きた。
移行対象プロジェクト
発生した問題
Case 1: Class 'Predis\Client' not found
詳細
セッションドライバをRedisに変更したらエラーが起きた。
-) SESSION_DRIVER=file
+) SESSION_DRIVER=redis
解決策
predis/predis
パッケージをインストール。
composer require predis/predis
Case 2: ReflectionException in Container.php
詳細
IndexController
が見つからない。
ReflectionException in Container.php line 779:
Class App\Http\Controllers\App\Controllers\IndexController does not exist
解決策
エラーメッセージをよくよく見ると、名前空間が怪しい。
L5ではコントローラのデフォルト名前空間が RouteServiceProvider
に定義されている。
protected $namespace = 'App\Http\Controllers';
L4の時はPSR-4に従いコントローラに名前空間を指定していたので、routes.php
は次のような書き方をしていた。
Route::get('/', array('uses' => 'App\ontrollers\IndexController@getIndex', 'as' => 'home'));
デフォルトの名前空間に uses
で指定された名前空間が結合されてしまってるのが原因だった。
ルーティングパスから名前空間を取り除くことで解決。
Route::get('/', array('uses' => 'IndexController@getIndex', 'as' => 'home'));
Case 3: Trait 'App\Models\SoftDeletingTrait' not found
詳細
EloquentのSoft Deleteを利用してる場合、SoftDeletingTrait
が無いと言われる。
解決策
SoftDeleteの名前空間とトレイトの変更が必要。
-) use Illuminate\Database\Eloquent\SoftDeletingTrait;
+) use Illuminate\Database\Eloquent\SoftDeletes;
class BaseModel extends \Eloquent {
-) use SoftDeletingTrait
+) use SoftDeletes;
Case 4: Redisの接続に失敗する
接続設定は正しいはずなのに繋がらない。
ConnectionException in AbstractConnection.php line 155:
`SELECT` failed: ERR invalid DB index [tcp://172.16.238.7:6379]
以下設定。
'redis' => [
'cluster' => false,
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_DATABASE', 0),
],
],
REDIS_HOST=172.16.238.7
REDIS_PORT=6379
REDIS_DATABASE=0
解決策
env('REDIS_DATABASE')
が空文字を返してしまう。0番を使いたい場合は文字列形式で指定が必要。
REDIS_DATABASE="0"
Case 5: カスタムバリデータの移行
詳細
L5が提供するサービスプロバイダ形式に変更する。
解決策
app\Validators
の下にカスタムバリデータを作成。
<?php
namespace App\Validators;
use DB;
class NotExistsValidator extends \Illuminate\Validation\Validator
{
/**
* @param $attribute
* @param $value
* @param $parameters
* @return int
*/
public function validateNotExists($attribute, $value, $parameters)
{
$count = DB::table($parameters[0])
->where($parameters[1], '=', $value)
->whereNull('delete_date')
->count();
if ($count) {
return false;
}
return true;
}
}
バリデータをプロバイダに登録。
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class ValidatorServiceProvider extends ServiceProvider
{
/**
* @return void
*/
public function boot()
{
\Validator::resolver(function($translator, $data, $rules, $messages) {
return new \App\Validators\NotExistsValidator($translator, $data, $rules, $messages);
});
}
/**
* @return void
*/
public function register()
{}
}
プロバイダを読み込むよう設定。
'providers' => [
'App\Providers\ValidatorServiceProvider',
]
Case 6: Class '\App\User' not found
詳細
ユーザ認証時にL5のAuthエラーが起こる。
解決策
モデルの設置場所を App/Models
に変更していたので、認証モデルのパスも変更が必要。
-) 'model' => 'App\User',
+) 'model' => 'App\Models\User',
Case 7: class 'Illuminate\Database\Eloquent\Collection' does not have a method 'getTotal'
詳細
Bladeのページャでエラー。
call_user_func_array() expects parameter 1 to be a valid callback,
class 'Illuminate\Database\Eloquent\Collection' does not have a method 'getTotal'
(View: /data/resources/views/summary/daily/index.blade.php)
解決策
メソッドの呼び出し方が変わってた。
-) @if ($activities->getTotal())
+) @if ($activities->total())
Case 8: Route::resouce()でミドルウェアを利用したい
詳細
routes.yml
上で特定のコントローラにミドルウェアを適用したい。
解決策
Route::group()
が使える。
Route::group(['middleware' => 'auth'], function() {
Route::resource('dashboard', 'DashboardController');
});
Case 9: AJAX通信時にTokenMismatchExceptionが発生
詳細
AJAXでPOSTやDELETEメソッドを実行するとCSRFチェックでエラーが起こる。
production.ERROR: exception 'Illuminate\Session\TokenMismatchException'
in /data/src/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php:46
解決策
L5からはCSRFチェックがすべてのページで有効になったので、AJAX通信時も _token
パラメータを送信する必要がある。
すべての通信で _token
を追加するのは面倒なので (jQuery限定の対策ではあるが)、<meta>
タグにトークンを記述し、AJAX通信時はjQuery経由でMETAからトークンを取得及びサーバへ送信する手段を用いた。
<meta name="csrf-token" content="{{ csrf_token() }}">
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
Case 10: PHPUnitでPOST送信時に "Failed asserting that Symfony\Component\HttpFoundation\Response Object" エラー
詳細
レスポンスオブジェクトをデバッグしてみると、CSRFエラーが起きていることがわかる。
Failed asserting that Symfony\Component\HttpFoundation\Response Object (...)
is an instance of class "Illuminate\Http\RedirectResponse".
解決策
リクエストパラメータにCSRFトークンを追加してあげれば良い。
$params = [
'contact_name' => 'test',
'email' => 'test@monelytics.me',
'contact_type' => '1',
'contact_message' => 'test',
'_token' => csrf_token()
];
$this->call('POST', '/contact/send', $params);
$this->assertRedirectedTo('/contact/done');
csrf_token()
を利用するに辺り、セッションを開始しておく必要がある。
public function setup()
{
parent::setup();
\Session::start();
}
※L5.2ではテスト時にトークンチェックは無視されるようになりました。
Case 11: PHPUnitでPOST送信時に "InvalidArgumentException: An uploaded file must be an array or an instance of UploadedFile." エラー
詳細
問題のコード。
$this->call(
'DELETE',
'/cost/constant/1',
[],
[],
['HTTP_REFERER' => $http_referer]
);
解決策
call() メソッドの引数4番目に $cookies
が追加されてた。
Response call(
string $method,
string $uri,
array $parameters = array(),
array $files = array(),
array $server = array(),
string $content = null,
bool $changeHistory = true
)
Response call(
string $method,
string $uri,
array $parameters = array(),
array $cookies = array(),
array $files = array(),
array $server = array(),
string $content = null
)
修正後のコード。
$this->call(
'DELETE',
'/cost/constant/1',
[],
[],
[],
['HTTP_REFERER' => $http_referer]
);