четверг, 18 сентября 2008 г.

Переезд

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

вторник, 27 мая 2008 г.

О плохих шаблонизаторах

Для меня стало неприятным открытием что многие до сих пор используют для шаблонизации таких уродцев как XTemplate, либо пишут свой такой же велосипед с квадратными колёсами.
И так, давайте рассмотрим принципы их работы.
Первоочередной задачей шаблонизации является разделение бизнес логики и логики представления. Говоря человеческим языком, для того чтобы изменить отображение страницы нужно изменить лишь часть, отвечающую за отображение, так называемый шаблон.
Что такое шаблон. Обычно это html код со вставкой специальных элементов разметки, которые позволяют добавлять динамические данные.
На первом этапе создаётся массив данных, которые нужно передать в шаблон, а затем выводится сам шаблон и делаются замены спец вставок, на данные полученные в первом этапе.
Для обнаружения и подмены вставок, обычно используются два способа - функция str_replace и регулярные выражения. Первый вариант довольно шустрый, но имеет рад ограничений, второй из-за использования регулярок довольно трудоёмок.
И так, что происходит внутри. Берётся файл с шаблоном, загружается в память, затем обрабатывается с помощью строковых функций и делаются замены. Парсинг целого шаблона штука довольно трудоёмкая, а главное бесполезная. У нас для шаблонов уже есть РНР, который позволяет писать этакий код:

<h1><?=$title?></h1>
<ul>
<? foreach $items AS $item { ?>
<li><?=$item?></li>
<? } ?>
</ul>

А теперь представьте шаблон  пару сотен кода, в котором должны делаться замены, которых можно было легко избежать, подумайте, насколько упадёт производительно?
Для чего же тогда написана такая громадная библиотека как smarty и его аналоги, если уже есть готовые средства.
И так, рассмотрим второй подвид шаблонизаторов. Смартиподобные шаблонизаторы имеет ещё одну прослойку - компиляция. То есть, как и у первых, берётся шаблон, но не делается замена вставка - значение переменной, а вставка - переменная, после это всё записывается в файловую систему, чтобы при повторном обращении не делать лишних преобразований. В итоге, мы получает php шаблон. Но скорость даже подобных шаблонизаторов хуже чем, ежели бы обошлись без них.
И на кой нам это?
Первое преимущество - безопасность. Убрав из шаблонов РНР мы делаем шаблоны безвредными.
Второе тесно связано с первым. Теперь изменением дизайна могут заниматься не программисты, нужно лишь не нарушать разметку.
Третье - автоматизация. Люди ленивы, а жизнь коротка. поятому нужно стараться не делать дважды отдно и тоже. Допустим в смарти есть элемент html_options, который позволяет создать список единого выбора, есть escape, который поваляет обезопасить вывод, избавившись от потенциально опасных тегов.
Четвёртое..... А ну его, додумайте сами.
Результаты.
Шаблонизания на основе компилируемых шаблонов, помогает сэкономить кучу системных ресурсов, по сравнению с дермошаблонизаторами, при этом расширяемость позволяет выходить за рамки логики отображения, но хорошо это или плохо, решать каждому для себя. Пользуйтесь качественным кодом.
P.S. Некоторые умеют летать, а другие сё ещё "залетают".© Кто-то умный.

понедельник, 19 мая 2008 г.

Всемогущий Google

Последнее время я, как и большинство айтишников и простых пользователей подсел на сервисы Google. Очень удобно и главное собрано в воедино. Но последнее время начали раздаваться тревожные возгласы, предупреждающие, что google - это не добрый дядюшка, а корпорация образца microsoft, нацеленная на получение прибыли. Я довольно скептически отношусь к угрозе быть загугленым, я человек добрый и честный, мне скрывать нечего.

Но для любителей теории всеобщего заговора и просто любознательных поделюсь ссылкой на забавный и симпатичный проект http://masterplanthemovie.com/

четверг, 1 мая 2008 г.

Flash сообщения/

Часто бродя по сайтам и заполняя формы встречаю некоторые типичные недоработки при обработке форм.
Например, после обработки забывают сделать редирект, в итоге при нажатии "Обновить", данные посылаются повторно, что очень неприятно.
Поэтому, не забываем
header('Location: ' . $url);

