Дружественные исключения

Обсуждаем разработку фреймворка: дизайн компонентов, API, пакеты
Ответить
Аватара пользователя
samdark
Администратор
Сообщения: 9175
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Дружественные исключения

Сообщение samdark » 2019.09.20, 18:57

Идея "дружественного исключения" в том, что можно реализовать интерфейс FriendlyExceptionInterface(https://github.com/yiisoft/friendlyexce ... erface.php) и предоставить для исключения описание того, как решить возникшую проблему. Обработчик ошибок, соответственно, может эту информацию показывать на экране ошибки вместе с обычными трейсами.

Изначально интерфейс был частью пакета yii-web, в котором уже есть обработчик, способный отображать дополнительную информацию, но, подумаю пару дней, я понял что интерфейс полезен для консольных приложений и использования вне Yii.

Итак, самый маленький пакет Yii 3:

https://github.com/yiisoft/friendlyexception

Что думаете?

yiiliveext
Сообщения: 361
Зарегистрирован: 2019.08.13, 01:49

Re: Дружественные исключения

Сообщение yiiliveext » 2019.09.20, 19:48

А какие мысли вы ожидаете? Сделать ревью кода? :-)

Аватара пользователя
samdark
Администратор
Сообщения: 9175
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Дружественные исключения

Сообщение samdark » 2019.09.20, 20:27

1. Втащили бы себе в проект?
2. Достаточно ли такого интерфейса?

yiiliveext
Сообщения: 361
Зарегистрирован: 2019.08.13, 01:49

Re: Дружественные исключения

Сообщение yiiliveext » 2019.09.20, 21:54

samdark писал(а):
2019.09.20, 20:27
1. Втащили бы себе в проект?
2. Достаточно ли такого интерфейса?
Давайте на примере. Допустим мы в консоли ввели команду ./yii myservice/updat вместо ./yii myservice/update. Нам нужно в решении предложить правильную команду. Как мы это сможем сделать?


yiiliveext
Сообщения: 361
Зарегистрирован: 2019.08.13, 01:49

Re: Дружественные исключения

Сообщение yiiliveext » 2019.09.20, 23:02

И? Где вы здесь хотите использовать getSolution? По факту это часть getMessage, которую можно выводить отдельно. Остальное все равно реализовывается отдельно в частных исключениях.

Аватара пользователя
samdark
Администратор
Сообщения: 9175
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Дружественные исключения

Сообщение samdark » 2019.09.21, 00:14

Не понял вопроса. Я не хочу использовать в исключении getSolution(), я его реализую в нём. getSuggestedAlternatives() в этом случае это как раз и есть getSolution().
По факту это часть getMessage, которую можно выводить отдельно.
Если отдельно, то не часть getMessage(). Например, getMessage() пишется в лог, а getSolution() уж точно в логе не место.
Остальное все равно реализовывается отдельно в частных исключениях.
Вот это совсем не понял :( Что остальное и в каких исключениях?

yiiliveext
Сообщения: 361
Зарегистрирован: 2019.08.13, 01:49

Re: Дружественные исключения

Сообщение yiiliveext » 2019.09.21, 14:59

samdark писал(а):
2019.09.21, 00:14
Не понял вопроса. Я не хочу использовать в исключении getSolution(), я его реализую в нём. getSuggestedAlternatives() в этом случае это как раз и есть getSolution().
Именно об этом я и говорю, getSolution() должен быть универсальным, а значит должен работать с контекстом. Потому что getSuggestedAlternatives() работает с контекстом (команда/роут). Значит нам нужен контекст в исключении и желательно чтобы он был доступен в обработчике. Например так.

Код: Выделить всё

interface FriendlyExceptionInterface
{
    public function getName(): string;
    public function getSolution(): ?string;
    public function getContext(): ?string;
}

Код: Выделить всё

class ErrorException extends \ErrorException implements FriendlyExceptionInterface
{
    //...
    private $context;
    
    public function __construct($message = '', $context = null, $code = 0, $severity = 1, $filename = __FILE__, $lineno = __LINE__, \Exception $previous = null)
    {
        parent::__construct($message, $code, $severity, $filename, $lineno, $previous);
        $this->context = $context;
        $this->addXDebugTraceToFatalIfAvailable();
    }
    
    public function getContext(): ?string
    {
        return $this->context;
    }
    
    //....
Тогда мы можем переписать UnknownCommandException так

Код: Выделить всё

lass UnknownCommandException extends ErrorException
{
    //....
    public function getSolution(): ?string
    {
        return $this->getSuggestedAlternatives();
    }
    
    protected function getSuggestedAlternatives()
    {
        //working with $this->context instead of $this->command
        //...
    }
}
Ну и обрабатывать так

Код: Выделить всё

try {
    //execute commands
} catch (\ErrorExeption $e) {
    $message = $e->getMessage();
    if ($e instanceof FriendlyExceptionInterface) {
        $name = $e->getName();
        $solution = $e->getSolution();
        $context = $e->getContext();
        $message = "{$name}:{$message}\n{$solution}";
        //use a custom solution
        if (($e instanceof UnknownCommandException) && empty($solution)) {
            $message .= "\n Command {$context} - ...custom solution...";
        }
        //custom action 
        if (($e instanceof BadPasswordException) && $context == 'account/send-money' ){
            sendEmailToSecurity('Danger! Unauthorized money transfer has been detected!');            
        }
    }
}

Аватара пользователя
samdark
Администратор
Сообщения: 9175
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Дружественные исключения

Сообщение samdark » 2019.09.21, 19:07

Контекст именно для этого интерфейса не важен. Интерфейс не про то, как мы будем собирать вывод, а про то, что мы будем выводить. Выводим мы сообщение, трейс и подсказку-решение. Если для их формирования в конкретном исключении необходимо название текущей команды, то требуем его в конструкторе этого исключения:

Код: Выделить всё

class UnknownCommandException extends ErrorException
{
    private $command;

    public function __construct(string $command)
    {
        $this->command = $command;
        parent::__construct('Unknown command '. $command);
    }

    public function getSolution(): ?string
    {
        $alternatives = $this->getSuggestedAlternatives();
        if ($alternatives === []) {
            return null;
        }
        return 'Did you mean one of"' . implode('", "', $alternatives) . '"?';
    }
    
    protected function getSuggestedAlternatives(): array
    {
        // working with $this->command
        // ...
    }
}

yiiliveext
Сообщения: 361
Зарегистрирован: 2019.08.13, 01:49

Re: Дружественные исключения

Сообщение yiiliveext » 2019.09.22, 09:55

samdark писал(а):
2019.09.21, 19:07
Контекст именно для этого интерфейса не важен. Интерфейс не про то, как мы будем собирать вывод, а про то, что мы будем выводить. Выводим мы сообщение, трейс и подсказку-решение. Если для их формирования в конкретном исключении необходимо название текущей команды, то требуем его в конструкторе этого исключения:
Ок, пусть будет так. А почему бы не вынести в независимый пакет https://github.com/yiisoft/yii-web/tree ... rorHandler целиком? Там вроде только одна зависимость фреймворка Yiisoft\Yii\Web\Info и этот момент решаем.

Аватара пользователя
samdark
Администратор
Сообщения: 9175
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Дружественные исключения

Сообщение samdark » 2019.09.23, 00:26

Можно и вынести, конечно. Вы первый кто спросил.

Ответить