diff --git a/manuals/1.0/en/01-overview.md b/manuals/1.0/en/01-overview.md index 9b8f331..bbf94aa 100644 --- a/manuals/1.0/en/01-overview.md +++ b/manuals/1.0/en/01-overview.md @@ -100,8 +100,12 @@ You can acquire the following new programming methods: 3. Express natural transformation (self-metamorphosis) instead of forcing objects to change 4. Trust the correct state instead of preventing errors +## So Why "DeletedUser"? + +Now back to the opening question. `new DeletedUser($activeUser)` is not an action—it is a transformation. The user is not "being deleted"; a new existence called `DeletedUser` is born from `$activeUser`. The type itself proves that deletion has occurred. There is no need to check a `$status` flag, no risk of calling methods on an already-deleted user. This is the essence of Be Framework: **express state transitions as new types, not as actions on existing objects.** + ## Let's Get Started -Let's learn from the foundation. Everything starts from [Input Classes]({{ '/manuals/1.0/en/02-input-classes.html' | relative_url }}) → +**Want to try it first?** Jump to the [Getting Started]({{ '/manuals/1.0/en/getting-started.html' | relative_url }}) guide for a hands-on Hello World, or the [Tutorial]({{ '/manuals/1.0/en/tutorial.html' | relative_url }}) for a real-world example. -Create your first object and experience why it is "DeletedUser". +**Want to understand the concepts?** Continue to [Input Classes]({{ '/manuals/1.0/en/02-input-classes.html' | relative_url }}) to learn the foundations step by step → diff --git a/manuals/1.0/en/03-being-classes.md b/manuals/1.0/en/03-being-classes.md index 7df8931..67e6782 100644 --- a/manuals/1.0/en/03-being-classes.md +++ b/manuals/1.0/en/03-being-classes.md @@ -37,6 +37,8 @@ final readonly class ValidatedUser } ``` +`#[Input]` parameters automatically receive values from the previous class's public properties by matching names. `UserInput`'s `public string $name` maps to `ValidatedUser`'s `#[Input] string $name`. `#[Inject]` parameters receive external dependencies from the DI container. The detailed rules of this automatic matching are explained in [Chapter 5: Metamorphosis](./05-metamorphosis-patterns.html). + ## Objects as Temporal Beings In Be Framework, objects are not treated as static data structures, but as temporal beings that exist only within a specific moment in time. @@ -51,7 +53,7 @@ Immanence meets Transcendence, the logic of transformation takes effect, and new ### Life (Being) -The object exposes its "form as it should be" to the world as `public readonly` properties. But no one touches those properties—the object vanishes shortly after birth, making way for the next. +The object exposes its "form as it should be" to the world as `public readonly` properties. The framework reads these properties and passes them as `#[Input]` to the next class in the chain. The object then vanishes, making way for the next. ### Becoming the Self You Want to Be diff --git a/manuals/1.0/en/04-final-objects.md b/manuals/1.0/en/04-final-objects.md index b3d987b..50ef8be 100644 --- a/manuals/1.0/en/04-final-objects.md +++ b/manuals/1.0/en/04-final-objects.md @@ -24,7 +24,7 @@ final readonly class SuccessfulOrder public string $confirmationCode; public DateTimeImmutable $timestamp; public string $message; - public BeenProcessed $been; // Evidence of completion + public BeenConfirmed $been; // Evidence of completion public function __construct( #[Input] Money $total, // Immanence @@ -37,7 +37,7 @@ final readonly class SuccessfulOrder $this->timestamp = new DateTimeImmutable(); $this->message = "Order Confirmed: {$this->orderId}"; - $this->been = new BeenProcessed( + $this->been = new BeenConfirmed( actor: $card->getHolderName(), timestamp: $this->timestamp, evidence: [ @@ -50,7 +50,9 @@ final readonly class SuccessfulOrder } ``` -In contrast to Input Classes, Final Objects fully express the richness of the domain. Immanence has met Transcendence, undergone transformation, and reached a complete state that requires no further change. Success and failure share the same structure. A `FailedOrder` also has a `$been`—`BeenRejected`. +The `$been` property uses an application-defined, domain-specific type. `SuccessfulOrder` has `BeenConfirmed`, `FailedOrder` has `BeenRejected`, a `DeletedUser` might have `BeenDeleted`. What counts as "evidence of completion" depends on the domain—you design the class to capture whatever your domain requires as proof. + +In contrast to Input Classes, Final Objects fully express the richness of the domain. Immanence has met Transcendence, undergone transformation, and reached a complete state that requires no further change. ## Completeness of Temporal Being @@ -75,7 +77,7 @@ In traditional programming, external tests judge whether an object has been proc ## Multiple Final Destinies -Objects can have multiple possible final forms determined by their nature: +Objects can have multiple possible final forms determined by their nature. Here `$becoming` triggers the metamorphosis chain—this mechanism is explained in the [next chapter](./04a-becoming.html): ```php $order = $becoming(new OrderInput($items, $card)); diff --git a/manuals/1.0/en/05-metamorphosis-patterns.md b/manuals/1.0/en/05-metamorphosis-patterns.md index 0097437..1b5b52b 100644 --- a/manuals/1.0/en/05-metamorphosis-patterns.md +++ b/manuals/1.0/en/05-metamorphosis-patterns.md @@ -43,39 +43,42 @@ Each moment never returns, and new existence preserves previous forms as memory Like living beings in reality, objects determine their own destiny through the interaction between immanence and transcendence. This is not following a predetermined route, but natural metamorphosis responding to the circumstances of that moment: ```php -#[Be([ApprovedApplication::class, RejectedApplication::class])] +#[Be([ApprovalNotification::class, RejectionNotification::class])] final readonly class ApplicationReview { - public ApprovedApplication|RejectedApplication $being; + public Approved|Rejected $being; public function __construct( - #[Input] array $documents, // Immanence - #[Inject] ReviewService $reviewer // Transcendence + #[Input] string $email, // Immanence + #[Input] array $documents, // Immanence + #[Inject] ReviewService $reviewer // Transcendence ) { $result = $reviewer->evaluate($documents); // Destiny is decided at this very moment $this->being = $result->isApproved() - ? new ApprovedApplication($documents, $result->getScore()) - : new RejectedApplication($result->getReasons()); + ? new Approved($email, $result->getScore()) + : new Rejected($email, $result->getReasons()); } } ``` +`Approved` and `Rejected` are Reason objects—like `Emergency` and `Observation` in the triage tutorial. The Reason determines the destiny: it carries the decision and its basis, and when assigned to `$being`, its type decides which class comes next. Decision logic that completes within the constructor belongs in the Reason layer. When deferred behavior is needed—methods to be called after construction, like `assignER()` in the triage example—those capabilities belong on the Final class. + ## Type-Based Continuation -Among the candidate classes specified in `#[Be()]`, the framework automatically selects the one whose constructor `#[Input]` parameter matches the type of a public property on the current object: +Among the candidate classes specified in `#[Be()]`, the framework automatically selects the one whose constructor `#[Input]` parameter can be satisfied by the current object's public properties: ```php -// If ApplicationReview's property is of type ApprovedApplication, -// this class is automatically selected because #[Input] type matches +// ApplicationReview's $being is of type Approved, +// so this class is selected because #[Input] Approved matches final readonly class ApprovalNotification { public function __construct( - #[Input] ApprovedApplication $application, + #[Input] Approved $approval, #[Inject] Mailer $mailer ) { - $mailer->send($application->getEmail(), 'Approved!'); + $mailer->send($approval->email, 'Approved! Score: ' . $approval->score); } } ``` diff --git a/manuals/1.0/en/08-reason-layer.md b/manuals/1.0/en/08-reason-layer.md index 1d5fc75..91632e0 100644 --- a/manuals/1.0/en/08-reason-layer.md +++ b/manuals/1.0/en/08-reason-layer.md @@ -65,6 +65,8 @@ final readonly class StandardDelivery The type `ExpressShipping $being` itself is the reason why it becomes `ExpressDelivery`. The framework reads this type and automatically selects the corresponding transformation destination. +Any Reason object can serve as either `#[Inject]` (providing transcendent capabilities) or `$being` (determining destiny). The difference is not in the object itself, but in how it is used. A `JTASProtocol` that evaluates patients as `#[Inject]` in one context could determine destiny as `$being` in another. + ## Defining Reason Classes Reason classes bundle the services necessary to realize a specific mode of existence: @@ -145,6 +147,102 @@ public function __construct( "What is needed to become ExpressDelivery?" — a single reason object answers that question. Objects themselves declare "what to become", while reason objects realize "how to achieve that state". +## Reason Returning Potential + +So far, Reason has returned immediate values like `Fee`. But consider order processing: inventory must be reserved, payment captured, and shipping arranged—and all three must succeed before any of them commit. If payment fails after inventory is reserved, that reservation must not persist. + +For this kind of all-or-nothing coordination, Reason returns a **Potential**—an object that holds a deferred operation, realized later via `be()`. This pattern is only needed when multiple external operations must all commit atomically. + +### Potential: Prepared but Uncommitted + +A Reason method prepares the external operation and returns a Potential: + +```php +final class PaymentGateway +{ + public function authorize(string $cardNumber, int $amount): PaymentCapture + { + $authCode = $this->api->authorize($cardNumber, $amount); + + return new PaymentCapture( + $authCode, + $amount, + fn () => $this->api->capture($authCode, $amount), + ); + } +} +``` + +`PaymentCapture` is a Potential—it holds the authorization code and a deferred capture operation. The payment is authorized but not yet captured. Calling `be()` commits it: + +```php +$capture = $gateway->authorize($cardNumber, $amount); +$capture->authorizationCode; // Available immediately +$capture->be(); // Commits the capture +``` + +### Moment: Holding Potential + +A class that holds a Potential from Reason is called a **Moment** (Hegel's 契機—an essential aspect that only makes sense as part of a whole). A Moment implements `MomentInterface`, provided by the framework: + +```php +interface MomentInterface +{ + public function be(): void; +} +``` + +```php +final readonly class PaymentCompleted implements MomentInterface +{ + public PaymentCapture $capture; + + public function __construct( + #[Input] public string $cardNumber, + #[Input] public int $amount, + #[Inject] PaymentGateway $gateway, + ) { + $this->capture = $gateway->authorize($cardNumber, $amount); + } + + public function be(): void + { + $this->capture->be(); + } +} +``` + +### Convergence: Final Realizes Moments + +When multiple Moments must all succeed together, a Final Object receives them and calls `be()` on each. This is not an external command—it is self-completion: + +```php +final readonly class OrderConfirmed +{ + public string $orderId; + public string $status; + + public function __construct( + public InventoryReserved $inventory, + public PaymentCompleted $payment, + public ShippingArranged $shipping, + ) { + $this->inventory->be(); + $this->payment->be(); + $this->shipping->be(); + + $this->orderId = 'ORD-' . date('Ymd') . '-' . bin2hex(random_bytes(4)); + $this->status = 'confirmed'; + } +} +``` + +If any Moment cannot be created (because its Reason failed), the Final Object is never constructed. If all Moments exist, `be()` commits every deferred operation. No manual rollback flags, no nested try-catch. + +### When to Use This Pattern + +Use Potential-returning Reason when multiple external operations must succeed atomically—all commit together, or none at all. For simple cases where Reason returns an immediate value, Potential is unnecessary. + --- The inability to exist is itself an existence. Learn how to handle this in [Validation and Error Handling](./09-error-handling.html) ➡️ diff --git a/manuals/1.0/en/tutorial.md b/manuals/1.0/en/tutorial.md index 89cf315..687e30c 100644 --- a/manuals/1.0/en/tutorial.md +++ b/manuals/1.0/en/tutorial.md @@ -143,9 +143,9 @@ final readonly class PatientArrival The `#[Be]` attribute declares destiny: this arrival WILL BECOME a TriageAssessment. -## Step 5: Create Destiny Markers +## Step 5: Create Reason Objects -These types represent the two possible destinies: +These Reason objects represent the two possible destinies. The Reason determines the destiny—"this is it": ```php // src/Reason/Emergency.php @@ -155,7 +155,7 @@ final readonly class Emergency {} final readonly class Observation {} ``` -These aren't empty classes—they ARE the distinction. An `Emergency` is fundamentally different from an `Observation`. The type itself carries meaning. +These aren't empty classes—they ARE the distinction. An `Emergency` is fundamentally different from an `Observation`. The type itself carries meaning. When assigned to `$being`, the Reason's type determines which Final class comes next. ## Step 6: Create Being Class @@ -367,9 +367,9 @@ src/ │ ├── EmergencyCase.php # Final form: emergency │ └── ObservationCase.php # Final form: observation ├── Reason/ -│ ├── Emergency.php # Destiny marker -│ ├── JTASProtocol.php # Transcendent wisdom -│ └── Observation.php # Destiny marker +│ ├── Emergency.php # Reason: determines destiny +│ ├── JTASProtocol.php # Reason: transcendent wisdom +│ └── Observation.php # Reason: determines destiny └── Semantic/ ├── BodyTemperature.php # What CAN exist └── HeartRate.php diff --git a/manuals/1.0/ja/01-overview.md b/manuals/1.0/ja/01-overview.md index 9dd26a4..1e20a51 100644 --- a/manuals/1.0/ja/01-overview.md +++ b/manuals/1.0/ja/01-overview.md @@ -94,8 +94,12 @@ Be Frameworkは異なるアプローチを採ります。操作が目的のデ 3. オブジェクトを無理に変更するのではなく、自然な変容(自己変容)を表現する 4. エラーを防ぐのではなく、正しい状態を信頼する +## なぜ「DeletedUser」なのか? + +冒頭の問いに戻りましょう。`new DeletedUser($activeUser)` は操作ではなく、変容です。ユーザーが「削除される」のではなく、`$activeUser`から`DeletedUser`という新しい存在が生まれるのです。型そのものが削除済みであることを証明しています。`$status`フラグを確認する必要も、削除済みのユーザーに誤ってメソッドを呼ぶ危険もありません。これがBe Frameworkの本質です:**状態遷移を既存オブジェクトへの操作ではなく、新しい型として表現する。** + ## さあ、始めましょう -基礎から学んでいきましょう。全ては[入力クラス]({{ '/manuals/1.0/ja/02-input-classes.html' | relative_url }})から始まります → +**まず動かしてみたい方は?** [Getting Started]({{ '/manuals/1.0/ja/getting-started.html' | relative_url }})でHello Worldを体験するか、[チュートリアル]({{ '/manuals/1.0/ja/tutorial.html' | relative_url }})で実践的な例に挑戦できます。 -最初のオブジェクトを作りながら、なぜ『`DeletedUser`』なのかを体感してください。 +**概念から理解したい方は?** [入力クラス]({{ '/manuals/1.0/ja/02-input-classes.html' | relative_url }})に進んで、基礎からステップバイステップで学びましょう → diff --git a/manuals/1.0/ja/03-being-classes.md b/manuals/1.0/ja/03-being-classes.md index 158c917..60a4fdb 100644 --- a/manuals/1.0/ja/03-being-classes.md +++ b/manuals/1.0/ja/03-being-classes.md @@ -39,6 +39,8 @@ final readonly class ValidatedUser } ``` +`#[Input]`パラメータには、前のクラスのpublicプロパティが名前の一致により自動的に渡されます。`UserInput`の`public string $name`は`ValidatedUser`の`#[Input] string $name`に対応します。`#[Inject]`パラメータにはDIコンテナから外部の依存が注入されます。この自動マッチングの詳細なルールは[5章 変容](./05-metamorphosis-patterns.html)で説明します。 + ## 時間的存在としてのオブジェクト Be Frameworkでは、オブジェクトを静的なデータ構造ではなく、特定の時間の中でのみ存在する時間的な存在として捉えます。 @@ -53,7 +55,7 @@ Be Frameworkでは、オブジェクトを静的なデータ構造ではなく ### 生 -オブジェクトは `public readonly` プロパティとして、その「あるべき姿」を世界に晒します。しかし、そのプロパティが参照されることはありません。生まれた直後に、次のオブジェクトへ引き継がれて消滅するからです。 +オブジェクトは `public readonly` プロパティとして、その「あるべき姿」を世界に晒します。フレームワークがこのプロパティを読み取り、次のクラスの `#[Input]` として引き渡します。その後、オブジェクトは消滅し、次の存在に道を譲ります。 ### なりたい自分になる diff --git a/manuals/1.0/ja/04-final-objects.md b/manuals/1.0/ja/04-final-objects.md index 9aa55f8..133f848 100644 --- a/manuals/1.0/ja/04-final-objects.md +++ b/manuals/1.0/ja/04-final-objects.md @@ -24,7 +24,7 @@ final readonly class SuccessfulOrder public string $confirmationCode; public DateTimeImmutable $timestamp; public string $message; - public BeenProcessed $been; // 完了の証跡 + public BeenConfirmed $been; // 完了の証跡 public function __construct( #[Input] Money $total, // 内在 @@ -37,7 +37,7 @@ final readonly class SuccessfulOrder $this->timestamp = new DateTimeImmutable(); $this->message = "注文確認: {$this->orderId}"; - $this->been = new BeenProcessed( + $this->been = new BeenConfirmed( actor: $card->getHolderName(), timestamp: $this->timestamp, evidence: [ @@ -50,7 +50,9 @@ final readonly class SuccessfulOrder } ``` -入力クラスとは対照的に、最終オブジェクトはドメインの豊かさを完全に表現した存在です。内在が超越と出会い、変容を経て、これ以上変わる必要のない完全な状態に達しています。成功も失敗も同じ構造です。たとえば`FailedOrder`も、拒否の証跡として`$been`を持ちます。 +`$been`プロパティはアプリケーションが定義する、ドメイン固有の型を持ちます。`SuccessfulOrder`には`BeenConfirmed`、`FailedOrder`には`BeenRejected`、`DeletedUser`なら`BeenDeleted`というように命名します。何を「完了の証跡」とするかはドメインによって異なり、必要な証跡に応じてクラスを設計します。 + +入力クラスとは対照的に、最終オブジェクトはドメインの豊かさを完全に表現した存在です。内在が超越と出会い、変容を経て、これ以上変わる必要のない完全な状態に達しています。 ## 時間的存在の完全性 @@ -75,7 +77,7 @@ Be Frameworkでは、オブジェクトの時間的存在を二つの軸で捉 ## 複数の最終的運命 -オブジェクトはその性質によって複数の最終形態を持つことができます: +オブジェクトはその性質によって複数の最終形態を持つことができます。ここで使っている`$becoming`は変容チェーンを起動する仕組みで、[次章](./04a-becoming.html)で説明します: ```php $order = $becoming(new OrderInput($items, $card)); diff --git a/manuals/1.0/ja/05-metamorphosis-patterns.md b/manuals/1.0/ja/05-metamorphosis-patterns.md index ea7ff28..dac7c81 100644 --- a/manuals/1.0/ja/05-metamorphosis-patterns.md +++ b/manuals/1.0/ja/05-metamorphosis-patterns.md @@ -43,39 +43,42 @@ final readonly class WelcomeMessage { /* ... */ } 現実の生物と同様に、オブジェクトは内在と超越の相互作用によって、自身の運命を決定します。これは予め決められたルートを辿るのではなく、その瞬間の状況に応じた自然な変容です: ```php -#[Be([ApprovedApplication::class, RejectedApplication::class])] +#[Be([ApprovalNotification::class, RejectionNotification::class])] final readonly class ApplicationReview { - public ApprovedApplication|RejectedApplication $being; + public Approved|Rejected $being; public function __construct( - #[Input] array $documents, // 内在 - #[Inject] ReviewService $reviewer // 超越 + #[Input] string $email, // 内在 + #[Input] array $documents, // 内在 + #[Inject] ReviewService $reviewer // 超越 ) { $result = $reviewer->evaluate($documents); // 運命は今この瞬間に決まる $this->being = $result->isApproved() - ? new ApprovedApplication($documents, $result->getScore()) - : new RejectedApplication($result->getReasons()); + ? new Approved($email, $result->getScore()) + : new Rejected($email, $result->getReasons()); } } ``` +`Approved`と`Rejected`はReasonオブジェクトです。Tutorialの`Emergency`や`Observation`と同じ役割です。理由(Reason)が運命を決めます。判定結果とその根拠を持ち、`$being`に代入されると、その型が次のクラスを決定します。コンストラクタ内で完結する判定ロジックはReason層に置きます。一方、生成後に呼び出される振る舞い—Tutorialの`assignER()`のような遅延実行メソッド—はFinalクラスに持たせます。 + ## 型による継続 -次の変容先は自動的に選択されます。具体的には、`#[Be()]`で指定された候補クラスのうち、現在のオブジェクトのpublicプロパティの型と`#[Input]`引数の型が一致するクラスが選ばれます: +`#[Be()]`で指定された候補クラスのうち、現在のオブジェクトのpublicプロパティで`#[Input]`コンストラクタ引数を満たせるクラスが自動的に選択されます: ```php -// ApplicationReviewのプロパティがApprovedApplication型なら -// #[Input]の型がマッチするこのクラスが自動的に選択される +// ApplicationReviewの$beingがApproved型なので +// #[Input] Approvedがマッチするこのクラスが選択される final readonly class ApprovalNotification { public function __construct( - #[Input] ApprovedApplication $application, + #[Input] Approved $approval, #[Inject] Mailer $mailer ) { - $mailer->send($application->getEmail(), 'Approved!'); + $mailer->send($approval->email, 'Approved! Score: ' . $approval->score); } } ``` diff --git a/manuals/1.0/ja/08-reason-layer.md b/manuals/1.0/ja/08-reason-layer.md index 2f0f79e..800f7f2 100644 --- a/manuals/1.0/ja/08-reason-layer.md +++ b/manuals/1.0/ja/08-reason-layer.md @@ -65,6 +65,8 @@ final readonly class StandardDelivery `ExpressShipping $being`という型そのものが、なぜ`ExpressDelivery`になるのかの理由です。フレームワークはこの型を読み取り、対応する変容先を自動選択します。 +どのReasonオブジェクトも`#[Inject]`(超越の能力を提供)にも`$being`(運命を決定)にもなれます。違いはオブジェクト自体ではなく、使われ方にあります。ある文脈で`#[Inject]`として患者を評価する`JTASProtocol`が、別の文脈では`$being`として運命を決定することもできます。 + ## 存在理由クラスの定義 存在理由クラスは、特定の存在様式を実現するために必要なサービスをまとめたものです: @@ -145,6 +147,102 @@ public function __construct( 「ExpressDeliveryになるには何が必要か?」という問いに、存在理由オブジェクト一つが答えます。オブジェクト自身は「何になるか」を宣言し、存在理由は「どうやってその状態になるか」を実現します。 +## Potentialを返すReason + +ここまでのReasonは`Fee`のような即時の値を返していました。しかし注文処理を考えてみましょう。在庫確保・決済・配送手配のすべてが成功してからコミットする必要があります。決済が失敗したのに在庫だけ確保されたままでは困ります。 + +このような一括実現が必要な場面で、Reasonは値の代わりに**Potential**を返します。Potentialは遅延操作を保持するオブジェクトで、後から`be()`で実現されます。このパターンは複数の外部操作をアトミックにコミットする必要があるときだけ使います。 + +### Potential: 準備済み・未コミット + +Reasonのメソッドは外部操作を準備し、Potentialを返します: + +```php +final class PaymentGateway +{ + public function authorize(string $cardNumber, int $amount): PaymentCapture + { + $authCode = $this->api->authorize($cardNumber, $amount); + + return new PaymentCapture( + $authCode, + $amount, + fn () => $this->api->capture($authCode, $amount), + ); + } +} +``` + +`PaymentCapture`はPotentialです。認証コードとキャプチャの遅延操作を保持しています。決済は認証済みですが、まだ確定していません。`be()`で確定します: + +```php +$capture = $gateway->authorize($cardNumber, $amount); +$capture->authorizationCode; // 即座に利用可能 +$capture->be(); // キャプチャを確定 +``` + +### Moment: Potentialを保持する + +ReasonからPotentialを受け取って保持するクラスを**Moment**(ヘーゲルの契機—全体の中でのみ意味を持つ不可欠な側面)と呼びます。Momentはフレームワークが提供する`MomentInterface`を実装します: + +```php +interface MomentInterface +{ + public function be(): void; +} +``` + +```php +final readonly class PaymentCompleted implements MomentInterface +{ + public PaymentCapture $capture; + + public function __construct( + #[Input] public string $cardNumber, + #[Input] public int $amount, + #[Inject] PaymentGateway $gateway, + ) { + $this->capture = $gateway->authorize($cardNumber, $amount); + } + + public function be(): void + { + $this->capture->be(); + } +} +``` + +### 収束: FinalがMomentを実現する + +複数のMomentがすべて揃う必要があるとき、Final Objectはそれらを受け取り、各Momentの`be()`を呼びます。これは外部からの命令ではなく、自己完成です: + +```php +final readonly class OrderConfirmed +{ + public string $orderId; + public string $status; + + public function __construct( + public InventoryReserved $inventory, + public PaymentCompleted $payment, + public ShippingArranged $shipping, + ) { + $this->inventory->be(); + $this->payment->be(); + $this->shipping->be(); + + $this->orderId = 'ORD-' . date('Ymd') . '-' . bin2hex(random_bytes(4)); + $this->status = 'confirmed'; + } +} +``` + +いずれかのMomentが生成できなければ(Reasonが失敗したため)、Final Objectは構築されません。すべてのMomentが存在すれば、`be()`がすべての遅延操作をコミットします。手動のロールバックフラグもネストされたtry-catchも不要です。 + +### このパターンを使う場面 + +複数の外部操作をアトミックに成功させる必要があるときに、Potentialを返すReasonを使います。Reasonが即時の値を返す単純なケースでは不要です。 + --- 存在できなかった、という結果もまた扱う必要があります。[検証とエラーハンドリング](./09-error-handling.html)でその扱い方を学びます ➡️ diff --git a/manuals/1.0/ja/tutorial.md b/manuals/1.0/ja/tutorial.md index 9a6877f..1c3f547 100644 --- a/manuals/1.0/ja/tutorial.md +++ b/manuals/1.0/ja/tutorial.md @@ -143,9 +143,9 @@ final readonly class PatientArrival `#[Be]` 属性は運命を宣言します:この到着は TriageAssessment に**なります**。 -## ステップ 5: 運命マーカーを作成 +## ステップ 5: Reasonオブジェクトを作成 -これらの型は2つの可能な運命を表します: +これらのReasonオブジェクトは2つの可能な運命を表します。理由(Reason)が「これでいい」と運命を決めます: ```php // src/Reason/Emergency.php @@ -155,7 +155,7 @@ final readonly class Emergency {} // 緊急 final readonly class Observation {} // 経過観察 ``` -これらは中身のないクラスに見えますが、型そのものが意味を持ちます。`Emergency` は `Observation` とは根本的に異なる存在です。 +これらは中身のないクラスに見えますが、型そのものが意味を持ちます。`Emergency` は `Observation` とは根本的に異なる存在です。`$being`に代入されると、Reasonの型が次のFinalクラスを決定します。 ## ステップ 6: Being クラスを作成 @@ -368,9 +368,9 @@ src/ │ ├── EmergencyCase.php # 最終形態:緊急 │ └── ObservationCase.php # 最終形態:経過観察 ├── Reason/ -│ ├── Emergency.php # 運命マーカー -│ ├── JTASProtocol.php # 超越的な知恵 -│ └── Observation.php # 運命マーカー +│ ├── Emergency.php # Reason: 運命を決定 +│ ├── JTASProtocol.php # Reason: 超越的な知恵 +│ └── Observation.php # Reason: 運命を決定 └── Semantic/ ├── BodyTemperature.php # 何が存在できるか └── HeartRate.php