九保すこひです(フリーランスのITコンサルタント、エンジニア)
さてさて、皆さんもご存知のとおりLaravelの新バージョン9.xの公開が近くなってきましたね。
今回はLTS(長ーーーくメンテンナンスしてくれるバージョン)なので注目度も高いのかもしれません。
【⚠ 追記:2022.02.26】
Laravel 9.xの公開直後にLTSではなくなってしまったようです😭
詳しくはこちらをご覧ください。
ちなみに、9.xは元々の予定では 「2021年9月」にリリース予定でしたが、
2021年9月 👉 2021年1月25日 👉 2022年2月8日
と延期になってきました。
私も「ものづくり」をする立場として、良いものを提供するために時間をかけることは何の問題もないんですが、それでもやはり「ドラクエが発売延期になっちゃった…😂」と同じような待ちきれない気持ちになっています。
そこで❗
もう待てないので(笑)今回はベータ版の9.xをインストールし、ちょっと先取りして、
おっ、これは良い!と思う新機能Best 5
を以下の公式ページの中からまとめてみることにしました。
📝 参考ページ: A look at what is coming to Laravel 9
ぜひ、何かの参考になりましたら嬉しいです。😄✨
※ ベータ版なので、正式版では変更になるかもしれません。
「さらに楽する機能が、
やってくる❗」
開発環境: Laravel 9.x-beta、PHP 8.1
目次
1位:コントローラー名を使ったルートの省略記述
日頃から「うーん、ルートに書くことが多くて見づらいんだけど…😣」となっていましたが、以下のようにcontroller()メソッドを使ってスッキリ書くことができるようになります。
※ 正確にはLaravel 8.80で追加されました
routes/web.php
Route::controller(BookController::class)->group(function() { // 👈 コントローラーまとめてセット❗
Route::get('/book', 'index');
Route::get('/book/create', 'create');
Route::post('/book', 'store');
});
※ ちなみにprefix()との合わせ技でさらに共通化して書くことができます。
Route::controller(BookController::class)->prefix('book')->group(function() {
Route::get('/', 'index');
Route::get('/create', 'create');
Route::post('/', 'store');
});
これが、8.xの場合だと、以下のようにコントローラーを何回も連呼しないといけなかったので可読性も良くなりますね。 (Route::resource()を使う手もありですが、例外のメソッドのときは結局手書きなので「うーん…」となってました)
// 8.x の例
Route::get('/book', [BookController::class, 'index']);
Route::get('/book/create', [BookController::class, 'create']);
Route::post('/book', [BookController::class, 'store']);
2位:Enum を使ったルート・バインディング
PHP 8.1から使えるようになるEnum(列挙タイプ)をルートにバインディングできるようになる新機能です。
Enumの詳しい紹介は以下になります。
📝参考ページ: 【Laravel】PHP 8.1 から使える Enumで、選択肢〜バリデーションまで実装してみる
では、食べ物のEnum、Foodを使って実装してみましょう。
app/Enums/Food.php
<?php
namespace App\Enums;
enum Food: string
{
case Meat = 'meat'; // 👈 肉
case Fish = 'fish'; // 👈 魚
case Vegetables = 'vegetables'; // 👈 野菜
}
そして、このEnumをルート内にバインディングできるようにしてみましょう。
use App\Enums\Food;
// 省略
Route::get('food/{food}', function(Food $food) { // 👈 Enum に存在しない値の場合 404 エラーになる
dd($food->name, $food->value);
});
例えばこの状態で「https://******/food/meat」にアクセスしてみます。

はい❗
Enumの中身を取得することができました。
では、逆にEnumには入っていない「drinks」をセットしてみましょう。
URLは「https://******/food/drinks」ですね。

はい!Enumには存在していない値なので、ルート自体が存在していないと判断されました。
なお、8.xまでは同様のことをするためにはルートにwhere()をつけていましたが、可読性もそうですし、他の場所でも使いやすいのでEnumの方がいいですね👍
// 8x. の例
Route::get('food/{food}', function($food) {
dd($food);
})->where('food', '(meat|fish|vegetable)'); // 👈 正規表現で制限をかける
3位:Enum を使ったキャスティング(型変換)
こちらもEnum関連ですが、要は「型の自動変換」機能です。
先ほどのFoodを使って実装してみましょう。
例えば、今以下のようなテーブルrecipesがあるとします。

そして、この中のcategoryがEnumの値になっています。
では、このcategoryをEnum化してみましょう。
app/Models/Recipe.php
<?php
namespace App\Models;
use App\Enums\Food;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Recipe extends Model
{
use HasFactory;
protected $casts = [
'category' => Food::class
];
}
こうすることで、以下のように自動的にcategoryの値がEnumになります。
$recipe = \App\Models\Recipe::find(1); dd($recipe->category); // 👈 ここは Enum になる
実行結果はこうなります。