Теперь появляется новая проблема - как сообщить о результатах обработки. Для этого воспользуемся так называемыми flash сообщениями.

Я приведу один из вариантов.

В начале сценария мы создаём объект $flash, при этом данные из $_SESSION['flash'] копируем в закрытую переменную класса и очищаем $_SESSION['flash'].

С помощью магических методо мы работаем с флэш данными:

1. __set() используем для занесения переменной в сессию.

2. __get() для извлечения флэш данных.

3. __isset() для проверки на сущестование данных.



<?php
error_reporting(E_ALL);
$flash = Amdy_Flash::Singleton();
if (isset( $flash->test ) ) echo 'true - ' , $flash->test;
else echo 'false';
$flash->test = 'ok';
class Amdy_Flash {
public static $instance;
protected $_data = array();
/**
* @return Amdy_Flash $instance
*/
public static function Singleton()
{
if (!isset(self::$instance)) {
$c = __CLASS__;
self::$instance = new $c();
}
return self::$instance;
}
public function __construct() {
if (!session_id()) session_start();
if ( isset($_SESSION['flash']) ) {
$this->_data = $_SESSION['flash'];
unset($_SESSION['flash']);
}
}

public function __set($varName, $varValue) {
$_SESSION['flash'][$varName] = $varValue;
}
public function __get($varName) {
return (isset($this->_data[$varName]) ? $this->_data[$varName] : null);
}
public function __isset($varName) {
if (isset($this->_data[$varName])) return true;
else return false;
}
}
?>

Кодировки

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


Перый шаг - настройка вашего любимого редактора. Ищем пункт encoding (кодировки) и ставим utf-8. Очень советую использоать именно юникод, чтобы избежать проблем в дальнейшем. Конечно, у юникода есть ряд недостатков, но пользы гораздо больше. Ах, да, ещё пару советов по настройке редактора. Выстовте конец строки в стиле unix  - \r. И используйте 4 пробела вместо табов.


Второй шаг - HTML. Добавляем строку

 <meta equiv="Content-Type" content="text/html; charset=utf-8"> 
в <head></head>.


Третий шаг - РНР. Некоторые браузеры пренебрегают  Content-Type и используют кодироку, посылаемую сервером, обычно по умолчанию iso-8859-1(latin1). Значит мы посылаем нужную нам -

header('Conten-type: text/html; charset=utf-8');

Четвёртый шаг - базы данных. Обычно данные берутся из базы данных, поэтому при создание таблиц и полей не забываем указывать нужную кодировку - utf8_general_ci. Обратите внимание, что именно general_ci, а не bin, как любят многие, это позволяет работать с данными как со строками, а не с двоичными данными. Но хостере и здесь заготовили подвох, и по умолчанию чаще стоит другая кодировка. Поэтому предупредим MySQL, что собираемся работать именно с utf-8, для этого сразу после подключения посылаем запрос:

SET NAMES utf8
Обратите внимание, что в названии кодировки нету дефиса. Некоторые советуют посылать ещё парочку, но у меня такой надобности не возникало.


Пятый шаг - если таки данные берутся из другой кодироки, для этого используется функция iconv(), которая конвертирует данные из одной кодировки в другую. На некоторых хостингах нету сего полезного расширения, но я предлогаю игнорировать хостинги с ненастроенным РНР и неустановленными популярными расширениями.


Дерзайте, все неудачные попытки конспектируйте и задавайте вопросы.

пятница, 25 апреля 2008 г.

Интернет-магазины

Сегодня нужно было купить пару железок для железного друга. Так как лень наше всё, решил сделать заказ через интернет. Нашёл, заказал, но плюх, оказывается доставки нет. Не стал отчаиваться и решил подышать свежим воздухом. 
Добрался, зашёл, осмотрелся.  Менеджер был занят заказчиком, пошёл посмотреть на витрины, пока смотрел пришло ещё два покупателя. Пока постоял и пока выдали заказ обратил внимание, что все пятеро уже заказали товар и пришли только оплатить и забрать. Сравнивая с другими местами, где терлось столько же народу, но в основном все приходили поглазеть, понял важность сайтов на шкуре заказчиков.
Разрабатывая сайты, не часто приходится пользоваться плодами своего творчества, а здесь всё как на ладони - посмотрел, оценил, буду другим рассказывать.

