[Laravel] 5.7.17がリリースされました
laravel/frameworkのバージョン5.7.17がリリースされました。追加された機能について確認します。
Database\Query\Builder::insertUsingメソッドが追加されました
データベースの内容をメモリに展開することなく、サブクエリから直接挿入できます。
$builder->from('table1')->insertUsing(
['foo'],
function (Builder $query) {
$query->select(['bar'])->from('table2')->where('foreign_id', '=', 5);
}
);
// insert into "table1" ("foo") select "bar" from "table2" where "foreign_id" = 5
Database\Query\Builder::havingBetweenメソッドが追加されました
SQLのHAVINGにBETWEENの条件が指定できます。
$builder->select('*')->from('users')->havingBetween('last_login_date', ['2018-11-16', '2018-12-16'])->orHavingRaw('user_foo < user_bar');
// select * from "users" having "last_login_date" between '2018-11-16' and '2018-12-16' or user_foo < user_bar
DetectsLostConnectionsトレイトにエラー処理が追加されました
MariaDBへの接続が失われた場合のメッセージが追加されました。
Postgresの外部キーを追加する場合の検証をスキップするためのオプションが追加されました
PostgresSQLのテーブルに外部キーを追加するには、既存のデータを検証するときにテーブルに排他アクセスロックが必要です。
notValid()メソッドを利用することで検証をスキップできます。
$blueprint->foreign('parent_id')->references('id')->on('parents')->onDelete('cascade')->deferrable()->notValid();
詳しい変更については以下を確認してください。
Release v5.7.17 · laravel/framework · GitHub
[Laravel] 5.7.16がリリースされました
laravel/frameworkのバージョン5.7.16がリリースされました。追加された機能について確認します。
403、503エラーページが多言語対応されました
マイグレーション実行時にファイル名が指定できるようになりました
詳しい変更については以下を確認してください。
Release v5.7.16 · laravel/framework · GitHub
[Laravel] Laravel WebSocketsパッケージがリリースされました
Laravel用にPHPで実装されたWebSocketサーバーLaravel WebSocketsがリリースされました。
システム上でWebSocketサーバーを利用するためには、代表的なNode.jsをはじめ様々なライブラリやサーバーを構築する必要があります。
今回リリースされたLaravel WebSocketsはWebSocketサーバーとして必要な機能が全て実装されているので、これ一つで高性能なWebSocketサーバーを起動することができます。
作者はLaravelの有名なパッケージを多数開発しているBeyond CodeとSpatieのエンジニアによって作成されました。
サードパーティーのパッケージではありますが、Laravel SocialiteやLaravel Horizonといったオフィシャルパッケージと同様の扱いがされるでしょう。
Laravel WebSocketsの内容
Laravel WebSocketsはWebSocketを処理するための低レベルのパッケージであるRatchetを基盤として実装されています。
冒頭でも説明しましたが、Laravel WebSocketsをComposerでインストールするだけで、他のライブラリや構築などは一切必要ありません。
PHPのみで動作し、artisanコマンドで即座にWebSocketサーバーが起動します。
php artisan websockets:serve
また、専用の管理画面も内包されており、ダッシュボード上でリアルタイムな情報が確認できます。
チャートにはピーク時の接続数、送信されたメッセージ数、受信されたAPIメッセージ数など、WebSocketアプリケーションのメトリックの重要な情報を提供するリアルタイムチャートが搭載されています。
内部にはPusherのMessage ProtooclのMessage Protooclも実装済みであり、Pusherをサポートする既存のパッケージやアプリケーションは全てLaravel WebSockets上で動作します。
Laravelで用意されているLaravel Echoやその他WebSocketを利用するJavaScriptのライブラリとも互換性があります。
Private Channel、Presence Channel、APIでの接続などPusherが提供する全ての機能が利用可能です。
動画
サンプルや実装例
こちらからデモアプリケーションを確認できることに加え、ドキュメントサイトにも組み込まれています。
また、サーバー監視サービスのOh Dear!ではすでに本番環境でLaravel WebSocketsを利用中とのことです。
Laravel WebSockets 🛰
★ Introducing laravel-websockets, an easy to use WebSocket server implemented in PHP - Freek Van der Herten's blog on PHP and Laravel
[Laravel] Eloquentでのサブクエリの有効な使い方
Eloquentを用いたリレーションテーブルにおいて、効果的なサブクエリの使い方を紹介します。
リレーショナルなテーブル扱う場合にはN+1問題は常に意識されるべきであり、Eager Loadingを使ったクエリの削減は誰もが行なっているでしょう。
Laravelにおいてもwithメソッドを使うことで簡単にEager Loadingが行えますが、1対多 (hasMany)や多対多 (belongsToMany)の関係においてはサブクエリを使うことでパフォーマンスが改善され、システムの見通しがとてもよくなる場合があります。
要件
例として、ユーザーがログインした時にログイン履歴を残すシステムがあり、ユーザー一覧を表示するページで、ユーザー情報に加えて最終ログインデータを表示させるものとします。
Name
Email
LastLogin
Adam Campbell
[email protected]
2018-11-10 12:01
Taylor Otwell
[email protected]
-
Jonathan Reinink
[email protected]
2018-01-02 05:30
Adam Wathan
[email protected]
2018-11-20 07:49
準備
ログイン情報を保存するloginsテーブルと、データベースを操作するLoginモデルを作成しましょう。
php artisan make:model Login -m
以下がマイグレーションするテーブルのスキーマです。
usersテーブルはLarevelに同封されているものを使います。
また、logins.user_idは外部キーでログイン毎に履歴としてレコードが作成されるものとします。
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Schema::create('logins', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id');
$table->string('ip_address');
$table->timestamps();
});
次に、作成したモデルを関連付けます。
class User extends Model
{
public function logins()
{
return $this->hasMany(Login::class);
}
}
class Login extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
ログイン履歴の追加方法については省略しますが、\Illuminate\Auth\Events\Loginイベントのリスナーとしてテーブルに追加される処理があるものとします。
Eager Loadingを使う方法
ただ単に最終ログインを表示さすことはとても簡単に実装できるでしょう。
$users = User::all();
@foreach ($users as $user)
<tr>
<td>{{ $user->name }}</td>
<td>{{ $user->email }}</td>
<td>
@if ($lastLogin = $user->logins()->latest()->first())
{{ $lastLogin->created_at->format('Y-m-d H:i:s') }}
@else
Never
@endif
</td>
</tr>
@endforeach
この方法では50人のユーザーが表示された場合、合計51のクエリが実行されます。いわゆるN+1にあたります。
select * from "users";
select * from "logins" where "logins"."user_id" = 1 and "logins"."user_id" is not null order by "created_at" desc limit 1;
select * from "logins" where "logins"."user_id" = 2 and "logins"."user_id" is not null order by "created_at" desc limit 1;
// ...
select * from "logins" where "logins"."user_id" = 49 and "logins"."user_id" is not null order by "created_at" desc limit 1;
select * from "logins" where "logins"."user_id" = 50 and "logins"."user_id" is not null order by "created_at" desc limit 1;
この問題を解決するにはEager Loadingを使うことです。
関連するログイン情報をwithメソッドを使って取得しましょう。
$users = User::with('logins')->get();
@foreach ($users as $user)
<tr>
<td>{{ $user->name }}</td>
<td>{{ $user->email }}</td>
<td>
@if ($user->logins->isNotEmpty())
{{ $user->logins->sortByDesc('created_at')->first()->created_at->format('Y-m-d H:i:s') }}
@else
Never
@endif
</td>
</tr>
@endforeach
これで発行されるクエリはユーザー情報の取得と、それに関連するログイン情報を取得する2つになりN+1問題は解決されました。
しかし、改善前と比べてメモリ消費量が増える場合があります。
ログイン履歴には平均して1ユーザー辺り250件の履歴が存在している場合、50ユーザー分のログイン情報を取得する場合には12,500件のレコードを取得することになります。
これはメモリの消費に加えて、各レコードをEloquentモデルとして初期化する時間もかかることになります。
上記した数字は例ですが、似たような状況で何百万ものレコードがロードされることもあるでしょう。
最終ログインを保持するカラムを用意する
対策の1つとして、専用の情報をデータベース上で保持することが考えられます。
Schema::create('users', function (Blueprint $table) {
$table->integer('last_login_id');
});
ユーザーのログイン時にログイン履歴に加えて、loginsテーブルの外部キーであるlast_login_idを更新するという方法です。
関連付けを行うlastLoginメソッドをUserモデルに作成し、この情報のEager Loadingを行います。
$users = User::with('lastLogin')->get();
これはとても有効な解決策の1つです。
しかし、キャッシュを頻繁に行うのは簡単ではないことに注意してください。
SubQueryを使う方法
この問題を解決する方法の1つがサブクエリを使う方法です。
サブクエリを使用すると、ユーザー情報の取得を行うクエリの中でログイン情報を取得に関する情報を扱うことができます。
LaravelにはselectSubメソッドを使うことでサブクエリの実行ができます。
$lastLogin = Login::select('created_at')
->whereColumn('user_id', 'users.id')
->latest()
->limit(1)
->getQuery();
$users = User::select('users.*')
->selectSub($lastLogin, 'last_login_at')
->get();
@foreach ($users as $user)
<tr>
<td>{{ $user->name }}</td>
<td>{{ $user->email }}</td>
<td>
@if ($user->last_login_at)
{{ $user->last_login_at->format('Y-m-d H:i:s') }}
@else
Never
@endif
</td>
</tr>
@endforeach
この例ではEager Loadingを使わず、サブクエリを使って各ユーザの最終ログイン日を属性として取得しています。
実際に実行されているデータベースクエリを見てみましょう。
select
"users".*,
(
select "created_at" from "logins"
where "user_id" = "users"."id"
order by "created_at" desc
limit 1
) as "last_login_at"
from "users"
このようにサブクエリを使用すると、ユーザーページに必要なすべての情報を単一のクエリで取得できます。
この手法では、データベースクエリとメモリ使用量を最小限に抑えることができ、キャッシングを使わなくて済むので、パフォーマンスが大幅に向上します。
Macroable
このようなサブクエリを使う場合の処理をマクロとして定義することもできます。
新しいaddSubSelectメソッドをクエリビルダに追加するために、AppServiceProviderに次のコードを記述します。
use Illuminate\Database\Query\Builder;
Builder::macro('addSubSelect', function ($column, $query) {
if (is_null($this->columns)) {
$this->select($this->from.'.*');
}
return $this->selectSub($query->limit(1), $column);
});
このマクロは次の処理が含まれています。
サブクエリに加えて、対象のテーブル情報を取得するためにselect('table.*')を追加しています。これはLaravelでサブクエリを定義した場合にはselect *が含まれないので必須です。
サブクエリは1つのみレコードを取得したいのでlimit(1)を指定します
そして、サブクエリとして引数に渡したクエリを追加します
このマクロを使ってコードを書き直してみましょう。
$users = User::addSubSelect('last_login_at', Login::select('created_at')
->whereColumn('user_id', 'users.id')
->latest()
)->get();
とてもスッキリして再利用まで可能になりました。
最後に
この記事は以下のページを参考にサブクエリの使い方について説明しました。
元ページでは更にScopeを使った例や、さらに詳しく説明されていますので是非ご覧になってください。
Dynamic relationships in Laravel using subqueries - Jonathan Reinink
[Laravel] 5.7.15がリリースされました
laravel/frameworkのバージョン5.7.15がリリースされました。追加された機能について確認します。
1. date_equalsのバリデーションメッセージが追加されました
resources/lang/en/validation.phpへ追加されています。
'date_equals' => 'The :attribute must be a date equal to :date.',
2. starts_withバリデーションルールが追加されました
リクエスト値が指定されたパラメータで始まっているかを検証します。(複数可)
以下はStripeの認証情報を検証するサンプルです。
public function rules()
{
return [
'stripe_publishable_key' => [
'starts_with:pk_test_,pk_live_',
],
'stripe_secret_key' => [
'starts_with:sk_live_,sk_test_',
],
];
}
3. Eloquentリレーションにメソッドが追加されました
BelongsToMany::getParentKeyName
BelongsToMany::getRelatedKeyName
HasManyThrough::getFirstKeyName
HasManyThrough::getForeignKeyName
HasManyThrough::getSecondLocalKeyName
HasOneOrMany::getLocalKeyName
MorphToMany::getInverse
4. Illuminate\Http\Resources\Json\ResourceCollectionにCountableが付与されました
ResourceCollectionを@eachに直接渡すことができるようになりました。
詳しい変更については以下を確認してください。
Release v5.7.15 · laravel/framework · GitHub
[Laravel] 5.7.14がリリースされました
laravel/frameworkのバージョン5.7.14がリリースされました。追加された機能について確認します。
1. Illuminate\Cookie\CookieJarクラスにMacroableトレイトが追加されました
2. パスワードリマインダー、パスワードリセットのRouteを無効にできるようになりました
システム上でユーザーにパスワードのリセットを許可しない場合に使用します。
Auth::routes()の引数で指定できます。
Auth::routes(['reset' => false]);
3. エラー関連のviewファイルが取り込まれるようになりました
vendor:publishコマンド実行時に、既存のviewファイルが/resources/views/errorsへコピーされます。
4. Notificationクラスにリトライ回数とタイムアウトを指定できるようになりました
$tries、$timeoutにて指定可能です。
class TestNotification extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 3; // Max tries
public $timeout = 15; // Timeout seconds
// ...
}
5. メール周りのログを別ファイルに出力可能になりました
メール周りのトラブルは付き物ですが、ログが埋まってしまいがちです。
専用のloggingを指定できるようになり、デバッグが容易になりました。
config/logging.phpにドライバを追加します。
'channels' => [
// ...
'mail' => [
'driver' => 'daily',
'path' => storage_path('logs/mail.log'),
'level' => 'debug',
'days' => 14,
],
],
config/mail.phpにlog_channelとして専用のドライバを指定可能です。
'log_channel' => 'mail',
6. 静的ファイルの配信URLが指定可能になりました
CDN等を利用してアセットファイルの配信を行う場合にとても便利な機能が追加されました。
config/app.phpにasset_urlとして配信元のURLを指定することで別ドメインとして利用可能です。省略した場合にはオリジナルのドメインで配信されます。
// config/app.php
`asset_url` => 'https://cdn.something.com',
asset('js/something.js');
// => https://cdn.something.com/js/something.js
7. DetectsLostConnectionsトレイトにError while sending QUERY packetメッセージが追加されました
8. コンソール実行中と判断するフラグが指定可能になりました
LARAVEL_RUNNING_IN_CONSOLEに値を指定することでフラグが立つようになりました。
// Illuminate\Foundation\Application
/**
* Determine if the application is running in the console.
*
* @return bool
*/
public function runningInConsole()
{
return env(
'LARAVEL_RUNNING_IN_CONSOLE',
php_sapi_name() === 'cli' || php_sapi_name() === 'phpdbg'
);
}
詳しい変更については以下を確認してください。
Release v5.7.14 · laravel/framework · GitHub
[Laravel] 5.7.13がリリースされました
laravel/frameworkのバージョン5.7.13がリリースされました。更新された機能について確認していきます。
Added
1. カスタムバリデーションルールでメッセージを配列で指定可能になりました #26327
2. CollectionにwhenEmpty()/whenNotEmpty()/unlessEmpty()/unlessNotEmpty()メソッドが追加されました #26345
3. Collectionにsome()メソッドが追加されました #26376, 8f7e647
4. Illuminate\Cache\Repositoryにmissing()メソッドが追加されました #26351
5. Illuminate\View\FactoryにMacroableトレイトが追加されました #26351
6. UNIONを使用する際にも集計クエリが可能になりました #26365
以下のようなクエリに対してもcount()で集計可能になりました。
DB::table('posts')->union(DB::table('videos'))->count();
select count(*) as aggregate from (
(select * from `posts`) union (select * from `videos`)
) as `temp_table`
詳しい変更については以下を確認してください。
Release v5.7.13 · laravel/framework · GitHub
[Laravel] 5.7.12がリリースされました
laravel/frameworkのバージョン5.7.12がリリースされました。更新された機能について確認していきます。
Added
1. CacheManagerにforgetDriver()メソッドが追加されました #26264, fd9ef49
このメソッドを使用すると、CacheManagerによって既に開かれている接続を削除できます。接続を強制的に再接続するか、設定オプションを変更して接続を再作成します。
2. Illuminate\Foundation\Http\KernelにgetMiddlewareGroups()メソッドが追加されました #26268
テスト時に指定されたmiddleware が利用されているかの確認ができるようになりました。
/** @test */
public function it_registers_the_track_utm_middleware_in_the_web_group()
{
$groups = resolve(\App\Http\Kernel::class)->getMiddlewareGroups();
$this->assertContains(\App\Http\Middleware\TrackUTM::class, $groups['web']);
}
3. SQLite利用じに外部キー制約の有効/無効が切り替え可能になりました #26298, #26306, 674f8be
config/database.phpにて設定が可能です。
'sqlite' => [
// ...
'foreign_key_constraints' => true,
],
詳しい変更については以下を確認してください。
Release v5.7.12 · laravel/framework · GitHub
[Laravel] 5.7.11がリリースされました
laravel/frameworkのバージョン5.7.11がリリースされました。更新された機能について確認していきます。
Added
1. Eloquentモデルでdecimal型へのキャストが指定可能になりました #26173
小数点付きの値を更新するときに型の保持ができるようになりました。
protected $casts = [
'amount' => 'decimal:2',
];
2. 403エラー時のビューが更新されました #26258
3. Intendedでリダイレクトする際のURLを指定するメソッドが追加されました #26227
今までもsessionで指定することで同じ動作が可能でしたが、setIntendedUrl()メソッドで簡単に指定可能になりました。
// before
session(['url.intended' => $intended]);
// after
app('redirect')->setIntendedUrl($intended);
4. OracleのORA-03114に関するエラーメッセージが追加されました #26233
詳しい変更については以下を確認してください。
Release v5.7.11 · laravel/framework · GitHub
[Laravel] 5.7.10がリリースされました
laravel/frameworkのバージョン5.7.10がリリースされました。更新された機能について確認していきます。
Added
1. Eloquent CollectionにloadCount()メソッドが追加されました #25997
取得済みのEloquent Collectionに関連するリレーションの数を取得することができます。
$events = Event::latest()->with('eventable')->paginate();
$groups = $events->map(function ($event) {
return $event->eventable;
})->groupBy(function ($eventable) {
return get_class($eventable);
});
$groups[Post::class]->loadCount('comments');
$groups[Comment::class]->loadCount('hearts');
return new EventIndexResponse($events);
テストも追加されていますので、気になる方はこちら
2. PostgreSQL 10+のIdentity columnsに対応しました #26096
3. assertSoftDeleted()メソッドが追加されました #26133, #26148
以下のようにレコードが論理削除済みかを確認できます。
$this->assertSoftDeleted($user);
// プライマリーキー指定
$this->assertSoftDeleted('users', ['id' => $user->id]);
4. apiResourceでexcept()メソッドを利用できるようになりました #26149
5. テスト時にmock()とspy()が追加されました #26171, b50f9f3
Mockeryなどの利用がとても簡単になりました。
$this->mock(Mailer::class, function ($mock) {
$mock->shouldReceive('send')->once();
});
$this->spy(Dispatcher::class, function ($mock) {
$mock->shouldReceive('dispatch')->andReturn(false);
});
6. uuidのバリデーションが追加されました #26135
5.6で追加されたStr::orderedUuid()などで生成したuuidに関するバリデーションです。
以下のように利用できます。
$request->validate([
'id' => 'required|uuid'
]);
7. Notificationのテスト時にlocaleを評価することができるようになりました #26205
詳しい変更については以下を確認してください。
Release v5.7.10 · laravel/framework · GitHub