4位:スコープを強制したルート・バインディング
「スコープを強制した」というのは過去記事で紹介した「整合性を考慮したバインディング」のことです。詳しくは以下のページをご覧ください。
📝 参考ページ: データの整合性を考慮したバインディング
「ひとやすみ」
つまり、以前から機能的には存在してましたが、これをもっとシンプルに書くことができるようになったんですね。
では、データベースにusersとordersテーブルを用意し、
User:Order = 1:多
の関係になる環境で見ていきましょう。
実際のテーブルはこちら。


では、この状態で新メソッドのscopeBindings()を使ってルートを設定してみます。
routes/web.php
use App\Http\Controllers\OrderController;
// 省略
Route::controller(OrderController::class)->prefix('order')->group(function(){
Route::get('/{user}/{order}/edit', 'edit')->scopeBindings(); // 👈 整合性のある2つのデータしか許可しない
});
そして、コントローラーです。
<?php
namespace App\Http\Controllers;
use App\Models\Order;
use App\Models\User;
use Illuminate\Http\Request;
class OrderController extends Controller
{
public function edit(User $user, Order $order)
{
dd($user->toArray(), $order->toArray());
}
}
では、この状態で「https://l9x-beta.test/order/1/5/edit」へアクセスしてみましょう。
※ 上がUser、下がOrderです。
はい❗
User の id: 1 = Order の user_id: 1
なので、うまくデータ取得することができました。
では次に、わざと「整合性がないデータ」を要求してみましょう。
「https://l9x-beta.test/order/1/20/edit」へアクセスします。

今回は「ページが見つかりません」と言われてしまいました。
データベースには、OrderのID:20が存在しているのに、です。

そうです。これは、「データはあるけど、違うユーザー ID だからダメよ❗」となっているわけですね。
つまり、「https://l9x-beta.test/order/2/20/edit」ならうまく表示されるということです。
※ なお、この機能とログインは関係ないので、実行権限チェックは引き続きGateやPolicy、if文が必要になることに注意してください。
5位:Accessor、Mutator の省略化
これは以前からLaravelの強力な機能として存在している「Accessor(取得時のデータ加工)」「Mutator(セット時のデータ加工)」を省略して書くことができる機能です。
では、Bookというモデルを使って実際のコードを見てみましょう。
app/Models/Book.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Book extends Model
{
use HasFactory;
// Accessor & Mutator
public function price(): Attribute
{
return new Attribute(
get: fn ($value) => number_format($value) .'円', // 👈 こっちが Accessor
set: fn ($value) => preg_replace('/[^0-9]/', '', $value) // 👈 こっちが Mutator
);
}
}
こうすることで、Accessorは
$book = \App\Models\Book::find(1); dd($book->price); // 👈 例えば「1,000円」になります
のように使えますし、Mutatorの方も
$book = new \App\Models\Book(); $book->title = 'テストタイトル'; $book->price = '1,500円'; // 👈 Mutator が効くので数値になります $book->save();
と書くことができるようになります。
なお、これまでは、それぞれ以下のように書いていました。
// 8x の場合
class Book extends Model
{
// 省略
// Accessor
public function getPriceAttribute($value)
{
return number_format($value) .'円';
}
// Mutator
public function setPriceAttribute($value)
{
$this->attributes['price'] = preg_replace('/[^0-9]/', '', $value);
}
}
ただ、ワンライナーで書ける場合はいいですけど、複数行必要な場合は以下のようにコードが長くなる気もするので、5位にしました。
public function price(): Attribute
{
return new Attribute(
get: function($value){
return number_format($value) .'円';
}
);
}
企業様へのご提案
記事内容を見ていただいたとおりLaravelは常に進化していて、よりメンテンナンスしやすくなったり、開発効率が高くなってきています。
そのため、過去に作成したLaravelシステムを最新版に置き換えることで、今後の業務が効率化される場合が多いと考えています。
※ さらにLaravel 9.xはLTSなのでバグ対応は 2024年2月8日 まで、さらにセキュリティメンテナンスは 2025年2月8日 まで実施される予定となっています。(2022.1.26 現在)
【⚠ 追記:2022.02.26】
冒頭でも紹介しましたとおり、公開直後LTSではなくなってしまいました。
なお、こちらのツイートのようにSymfonyの決定待ちとの情報もあります。
9.xのメンテナンス期間につきましてはこちらをご覧ください。
リプレイス作業をご希望でしたらお問い合わせからご連絡ください。
ぜひお待ちしております。😄✨
おわりに
ということで、今回はLaravel 9.xで「おっ❗」と思った新機能Best 5をお届けしました。
みなさんのBest 5はどんなカンジでしょうか。
ぜひ一度試してくださいね。
ではでは〜❗

「緊急投稿なのに、
いっぱい書いちゃった…(笑)」