четверг, 10 апреля 2008 г.

Мысли о скобках

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



<?php
$template = new template;
// здесь присваивание переменных. вывод куска шаблона,
// а после
// я так понял, это автор хотел очистить переменные
$template->freshall;
?>

Если в первом случае автор сэкономив на скобках ничего не потерял, то во втором случае последствие могут быть фатальными. Вместо метода класса был вызван атрибут, компилятору ругаться незачем и ошибка остаётся незамеченной.

четверг, 3 апреля 2008 г.

Собственный обработчик сессий.

В предыдущей части был описан принцип работы сессий по умолчанию, сейчас постараемся его переопределить.
Для этого нам нужна следующая функция
bool session_set_save_handler ( callback open, callback close, callback read, callback write, callback destroy, callback gc )
Она вызывается перед использованием session_start().
Параметры – это кэлбак функции, которые вызываются при определённом вызове.
1) bool open(string path , string filename) – вызывается при вызове session_start(), на вход получает 2 значения – путь к директории с файлами сессий и имя файла. Возвращает true, если успешно.
2) bool close() – вызывается при завершении работы сценария. Возвращает true в случае успеха.
3) array read(string session_id) – вызывается при старте сессии, получает идентификатор сессии, заполняет $_SESSION;
4) bool write(string session_id, array data) – записывает изменения.
5) bool destroy(string session_id) – вызывается при session_destroy()
6) bool gc(int time) – уборщик мусора, на вход получает время жизни сессии.
Сейчас сделаем обёртку для хранения данных в базе данных.

<?php
class Session {
private $dbh; // хендл на соединение с базой данных
private $sessionLife = 3600; // время жизни сессии
// в конструкторе устанавливаем обработчик, устанавливаем время жизни сессионной куки и стартуем сессию
public function __construct() {
session_set_save_handler(
array($this, '_open'),
array($this, '_close'),
array($this, '_read'),
array($this, '_write'),
array($this, '_destroy'),
array($this, '_gc')
);
session_set_cookie_params($this->sessionLife);
session_start();
}
// при старте сессии открываем соединение с базой данных
private function _open($dir, $name) {
$this->dbh = mysql_connect('localhost', 'root', '');
if (!$this->dbh) die('Bad database connect');
if (!mysql_select_db('test')) die('Bad db table name');
return true;
}
// при окончании работы закрываем соединение
public function _close() {
mysql_close($this->dbh);
}
// считываем данные из базы данных и десериализируем их
private function _read($id) {
$sql = 'SELECT `data`
FROM `session`
WHERE `session_id`=\''.mysql_real_escape_string($id).'\'
LIMIT 1
';
$data = mysql_query($sql, $this->dbh);
if (!$data) die(mysql_errno() . ": " . mysql_error() . "\n");
$data = mysql_fetch_assoc($data);
if (isset($data['data'])) {
return unserialize( $data['data'] );
} else {
return array();
}
}
// при записи сериализируем данные и засовываем их в базу данных
public function _write($id, $data) {
$sql = 'REPLACE INTO `session`(`session_id`, `data`, `date_updated`)
VALUES (\''.mysql_real_escape_string($id).'\',
\''.mysql_real_escape_string(serialize($data)).'\',
UNIX_TIMESTAMP() )
';
if (mysql_query($sql, $this->dbh)) {
return true;
} else {
die(mysql_errno() . ": " . mysql_error() . "\n");
}
}
// при убийстве очищаем массив с данными, убиваем куку и чистим базу
public function _destroy($id) {
$_SESSION = array();
unset($_COOKIE[session_name()]);
setcookie(session_name(), '', time()-42000, '/');
$sql = 'DELETE FROM `session`
WHERE `session_id`=\''.mysql_real_escape_string($id).'\'
LIMIT 1
';
if (mysql_query($sql, $this->dbh)) {
return true;
} else {
die(mysql_errno() . ": " . mysql_error() . "\n");
}
}
// убираем старые сессии
public function _gc($time) {
$sql = "DELETE FROM `session`
WHERE `date_updated` < UNIX_TIMESTAMP() - {$this->sessionLife}
";
if (mysql_query($sql, $this->dbh)) {
return true;
} else {
die(mysql_errno() . ": " . mysql_error() . "\n");
}
}

}
// тестируем
$session = new Session();
$_SESSION['test'] = 1;
var_dump($_SESSION);
?>

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

