値オブジェクトであるクラスCrate
を考えます。クレートは有効な3次元ボックスを表します。
コンストラクターで指定されたパラメーターを検証し、指定されたディメンションパラメーターが無効な場合は例外をスローします。
class Crate
{
protected $length, $width, $height;
function __construct(float $length, float $width, float $height)
{
if ($length <= 0 || $width <=0 || $height <=0)
throw new RuntimeException("Invalid Dimension");
$this->length = $length;
$this->width = $width;
$this->height = $height;
}
function getDimensions()
{
echo $this->length . 'x' . $this->width . 'x' . $this->height;
}
}
私のプロジェクトでは、いくつかのクレートボックス構成の寸法を表示する必要があります。つまり、showCrates
の配列を受け入れ、その配列を使用して各クレートの寸法およびその他の情報を表示するCrate
メソッドがあります。
function showCrates(array $crates)
{
foreach($crates as $key => $crate)
echo 'Crate #' . $key . ':' . $crate->getDimensions() . PHP_EOL;
}
showCrates
は、すべてのCrate
オブジェクトに指定されたすべてのパラメーターが有効な場合に適切に機能します。ただし、Crate
オブジェクトが無効なパラメーターで例外をスローすると、コードの実行が停止します。
実行を継続して有効な木枠を表示したいが、無効な木枠に対して「インデックスi
の木枠が無効でした」というメッセージが表示されるようにしたい。
私が期待するサンプル出力は次のとおりです:
Crate #1: 2x5x9
Crate #2: 1x3x4
Crate #3 is invalid, because supplied dimensions were: 0x0x0
Crate #4: 5x6x3
質問
Crate
オブジェクト自体を変更せずに上記の出力を表示できる方法を探しています。
私が拒否している可能性のある解決策:
私の質問を解決する1つの方法は、無効なCrate
オブジェクトを許可し、その中にCrate
が有効かどうかを示すエラーブールフラグを設定することです。次に、showCrates
メソッドシグネチャを保持して、Crate
オブジェクトの配列を受け入れるようにしますが、最初にCrate
が有効かどうかをチェックし、それに応じて必要な出力を表示するように変更します。 。
ただし、非常に強力な引数がない限り、Crate
オブジェクトを変更しないことが理想的です。 Crate
オブジェクトを不変の値オブジェクトとして構成しました。このオブジェクトは、指定されたパラメーターが有効な場合にのみ存在し、それ以外の場合は例外をスローします。理由については、この方法でCrate
をテストする方が簡単で、Crate
が有効かどうかを確認するための追加のチェックはないと私は信じています。
サンプル呼び出しコード
class CrateRequestHandler
{
function handle(ServerRequestInterface $request)
{
// mocked up data from Request
// this is sample data for showcase purposes only
// it usually supplied by user or from database
// and it is impossible to tell ahead of time
// if it will be correct for Crate purposes
$crates = array();
$crates[0] = new Crate(2, 5, 9);
$crates[1] = new Crate(1, 3, 4);
$crates[2] = new Crate(0, 0, 0);
$crates[3] = new Crate(5, 6, 3);
// send to View
$this->showCrates($crates);
}
function showCrates(array $crates)
{
foreach($crates as $key => $crate)
echo 'Crate #' . $key . ':' . $crate->getDimensions() . PHP_EOL;
}
}
私には、CrateCreation
という別の値オブジェクトを作成して、箱の作成をモデル化し、showCrates
に実際のリストの代わりにCrateCreation
オブジェクトのリストを受け入れさせるCrate
オブジェクト。 CrateCreation
オブジェクトは、次の情報を保持します:
Crate
オブジェクト。サンプルクラス:
<?php
class CrateCreation
{
private $length, $width, $height;
private $crate;
function __construct(float $length, float $width, float $height)
{
$this->length = $length;
$this->width = $width;
$this->height = $height;
try {
$this->crate = new Crate($length, $width, $height);
}
catch(RuntimeException $e)
{
// do some loging here
}
}
// getters here
// the same `getDimensions` method here
}
次に、showCrates
関数を次のように変更する必要があります。
<?php
function showCrates(array $crateCreations)
{
foreach($crateCreations as $key => $crateCreation)
{
$crate = $crateCreation->getCrate();
if (!$crate)
{
echo 'Crate #' . $key . ' is invalid, because supplied dimensions were: ' . $crateCreation->getDimensions() . PHP_EOL;
continue;
}
echo 'Crate #' . $key . ':' . $crate->getDimensions() . PHP_EOL;
}
}
あなたはCrateオブジェクトが常に有効であるべきであると主張するのは正しいです。これは、その不変式を強制する必要があるビジネスクラスであり、現在実行されています。問題は、ユーザー入力の取得にあります。ユーザー入力がビジネスルールや制約に違反することを許可する必要があります。他に少なくとも2つのクラスが必要です。
ビジネスルールを適用せずにCrateオブジェクトを再構築できる「ビューモデル」または「パラメータ」オブジェクト。
検証される各プロパティについて、リクエストデータを検査し、コレクション内の各オブジェクトのエラーメッセージを収集する「バリデータ」オブジェクト。
これらのデータ検証は、ビジネスルールに違反している可能性があるオブジェクトのこのコレクションに対して実行する必要があります。検証が失敗した場合、エラーメッセージのコレクションをクライアントに返します。
検証が成功した場合にのみ、新しいCrateオブジェクトを初期化する必要があります。
これは、基本的にユーザー入力の検証に要約されます。
私は2番目の回答を追加することはあまりありませんが、もっと意味があり、必要なコードが少ない別のアプローチがあります。それでも、他の回答にも価値があると思います。
PHPでは、値の配列を返し、それらを呼び出し側で複数の変数に分割できます。
Crateコンストラクターへの引数を受け入れる静的メソッドをCrateクラスに追加し、初期化中にブール、Crateオブジェクト、およびExceptionオブジェクトがスローされた配列を返すことができます。
class Crate
{
public static function create(float $length, float $width, float $height) {
$isValid = false;
$crate = null;
$error = null;
try {
$crate = new Crate($length, $width, $height);
$isValid = true;
} catch (Exception $err) {
$error = $err;
$crate = null;
}
return array($isValid, $crate, $error);
}
}
そしてそれを使う例:
list($isValid, $crate, $error) = Crate::create(10, 4, 8);
if ($isValid) {
// Do something with $crate
}
else {
// Log the $error
}
ここでの利点は、この作成ロジックがクレートに特化したクラスであるCrateクラスに保持され、より多くのクラスでコードベースが肥大化しないことです。
したがって、実行時に無効なクレートを生成するコードをコンパイルできないようにしてください。
これは思ったほど不可能ではありませんが、強く型付けされた言語を使用する必要があります。あなたがPHPでそれを行うことができるかわかりません
たとえば、少しの間、サイズ0のクレートを許可するとします。構築パラメーターを符号なし整数に変更できます。これで、負の値を渡すことができなくなりました。
要件を表現する型をさらに作成して、基になる値型ではなくそれらを使用するよりもさらに想像することができます。
明らかに、ある段階で入力タイプを特別なタイプに解析する必要があり、その時点で例外をスローするか、または解析を定義して例外を不可能にすることができます。たとえば、0は1として読み取られます。
別の方法は、無効なクレートを意味するためにnullを使用することです。これで、パラメーターが無効な場合にnullを返すCrateFactory.CreateCrate(x,y,z)
と、nullをテストして必要な文字列を出力するshow crates関数を作成できます。
それがbool IsValidフラグと大きく異なるかどうかはわかりませんが
OPはこちら。私の問題は、例外処理メッセージを延期する必要があると考えました。つまり、例外処理によってプログラムのフローが中断され、障害が発生した時点で例外メッセージが配信されます。ビューレイヤーの時間が有効になるまで、このレポートを延期する必要がありました。
つまり、クレートの初期化に失敗した場合に備えて、例外メッセージとCrate
の不足に関する情報を保存する方法が必要でした。
このようにして、[Crate|NULL
+ Exception message|NULL
]のラッパーオブジェクトを作成し、それをCrateRecord
と呼びました
それが行く方法であるかどうかわかりませんが、それは働いているようです。
class CrateRecord
{
/** @var Crate */
protected $crate;
/** @var string */
protected $error;
function __construct(Crate $crate = null, string $error)
{
$this->crate = $crate;
$this->error = $error;
}
public function getCrate()
{
return $this->crate;
}
public function getError()
{
return $this->error;
}
}
class CrateRequestHandler
{
function handle()
{
// mocked up data from Request
// this is sample data for showcase purposes only
// it usually supplied by user or from database
// and it is impossible to tell ahead of time
// if it will be correct for Crate purposes
$data = array(
array(2, 5, 9),
array(1, 3, 4),
array(0, 0, 0), //invalid
array(5, 6, 3)
);
$crateRecords = array();
foreach($data as $key => $d)
{
try
{
$crate = new Crate($d[0], $d[1], $d[2]);
$error = "";
}
catch (Exception $e)
{
$crate = null;
$error = $e->getMessage();
}
$crateRecords[$key] = new CrateRecord($crate, $error);
}
// send to View
$this->showCrates($crateRecords);
}
/**
*
* @param CrateRecord[] $crateRecords
*/
function showCrates(array $crateRecords)
{
foreach($crateRecords as $key => $crateRecord)
if ($crateRecord->getCrate())
echo 'Crate #' . $key . ': ' . $crateRecord->getCrate()->getDimensions() . PHP_EOL;
else
echo 'Crate #' . $key . ': ' . $crateRecord->getError() . PHP_EOL;
}
}
// To call
$handler = new CrateRequestHandler();
$handler->handle();
ここでの問題は、問題の説明とメソッドに付けた名前にもかかわらず、実際にCrate
を表示していないことです。何か他のものを一緒に表示しています。 Crate
を作成するためのattemptingの結果を示すmessageを表示しています。
class CrateRequestHandler
{
function handle(ServerRequestInterface $request)
{
$messages = [];
// data is undefined
foreach ($data as $key => $dimensions) {
try {
$crate = new Crate(...$dimensions);
$messages[$key] = 'Crate #' . $key . ':' . $crate->getDimensions();
} catch (Exception $ex) {
$messages[$key] = 'Crate #' . $key . ':' . $ex->getMessage();
}
}
$this->showMessages($messages);
}
function showMessages(array $messages) : void
{
foreach ($messages as $message) {
echo $message . PHP_EOL;
}
}
}
このスレッドでの答えと、単純なtry/catch
問題の理解における微妙な変化とともに、必要なのはすべてです。