Laraevl選手権8と言っていますが8回もやってません。versionに合わせました。
laravelのchangelogを眺めて、新しく追加された細かい機能を見ていこうぜ!っていうコーナーです
全部を網羅した訳ではなく、Addedされた中でも自分が気になるものをピックアップしました。
paginationにlinksプロパティーが追加
- v8.0.3
- src/Illuminate/Pagination/LengthAwarePaginator.php
こんなふうにpaginateをjsonで返した場合にlinksプロパティーが追加されました。linksプロパティーの詳細は「laravel pagination カスタマイズ」で検索!
Route::get('/users', function () {
return User::paginate();
});
{
"total": 50,
"per_page": 15,
"current_page": 1,
"last_page": 4,
"first_page_url": "http://laravel.app?page=1",
"last_page_url": "http://laravel.app?page=4",
"next_page_url": "http://laravel.app?page=2",
"prev_page_url": null,
"path": "http://laravel.app",
"from": 1,
"to": 15,
"data":[
{
// レコード…
},
{
// レコード…
}
],
// ↓追加された
"links": [
{
"url": null,
"label": "« Previous",
"active": false
},
{
"url": "http://laravel.app?page=1",
"label": "1",
"active": true
},
{
"url": null,
"label": "Next »",
"active": false
}
],
}
Testクラス内でjson型のカラムと比較できるcastAsJson()
メソッドが追加
- v8.3.0
- src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php
$this->assertDatabaseHas('users', [
'name' => 'Peter Parker',
'email' => 'spidey@yahoo.com',
'skills' => $this->castAsJson(json_encode(['web slinging', 'spidey-sense', 'sticky feet'])),
]);
キュー処理のジョブバッチで使用するbatchメソッドにクロージャ(無名関数)を渡せるように
- v8.3.0
- src/Illuminate/Bus/Batch.php
Bus::batch([
new ProcessPodcast,
function () {
// ...
},
new ReleasePodcast
])->dispatch();
ジョブをクリアできるphp artisan queue:clear
コマンドが追加
- v8.4.0
- src/Illuminate/Queue/Console/ClearCommand.php
ジョブ(jobsテーブル)はクリアできるけど、ジョブバッチ(job_batchesテーブル)には残るので注意です!
v8.21.0からは別コマンドでジョブバッチもクリアできます
php artisan queue:clear
クエリービルダにcrossJoinSub()
メソッド追加
- v8.5.0
- src/Illuminate/Database/Query/Builder.php
joinSub、leftJoinSub、rightJoinSubに加えてcrossJoinSubが追加
LazyCollectionにタイムアウトを設定できるように
- v8.6.0
- src/Illuminate/Collections/LazyCollection.php
LazyCollectionにタイムアウトを設定できるようになりました。
$lazyCollection
->takeUntilTimeout(now()->add(2, 'minutes'))
->each(fn ($item) => doSomethingThatMayTakeSomeTime($item));
// ^^ This will only process items for up to 2 minutes ^^
Httpクライアントでのレスポンスエラー発生時にonError()
でコールバック処理ができるように
- v8.7.0
- src/Illuminate/Http/Client/Response.php
throw()
との違いは例外を投げるかどうかです。
Before
$response = $client->withHeaders($headers)->post($url, $payload);
if ($response->failed()) {
Log::error('Twitter API failed posting Tweet', [
'url' => $url,
'payload' => $payload,
'headers' => $headers,
'response' => $response->body(),
]);
$response->throw();
}
return $response->json();
After
return $client->withHeaders($headers)
->post($url, $payload)
->onError(fn ($response) =>
Log::error('Twitter API failed posting Tweet', [
'url' => $url,
'payload' => $payload,
'headers' => $headers,
'response' => $response->body(),
])
)->throw()->json();
CollectionにpipeInto()
メソッドが追加されました
- v8.8.0
- src/Illuminate/Collections/Traits/EnumeratesValues.php
APIリソースを返す時に使えそうです
pipeInto
class ResourceCollection
{
/**
* コレクションインスタンス
*/
public $collection;
/**
* 新しいResourceCollectionインスタンスの生成
*
* @param Collection $collection
* @return void
*/
public function __construct(Collection $collection)
{
$this->collection = $collection;
}
}
$collection = collect([1, 2, 3]);
$resource = $collection->pipeInto(ResourceCollection::class);
$resource->collection->all();
// [1, 2, 3]
HttpクライアントでuserAgentをセットするwithUserAgent()
メソッドが追加
- v8.8.0
- src/Illuminate/Collections/Traits/EnumeratesValues.php
地味だけど便利
Before
Http::withHeaders(['User-Agent' => $userAgent])->get($url);
After
Http::withUserAgent($userAgent)->get($url);
スケジュールをローカルで実行できるphp artisan schedule:work
が追加
今まではスケジュールを動かすのにschedule:run
コマンドをcronに登録しておく必要がありましたが、ローカルからフォアグラウンドで動かしておけるschedule:work
が追加されました。
これは結構便利ですね
paginatorのアイテムを変換できるthrough()
メソッドが追加
- v8.9.0
- src/Illuminate/Pagination/AbstractPaginator.php
今までは$paginator->getCollection()->transform()
なんてしていましたが、その必要も無くなりました!
return Inertia::render('Contacts/Index', [
'contacts' => Contact::paginate()->through(function ($contact) {
return [
'id' => $contact->id,
'name' => $contact->name,
'phone' => $contact->phone,
'city' => $contact->city,
'organization' => optional($contact->organization)->only('name')
];
}),
]);
Eloquentやクエリービルダでupsertが使えるように
- v8.10.0
- src/Illuminate/Database/Query/Builder.php
MySQLの場合on duplicate key updateを使っており、uniqueかprimary keyが必要なので注意!
User::upsert([
['id' => 1, 'email' => 'taylor@example.com'],
['id' => 2, 'email' => 'dayle@example.com'],
], 'email');
バリデーションで倍数のチェックができるように
- v8.10.0
- src/Illuminate/Validation/Concerns/ValidatesAttributes.php
<input type="number" step="0.5" name="foo">
public function rules(): array
{
return [
'foo' => [
'multiple_of:0.5',
]
];
}
マイグレーションで外部キー制約のカラム削除が簡単に
- v8.10.0
- src/Illuminate/Database/Schema/Blueprint.php
これも地味に便利
Before
class AddCategoryIdToPostsTable extends Migration
{
public function down()
{
Schema::table('posts', function (Blueprint $table) {
$table->dropForeign(['category_id']);
$table->dropColumn('category_id');
});
}
}
After
class AddCategoryIdToPostsTable extends Migration
{
public function down()
{
Schema::table('posts', function (Blueprint $table) {
$table->dropConstrainedForeignId('category_id');
});
}
}
Eloquentのcastsにencrypted
が使えるように
- v8.12.0
- src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php
以前はアクセサやカスタムキャストを作る必要がありましたが、直接指定できるように
public $casts = [
'access_token' => 'encrypted',
];
リレーションのクエリにwithMax()
|withMin()
|withSum()
|withAvg()
メソッドが追加
- v8.12.0
- src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php
これは結構便利ですね。ただ発行されるSQLはゴリゴリのサブクエリなので注意
Post::withCount('comments');
Post::withMax('comments', 'created_at');
Post::withMin('comments', 'created_at');
Post::withSum('comments', 'foo');
Post::withAvg('comments', 'foo');
Eloquentやクエリービルダでexplainが使えるように
- v8.12.0
- src/Illuminate/Database/Concerns/ExplainsQueries.php
便利!
>>> DB::table('users')->where('name', 'Illia Sakovich')->explain()
=> Illuminate\Support\Collection {#5348
all: [
{#5342
+"id": 1,
+"select_type": "SIMPLE",
+"table": "users",
+"partitions": null,
+"type": "ALL",
+"possible_keys": null,
+"key": null,
+"key_len": null,
+"ref": null,
+"rows": 9,
+"filtered": 11.11111164093,
+"Extra": "Using where",
},
],
}
ルーティングに正規表現メソッドが追加
- v8.12.0
- src/Illuminate/Routing/RouteRegexConstraintTrait.php
whereNumber
|whereAlpha
|whereAlphaNumeric
|whereUuid
などが使えるようです
Before
Route::get('authors/{author}/{book}')->where(['author' => '[0-9]+', 'book' => '[a-zA-Z]+']);
After
Route::get('authors/{author}/{book}')->whereNumber('author')->whereString('book');
EloquentのCollectionにloadMax()
|loadMin()
|loadSum()
|loadAvg()
メソッド追加。EloquentにloadMax()
|loadMin()
|loadSum()
|loadAvg()
|loadMorphMax()
|loadMorphMin()
|loadMorphSum()
|loadMorphAvg()
メソッド追加
- v8.13.0
- src/Illuminate/Database/Eloquent/Collection.php
- src/Illuminate/Database/Eloquent/Model.php
これも便利ですね。ただゴリゴリのサブクエリ(略
//Eloquent/Collection
public function loadAggregate($relations, $column, $function = null) {...}
public function loadCount($relations) {...} //Just modified.
public function loadMax($relations, $column) {...}
public function loadMin($relations, $column) {...}
public function loadSum($relations, $column) {...}
public function loadAvg($relations, $column) {...}
//Eloquent/Model
public function loadAggregate($relations, $column, $function = null) {...}
public function loadCount($relations) {...} //Just modified.
public function loadMax($relations, $column) {...}
public function loadMin($relations, $column) {...}
public function loadSum($relations, $column) {...}
public function loadAvg($relations, $column) {...}
public function loadMorphAggregate($relation, $relations, $column, $function = null) {...}
public function loadMorphCount($relation, $relations) {...} //Just modified.
public function loadMorphMax($relation, $relations, $column) {...}
public function loadMorphMin($relation, $relations, $column) {...}
public function loadMorphSum($relation, $relations, $column) {...}
public function loadMorphAvg($relation, $relations, $column) {...}
こんな感じで使える
$user = User::find(1);
$user->loadCount('posts');
$user->loadMax('posts', 'created_at');
Eloquentのcastsencrypted
で使用する暗号鍵を設定できるModel::encryptUsing()
が追加
- v8.14.0
- src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Encryption\Encrypter;
$databaseEncryptionKey = config('database.encryption_key');
$encrypter = new Encrypter($databaseEncryptionKey);
Model::encryptUsing($encrypter);
HttpのResponseを返す際に指定のクッキーを無くすwithoutCookie()
メソッドが追加
- v8.15.0
- src/Illuminate/Http/ResponseTrait.php
単純に有効期限切れにする感じですね
/**
* Expire a cookie when sending the response.
*
* @param \Symfony\Component\HttpFoundation\Cookie|mixed $cookie
* @param string|null $path
* @param string|null $domain
* @return $this
*/
public function withoutCookie($cookie, $path = null, $domain = null)
{
if (is_string($cookie) && function_exists('cookie')) {
$cookie = cookie($cookie, null, -2628000, $path, $domain);
}
$this->headers->setCookie($cookie);
return $this;
}
アップロードファイルのテスト時にexistsと合わせてcontentもチェックできるように
- v8.15.0
- src/Illuminate/Filesystem/FilesystemAdapter.php
これも便利
Before
Storage::disk('reports')->assertExists('foo.csv');
$this->assertSame('my;csv;content', Storage::disk('reports')->read('foo.csv'));
After
Storage::disk('reports')->assertExists('foo.csv', 'my;csv;content');
Collectionで複数ソートができるように
- v8.16.0
- src/Illuminate/Collections/Collection.php
これはナイス
$collection = collect([
['name' => 'Taylor Otwell', 'age' => 34],
['name' => 'Abigail Otwell', 'age' => 30],
['name' => 'Taylor Otwell', 'age' => 36],
['name' => 'Abigail Otwell', 'age' => 32],
]);
$sorted = $collection->sortBy([
['name', 'asc'],
['age', 'desc'],
]);
$sorted->values()->all();
/*
[
['name' => 'Abigail Otwell', 'age' => 32],
['name' => 'Abigail Otwell', 'age' => 30],
['name' => 'Taylor Otwell', 'age' => 36],
['name' => 'Taylor Otwell', 'age' => 34],
]
*/
DBに接続できるphp artisan db
が追加
- v8.16.0
- src/Illuminate/Database/Console/DbCommand.php
べんり!でもmysql-clientがインストールされている必要があるので注意!
factoryで、存在する親モデルのインスタンスを紐付けするサポートが追加
- v8.18.0
- src/Illuminate/Database/Eloquent/Factories/Factory.php
factoryを作成する際にforでは親のfactoryを渡す方法だけでしたが
use App\Models\Post;
use App\Models\User;
$posts = Post::factory()
->count(3)
->for(User::factory()->state([
'name' => 'Jessica Archer',
]))
->create();
親のモデルインスタンスを渡して紐付けできるように
$user = User::factory()->create();
$posts = Post::factory()
->count(3)
->for($user)
->create();
メール本文の内容をテストするためのメソッドがいくつか追加
- v8.18.0
- src/Illuminate/Mail/Mailable.php
これは積極的に使っていきたい
use App\Mail\InvoicePaid;
use App\Models\User;
public function test_mailable_content()
{
$user = User::factory()->create();
$mailable = new InvoicePaid($user);
$mailable->assertSeeInHtml($user->email);
$mailable->assertSeeInHtml('Invoice Paid');
$mailable->assertSeeInText($user->email);
$mailable->assertSeeInText('Invoice Paid');
}
スケジュール一覧を表示するphp artisan schedule:list
が追加
- v8.19.0
- src/Illuminate/Console/Scheduling/ScheduleListCommand.php
ジョブデータを暗号化できるように
- v8.19.0
- src/Illuminate/Contracts/Queue/ShouldBeEncrypted.php
jobsテーブルのpayloadを見ると大体の内容がわかってしまうが、ShouldBeEncryptedを実装してあげれば暗号可能に
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
class VerifyUser implements ShouldQueue, ShouldBeEncrypted
{
private $user;
private $socialSecurityNumber;
public function __construct($user, $socialSecurityNumber)
{
$this->user = $user;
$this->socialSecurityNumber = $socialSecurityNumber;
}
}
ジョブバッチをクリアできるphp artisan queue:prune-batches
コマンドが追加
- v8.21.0
- src/Illuminate/Queue/Console/PruneBatchesCommand.php
>>> php artisan queue:prune-batches
199 entries deleted.
$schedule->command('queue:prune-batches --hours=48')->daily();
クエリービルダに、レコードが1つだけ存在かつそれを取得するsole()
メソッドが追加
- v8.23.0
- src/Illuminate/Database/Concerns/BuildsQueries.php
レコードが存在しない、複数レコードが存在する場合は例外を投げます。
Djangoではget
、Railsではsole
やfind_sole_by
で存在するそうです
$user = User::where('name', 'test1')->sole();
throw_if / throw_unless がデフォルトでRuntimeExceptionを投げるように
- v8.23.0
- src/Illuminate/Support/helpers.php
これは、、、
並列テストが可能に
- v8.25.0
- src/Illuminate/Testing/ParallelRunner.php
これはありがたい!詳しい使い方はドキュメントをチェック!
Listeners, Mailables, NotificationsでShouldBeEncryptedが使えるように
- v8.25.0
- src/Illuminate/Events/CallQueuedListener.php
- src/Illuminate/Mail/SendQueuedMailable.php
- src/Illuminate/Notifications/SendQueuedNotifications.php
v8.19.0でジョブの内容を暗号化するようにリスナー、メール、通知でも使えるみたいですね
ルーティングにmissing()
メソッドが追加
- v8.26.0
- src/Illuminate/Routing/Middleware/SubstituteBindings.php
ルートモデルバインディングを使用した際に、該当のデータがない場合にModelNotFoundExceptionが投げられますが、それを任意で処理できるように
Route::get('/locations/{location:slug}', [LocationsController::class, 'show'])
->name('locations.view')
->missing(fn($request) => Redirect::route('locations.index', null, 301));
after columnで複数追加できるように
- v8.27.0
- src/Illuminate/Database/Schema/Blueprint.php
Schema::table('users', function (Blueprint $table) {
$table->after('remember_token', function ($table){
$table->string('card_brand')->nullable();
$table->string('card_last_four', 4)->nullable();
});
});
EloquentのcastにAsArrayObject、AsCollectionが使えるように
- v8.28.0
- src/Illuminate/Database/Eloquent/Casts/AsArrayObject.php
- src/Illuminate/Database/Eloquent/Casts/AsCollection.php
castsにarrayを指定すると、jsonやtext型のフィールドに対して配列形式で保存することが可能ですが、AsArrayObjectやAsCollectionを指定することでObjectやCollectionで扱えるようになりました。詳しくはドキュメントをチェック!
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* キャストする必要のある属性
*
* @var array
*/
protected $casts = [
'options' => AsArrayObject::class,
];
}
バリデーションで最初にエラーが見つかったら即終了するstopOnFirstFailure()
メソッドが追加
- v8.30.0
- src/Illuminate/Foundation/Http/FormRequest.php
if ($validator->stopOnFirstFailure()->fails()) {
// ...
}
FluentなjsonのAssertが追加
- v8.32.0
- src/Illuminate/Testing/Fluent/Assert.php
Fluent(メソッドチェーンライク)にjsonのassertができるようになりました。
use Illuminate\Testing\Fluent\Assert;
class PodcastsControllerTest extends TestCase
{
public function test_can_view_podcast()
{
$this->get('/podcasts/41')
->assertJson(fn (Assert $json) => $json
->has('podcast', fn (Assert $json) => $json
->where('id', $podcast->id)
->where('subject', 'The Laravel Podcast')
->where('description', 'The Laravel Podcast brings you Laravel & PHP development news.')
->has('seasons', 4)
->has('seasons.4.episodes', 21)
->has('host', fn (Assert $json) => $json
->where('id', 1)
->where('name', 'Matt Stauffer')
)
->has('subscribers', 7, fn (Assert $json) => $json
->where('id', 2)
->where('name', 'Claudio Dekker')
->where('platform', 'Apple Podcasts')
->etc()
->missing('email')
->missing('password')
)
)
);
}
}
Eloquentにlazy()
とlazyById()
メソッドが追加
- v8.34.0
- src/Illuminate/Database/Concerns/BuildsQueries.php
lazyを使うことで、LazyCollectionが返ってくるのでメモリ消費を抑えつつ、Collectionのように扱えるようになった!という感じですね
$lazyCollection = User::lazy();
MySQLのdatetime型へのuseCurrentOnUpdateをサポート
- v8.36.0
- src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php
無名クラスでマイグレーションが可能に
- v8.37.0
- src/Illuminate/Database/Migrations/Migrator.php
クラス名の衝突がなくなりますね
<?php
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
//
};
Httpクライアントで同時非同期リクエスト処理ができるように
- v8.37.0
- src/Illuminate/Http/Client/PendingRequest.php
- src/Illuminate/Http/Client/Pool.php
これもかなり便利!
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;
$responses = Http::pool(fn (Pool $pool) => [
$pool->get('http://localhost/first'),
$pool->get('http://localhost/second'),
$pool->get('http://localhost/third'),
]);
return $responses[0]->ok() &&
$responses[1]->ok() &&
$responses[2]->ok();
Stringable::whenNotEmpty()
が追加
- v8.39.0
- src/Illuminate/Support/Stringable.php
これは汎用性高そう!
Str::of(env('SCOUT_PREFIX', ''))
->whenNotEmpty(fn (Stringable $prefix) => $prefix->finish('_'));
バリデーションにPasswordルールが追加
- v8.39.0
- src/Illuminate/Validation/Rules/Password.php
こんな感じで使えて
$request->validate([
// Makes the password require at least one uppercase and one lowercase letter.
'password' => ['required', 'confirmed', Password::min(8)->mixedCase()],
// Makes the password require at least one letter.
'password' => ['required', 'confirmed', Password::min(8)->letters()],
// Makes the password require at least one number.
'password' => ['required', 'confirmed', Password::min(8)->numbers()],
// Makes the password require at least one symbol.
'password' => ['required', 'confirmed', Password::min(8)->symbols()],
// Ensures the password has not been compromised in data leaks.
'password' => ['required', 'confirmed', Password::min(8)->uncompromised()],
]);
全部を組み合わせることも可能
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => ['required', 'confirmed', Password::min(8)
->mixedCase()
->letters()
->numbers()
->symbols()
->uncompromised(),
],
]);
Modelのイベントを発生せずに更新するメソッドが追加
- v8.41.0
- src/Illuminate/Database/Eloquent/Model.php
update([])
と同じように使えます
Model::updateQuietly([])
ちなみにsaveメソッドにはすでにあります
$user = User::findOrFail(1);
$user->name = 'Victoria Faith';
$user->saveQuietly();
カーソルページネーションが追加
- v8.41.0
- src/Illuminate/Contracts/Pagination/CursorPaginator.php
無限スクロールや、ビッグデータを扱う際のページング時に使うといいらしい
詳しくはドキュメントをチェック!
$users = DB::table('users')->orderBy('id')->cursorPaginate(15);
おわりに
全部は見切れませんでした!すみません!
changelogは議論も含め結構おもしろいので、Laravelっ子はぜひ見てみてください
collect()->containsOneItem()
のくだりはさすがに笑った
認識間違っている場合はコメントくださると!