モデルからのレスポンスとエラーを考える

何かモデルを設計するときに、モデルのレスポンスはどうしよう?モデルが失敗した場合どうやって補足しよう?と迷うことがあるので整理するためにもいくつかのやり方を疑似コードで書いておく。

失敗した時はfalseを返す

まずは最も素朴なやり方で。

<?php

$result = $model->fetchSomething();
if ($result === false) {
    fail();
}
success($result);
欠点
  • 失敗に関する情報が無い

失敗したかどうかとエラー情報も一緒に返す

上記のやり方の欠点を防いだもの。

<?php

list($success, $result, $error) = $model->fetchSomething();

if (!$success) {
    fail($error);
}
success($result);
欠点
  • 成功した場合、$successと$errorという使わない変数ができる

例外を使う

失敗した場合は例外を投げる。

<?php

try {
    $result = $model->fetchSomething();
} 
catch (FooException $e) {
    foo_fail($e);
}
catch (BarException $e) {
    bar_fail($e);
}
success($result);

まあ無難。

欠点
  • 例外クラス作るのめんどくさい

Nullパターンを使う

モデルの結果を表現するクラスを作り、さらにそのクラスを継承したNullクラスを失敗とする。

<?php
class ModelResult
{
    protected $result;
    function __construct($foo, $bar, $foobar)
    {
        $this->result = func_get_args();
    }
    function get()
    {
        return $this->result;
    }
    function getFoo()
    {
        return $this->result[0];
    }
    function isNull()
    {
        return false;
    }
    function getError() { }
}

class NullResult extends ModelResult
{
    protected $error;
    function __construct($error_code, $msg)
    {
        $this->error = func_get_arg();
        parent::__construct(null, null, null);
    }
    function isNull()
    {
        return true;
    }
    function getError()
    {
        return $this->error;
    }
}

$result = $model->fetchSomething();
if ($result->isNull()) {
    fail($result->getError());
}
success($result);

型安全。

欠点
  • Nullクラス書くのめんどくさい

追記: おまけ

<?php
interface Option
{
    function isEmpty();
    function get();
    function orElse($val);
    function apply($callback);
}

class Some implements Option
{
    protected $val;
    function __construct($val)
    {
        $this->val = $val;
    }
    function isEmpty()
    {
        return false;
    }
    function get()
    {
        return $this->val;
    }
    function orElse($val)
    {
        return $this->val;
    }
    function apply($callback)
    {
        $result = call_user_func($callback, $this->val);
        return new self($result);
    }
}

class None implements Option
{
    private function __construct() {} 
    function isEmpty()
    {
        return true;
    }
    function get() {}
    function orElse($val)
    {
        return $val;
    }
    function apply($callback)
    {
        return $this;
    }
    static function it()
    {
        static $obj = null;
        return $obj ? $obj : $obj = new self;
    }
}

$result = $model->fetchSomething()->apply('foo_function')->orElse('default value');

ScalaのOption型っぽいの。
外からは$model->fetchSomething()がOption型を返すとする。
内部的には成功した場合にはSome型を返し、失敗した場合にはNone型を返す。