Может войдёшь?
Черновики Написать статью Профиль

Продвинутая проверка ввода в Laravel

перевод

  1. 1. «Как это нужно делать»
  2. 2. Сервис проверок
  3. 3. Всё целиком

Сегодня мы более подробно остановимся на теме, которую я затронул в предыдущей статье — «Создание и проверка форм». Надеюсь, у вас появится пару светлых идей, которые вы сможете разработать и использовать самостоятельно.

Если вы ещё не прочитали предыдущую статье я советую вам сделать это перед продолжением, хотя, думаю, вы справитесь и без неё.

«Как это нужно делать»

Вы часто слышите эту фразу, верно? Я — да. Бесконечное число людей обсуждали со мной свои сумасбродные идеи о том, как нужно решать определённую проблему правильно. Это всё замечательно и хорошо, но в конечном счёте как это реализовать — целиком ваша забота. И когда я думаю о том, «как это нужно делать», то на самом деле я думаю о лучшем решении для меня самого — так что помните об этом.

К слову, какие существуют общепринятые механизмы проверки пользовательского ввода? Часто говорят, что проверку не нужно делать в контроллере (или в маршруте), но нужно это делать в модели. Мне близка эта идея, я в любом случае люблю поддерживать хорошее ответственности. Работая над проверкой я предпочитаю использовать исключения и блоки try..catch.

Сервис проверок

Для поддержания чистоты мы создадим класс-сервис для наших проверок. Он будет ответственным за создание правил, сообщений ошибок и собственно проверку. Он будет очень простой:

PHP
<?php
namespace Services;
use 
ValidatorExceptionValidateException;

abstract class 
Validation {
  protected 
$validator;
  protected 
$data;

  public 
$rules = array();
  public 
$messages = array();

  public function 
__construct($input) {
    
$this->input $input;
  }

  protected function 
validate() {
    
$this->validator Validator::make($this->input$this->rules$this->messages);

    if (
$this->validator->invalid()) {
      throw new 
ValidateException($this->validator);
    }
  }

  public function 
__set($key$value) {
    
$this->data[$key] = $value;
  }

  public function 
__get($key) {
    if (!
array_key_exists($key$this->data)) {
      throw new 
Exception('Значение [' $key '] не найдено в массиве данных Services\\Validation.');
    }

    return 
$this->data[$key];
  }
}

Довольно просто. Это абстрактный класс, поэтому мы не можем создавать его экземпляры и должны расширить его («extend»). Вы, наверное, заметили, что в нём используются сразу два класса исключенийException и ValidateException. Второй из них нам нужно создать:

PHP
<?php
class ValidateException extends Exception {
  private 
$errors;

  public function 
__construct($container) {
    
$this->errors = ($container instanceof Validator) ? $container->errors $container;

    
parent::__construct(null);
  }

  public function 
get() {
    return 
$this->errors;
  }
}

Сохраним этот код в application/libraries/exceptions.php для того, чтобы позже можно было легко добавлять новые исключения. Из-за названия класса и его нестандартного расположения нам нужно зарегистрировать его в автозагрузчике (файл application/start.php):

PHP
Autoloader::map(array(
  
'ValidateException' => path('app') . 'libraries/exceptions.php'
));

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

Всё целиком

Раз мы всё настроили нам требуется что-то для проверки. Я буду использовать выдуманные значения для примера, но в вашем случае они будут поступать из формы или откуда-то из другого источника. Давайте предположим, что мы проверяем форму комментария перед его публикацией. Создадим сервис проверки комментария для поддержания высокой организации нашего кода:

PHP
<?php
namespace Services\Comments;
use 
Services\Validation as Validation_Service;

class 
Validation extends Validation_Service {
  public function 
publish() {
    
$this->rules = array(
      
'name'    => array('required'),
      
'email'   => array('required''email'),
      
'comment' => array('required''max:200')
    );

    
$this->validate();
  }
}

Если нам потребуется проверить другие типы данных мы можем создать метод для каждого из них — например, для проверки редактирования комментария. Хотя всё пока просто, вы можете пойти дальше и определить общие правила для проверок в конструкторе нового подкласса, а затем вызвать родительский конструктор. В общем, вы ограничены только своей фантазией.

Теперь когда мы смотрим на маршрут комментирования мы видим аккуратный блок try..catch:

PHP
Route::post('posts/(:num)/comment', array('before' => 'csrf', function ($id) {
  try {
    
$validation = new Services\Comments\Validation(Input::all());

    
$validation->publish();
  } catch (
ValidateException $errors) {
    return 
Redirect::to('posts/' $id)->with_errors($errors->get());
  }

  
// Теперь мы можем добавить комментарий. Мы могли бы создать для этого новый сервис комментариев.
  
Services\Comments\Creator::create(Input::all(), $id);
});

Если проверка не прошла мы ловим исключение (catch) и получаем ошибки («get»). Весьма просто.

Вы можете пойти дальше и установить try..catch в методе PHPServices\Comments\Creator::create() — и если он сработает, то просто повторно возбудите пойманное исключение:

PHP
<?php
namespace Services\Comments;

class 
Creator {
  public static function 
create($input$post_id) {
    try {
      
$validation = new Validation($input);

      
$validation->publish();
    } catch (
ValidateException $errors) {
      throw 
$errors;
    }

    
// код добавления комментария.
  
}
}

А теперь сам код маршрута:

PHP
Route::post('posts/(:num)/comment', array('before' => 'csrf', function ($id) {
  try {
    
Services\Comments\Creator::create(Input::all(), $id);

    
// Комментарий был успешно добавлен.
    
return Redirect::to('posts/' $id);
  } catch (
ValidateException $errors) {
    return 
Redirect::to('posts/' $id)->with_errors($errors->get());
  }
});

Вот и всё. Теперь вы знакомы с несколькоими вариантами решения этой задачи. Я не утверждаю, что способ, продемонстрированный мной здесь — лучший или единственный; вполне возможно, что вы можете найти более подходящее под ваши условия решение. Это просто ещё один из методов проверки данных без лишних сложностей и с минимумом кода.

Как вы считаете, полезен ли этот материал? Да Нет

Написать комментарий

Разметка: ? ?

Авторизуйся, чтобы прокомментировать.