суббота, 29 марта 2008 г.

Авторизация пользователей.


В РНР доступно 2 правильных способов для узнавания пользователя.
1) Модификация адресной строки.
2) Куки
Они основаны на передаче данных от пользователя.
При первом способе добавляется переменная в строку запроса – index.php?id=1
Второй Куки (Cookie).
При отправке HTTP-объекта клиенту сервер также может отправить блок информации о состоянии соединения, который будет храниться у клиента. В состав блока включается описании диапазона URL-адресов, для которых это состоянии достоверно. Все последующие HTTP-запросы, сделанные клиентом и попадающие в этот диапазон, будут включать в себя пересылку текущего значения данного объекта состояния от клиента обратно серверу. Объект состояния называется cookie (печенюшки) по причинам. Которые не обсуждаются.

То есть, мы записываем значение id=1 в куки и оно каждый раз при запросе с данного диапазона возвращается серверу.

Модификацию адресной строки отметаем сразу же. Остаются куки. У них как минимум 2 недостатка: они доступны для пользователя, они не защищают неРНР контент (его обсуждать не будем).
То, что кука доступна пользователю обозначает, что он может её модифицировать, от этого можно защититься с помощью криптографических функций mcrypt*. Вторая неприятность трафик, который приходится гонять в сторону сервера.
Так как для идентификации пользователя нам достаточно только уникального ключа, логично передавать только его, а на основании значения на сервере восстанавливать данные. Разработчики РНР продумали это и получились сессии.
Для работы с сессиями достаточно вызвать session_start() до вывода чего-либо в браузер, после этого становится доступен суперглобальный массив $_SESSION, в котором и хранится информация.

Важно. Идентификатор сессии может передаваться через get, post и cookie. Get и post данные являются легко изменяемыми, например пользователю подсунули ссылку с идентификатором или заставили залогиниться через поддельную форму с другого сайта, после чего злоумышленнику доступен идентификатор сессии.

Для получения идентификатора из куки в ини файле.
session.use_trans_sid = 0 – в целях безопасность отключаем.
session.use_cookies = 1 – разрешаем использовать куки.
session.use_only_cookies = 1 – используем только куки, опция отключена по умолчанию.

Теперь немного о процессе, который происходит внутри.
Сервер получает идентификатор, если он есть, имя которого можно получить-изменить с помощью session_name(), по умолчанию (session.name = PHPSESSID). Механизм сессий весьма гибок и позволяет менять обработчик сессии, для этого существует session_set_save_handler(). При вызове session_start() вызывается этот обработчик (session.save_handler = files). Если пришёл идентификатор, то восстанавливается старая сессия, если его нет, стартует новая, для этого посылается заголовок для установки куки. Данные по умолчанию хранятся в файле, который находится в директории session_save_path() и имеет имя sess_ИДЕНТИФИКАТОР. Так как он хранится в плоском файле, для записи-чтения его нужно сериализировать-десериализировать, опять же для гибкости можно менять обработчик (session.serialize_handler = php), этот процесс проходит в начале работы с сессией и по завершению работы скрипта, даже если он завершился по таймауту. При вызове session_start() вызывается open, затем read, который возвращает результат в суперглобальную переменную $_SESSION. При окончании работы скрипта или при вызове session_write_close(), данные сериализируются и записываются. Метод close вызывается в конце работы. Метод destroy, нужен для уничтожения сессии. Стоит отметить gc, который нужен для уборки мусора он вызывается с частотой session.gc_probability/session.gc_divisor.
На этом первая часть заканчивается, и переходим непосредственно к практической стороне дела.

вторник, 19 февраля 2008 г.

Пожалуй начну обогощать блог

Затянул с наполнением блога, набралось много чего, чем не жадко поделиться с миром. Думаю писать профессиональные статьи о вэб разработке, PHP, MySQL, XML, XSLT, ООП, Linux, LAMP ну и естественно будут лирические отступления.