Укр
Пишем чистые функции на PHP

Пишем чистые функции на PHP

  • 31 октября, 2022
  • читать 10 мин
Александр Стародубцев
Александр Стародубцев Tech Lead в EvoPlay

В этой статье мы продолжим говорить о том, как писать чистый и понятный код на языке программирования PHP.

Рекомендуем публикацию по теме

Как уже было сказано в предыдущей части, писать рабочий код иногда недостаточно, особенно если он пишется для долгосрочного проекта.

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

Сегодня мы поговорим о том, как типизация параметров поможет в написании понятных сигнатур функций и методов.

Типизация параметров

Как известно, PHP является языком программирования с динамической типизацией, не требующей указания типа во время объявления переменных.

Это означает, что тип переменной вычисляется на основе значения, которое присвоено этой переменной. Это также означает, что в разный момент времени переменная может сохранять разные типы значений.

Для того, чтобы преобразовать тип значения, мы можем использовать специальные функции или преобразования в стиле языка программирования Си:

$numeric = '1234567890';

$int = intval($numeric); // Функція перетворення
$int = (int) $numeric;   // Перетворення у стилі Сі

Контроль типов, которыми манипулирует код программы, является важным аспектом написания эффективного кода, чем иногда пренебрегают начинающие программисты.

Давайте взглянем на сигнатуру функции:

function calculate($a, $b, $operation);

Можем ли мы, не изучая детали реализации этой функции, понять, какие типы данных мы можем передавать при ее вызове?

Скорее всего нет, и нам придется потратить время на изучение того, что делает функция:

function calculate($a, $b, $operation)
{
    if (!is_numeric($a) || !is_numeric($b)) {
        throw new InvalidArgumentException('$a and $b must be numeric');
    }

    return match ($operation) {
        '+' => $a + $b,
        '-' => $a - $b,
        '*' => $a * $b,
        '/' => $a / $b,
        default => throw new InvalidArgumentException('Unknown operation: ' . $operation),
    };
}

Итак, на основе представленных выше фрагментов исходного кода можно выделить две проблемы:

  1. Сигнатура функции неочевидна и требует изучения реализации
  2. Реализация функции кроме основной задачи выполняет также проверки на соответствие типов передаваемых ей данных

С помощью типизации параметров функции можно решить эти две проблемы.

Давайте проанализируем код функции: какие типы параметров предусматриваются разработчиком? Относительно параметров $a и $b — вероятнее всего, речь идет о числах; Параметр $operation — это символ.

Давайте изменим сигнатуру функции, указав типы этих параметров во время объявления функции:

function calculate(int|float $a, int|float $b, string $operation)
{
    return match ($operation) {
        '+' => $a + $b,
        '-' => $a - $b,
        '*' => $a * $b,
        '/' => $a / $b,
        default => throw new InvalidArgumentException('Unknown operation: ' . $operation),
    };
}

Как видно, мы использовали указание типов в параметрах функции. Это не только визуальная подсказка для разработчика, у такого приема есть функциональное назначение: если тип передаваемой функции параметра не соответствует указанному, то PHP сделает попытку превратить это значение в нужный тип. Если преобразование невозможно, то будет выброшена ошибка Fatal error: Uncaught TypeError. Таким образом, мы можем избавиться от ненужных проверок и преобразований в теле самой функции.

При объявлении типов параметров можно указывать bool, int, float, string, array, object, callable, iterable, mixed и имя любого класса или интерфейса. Также можно указывать и объединенные типы через символ |.

Например: int|float означает, что функция ожидает переменную типа int (целое число) ИЛИ float (число с плавающей точкой).

Также можно указывать тип возвращаемого значения. Сделать это можно, если после закрывающей круглой скобки поставить оператор двоеточие и указать нужный нам тип.

Например:

function calculate(int|float $a, int|float $b, string $operatior): int|float;

Строгая типизация

Как уже было сказано, PHP будет пытаться преобразовать значение другого типа в ожидаемый скалярный тип, если это возможно.

Например, метод, ожидающий строку, можно по-прежнему вызывать с целочисленным аргументом, поскольку целое число можно превратить в строку:

class User
{

    private string $name;

    // Очікується рядок
    public function setName(string $name): void
    {
        $this->name = $name;
    }

    public function getName(): string
    {
        return $this->name;
    }

}

$user = new User();
$user->setName(1); // Передаємо число

var_dump($user->getName());

// Результат:
// string(1) "1"

Начиная с PHP 7.0 появилась возможность использовать строгий режим контроля типов.

В строгом режиме типы переменных должны строго совпадать с указанными, так как в случае несоответствия типа переменных вместо преобразования будет ошибка TypeError.

Чтобы включить режим строгой типизации, нужно сразу после открывающего тега <?php прописать:

declare(strict_types = 1);

Теперь, если мы запустим код из предыдущего примера, то получим ошибку:

Fatal error: Uncaught TypeError: User::setName(): Argument #1 ($name) must be of type string, int given, called in /home/hillel/index.php on line 18 and defined in /home/hillel/index.php:10 Stack trace:
#0 /home/hillel/index.php.php(18): User->setName()
#1 {main}
  thrown in /home/hillel/index.php on line 10

Директива declare(strict_types=1) должна быть первой строкой вашего кода, в противном случае вы получите ошибку компиляции.

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

Также следует учитывать, что если функция была объявлена в файле без директивы declare(strict_types=1), вы по-прежнему можете включить строгий режим в файле, где происходит вызов этой функции.

И последнее: строгая типизация работает только со скалярными типами данных (int, float, string и bool).

Зачем использовать режим строгой типизации?

  1. Во-первых, это избавляет нас от непредвиденных ситуаций, когда происходит неявное преобразование типа значения
  2. Во-вторых, позволяет на ранних этапах отследить возможное несоответствие передаваемых или получаемых типов
  3. Ну и, наконец, строгий режим подразумевает то, что программист знает, с какими типами должна работать программа, а значит уделяет большее внимание проработке алгоритмов

Рекомендуем курс по теме