48
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

LaravelAdvent Calendar 2021

Day 13

細かすぎて伝わらないLaravel選手権8(laravel8)

Last updated at Posted at 2021-12-12

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ではsolefind_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()のくだりはさすがに笑った

認識間違っている場合はコメントくださると!

次は
LaravelリポジトリのDiscussionされてるアイデアを読んでみる
by @hiro5963 さん

48
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
48
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?