Статьи

Работа с объектами запроса в PHP

Обычно, когда мы работаем с веб-приложениями, нам нужно обрабатывать объекты Request. Запросы являются входом наших приложений. Согласно золотому правилу безопасности:

Фильтр ввода-вывода выхода

Мы не можем использовать суперглобальные переменные $ _GET и $ _POST. Хорошо, мы можем использовать тогда, но мы не должны их использовать. Обычно веб-фреймворки делают эту работу за нас, но не все это фреймворки.

Недавно я работал в небольшом проекте без каких-либо рамок. В этом случае мне также нужно обрабатывать объекты запроса. Из-за этого я построил эту маленькую библиотеку. Позвольте мне показать это.

По сути, идея заключается в следующем. Я хочу отфильтровать свои входные данные, и я не хочу помнить полное имя каждой входной переменной. Я хочу определить объект запроса один раз и использовать его везде. Представьте себе небольшое приложение с простым вводом под названием param1. URL будет:

test1.php?param1=11212

и мы хотим построить этот простой скрипт:

echo "param1: " . $_GET['param1'] . '<p/>';

Проблема этого скрипта в том, что мы не фильтруем ввод. И нам также нужно помнить, что имя параметра — param1. Если нам нужно использовать параметр param1 в другом месте, нам нужно помнить, что его имя — param1, а не Param1 или para1. Это может быть очевидно, но легко ошибиться.

Мое предложение следующее. Я создаю простой PHP-класс под названием Request1, расширяющий объект RequestObject:

Пример 1: простой пример

class Request1 extends RequestObject
{
    public $param1;
}

Теперь, если мы создаем экземпляр Request1, мы можем использовать следующий код:

$request = new Request1();
echo "param1: " . $request->param1 . '<p/>';

Сейчас я не собираюсь объяснять магию, но с помощью этого простого скрипта мы отфильтруем входные данные к типу по умолчанию (строка) и получим следующие результаты:

test1.php?param1=11212
param1: 11212

test1.php?param1=hi
param1: hi

 

 

Может быть, это сложно объяснить словами, но с помощью примеров проще показать вам, что я хочу:

Пример 2: типы данных и значения по умолчанию

class Request2 extends RequestObject
{
    /**
     * @cast string
     */
    public $param1;
    /**
     * @cast string
     * @default default value
     */
    public $param2;
}

$request = new Request2();

echo "param1: <br/>";
var_dump($request->param1);
echo "<br/>";

echo "param2: <br/>";
var_dump($request->param2);
echo "<br/>";

Теперь мы будем фильтровать параметр param1 в строку и param2 в строку, но мы назначим переменную по умолчанию для параметра, если у нас нет ввода пользователя.

test2.php?param1=hi&param2=1

param1: string(2) "hi"
param2: string(1) "1"

test2.php?param1=1&param2=hi

param1: string(1) "1"
param2: string(2) "hi"

test2.php?param1=1
param1: string(1) "1"
param2: string(13) "default value"

 

 

Пример 3: валидадорс

class Request3 extends RequestObject
{
    /** @cast string */
    public $param1;
    /** @cast integer */
    public $param2;

    protected function validate_param1(&$value)
    {
        $value = strrev($value);
    }

    protected function validate_param2($value)
    {
        if ($value == 1) {
            return false;
        }
    }
}
try {
    $request = new Request3();

    echo "param1: <br/>";
    var_dump($request->param1);
    echo "<br/>";

    echo "param2: <br/>";
    var_dump($request->param2);
    echo "<br/>";
} catch (RequestObjectException $e) {
    echo $e->getMessage();
    echo "<br/>";
    var_dump($e->getValidationErrors());
}

Теперь сложный пример. param1 — это строка, а param2 — целое число, но мы также проверим их. Мы изменим значение param1 (простая строчка ) и также создадим исключение, если param2 равен 1

test3.php?param2=2&param1=hi
param1: string(2) "ih"
param2: int(2)

test3.php?param1=hola&param2=1
Validation error
array(1) { ["param2"]=> array(1) { ["value"]=> int(1) } }

 

 

Пример 4: Динамические проверки

class Request4 extends RequestObject
{
    /** @cast string */
    public $param1;
    /** @cast integer */
    public $param2;
}

$request = new Request4(false); // disables perform validation on contructor
                               // it means it will not raise any validation exception
$request->appendValidateTo('param2', function($value) {
        if ($value == 1) {
            return false;
        }
    });

try {
    $request->validateAll(); // now we perform the validation

    echo "param1: <br/>";
    var_dump($request->param1);
    echo "<br/>";

    echo "param2: <br/>";
    var_dump($request->param2);
    echo "<br/>";
} catch (RequestObjectException $e) {
    echo $e->getMessage();
    echo "<br/>";
    var_dump($e->getValidationErrors());
}

Более сложный пример. Param1 будет приведен как строка, а param2 снова как целое число, так же, как и для param2 (исключение, если значение равно 1), но теперь правило проверки не будет установлено в определении класса. Мы добавим динамически после создания экземпляра класса.

test4.php?param1=hi&param2=2
param1: string(4) "hi"
param2: int(2)

test4.php?param1=hola&param2=1
Validation error
array(1) { ["param2"]=> array(1) { ["value"]=> int(1) } }

Пример 5: массивы и параметры по умолчанию

class Request5 extends RequestObject
{
    /** @cast arrayString */
    public $param1;

    /** @cast integer */
    public $param2;

    /**
     * @cast arrayString
     * @defaultArray "hello", "world"
     */
    public $param3;

    protected function validate_param2(&$value)
    {
        $value++;
    }
}

$request = new Request5();

echo "<p>param1: </p>";
var_dump($request->param1);

echo "<p>param2: </p>";
var_dump($request->param2);

echo "<p>param3: </p>";
var_dump($request->param3);

Теперь простой пример, но входные параметры допускают массивы и значения по умолчанию.

test5.php?param1[]=1&param1[]=2&param2[]=hi
param1: array(2) { [0]=> int(1) [1]=> int(2) }
param2: int(1)
param3: array(2) { [0]=> string(5) "hello" [1]=> string(5) "world" }

test5.php?param1[]=1&param1[]=2&param2=2
param1: array(2) { [0]=> string(1) "1" [1]=> string(1) "2" }
param2: int(3)
param3: array(2) { [0]=> string(5) "hello" [1]=> string(5) "world" }

 

 

RequestObject

The idea of RequestObject class is very simple. When we create an instance of the class (in the constructor) we filter the input request (GET or POST depending on REQUEST_METHOD) with filter_var_array and filter_var functions according to the rules defined as annotations in the RequestObject class. Then we populate the member variables of the class with the filtered input. Now we can use to the member variables, and auto-completion will work perfectly with our favourite IDE with the parameter name. OK. I now. I violate encapsulation principle allowing to access directly to the public member variables. But IMHO the final result is more clear than creating an accessor here. But if it creeps someone out, we would discuss another solution :) .

Full code here on github

What do you think?