web-dev-qa-db-ja.com

そのようなオブジェクトの配列を処理したいときに、値オブジェクトの作成の失敗に関する情報を表示するようにコードを変更するにはどうすればよいですか?

値オブジェクトであるクラス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;
    }
}
2
Dennis

私には、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;
    }
}
1
Hieu Le

あなたはCrateオブジェクトが常に有効であるべきであると主張するのは正しいです。これは、その不変式を強制する必要があるビジネスクラスであり、現在実行されています。問題は、ユーザー入力の取得にあります。ユーザー入力がビジネスルールや制約に違反することを許可する必要があります。他に少なくとも2つのクラスが必要です。

  1. ビジネスルールを適用せずにCrateオブジェクトを再構築できる「ビューモデル」または「パラメータ」オブジェクト。

  2. 検証される各プロパティについて、リクエストデータを検査し、コレクション内の各オブジェクトのエラーメッセージを収集する「バリデータ」オブジェクト。

これらのデータ検証は、ビジネスルールに違反している可能性があるオブジェクトのこのコレクションに対して実行する必要があります。検証が失敗した場合、エラーメッセージのコレクションをクライアントに返します。

検証が成功した場合にのみ、新しいCrateオブジェクトを初期化する必要があります。

これは、基本的にユーザー入力の検証に要約されます。

1
Greg Burghardt

私は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クラスに保持され、より多くのクラスでコードベースが肥大化しないことです。

1
Greg Burghardt

したがって、実行時に無効なクレートを生成するコードをコンパイルできないようにしてください。

これは思ったほど不可能ではありませんが、強く型付けされた言語を使用する必要があります。あなたがPHPでそれを行うことができるかわかりません

たとえば、少しの間、サイズ0のクレートを許可するとします。構築パラメーターを符号なし整数に変更できます。これで、負の値を渡すことができなくなりました。

要件を表現する型をさらに作成して、基になる値型ではなくそれらを使用するよりもさらに想像することができます。

明らかに、ある段階で入力タイプを特別なタイプに解析する必要があり、その時点で例外をスローするか、または解析を定義して例外を不可能にすることができます。たとえば、0は1として読み取られます。

別の方法は、無効なクレートを意味するためにnullを使用することです。これで、パラメーターが無効な場合にnullを返すCrateFactory.CreateCrate(x,y,z)と、nullをテストして必要な文字列を出力するshow crates関数を作成できます。

それがbool IsValidフラグと大きく異なるかどうかはわかりませんが

0
Ewan

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();
0
Dennis

ここでの問題は、問題の説明とメソッドに付けた名前にもかかわらず、実際に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問題の理解における微妙な変化とともに、必要なのはすべてです。

0
king-side-slide