Страницы

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

вторник, 28 мая 2019 г.

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


Ответ

Вы забыли о еще одном очень-очень важном моменте. В первом вашем варианте конструкция:
$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.

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

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