Страницы

Поиск по вопросам

воскресенье, 8 марта 2020 г.

php, перфекционизм

#php #алгоритм #инспекция_кода


Есть бэкенд на PHP, обслуживающий разные запросы и возвращающие json.

Точка входа(упрощенно):

try {
    switch ($method) {
        case 'login':
            $call = new Login($data);
            break;
        case 'list':
            $call = new List($data);
            break;
        default:
            $call = new WrongMethod($data);
    }
    $out  = ['r' => $call(),'n' => (string)Error::OK,'e' => ''];
} catch (Error $e) {
    $out = [
        'r' => $call::defaultResult())),
        'n' => (string)$e->getCode(),
        'e' => $e->getMessage()
    ];
}
print_r(json_encode($out, JSON_UNESCAPED_UNICODE), false);


Уже увидели ошибку? ;)

Проблема: 
$call::defaultResult() - в блоке catch объект $call не определен.

Все классы пуляют Error extends Exception и реализуют интерфейс

interface Call {
    public function __construct(array $data);
    public function __invoke();
    public static function defaultResult();
}


defaultResult нужен потому, что _invoke может вернуть скаляр, обычный массив либо
ассоциативный массив и если при ошибке использовать значение какого-то одного типа,
то фронт споткнется на десериализации. То есть мне нужно гарантированно получить в
блоке catch такую же структуру поля 'r', как и в блоке try

Я вижу два варианта решения:


Вынести из конструктора все, что может бросить исключение. Такой подход не нравится
тем, что у меня в конструкторах исключительно проверка входных данных на валидность
и их приведение в случае необходимости. ИМХО этому функционалу самое место в конструкторе.
Перейти на тёмную сторону и сделать как-то так:

(Этот вариант мне вообще не нравится. Прям вызывает дискомфорт и потерю аппетита.)

function getMethodClass($method) {
    switch ($method) {
        case 'login':
            return "Login";
        case 'list':
            return "GetList";
        default:
            return "WrongMethod";
    }
}
$className = $callsNameSpace."\\" . getMethodClass($method);
try {
    $call = new $className($data);
...
} catch (Error $e) {
    $out = ['r' => call_user_func(array($className, 'defaultResult')),
...
}



Внимание, вопрос!

Может я не прав относительно своих фобий и первый(или второй) вариант вполне себе хорош?

Может я заработался и не вижу простого и очевидного решения? Такого чтоб и работало
нормально и с души не воротило?

UPD PHP 5.6
    


Ответы

Ответ 1



Вы забыли о еще одном очень-очень важном моменте. В первом вашем варианте конструкция: $call::defaultResult() невалидна (как минимум, идеологически). Фактически вы пытаетесь вызвать метод класса у экземпляра, что лишено смысла. С учетом этого, второй ваш вариант намного лучше. Хотя я немного причесал бы его (в примере ниже использую PHP 5.5+): $map = [ 'login' => Login::class, 'list' => List::class, ]; $class_name = isset($map[$method]) ? $map[$method] : WrongMethod::class; try { $call = new $class_name($data); // ... } catch (Error $e) { $out = ['r' => $class_name::defaultResult()]; } Замечание: Для ошибок валидации входных данных в PHP традиционно используется \InvalidArgumentException и его подклассы. А \Error - это специальный класс ошибок введенный в PHP7. Возможно стоит немного изменить имя вашего класса исключений, чтобы повысить читаемость кода и избежать двусмысленности после перехода на PHP7.

Комментариев нет:

Отправить комментарий