Опубликован: 22.10.2016 | Уровень: для всех | Доступ: платный
Лекция 2:

Редактор сайта

< Лекция 1 || Лекция 2: 1234 || Лекция 3 >

Первый класс

Один из самых важных аргументов для помещения кода в более крупные блоки – это абстрагирование, то есть скрытие некоторого комплекса действий программы за одним общим именем. Это полезно и удобно. Такой пример. Долгое время в Php работали функции вида mysql_connect, затем Php объявил их устаревшими и стало можно использовать только новые функции, вида mysqli_connect, с другим синтаксисом (нельзя было слепо заменить в вашем коде строку "mysql" на строку "mysqli").

Если вы построили работу с БД, как в файле index4-1.php http://nichtig.ru/index4-1.php?code , то вам придётся лазать по всему коду и везде исправлять логику работы, синтаксис. Если же вы, как в следующей версии нашей программы (index4-2.php http://nichtig.ru/index4-2.php?code ), будете использовать в коде функции своего собственного класса-обёртки (вида Mysql::select_table), то вам нужно будет изменить логику работы только в одном месте – в вашем классе Mysql: ведь вызовы рабочих функций во всём коде останутся теми же самыми "Mysql::select_table". Во многом именно ради этого всё и усложняется – чтобы при рефакторинге (посмотрите в словаре, что это) или при использовании программы на другом сайте можно было менять код только в одном месте.

Проследить логику работы класса проще "с конца". У нас две "рабочих" функции (select_row для извлечение одной строки из mysql-таблицы и select_table для извлечение нескольких строк), которые будут многократно вызываться из какого-то другого кода. Любая из этих функций сразу обращается к чему-то вроде конструктора – к "двойной звезде" состоящей из функции query, которая обращается к собственно конструктору – функции _init, которая устанавливает соединение с БД, кодировку соединения и текущую БД.

Функция query устроена сложнее. Mysql может возвращать данные, а может и не возвращать (например, при запросах вида update). Наша функция предусматривает оба варианта: если данные есть, второй элемент возвращаемого массива будет содержать число больше нуля, и "рабочие" функции могут приступать к передаче данных вышестоящему коду.

Внутри "конструктора" _init мы используем функцию query, которая сама вызывает _init. Есть опасность "зацикливания". Но функция _init вообще не начинает работу, если соединение уже было установлено (переменная self::$link не пуста), так что зацикливания не будет. Другое дело, что наш класс не может, например, сейчас соединяться с разными БД. Но, когда нам это понадобится, мы сделаем. Это важный принцип программирования: не буду думать об этом сегодня, буду думать о этом завтра. Иначе вы вообще ничего сделать не сможете (будете только "думать").

***

Обратите внимание: мы изменили архитектуру программы (добавили новый класс), от этого изменилась работа всей системы. Кое-что испортилось: сейчас при обращении к несуществующей записи в БД (например, index4_2.php?id=222) мы будем видеть не ответ 404, а рекомендации по настройке подключения к Mysql.

Так бывает всегда при серьёзных изменениях структуры кода - появляются новые (или старые) ошибки.

Мы не будем сейчас "латать дыры", потому что следующим шагом опять поменяем структуру, и там решим проблему правильных ответов сервера на более общем уровне.

Немного о безопасности. Ещё один класс

Способы защиты сайта от всевозможных угроз муссируются на многочисленных интернет-форумах: sql-injection, js и css-хаки, XSS, shell'ы... Реализация наиболее серьёзных угроз связана со взломом хостинга, а не сайта, поэтому нет большого смысла о них беспокоиться. А у нас пока есть наша собственная нерешённая проблема. Выше мы обеспечили адекватный ответ сервера (404) для URL вида ?id=22 при несуществующей записи в БД. Но кто сказал, что только через параметр 'id' можно прислать на сервер ложные данные? Можно заставить поисковые системы (ПС) проиндексировать страницы нашего сайта, например, с параметром ?bd=22, или ?ed=22, или ?ebd=322 – все они будут выдавать одно и то же содержимое с http кодом "200 OK", но технически являться разными страницами.

Это вообще неправильно – давать http-ответы с кодом "200" в ответ на любые запрашиваемые параметры. Список "валидных" параметров должен быть извествен серверу, надо составить список всех допустимых параметров и при появлении незнакомого либо выдавать код "301 moved permanently" (и перенаправлять пользователя на "канонический" URL), либо уже знакомый нам "404 not found". Пока мы ещё вообще не решили, долго ли будет жить эта система с вопросиками в URL (или мы завтра перейдём на ЧПУ), используем второй вариант – с 404. Заодно упорядочим связь накопившихся параметров URL с функциями, выдающими в ответ какое-то содержимое сайта:

<?php
//...
function response() {
	$valid = array(
		'id' => 'simple', 
		'edit' => 'auth_form', 
		'out' => 'sessdestroy', 
		'code' => 'print_code',
	);
	$n = count($_GET); 
	if ($n > 0) {
		$param = each($_GET); // самое простое: пропускаем только первый параметр
		if ($n > 1 || !isset($valid[$param['key']])) {
			_404();
		}
		else {
			if ($param['key'] == 'id') {
				Page::$data = get_data('mypages', _es($param, 'value', 1));
				if (!Page::$data) {
					_404();
				}
			}
			Page::$valid[$param['key']]();
		}
	}
	else {
		check_auth(); // Запрос POST принимаем только без параметров GET
		Page::$data = Page::default_();
		Page::simple();
	}
}
//...
?>

Упорядочивание принимаемых в запросе параметров привело к существенным изменениям. Мы добавили новый класс Page, в котором будут храниться все функции, выдающие разное содержание в http-ответе. Логика ответов сервера получилась уже довольно сложная:

  1. При незарегистрированных параметрах GET возвращаем ответ 404;
  2. При известном (ожидаемом) параметре вызываем функцию Page::Параметр();
  3. При параметре id пытаемся получить данные из таблицы mypages – строку с id равным значению параметра;
  4. Если строку из таблицы pages получить не удаётся, возвращаем ответ 404;
  5. Если параметров нет, вызываем "шаблон" по умолчанию – Page::simple(), предварительно установив данные для Page по умолчанию, то есть id = 1; если здесь данных нет, заполняем данные фиксированными значениями (content coming soon || настройте mysql);

Существенные изменения в функциях:

  1. При параметре edit (влекущем вызов функции Page::auth_form()) проверяем ещё и POST и авторизуем пользователя (если пароль подходит).
  2. Поскольку ответ "404 Not found" теперь наш код может возвращать из нескольких мест, мы оформляем этот ответ отдельной функцией _404();
  3. Создаём функцию get_data() для получения данных (одной строки) с параметрами "Таблица" и "Номер строки" – предполагаем, что таблиц у нас вскоре станет больше одной.

Наш файл index4-3.php http://nichtig.ru/index4-3.php?code приобретает всё более приличные очертания, код готовится к разделению по файлам: мы вызываем в общем пространстве только одну функцию response(). Но не будем торопиться: следующий шаг всё разрушит, и нам опять придётся наводить порядок.

< Лекция 1 || Лекция 2: 1234 || Лекция 3 >
Михаил Гутентог
Михаил Гутентог

Этот курс ( Практикум по разработке CMS ) создавался, когда у PHP была версия 5.3 или 5.4. Со временем какие-то функции PHP устаревают (mysql, each), какие-то начинают работать по-другому (empty). Пожалуйста, следите за изменениями в PHP по сайту php.net!

Александр Мельников
Александр Мельников

Изучаю курс "Практикум по созданию CMS" в листинге 4.3

$n = count($_GET); if ($n > 0) { $param = each($_GET); // самое простое: пропускаем только первый параметр if ($n > 1 || !isset($valid[$param['key']])) { _404(); }

При попытке просмотра в браузере получаю ошибку: Deprecated: The each() function is deprecated.  И не пойму как исправить ситуацию.

Елена Суханова
Елена Суханова
Россия, Москва, МИЭТ, 2011
Анастасия Щитова
Анастасия Щитова
Россия, Москва, ФГБОУ ВО "Московский государственный юридический университет имени О.Е. Кутафина (МГЮА)", 2016