Tempestは「The PHP framework that gets out of your way」を掲げるPHPフレームワークです。属性(Attributes)を標準の設定手段に据え、ルートもコンソールコマンドも設定ファイルへ登録せずDiscoveryが自動で拾います。ただし3.x系はPHP 8.5以上が必須で、この一点だけで採用可否が決まる現場も多いはずです。本稿では2026年9月時点の最新版v3.19.2をPHP 8.5.8で実際に導入し、起動・ルーティング・DB操作まで動かした結果をもとに、導入手順と他フレームワークとの違いを整理します(扱うのはPHPフレームワークのtempestphp/tempest-frameworkです)。
まとめ:Tempest導入前に押さえる5点
- 現行版はv3.19.2(2026-09-08公開)、必要PHPは
^8.5。PHP 8.4環境ではComposerがインストールを拒否します。 - 初期状態は3ファイルだけ。
composer create-project tempest/appで生成されるアプリコードはHomeController.php・home.view.php・HelloCommand.phpの3つで、routes定義もconfigディレクトリもありません。 - Discoveryキャッシュが本番と開発で挙動を変えます。本番・stagingは既定でfullになるため、前回生成した古いfullキャッシュが残ったままのディレクトリへデプロイすると、追加したルートが404のままになります。
- コンソールはSymfony Consoleではありません。
symfony/consoleは依存に入らず、Tempest\Consoleの独自実装です。 - 雛形生成コマンドの出力はそのままでは動きません。
make:modelとmake:migrationの生成物が噛み合わず、マイグレーションにモデル側の列を足し、日時列に既定値を指定しないと保存できません。
Tempestの現在地:v3.19.2とPHP 8.5要件
Tempestは作者のBrent Roose氏が教育目的のプロジェクトとして始めたもので、公式ブログにも「When I started Tempest 2 years ago, the goal was for it to be an educational project, nothing more.」と書かれています。そこから正式なフレームワークへ育ち、最初の安定版1.0は2025年6月27日に公開されました。「2024年に1.0が出た」という説明を見かけますが、2024年9月16日に出たのはv1.0.0-alpha.1で、安定版ではありません。
バージョン履歴とPHP要件の境界
Packagistの配布データで追うと、必要PHPが上がった地点は2か所だけです。特にv3.0.0(2026-02-12)で^8.4から^8.5へ切り上がったのが、現在の導入可否を分ける境界になります。
| バージョン | 公開日 | 必要PHP | 位置づけ |
|---|---|---|---|
| v1.0.0-alpha.1 | 2024-09-16 | ^8.3 | 最初の公開リリース |
| v1.0.0-alpha.5 | 2025-02-24 | ^8.4 | PHP要件の1回目の引き上げ |
| v1.0.0 | 2025-06-27 | ^8.4 | 最初の安定版 |
| v2.0.0 | 2025-09-16 | ^8.4 | メジャー更新 |
| v3.0.0 | 2026-02-12 | ^8.5 | PHP要件の2回目の引き上げ |
| v3.19.2 | 2026-09-08 | ^8.5 | 本稿の検証対象 |
公開から2年で102件が配布されており、そのうち3.x系はパッチ版を含め49件です(2026-09-08時点)。教育プロジェクト出身という経緯からは想像しにくいほど更新は速く、裏を返せばマイナー版でも挙動が動く前提で追従計画を組む必要があります。PHP 8.5そのものの変更点はPHP 8.5の変更点と新機能まとめ|リリース日・主要機能・非推奨と移行ポイントで整理しています。
自社環境で入れられるかの判定
判定は単純です。php -v が8.5未満なら、Tempest 3.xは入りません。PHP 8.4.24で tempest/framework:^3.0 を要求すると、依存解決の段階で次のように弾かれます。
$ php composer.phar require tempest/framework:^3.0
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Root composer.json requires tempest/framework ^3.0 -> satisfiable by tempest/framework[v3.0.0, ..., v3.19.2].
- tempest/framework[v3.0.0, ..., v3.19.2] require php ^8.5 -> your php version (8.4.24) does not satisfy that requirement.
8.4で止めざるを得ない事情があるなら選択肢は2つで、PHPを8.5へ上げるか、tempest/framework ^2.0 を明示してv2系に留まるかです。ただしv2系は2025年内で更新が止まっており、新規採用でv2を選ぶ理由はほとんどありません。PHP 8.5へ上げられないなら、Tempestは見送りが妥当です。
インストールから起動確認までの実測手順
以下は2026年9月19日に、macOS 26.6.2・PHP 8.5.8 (cli)・tempest/framework v3.19.2 の組み合わせで確認した結果です(DBはPDO経由のSQLite 3.53.4)。公式が案内するのは composer create-project tempest/app my-app の1行です。実際に流すと、依存の取得後に tempest install framework -f・discovery:generate・key:generate が自動で走り、署名キーの生成まで終わった状態になります。
# 必要条件の確認(8.5未満なら install が失敗する)
php -v
# PHP 8.5.8 (cli) (built: Jul 1 2026 03:46:27) (NTS)
composer create-project tempest/app my-app
cd my-app
php tempest serve
# [Sat Sep 19 15:05:10 2026] PHP 8.5.8 Development Server (http://127.0.0.1:8000) started
php tempest serve はPHPビルトインサーバーを127.0.0.1:8000で起動します。トップページはHTTP 200、未定義のパスは404が返り、追加設定なしでルーティングが機能していることを確認できました。
生成直後のアプリコードは次の3ファイルだけです。routes/web.php に相当するものも、config/ ディレクトリもありません。
app/HomeController.php # ルート定義を兼ねるコントローラ
app/home.view.php # トップページのビュー
app/HelloCommand.php # サンプルのコンソールコマンド
プロジェクト直下には実行ファイル tempest、公開ディレクトリ public/、キャッシュ置き場 .tempest/ が置かれます。開発用依存にはPHPUnit 12.5系と、Rust製のPHPツールチェーンであるMagoが最初から含まれており、composer qa で整形・lint・解析・テストが一括で走る構成になっています。Mago自体の使い方とPHPStanとの違いはMagoとは?PHPのRust製リンター・静的解析の使い方とPHPStanとの違いで扱っています。テスト基盤を別途選び直したい場合の比較はPestPHPとは?PHPUnitとの違いと導入手順|Pest 5の新機能まで実測で解説を参照してください。
Discoveryの実体:属性だけでルートとコマンドが登録される仕組み
公式ドキュメントはDiscoveryを「Tempest automatically locates controller actions, event handlers, console commands, and other components of your application, without needing any configuration from you.」と説明しています。ただし属性でルートを書けること自体はTempest固有ではありません。Symfonyにも #[Route] 属性があり、標準構成で有効です。差が出るのは適用範囲で、Tempestはルートに限らずコンソールコマンド・イベントハンドラ・ビューコンポーネントまで同じ仕組みで拾います。
生成されたコントローラを見ると、ルートは属性1行で表現されています。
<?php
namespace App;
use Tempest\Router\Get;
use Tempest\View\View;
use function Tempest\View\view;
final readonly class HomeController
{
#[Get('/')]
public function __invoke(): View
{
return view('./home.view.php');
}
}
属性は Tempest\Router 名前空間にあり、Get・Post・Put・Patch・Delete・Head・Options・Trace・Connect・Query が用意されています。初期のv1.0.0-alpha.1では同じ属性が Tempest\Http に置かれていました。安定版1.0の時点で Tempest\Router へ移っているため、alpha期のサンプルを写すとクラスが解決できません。
コンソールコマンドも同じ発想で、クラスをコマンドとして宣言するのではなくメソッドに属性を付けたものがコマンドになります。引数の型と既定値がそのままCLI引数の仕様になる点が特徴です。
<?php
namespace App;
use Tempest\Console\Console;
use Tempest\Console\ConsoleCommand;
final class HelloCommand
{
public function __construct(
private readonly Console $console,
) {}
#[ConsoleCommand]
public function world(string $name = 'stranger'): void
{
$this->console->success("Hello, {$name}!");
}
}
クラス名とメソッド名から hello:world というコマンド名が導出され、php tempest hello:world Issoh で Hello, Issoh! が出力されました。登録用の設定は一切書いていません。登録済みのルートは php tempest routes で一覧でき、php tempest about を叩けばフレームワーク版・PHP版・キャッシュ状態・接続中のDBが1画面で出ます。
Discoveryキャッシュの3モードと、本番だけ404になる条件
ここがTempestで最初につまずきやすい箇所です。Discoveryはコードを走査するので当然キャッシュが挟まり、その既定値が環境ごとに違います。
| DISCOVERY_CACHE | キャッシュ対象 | 既定になる環境 |
|---|---|---|
| partial | vendorのみ(アプリ側は毎回再走査) | local |
| true(full) | vendorとアプリの全体 | production / staging |
| false(none) | キャッシュしない | ci / testing / other |
localではpartialなので、コントローラを足せばその場でルートに反映されます。実際に php tempest make:controller Article で生成したクラスは、キャッシュ生成を挟まずに routes へ現れました。問題になるのは本番です。ENVIRONMENT がproductionまたはstagingだと既定がfullに切り替わります。手元で再現したのが次の結果です。
# DISCOVERY_CACHE=full の状態で discovery:generate 済み
# その後 app/LateController.php(#[Get('/late')])を追加
php tempest routes
# GET / App\HomeController::__invoke()
# GET /dummy-path App\Article::__invoke()
# → /late は出てこない
php tempest discovery:generate --no-interaction
php tempest routes
# GET /late App\LateController::__invoke()
条件は正確に切り分けておきます。ルートが消えるのは前回生成したfullキャッシュが残っている場所へ、その後に追加したクラスを持ち込んだときだけです。キャッシュを持たないディレクトリへ配置した場合は、DISCOVERY_CACHE=full でも保存済みの戦略が見つからず無効扱いになるため、Discoveryは非キャッシュで走り、追加したルートはそのまま検出されました。危ないのはリリースディレクトリやコンテナ層を使い回す運用で、デプロイ手順に ./tempest discovery:generate --no-interaction を入れておけばどちらでも揃います。Laravelの route:cache を張ったまま更新したときの症状に近いものです。
もう一点、表の既定値は DISCOVERY_CACHE を設定しなかった場合の値です。スターター同梱の .env.example は DISCOVERY_CACHE=partial を明示しており、.env 自体は .gitignore 対象です。明示値は環境ごとの既定より優先されるため、雛形をそのまま本番へ持ち込むと今度は毎リクエストでアプリ側を再走査し続けるという逆向きの損をします。現状は php tempest discovery:status と php tempest cache:status で確認でき、開発中にvendor側の変更まで拾わせたいときは DISCOVERY_CACHE=false を指定します。
なお make:controller Article が生成するのは app/Article.php で、ルートは #[Get(uri: '/dummy-path')]、返すビューは view('dummy-view') という文字どおりのプレースホルダです。ファイル名もURIも自分で書き換える前提の雛形なので、生成してそのまま使うものではありません。
データベース層と、雛形コマンドが噛み合わない落とし穴
DB層は Tempest\Database で、SQLite・MySQL・PostgreSQLをクエリビルダとモデルの両方から扱えます。マイグレーションはファイル名の日付規約ではなく、MigratesUp / MigratesDown を実装したクラスがDiscoveryで拾われる形です(.sql ファイルも同様に検出されます)。
ここで make:model Post と make:migration create-posts-table を叩くと、次の2つが生成されます。雛形どうしが噛み合っていないのがこの2つを並べると分かります。
// app/Post.php(make:model の生成物)
final class Post
{
use IsDatabaseModel;
public function __construct(
#[HasLength(min: 1, max: 120)]
public string $title,
) {}
}
// app/CreatePostsTable.php(make:migration の生成物)
public function up(): QueryStatement
{
// title 列は生成されない(モデルは title を保存しようとする)
return new CreateTableStatement('posts')
->primary()
->datetime('created_at') // nullable も既定値も無い
->datetime('updated_at');
}
ずれは2か所あります。まずマイグレーションの雛形は主キーと日時2列しか作らないため、title を持つモデルをそのまま保存すると列が無いと言われます。次にその title 列を足しても、日時2列がNOT NULLで既定値を持たないため今度はそこで落ちます。順に潰していったときに出たエラーは次の2つです。
// 1. 雛形のまま save() したとき
SQLSTATE[HY000]: General error: 1 table posts has no column named title
// 2. text('title') を足したあと save() したとき
SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: posts.created_at
Laravelの $table->timestamps() がモデル側の日時管理とセットで用意されるのに慣れていると見落とす差分です。マイグレーションを次の形にすると保存が通りました。
return new CreateTableStatement('posts')
->primary()
->text('title') // モデルの $title に対応(雛形には無い)
->datetime('created_at', current: true) // DEFAULT CURRENT_TIMESTAMP を付ける
->datetime('updated_at', current: true);
ここで current: true が生成するのは DEFAULT CURRENT_TIMESTAMP だけです。挿入時の初期値が入るという意味であって、レコード更新時に updated_at が自動で進むわけではありません。更新日時を持たせたいなら、モデル側で値を代入するか更新クエリで明示する必要があります。
この形で php tempest migrate:fresh を流し、モデル経由で2件保存して読み出したところ、#1 と #2 が採番されて取得できました。保存時にはモデルの検証属性も働きます。title を空文字で保存しようとすると ValidationFailed が送出され、リクエスト層ではなくモデルの save() の時点で検証が走る点がLaravelのFormRequest中心の設計と異なります。
マイグレーション系のコマンドは migrate:up / migrate:down / migrate:fresh / migrate:validate / migrate:rehash が揃っています。migrate:validate は、up() と down() が生成するSQLを最小化したうえで xxh128 でハッシュ化し、適用時に記録した値と突き合わせます。照合対象はSQLなので、コメントや整形だけを変えた場合は検出されません。
ビュー・ミドルウェア・コンソールの書き方
ビューの既定は TempestViewRenderer、つまり独自のTempest Viewです。公式は「Tempest View uses a syntax that can be thought of as a superset of HTML.」と説明しており、{{ $value }} によるエスケープ出力、{!! $value !!} による非エスケープ出力、:if や :foreach といった属性ディレクティブを使います。生成された home.view.php も <x-base :title="..."> というコンポーネント記法で書かれていました。
BladeやTwigも使えますが、期待の仕方に注意が必要です。フレームワークには BladeViewRenderer と TwigViewRenderer が同梱されている一方、Twig本体とBlade本体はvendorに入りません。手順は3段階で、Twigなら twig/twig、Bladeなら tempest/blade を composer require し、TwigConfig または BladeConfig の viewPaths にテンプレートの探索先を指定したうえで、ViewConfig の rendererClass を差し替えます。ただし tempest/blade は2025年2月のv0.1.0が最新で1.0に達していないため、Blade前提の移行は保守状況を確かめてから判断してください。
ミドルウェアはHTTPとコンソールで別インターフェースです。実装ファイルを直接確認したシグネチャは次のとおりでした。
// Tempest\Router\HttpMiddleware
public function __invoke(Request $request, HttpMiddlewareCallable $next): Response;
// Tempest\Console\ConsoleMiddleware
public function __invoke(Invocation $invocation, ConsoleMiddlewareCallable $next): ExitCode|int;
ここで旧来の解説に多い誤りを1つ正しておきます。TempestのコンソールはSymfony Consoleベースではありません。v3.19.2の依存関係にはsymfony/cache・filesystem・mailer・process・uid・var-dumper・yamlなどが入りますが、symfony/console は含まれず、実際にインストール後の vendor/symfony/ にもconsoleディレクトリは存在しませんでした。引数定義の作法はSymfonyの addArgument() ではなく、前述のとおりPHPのメソッドシグネチャそのものです。
標準コマンドには static:generate(静的ページの生成)、generate:typescript-types(PHPクラスからTypeScript型を生成)、schedule:run、tail:logs、さらに mcp:serve(MCPサーバーの提供)まで含まれます。長時間稼働向けには WorkerModeApplication::boot() が用意されており、FrankenPHPとは?PHP-FPMとの違い・ワーカーモードの仕組みと導入手順【v1.12対応】で扱うようなワーカー常駐型の実行にも対応できる構造です。
Tempest・Laravel・Symfonyの選定基準
公式サイトは利用者の声として「People using Tempest say it’s the sweet spot between the robustness of Symfony and the eloquence of Laravel.」を掲げ、開発方針を「Tempest relies on attributes wherever possible, not as an option, but as the standard.」と表現しています。属性を選択肢ではなく標準に据えたこと、そしてプロジェクト構造を強制しないことが設計上の主張です。
配置の自由度は実際に確認できました。app/ 以下へコントローラ・モデル・マイグレーションを置いただけで、登録用の記述なしにルートとコマンドへ反映されます。MVCでもDDDでも、置き場所をフレームワークに指定されません。ただし規約が無いわけではありません。Composerのオートロード設定、属性、インターフェース、設定・ビューファイルの命名は、そのままDiscoveryの検出規則です。自由なのは配置であり、その分だけディレクトリ方針と命名をチームで決める必要があります。
そのうえで、採用を見送るべき条件を先に挙げます。PHP 8.5へ上げられない環境、認証・課金・管理画面まで作り込まれたエコシステムを前提に工数を見積もっている案件、日本語の一次資料と求人の厚みを重視する案件では、現時点のTempestは合いません。Laravelとの比較でエコシステムの厚みが判断材料になる場合はLaravelの将来性を2026年のデータで判断する|サポート期限・求人単価・AI対応が参考になります。
逆に向くのは、依存を薄く保ちたいAPIやバッチ、PHPの新機能を前提に書けるチーム、そして「フレームワークの流儀を覚える時間」を減らしたい小〜中規模の新規開発です。設定ファイルを書かずにルートとコマンドが動き出す体験は、記述量の削減以上にレビュー対象のファイル数が減る効果として効きます。本番性能を詰める段階では、OPcacheとは?読み方とPHP 8.5の変更点・設定・確認方法で扱うバイトコードキャッシュの設定と、前述のDiscoveryキャッシュの両方を揃えておくのが前提になります。
よくある質問
TempestはPHP 8.4のままで導入できますか?
3.x系はできません。composer.json の要求が "php": "^8.5" のため、PHP 8.4環境ではインストールが拒否されます。8.4に留まるなら tempest/framework ^2.0 を指定してv2系を使うことになりますが、v2系の更新は2025年12月で止まっているため、新規採用では推奨できません。
Tempestの現在のバージョンと、1.0が出たのはいつですか?
2026年9月時点の最新はv3.19.2(2026-09-08公開)です。最初の安定版1.0の公開は2025年6月27日で、2024年9月16日に出たv1.0.0-alpha.1と混同されがちです。累計の配布版は102件で、そのうち3.x系はパッチ版を含め49件です。
TempestのCLIはSymfony Consoleを使っているのですか?
使っていません。symfony/console は依存に含まれず、Tempest\Console の独自実装です。コマンドは #[ConsoleCommand] をメソッドへ付けて定義し、引数はPHPのメソッド引数がそのままCLIの引数仕様になります。
Tempest ViewをBladeやTwigに置き換えられますか?
置き換えられます。フレームワークに BladeViewRenderer と TwigViewRenderer が同梱されており、ViewConfig の rendererClass を差し替えれば切り替わります。ただしTwig本体・Blade本体は初期状態のvendorに入らないため、別途 composer require が必要です。
Tempestで静的サイトやCMS的な使い方はできますか?
静的ページの生成は標準機能として用意されています。php tempest static:generate でルートを静的ファイルへコンパイルし、static:clean で削除します。ルート側で StaticPage を指定する形なので、記事一覧のような動的ルートを含むサイトでも部分的に静的化できます。ただし管理画面や入稿機能は同梱されないため、既製CMSの代替として即使えるものではありません。