(jp) =
すべてのプロジェクトの中核には、データがあります。 ほとんどすべてのアプリケーションのタスクは次のように要約できます。ビジネスが望む方法でデータを提供、解釈、操作します。
プロジェクトの開始時に、コントローラーやジョブの構築を開始するのではなく、Laravel がモデルと呼ぶものを構築することから始めます。 大規模なプロジェクトでは、ERD やその他の種類の図を作成して、アプリケーションによって処理されるデータを概念化することが役立ちます。 それが明確になった場合にのみ、データを操作するエントリ ポイントとフックの構築を開始できます。
この章では、構造化された方法でデータを操作する方法を詳しく見ていきます。これにより、チームのすべての開発者がこのデータを予測可能かつ安全な方法で処理するアプリケーションを作成できます。
今、モデルについて考えているかもしれませんが、最初はさらに数歩戻る必要があります。
# 型理論
データ転送オブジェクトの使用法を理解するには — スポイラー: それがこの章の内容です — 型システムに関する背景知識が必要です。
型システムについて話すときに使用される語彙について、誰もが同意するわけではありません。 そこで、ここで使用するいくつかの用語を明確にしましょう。
型システムの強さ (強い型か弱い型か) によって、変数が定義後にその型を変更できるかどうかが決まります。
簡単な例: 文字列変数が与えられた場合 $a="test";
; 弱い型システムにより、その変数を別の型に再割り当てできます。たとえば、 $a = 1;
、整数。
PHP は型付けが弱い言語です — より現実的な例が用意されているように感じます。
$id = '1';
function find(int $id): Model
find($id);
はっきりさせておきますが、PHP の型システムが弱いのは理にかなっています。 主に HTTP リクエストで動作する言語であるため、基本的にすべてが文字列です。
最新の PHP では、厳格な型機能を使用することで、この舞台裏での型の切り替え (型のジャグリング) を回避できると思うかもしれませんが、それは完全に正しいわけではありません。 厳密な型を宣言すると、他の型が関数に渡されなくなりますが、関数自体で変数の値を変更することはできます。
declare(strict_types=1);
function find(int $id): Model
$id = '' . $id;
find('1');
find(1);
厳密な型と型ヒントを使用しても、PHP の型システムは脆弱です。 型ヒントは、その時点での変数の型のみを保証し、変数が持つ可能性のある将来の値については保証しません。
前に述べたように、PHP が処理しなければならないすべての入力は文字列として開始されるため、PHP が弱い型システムを持つことは理にかなっています。 ただし、強い型には興味深い特性があります。いくつかの保証が付属しています。 変数の型が変更不可能な場合、予期しない動作の全範囲が発生しなくなります。
おわかりのように、厳密に型付けされたプログラムがコンパイルされた場合、そのプログラムが弱く型付けされた言語に存在する可能性のあるさまざまなバグを持つことは不可能であることが数学的に証明されています。 言い換えれば、厳密な型は、コードが実際に想定どおりに動作するというより良い保証をプログラマーに与えます。
補足として: これは、厳密に型指定された言語にバグがないという意味ではありません! バグのある実装を完全に作成できます。 しかし、厳密に型指定されたプログラムが正常にコンパイルされると、そのプログラムで特定の一連のバグやエラーが発生しないことが確実になります。
強力な型システムにより、開発者はコードを実行するのではなく、コードを記述するときにプログラムをより深く理解することができます。
もう 1 つ確認する必要がある概念があります。それは静的型と動的型です。ここから興味深いことが始まります。
ご存じのとおり、PHP はインタープリター言語です。 これは、PHP スクリプトが実行時にマシン コードに変換されることを意味します。 PHPを実行しているサーバーにリクエストを送信すると、プレーンなものが取得されます .php
ファイルを開き、その中のテキストをプロセッサが実行できるものに解析します。
繰り返しになりますが、これは PHP の強みの 1 つです。つまり、スクリプトの記述、ページの更新、すべてがそこにあるというシンプルさです。 これは、実行する前にコンパイルする必要がある言語と比べて大きな違いです。
明らかに、これを最適化するキャッシュ メカニズムがあるため、上記のステートメントは単純化しすぎています。 それでも次のポイントを獲得するには十分です。
ここでも欠点があります。PHP は実行時にのみ型をチェックするため、実行時にプログラムの型チェックが失敗する可能性があります。 これは、デバッグするより明確なエラーがある可能性があることを意味しますが、それでもプログラムはクラッシュしています。
この実行時の型チェックにより、PHP は動的型付け言語になります。 一方、静的に型付けされた言語では、コードが実行される前にすべての型チェックが行われます。
PHP 7.0 の時点で、その型システムは大幅に改善されました。 PHPStan、phan、psalm などのツールが最近人気を博し始めたほどです。 これらのツールは動的言語である PHP を使用しますが、コードに対して一連の静的分析を実行します。
これらのオプトイン ライブラリは、コードを実行したり単体テストしたりすることなく、コードに非常に多くの洞察を提供できます。PhpStorm のような IDE には、これらの静的チェックの多くが組み込まれています。
このすべての背景情報を念頭に置いて、アプリケーションの核心であるデータに戻りましょう。
# 非構造化データの構造化
実際には単なるリスト以上の「ものの配列」を操作しなければならなかったことがありますか? 配列キーをフィールドとして使用しましたか? そして、その配列に何が入っているのか正確にわからないという苦痛を感じましたか? その中のデータが実際に期待どおりのものであるかどうか、またはどのフィールドが利用可能か確信が持てませんか?
私が話していることを視覚化しましょう: Laravel のリクエストを操作します。 この例は、既存の顧客を更新するための基本的な CRUD 操作と考えてください。
function store(CustomerRequest $request, Customer $customer)
$validated = $request->validated();
$customer->name = $validated['name'];
$customer->email = $validated['email'];
すでに問題が発生しているのを目にしているかもしれません。 $validated
配列。 PHP の配列は多用途で強力なデータ構造ですが、「もののリスト」以外のものを表すために使用されるとすぐに、問題を解決するためのより良い方法があります。
ソリューションを見る前に、次のことを確認してください。 できる この状況に対処するには:
- ソースコードを読む
- ドキュメントを読む
- ごみ
$validated
それを検査する - または、デバッガーを使用して検査します
ここで、このプロジェクトで数人の開発者のチームと一緒に作業していて、同僚が 5 か月前にこのコードを書いたとしましょう。自分がどのデータを扱っているか分からないことは保証できます。 、上記の面倒なことは何もしません。
厳密に型付けされたシステムを静的分析と組み合わせると、私たちが何を扱っているのかを正確に理解するのに非常に役立つことがわかりました。 たとえば、Rust のような言語は、この問題をきれいに解決します。
struct CustomerData
name: String,
email: String,
birth_date: Date,
構造体が必要です! 残念ながら、PHP には構造体がありません。 配列とオブジェクトがあり、それだけです。
しかし… オブジェクトとクラスで十分かもしれません:
class CustomerData
public string $name;
public string $email;
public Carbon $birth_date;
今私は知っている; 型付きプロパティは、PHP 7.4 以降でのみ使用できます。 この本をいつ読むかによっては、まだそれらを使用できない場合があります — この章の後半に解決策があります。読み続けてください。
PHP 7.4 以降を使用できる場合は、次のようなことができます。
function store(CustomerRequest $request, Customer $customer)
$validated = CustomerData::fromRequest($request);
$customer->name = $validated->name;
$customer->email = $validated->email;
$customer->birth_date = $validated->birth_date;
IDE に組み込まれている静的アナライザーは、処理しているデータを常に教えてくれます。
データを信頼できる方法で使用できるように、構造化されていないデータを型でラップするこのパターンは、「データ転送オブジェクト」と呼ばれます。 これは、平均よりも大きな Laravel プロジェクトで使用することを強くお勧めする最初の具体的なパターンです。
同僚、友人、または Laravel コミュニティ内でこの本について話し合っていると、強い型システムについて同じビジョンを共有していない人々に出くわすかもしれません。 実際、PHP の動的で弱い側面を受け入れることを好む人はたくさんいます。 そして、それには間違いなく言いたいことがあります。
私の経験では、プロジェクトで数人の開発者のチームと長時間にわたって作業する場合、強く型付けされたアプローチにはより多くの利点があります。 認知負荷を軽減するために、あらゆる機会を利用する必要があります。 開発者が、変数の内容を正確に知りたがるたびにコードのデバッグを開始する必要はありません。 開発者が重要なこと、つまりアプリケーションの構築に集中できるように、情報は手元にある必要があります。
もちろん、DTO の使用には代償が伴います。これらのクラスを定義するオーバーヘッドだけではありません。 たとえば、要求データを DTO にマッピングする必要もあります。
DTO を使用する利点は、支払う必要があるこのコストを確実に上回ります。 このコードを書くことで時間を失ったとしても、長い目で見れば埋め合わせることができます。
ただし、「外部」データから DTO を構築することについては、まだ回答が必要です。
# DTO ファクトリー
DTO をどのように構築するのか? 2 つの可能性を紹介し、どちらが個人的に好みかを説明します。
最初のものは最も正しいものです: 専用の工場を使用します。
class CustomerDataFactory
public function fromRequest(
CustomerRequest $request
): CustomerData
return new CustomerData([
'name' => $request->get('name'),
'email' => $request->get('email'),
'birth_date' => Carbon::make(
$request->get('birth_date')
),
]);
分離されたファクトリを使用すると、プロジェクト全体でコードがクリーンに保たれます。 このファクトリがアプリケーション層に存在することは最も理にかなっています。
正しい解決策ではありますが、前の例で、DTO クラス自体に省略表現を使用したことに気付いたでしょう。 CustomerData::fromRequest
.
このアプローチの何が問題なのですか? たとえば、ドメインにアプリケーション固有のロジックを追加します。 ドメインに存在する DTO は、ドメインについて知る必要があります。 CustomerRequest
アプリケーション層に存在するクラス。
use Spatie\DataTransferObject\DataTransferObject;
class CustomerData extends DataTransferObject
public static function fromRequest(
CustomerRequest $request
): self
return new self([
'name' => $request->get('name'),
'email' => $request->get('email'),
'birth_date' => Carbon::make(
$request->get('birth_date')
),
]);
明らかに、ドメイン内にアプリケーション固有のコードを混在させることは最善のアイデアではありません。 ただし、それには私の好みがあります。 それには2つの理由があります。
まず第一に、DTO がコードベースへのデータのエントリ ポイントであることはすでに確立しています。 外部からのデータを扱うとすぐに、それを DTO に変換したいと考えています。 このマッピングを行う必要があります どこか、したがって、それが意図されているクラス内で行うこともできます。
第二に、これがより重要な理由です。 PHP 自体の制限の 1 つである名前付きパラメーターをサポートしていないため、私はこのアプローチを好みます。
ほら、DTO が最終的に各プロパティの個別のパラメーターを持つコンストラクターを持つことは望ましくありません。これはスケーリングされず、null 許容または既定値のプロパティを操作するときに非常に混乱します。 そのため、私は配列を DTO に渡し、その配列内のデータに基づいてそれ自体を構築するというアプローチを好みます。 余談ですが、spatie/data-transfer-object パッケージを使用して、まさにこれを行います。
名前付きパラメーターがサポートされていないため、利用可能な静的分析もありません。つまり、DTO を構築するときはいつでも、必要なデータについてわからないことになります。 私は、これを DTO クラス内で「暗闇の中で」保持することを好みます。これにより、外部から余計なことを考えずに使用できるようになります。
ただし、PHP が名前付きパラメーターのようなものをサポートする場合は、ファクトリ パターンが適していると言えます。
public function fromRequest(
CustomerRequest $request
): CustomerData
return new CustomerData(
'name' => $request->get('name'),
'email' => $request->get('email'),
'birth_date' => Carbon::make(
$request->get('birth_date')
),
);
構築時に配列がないことに注意してください CustomerData
.
PHP がこれをサポートするまでは、理論上の正しい解決策よりも実用的な解決策を選択します。 それはあなた次第です。 チームに最適なものを自由に選択してください。
# 型付きプロパティの代替
前に述べたように、型付きプロパティを使用して DTO をサポートする代わりに、docblock があります。 以前にリンクした DTO パッケージもそれらをサポートしています。
use Spatie\DataTransferObject\DataTransferObject;
class CustomerData extends DataTransferObject
public $name;
public $email;
public $birth_date;
ただし、デフォルトでは、docblock は、データが指定された型であることを保証しません。 幸いなことに、PHP にはリフレクション API があり、これを使用するとさらに多くのことが可能になります。
このパッケージが提供するソリューションは、PHP 型システムの拡張と考えることができます。 ユーザーランドと実行時にできることは限られていますが、それでも付加価値があります。 PHP 7.4 を使用できず、docblock タイプが実際に尊重されていることをもう少し確実にしたい場合は、このパッケージでカバーできます。
データはほぼすべてのプロジェクトの中核に存在するため、最も重要なビルディング ブロックの 1 つです。 データ転送オブジェクトは、構造化されたタイプ セーフで予測可能な方法でデータを操作する方法を提供します。
この本全体を通して、DTO は頻繁に使用されることに注意してください。 そのため、最初にそれらを詳しく調べることが非常に重要でした。 同様に、徹底的な注意が必要なもう 1 つの重要な構成要素があります。アクションです。 それが次の章のトピックで、来週リリースされます。