Пишемо чисті функції на PHP

  • 507
  • 4
  • 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. Ну і, врешті-решт, суворий режим має на увазі те, що програміст знає, з якими типами повинна працювати програма, а значить приділяє більшу увагу опрацюванню алгоритмів

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