'Exception' як філософія проектування в PHP

5 хв. читання

Винятки (Exception) – справжня філософія, про яку знають не так багато PHP програмістів, і ще менше розуміють, як її правильно використовувати.

Проблематика

Процедурне минуле PHP заклало як стандарт повернення TRUE/FALSE у випадку успішного або не успішного виконання функції. Отриману відповідь потрібно опрацювати і вивести помилку, якщо в цьому є необхідність.

У випадку з винятками, не потрібно перевіряти результат роботи функції на TRUE/FALSE при валідації вхідних даних, перед виконанням основної логіки функції. Крім того, якщо у функції може бути кілька різних варіантів помилки, то доведеться додавати різні костилі. Наприклад, повертати код помилки, і тоді вже розуміти, в чому саме проблема. Подібний підхід є дуже не зручним.

Основний принцип

З приходом OOP, в PHP проблема валідації даних була вирішена завдяки викиданню винятків:

try {
	if (empty($_POST['email'])) {
		throw new RuntimeException('Invalid Email');
	}

	if (empty($_POST['name'])) {
		throw new RuntimeException('Invalid Full Name');
	}

	if (empty($_POST['phone'])) {
		throw new RuntimeException('Invalid Phone number');
	}

	//Save POST data
} catch (RuntimeException $error) {
	echo $error->getMessage();
}

Подібна перевірка може відбуватися в середині якоїсь функції/методу, і там викинута наверх:

class validation 
{
	static public function contactForm($data)
	{
		$allowed_elements = [
			'name' =>'Invalid Full Name',
			'email' => 'Invalid Email',
			'phone' => 'Invalid Phone number',
		];

		foreach ($allowed_elements as $el_name => $error_message) {
			if (empty($data[$el_name])) {
				throw new RuntimeException ($error_message);
			}
		}
	}
}

//Далі ми просто викликаємо метод валідації й відловлюємо помилку:

try {
	validation::contactForm($_POST);

	//Save POST data
} catch (RuntimeException $error) {
	echo $error->getMessage();
}

Види винятків

Є 2 основних види винятків, які ми використовуємо у скриптах:

  • RuntimeException,
  • LogicException

Обидва класи винятків наслідуються від батьківського класу Exception, тому якщо потрібно відловити всі винятки, можна просто ловити батьківський клас. Якщо тільки якийсь конкретний тип помилки — то ловити лише дочірній клас:

try {
	try {
		validation::contactForm($_POST);
	} catch (LogicException $error) { //Даний catch не відловить RuntimeException, оскільки LogicException наслідується лише від Exception
		echo $error->getMessage(); 
	}

	//Save POST data
} catch (Exception $error) { //Тут catch зловить всі винятки, які наслідуються від Exception
	echo $error->getMessage(); 
}

Принципова різниця між винятками

RuntimeException – винятки, які стосуються зовнішніх чинників, і помилку, яка була викинута цим типом винятків, не можна виправити шляхом зміни коду.

Наприклад: якщо робиться запит в довільну REST API, і отримується неочікувана помилка, то в даному випадку вона має буди викинута за допомогою RuntimeException, оскільки виправити роботу стороннього протоколу ми не можемо. \ Те саме стосується ситуації, коли в фабричному методі робиться запит в базу даних, а в базі немає записів по вказаних критеріях. В даному випадку теж потрібно викинути RuntimeException виняток, оскільки метод не може коректно завершити роботу і щоб виправити ситуацію, потрібно виправляти базу даних, а не код.

LogicException – винятки, які відловлюють помилки розробника. Тобто, це ті помилки, які можна виправити шляхом зміни коду.

Наприклад: є метод user::add_new($data), який створює новий обліковий запис в базі даних. В ролі масиву параметрів, в нього потрібно передати 1 аргумент. Серед обов'язкових полів в нас є email і password – якщо їх не передати або вони є неправильними (недійсна скринька або заслабкий пароль), то ми створимо «неправильного» користувача. В такому випадку потрібно викинути виняток InvalidArgumentException (наслідується від LogicException). \ Чому саме так? На перший погляд, на рівні методу ми не можемо впливати на те, що нам приходить зверху. Але перевірку валідності даних потрібно робити перед тим, як передавати їх методу. І якщо вони все ж прийшли невалідними, значить на рівні вище немає достатньої перевірки з RuntimeException винятком і це помилка розробника – саме тому в середині методу user::add_new($data), при перевірці вхідних даних, має викидатися InvalidArgumentException, а перед викликом методу має бути RuntimeException:

class user
{
	static public function add_new($data)
	{
		if (empty($data['email']) || validate::email($data['email'])) {
			throw new InvalidArgumentException('Invalid Email');
		}

		if (empty($data['password']) || strlen($data['password']) < 10) {
			throw new InvalidArgumentException('Invalid Password');
		}
	}
}

try {
	if (empty($_POST['email']) || validate::email($_POST['email'])) {
		throw new RuntimeException('The Email has invalid value. Please, check it.');
	}

	if (empty($_POST['password']) || strlen($_POST['password']) < 10) {
		throw new RuntimeException('The Password must be longer or equal than 10 chars');
	}

	user::add_new($_POST);
} catch (RuntimeException $error) {
	echo $error->getMessage();
}

Існує думка, що LogicException (і його нащадків) не можна перехоплювати, оскільки код не має видавати помилок – і в принципі це правильний підхід. Та з іншого боку, завжди є винятки з правил, коли між вибором показати користувачу білий екран з помилкою і переадресувати його на заготовлену заглушку – вибирають другий варіант, як клієнто-орієнтований.

Процедурний підхід

З появою винятків у PHP, процедурний підхід став значно менше використовуватися, але він не зник зовсім.

Його все ж доречно використовувати у функціях/методах стану – коли потрібно щось порівняти або визначити, чи відповідає очікуваному стану якась змінна. Наприклад, з валідацією пошти в останньому прикладі. Там було використано метод validate::email($data['email']), який має вертати TRUE/FALSE, в залежності від того, що йому було передано.

Чистого коду й попутних завдань.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.6K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація