Category Archives: класс

PHP: HTML::render – грамотно рендерим свои странички

Здравствуйте.

Лет 5 назад мы работали над одним сайтом, и использовали какой-то open source framework, я уже не помню какой именно. Так вот, он мне нравился, хоть и был слегка замороченный, я в нём ковырялся, и наткнулся на функцию рендеринга конечной веб-страницы. Она мне показалась довольно милой, я её максимально сократил, оставив практически всю функциональность, и облёк в форму класса.

Это мощный рендерер занимает всего строчек 40, очень удобный и быстрый. Я им пользуюсь уже 5 лет и ни разу не пожалел об этом. Всегда используйте этот рендерер, даже в демо-проектах, нечего сваливать PHP-код и HTML-разметку в кучу.

HTML::render

Вот его полный код:

///////////////////////////////////////////////
//////////// PARSE HTML FUNCTIONS ////////////
/////////////////////////////////////////////
// use STATIC rendering - используем статический рендеринг
class HTML
{
        // deafult folder for html pages (templates)
	static private $folder = 'html';
	// change default folder for html templates
	static public function changeFolder($folder) {
		self::$folder = $folder;
	}
 
	static public function render($template, $data = array()) {
		$content = file_get_contents(self::$folder."/{$template}.html");
		$content = self::design_render_text($content, $data);
		return $content;
	}
 
	static private function design_render_text($content, $data = array()) {
		$content = self::design_parse_function($content, $data);
		$content = self::design_parse($content, $data);
		return $content;
	}
	static private function design_parse_function($content, $data = array()) {
		preg_match_all('/\<\<(.*?)\>\>/is', $content, $res);
		if (@$res[1])
			foreach ($res[1] as $el) {
				$middle = self::design_parse($el, $data);
				$middle = '$result = '.$middle.';';
				eval($middle);
 
				$content = str_ireplace('<<'.$el.'>>', $result, $content);
			}
		return $content;
	}
	static private function design_parse($content, $data) {
		preg_match_all('/\%\%(.*?)\%\%/si', $content, $res);
		if (@$res[1])
			foreach ($res[1] as $el) $content = str_ireplace('%%'.$el.'%%', $data[$el], $content);
		return $content;
	}
}

Достаточно включить этот класс в свой проект и вы можете легко и просто рендерить странички. Как он работает?

Синтаксис:
HTML::render( template [ , array ] );

template – имя шаблона веб-странички. Используется без расширения. По умолчанию шаблоны расположены в папке html (её можно поменять вызовом HTML::changeFolder(‘tpl’), чтобы использовать папку tpl вместо html, к примеру). Шаблоны в папке должны иметь расширение html. Можно использовать любое кол-во вложенных подпапок, естественно.

array – ассоциативный массив, все ключи которого сопоставляются с вхождениями %%…%% в шаблоне и с заменой их значениями. Можно не использовать, если на страничке нет таких элементов.

Например:

$html = array('TITLE' => 'Добро пожаловать!');
$page = HTML::render('index', $html);
die($page);

и браузер выдаст нам отрендеренную страничку. Если в шаблоне html/index.html будет присутствовать вхождение %%TITLE%%, оно будет заменено в данном случае на Добро пожаловать!

Странички состоят из простейших шаблонов – HTML-код + вставка переменных и любых PHP-конструкций.
%%VAR%% – используем подстановку для VAR. VAR – ключ ассоциативного массива, который идёт вторым параметром в вызове HTML::render() (см. выше).
<<PHP_code>> – выполнение PHP кода и отображение на страничке его результата. Можно использовать любые доступные при вызове рендерера функции, и супер-глобальные переменные, например <<$_SERVER[‘HTTP_HOST’]>>
Также может быть использовано для логического ветвления в шаблоне, когда рендерер отобразит особую часть из шаблона при выполнении определённого условия. Пример смотрите ниже.

Пример

Но всё слова-слова, но без примера не всё так понятно, верно.

Вот наш шаблон:

<!DOCTYPE html>
<html lang="ru">
 
<head>
	<meta charset="utf-8">
	<title>Ошибка соединения с БД</title>
</head>
 
<body>
<h1 style="color: blue">Ошибка соединения с БД.<br>
Повторите попытку через минуту!</h1>
<<('%%DEBUG_MODE%%')?'
<div class="info">
%%DB_ERR%%
</div>':''>>
</body>
</html>

Использование:

$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
$user = 'dbuser';
$password = 'dbpass';
 
try {
    $dbh = new PDO($dsn, $user, $password);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    die(HTML::render('err/db', 
      array('DEBUG_MODE' => $_DEBUGGING, 'DB_ERR' => $e->getMessage())))
}

В этом примере делается попытка соединиться с БД, и если она не удалась, выводится страница из шаблона, находящегося в файле html/err/db.html. %%DB_ERR%% заменяется значением $e->getMessage(), и оно будет включено в страничку, только если %%DEBUG_MODE%%, который преобразуется в true/false согласно переменной $_DEBUGGING, будет равен true.

Этот класс можно применять не только для рендеринга цельных (оконечных) страничек, но и для обработки любых HTML-кусков. Да, возможно не хватает выполнения циклических блоков, как в Smarty, например, но для 40-ка строчек PHP-кода, я полагаю, и это совсем неплохо. Любую нужную функциональность уже добавляем по мере необходимости и по прихоти клиентов.

Удачи на даче! И крепкого чая, крепкой любви и крепкого имбиря.

Немного философии или крутой класс-обертка над PHP PDO (MySQL, SQlite etc.)

Здравствуйте.

Читатель моего блока наверное недоумевает – блог называется “Философия программирования”, но вот философии в нем что-то не наблюдается. Я хотел написать пост о программистах странных, но позже напишу. Сейчас хочу представить класс-обёртку для PHP/PDO.

Класс-обёртка для PHP/PDO

Если вы давно читаете мой блог (ну или понятное дело нагуглили пару постов в нем) вы знаете что я поклонник минимализма и простоты в программировании. Я пытаюсь упростить везде, где только возможно.

А как же русская народная пословица (мудрость?) “Простота хуже воровства”, спросите вы и поставите меня в тупик.
Когда я думал об этой пословице, я долго бил себя кулаком в грудь, и пытался сам себе доказать, что пословица неверна, что я прав, и лучше всё упрощать, чем усложнять. Но червь сомнения всё равно селился в моей душе и грыз меня изнутри, доводя до слёз.

Но время лечит (хоть и не всегда), и, наверное, это пословица действительно неверна, а скорее всего применима в узком спектре существования (в частных случаях), когда простота действительно раздражает.

Но в общем случае, в большинстве случаях и объективно говоря лучше всё упрощать, чем усложнять, согласитесь? Обычно усложняет тот, кто не может сделать проще или пытается скрыть какой-то обман.

Что касаемо кода (программирования), то чем проще и понятней код, чем его меньше, тем проще его поддерживать и отлаживать, верно? Тут нет никаких сомнений или исключений.

С другой стороны мы понимаем, что вряд ли возможно сократить/упростить какое-нибудь хитроумное уравнение из ядерной/квантовой физики или высшей математики (хотя кто его знает, может и можно), или молекулы ДНК, состоящие из миллионов нуклеотидов (хотя, наверное всё же можно), а также их кодирование в вычисляемый машинами код (на каком-либо языке программирования).

Хотя изумительно сложные по виду и разнообразные фракталы (например, классический фрактал Мандельброта), как вы знаете, рисуются буквально десятком (или менее) строчек кода, надо просто знать соотв. уравнения.

ОК, может хватить философии и займёмся делом?

Класс-обёртка для PHP/PDO – теперь уже точно! ))

Зачем нужны обертки, спросит некий наивный программист (новичок?). Грамотные обертки могут значительно упростить и классифицировать создания и использование узконаправленного кода.

Недостатки оберток – необходимо запомнить его функциональность (переменные, вызываемые методы и т.п.), а также вычисление машиной (процессором) лишнего кода, так как любая обертка в итоге обращается к своему родителю (то, что она оборачивает).

Преимущества оберток – значительное упрощение вычислений и кода.

Например, случай PHP PDO.

Да, в PDO есть все методы для работы с базами данных, это универсальная обертка над различными драйверами БД. В нем есть такие методы, как PDO::query, PDO::prepare, PDO::exec и т.д.

Также надо знать и учитывать класс PDOStatement, который позволяет подготавливать и вычислять различные запросы к БД и получать из них результаты.

Общие случаи работы с PDO таков:

Создаём экземпляр класса PDO:

try
{
	$db = new PDO(
		"mysql:host=localhost;dbname=my_database;charset=utf8",
		'username',
		'password',
		array(PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES UTF8')
	);
}
catch (PDOException $e)
{
	die('Сайт перегружен. Повторите попытку через 5 секунд'); 
}

После успешного выполнения этого куска кода у нас образуется переменная $db, с помощью которой мы можем выполнять любые операции над открытой базой данных.

Например:

$prep = $db->prepare("SELECT id, name, active FROM partners WHERE email=? AND pass=? LIMIT 1");
$prep->execute(array($email, $pass));
$ar = $prep->fetch(PDO::FETCH_ASSOC);
if(!count($ar)) {
  echo 'Неверное имя или пароль';
  return false;
}
// ... продолжение кода

Таким образом, используя $prep как экземпляр PDOStatement, мы получаем в $ar ассоциативный массив выборки из таблицы partners открытой БД. Теперь в $ar[‘id’], $ar[‘name’, $ar[‘active’] содержат нужные значения.

Используя же обертку, мы можем написать примерно так:

$prep = DB::execute("SELECT id, name, active FROM partners WHERE email=? AND pass=? LIMIT 1", array($email, $pass)); 
$ar = $prep->fetch(PDO::FETCH_ASSOC);
if(!count($ar)) {
  echo 'Неверное имя или пароль';
  return false;
}
// ... продолжение кода

В нашей обёртке все обращения к БД осуществляются через статический класс DB, таким образом нам не нужно следить, чтобы указатель на открытую БД всегда находился в пределах видимости функций работы с ним. Это уже большой плюс.
Кроме того, обертка как мы видим в предыдущем примере упрощает ряд вызовов, например вместо двух вызовов с созданием промежуточной переменной $prep типа PDOStatement мы в данном случае используем один.

Класс DB (static)

Итак, привожу полный пример класса DB, а после – краткое описание методов класса и примеры работы с ним.

//////////////////////////////////////////////////////////
//////////////// PDO DB wrapper	/////////////////////////
////////////////////////////////////////////////////////
class DB
{
// use STATIC rendering
	static private $db;	// db handler
 
	static public function init() {
		try {
			self::$db = new PDO(
				"mysql:host=localhost;dbname=my_database;charset=utf8",
				'username', 'password',
				array(PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES utf8')
			);
			self::$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
			//self::$db->exec("SET NAMES utf8"); -- не нужно если сработает PDO::MYSQL_ATTR_INIT_COMMAND?
		}
		catch (PDOException $e)
		{
			die('<h3 style="color: blue">Ошибка соединения с БД. Повторите попытку через полминуты</h3>');
		}
	}
 
	static public function query($sql) {
		return self::$db->query($sql);
	}
 
	static public function exec($sql) {
		return self::$db->exec($sql);
	}
 
	// one column result
	static public function column($sql) {
		return self::$db->query($sql)->fetchColumn();
	}
 
	// intval one column result
	static public function columnInt($sql) {
		return intval(self::$db->query($sql)->fetchColumn());
	}
 
	static public function prepare($sql) {
		return self::$db->prepare($sql);
	}
 
	static public function lastInsertId() {
		return self::$db->lastInsertId();
	}
 
	// prepares and execute one SQL
	static public function execute($sql, $ar) {
		return self::$db->prepare($sql)->execute($ar);
	}
 
	// returns error info on db handler (not stmt handler)
	static public function error() {
		$ar = self::$db->errorInfo();
		return $ar[2] . ' (' . $ar[1] . '/' . $ar[0] . ')';
	}
 
	// returns one row fetched in FETCH_ASSOC mode
	static public function fetchAssoc($sql) {
		return self::$db->query($sql)->fetch(PDO::FETCH_ASSOC);
	}
 
	// returns one row fetched in FETCH_NUM mode
	static public function fetchNum($sql) {
		return self::$db->query($sql)->fetch(PDO::FETCH_NUM);
	}
 
}

Для запуска БД надо написать:

DB::init();

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

Единственная не универсальность, это:

catch (PDOException $e)
{
  die('<h3 style="color: blue">Ошибка соединения с БД. Повторите попытку через полминуты</h3>');
}

Тут естественно Вам надо придумать свой обработчик ошибки открытия БД и уведомления пользователей, но в большинстве случаев сайт не может функционировать, и просит пользователя выполнить дополнительные действия.

Описание методов класса DB

Итак, вот описание всех методов класса:

DB::query($sql) – выполнить один запрос к БД с возвращением результата PDOStatement. его следует использовать только в том случае, если запрос не содержит необработанные данные, например из внешних источников. При использовании данных в запросе от внешних источников всегда следует применять подготовленные запросы (prepared statements) – смотрите ниже – в целях предотвращения SQL Injection (инъекции SQL запроса) и уверенности в целостности конечного запроса.

DB::exec($sql) – выполнение запроса, не требующего возврата результата. К нему относится тоже примечание, что и для DB::query. Так же его всегда следует использовать вместо DB::query, если запрос НЕ возвращает результата (обычно это UPDATE или DELETE), так как он, как указано, работает быстрее.
Возвращает кол-во затронутых строк.

DB::column($sql) – первая вкусность, которая упрощает жизнь. Выбор только одного значения из БД, соотв. запросу.
DB::columnInt($sql) – тоже самое, только возвращается intval значение поля выборки, что бывает полезно.

DB::prepare($sql) – подготовка запроса, возвращает PDOStatement.
DB::lastInsertId() – получение lastInsertId от последнего запроса INSERT.

DB::execute($sql, $ar) – подготавливает и сразу выполняет запрос. Каждый ? или :var в теле запроса должен соотв. значению из массива $ar. Пример смотрите выше. Возвращает true/false.

DB::error – возвращает строку последней ошибки над DB вместе с кодами в скобках. Помните, что при использования PDOSatement’ов ошибки, связанные именно с их исполнением, следует обрабатывать над ними, а не над общей DB.

И, наконец, две последние вкусности:
DB::fetchAssoc($sql)
DB::fetchNum($sql)

Возвращают результат выполнения $sql, в первом случае как PDO::FETCH_ASSOC, во втором – как PDO::FETCH_NUM.
Заметьте, что запрос НЕ подготовлен, поэтому можно использовать только с проверенными данными (НЕ из внешних источников). Но так как в большинстве случаев мы пишем запросы с корректными данными, эти функции часто бывают очень полезны и сокращают код программы.

Пример их использования

Получения данных клиента по его ID. Так как переменная ид у нас всегда целое число, использование его в запросе безопасно.

$id = intval($id); // обычно это уже сделано, то есть $id - у нас целая переменная,
// и не может содержать строки или ещё какой-нибудь мусор
$ar = DB::fetchAssoc("SELECT name,surname,email FROM cleints WHERE id=$id");
extract($ar);
// теперь в переменных $name, $surname, $email у нас необходимые данные клиента.

Ещё пример:

$id = intval($id); // обычно это уже сделано, то есть $id - у нас целая переменная,
// и не может содержать строки или ещё какой-нибудь мусор
 
list($name, $dtm) = DB::fetchNum("SELECT CONCAT(name, ' ', surname), DATE(dk_dtend) 
			FROM clients
            WHERE DATE(dk_dtend)>'2007-01-01' AND id=$id");

Этот прием можно было использовать и в предыдущем примере, всё зависит от стиля, настроения и желания. Мы выбрали из таблицы два поля – имя ($name) и дату ($dtm) одним запросом. Здесь мы также уверенны, что подставляемая в запрос переменная $id не может содержать мусор или непроверенные данные.

Заключение

Ну, пока всё, в будущем сделаю ещё пост про HTML::render – также крутая штука, упрощающая жизнь PHP-программиста (который не использует сомнительные тяжёлые фрэймворки, а делает всё сам, своими руками, как это делаю я, в большинстве случаев). Хотя фрэймворки если уметь с ними работать могут упростить написание конечного кода, но они зачастую бывают слишком тяжелы и неповортливы, кроме того, небезопасны в использовании, так как в них могут находится уязвимости.

Но ничего не обещаю. Ещё Linux – компиляция программ с GCC/G++, make, cmake – ох, это чудо. GNU Utils, я люблю Вас! Сам не знаю за что.

До новых встреч – вкусного горячего кофе, удобной позы в работе, а самое главное – я не устаю это повторять, хотя в зубах завязло – отличного настроения! Это главное. Сейчас – отличное настроение. Не потом, не завтра, не в обед пятницы, не через год, на Гавайях, а сейчас. Отличное настроение – это бодрость духа – решает всё. ИМХО, конечно, по моему скромному мнению.

Удачи на даче!

PHP: простой класс для ведения логов

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

Вот как он выглядит:

class LOG
{
// use STATIC rendering
	static private $fplog;	// file handler for logging
 
	// open logging file for writing
	static public function start($flogname = 'log.txt') {
		self::$fplog = fopen($flogname,'ab');
	}
 
	static public function stop() {
		fclose(self::$fplog);
	}
 
	static public function write($s, $usedate = true) {
	// пишем в лог-файл строку $s,
	// $usedate - вставлять ли в лог дату/время текущие
		if($usedate) 
			$tim = '['.date('Y-m-d H:i:s').'] ';
		else
			$tim = '';
		fwrite(self::$fplog,$tim.$s."\n");
	}
}
 
LOG::start();	// start default logging

Замететь, что после создания этого статичного класса мы сразу стратуем журнал лога через LOG::start() метод. После этого методами LOG::write() мы можем писать в лог всё что нам вздумается.

Да, и сразу отвечаю на пару вопросов.
1. Используем статичные методы для того, чтобы вести логи без создания экземпляра класса, через статический метод LOG::write(сообщение для лога). По умолчанию в логе пишется время перед сообщением в формате [YYYY-mm-dd H:i:s], но если мы во втором параметре передадим false, то время писаться для данного сообщения не будет. Естественно, это поведение можно изменить заменив в методе write() второй параметр с $usedate = true на $usedate = false.

2. Но почему просто не использовать один экземпляр класса? Но тогда во всех функциях его надо будет объявлять отдельно как глобальный, а нам этого делать не хочется.

Из любых функций, методов мы пишем LOG::write() и наш лог заполнен.

Лог пишется в файл log.txt в текущей директории, но мы можем при вызове метода start передать другое имя для файла лога.

Чтобы в Apache environment закрыть доступ к логу со стороны внешних запросов можно использовать такое простое правило Apache‘а:

<Files log.txt>
Deny from all 
</Files>

Кинув это правило в файл .htaccess в папке с log.txt, мы оградим наш лог от внешних любопытных взглядов.

Пока всё, на сим раскланиваюсь, с наступающим Новым Годом вас и православным Рождеством.

Отличного настроения, здоровья и ясного сознания. Keep you mind clear. Keep your mind open.

До встречи, друзья!