Проверяем, является ли переменная числом
if (!preg_match("|^[\d]+$|", $var)) ...
if (!preg_match("|^[\d]+$|", $var)) ...
Например: <TITLE> ... </TITLE> заменить аналогично на <МОЙ_ТЕГ> ... </МОЙ_ТЕГ> в HTML-файле:
preg_replace("!<title>(.*?)</title>!si","<МОЙ_ТЕГ>\\1</МОЙ_ТЕГ>",$string);
if (preg_match('|<title.*?>(.*)</title>|sei', $str, $arr)) $title = $arr[1];
else $title='';
preg_replace("/.*?\./", '', 'photo.jpg');
Пишу эту заметку для того, что бы не писать по сто раз одно и то же для тех, кто еще не в курсе. Всё же удобнее - кинул ссылку, пусть читают.
Я думаю, не надо рассказывать, что UTF-8 уже является стандартом для большинства web-приложений и пока не планируется особых замен. Но на текущий момент множество начинающих "познавателей" PHP сталкиваются с проблемами в этом месте и постят кучу топиков в связи с тем, что PHP пока не поддерживает кодировку UTF-8 "из коробки" и приносит вышеуказанным людям кучу непонимания и проблем. Кракозяблики, кракозяблики, кракозяблики... Проблема с UTF-8... Что делать? Помогите - слышно тут и там... На самом деле всё очень просто. Ниже я опишу несколько правил, которые позволяют избавиться от "проблем" с UTF-8 при разработке приложений на PHP.
Для начала, когда, пользуясь здравым смыслом, мы решаем работать с UTF-8 для простоты дела мы утверждаем, что от ныне абсолютно всё у нас будет храниться, писаться, выводиться в UTF-8. Второе, что нам будет нужно, это наличие php_mbstring расширения на сервере, которое предоставляет функции для работы со строками в многобайтном режиме. Для тех, кто в танке это функции, которые начинаются на mb_. Если ваш хостинг провайдер не потрудился установить у себя это расширение - смело выбирайте другого. Итак, если если ваш php поддерживает mb_ функции - пол дела сделано. Остальные пол дела для работы с UTF-8 в PHP заключаются в следующем:
1. В начале работы скрипта необходимо указать кодировку входа и выхода с помощью функции mb_internal_encoding:
Listing №1 (PHP)
mb_internal_encoding('UTF-8');header('Content-Type: text/html; charset=UTF-8');<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
SET NAMES "utf8" -- и SET CHARACTER SET "utf8"
В этой короткой статье я опишу реализацию шаблона проектирования итератор (Iterator). Как я и обещал, мы рассмотри класс ArrayList, упомянутый в прошлой статье. Давайте сначала рассмотрим некоторые особенности класса ArrayList, а потом я расскажу о реализации паттерна итератор.
Зачем нужен класс ArrayList? Многие умные дядьки скажут, что он и нафиг не нужен, и, и без этого можно обойтись. Конечно можно, но некоторым, таким как я, это вполне может показаться удобным на столько, что данный функционал можно просто использовать. Но не будем тратить лишние символы, которые и так на данный момент не сжимаются ни каким gzip-ом или чем-то типа того. Первое и, я считаю, главное, что делает данный класс - это нормальный обход генерации ошибки уровня E_NOTICE, при доступе к неинициализированному элементу массива. То есть, когда мы делаем так:
Listing №1 (PHP)
<?php error_reporting(E_ALL); $ar = array(); echo $ar[0] ?>
//инициализация ссылки
$NativeArray = array();
$ObjectArray = new ArrayList();
//запись элемента
$NativeArray['Name'] = 'Vova';
$ObjectArray->Name = 'Vova';
//чтение элемента
if(isset($NativeArray['Name'])) {
echo $NativeArray['Name'];
}
echo $ObjectArray->Name;class ArrayList {
/**
* @var array
*/
private $_Items;
public function __construct()
{
$this->_Items = array();
}
/**
* @param string $VarName
* @return mixed
*/
public function __get($VarName)
{
if(isset($this->_Items[$VarName])) {
return $this->_Items[$VarName];
}
else {
return NULL;
}
}
/**
* @param string $VarName
* @param mixed $VarValue
*/
public function __set($VarName, $VarValue)
{
$this->_Items[$VarName] = $VarValue;
}Iterator extends Traversable {
/* Methods */
abstract public mixed current ( void )
abstract public scalar key ( void )
abstract public void next ( void )
abstract public void rewind ( void )
abstract public boolean valid ( void )
}class ArrayList implements Iterator {
/**
* @see Iterator
*/
public function rewind()
{
reset($this->_Items);
}
/**
* @see Iterator
*/
public function current()
{
return current($this->_Items);
}
/**
* @see Iterator
*/
public function key()
{
return key($this->_Items);
}
/**
* @see Iterator
*/
public function next()
{
return next($this->_Items);
}
/**
* @see Iterator
*/
public function valid()
{
return ($this->current() !== FALSE);
}$Errors = new ArrayList();
$Errors->LoginError = 'Login is empty';
$Errors->PasswordError = 'Password is small';
foreach($Errors as $key=>$value) {
echo "$key: \"$value\"<br>";
}class ArrayList implements IteratorAggregate {
public function getIterator()
{
return $this->_Items;
}class ArrayList implements IteratorAggregate {
public function getIterator()
{
return new ArrayIterator($this->_Items);
}class ArrayList implements Iterator, Countable {
/**
* @see Countable
*/
public function Count()
{
return count($this->_Items);
}$Errors = new ArrayList();
$Errors->LoginError = 'Login is empty';
$Errors->PasswordError = 'Password is small';
foreach($Errors as $key=>$value) {
echo "$key: \"$value\"<br>";
}
echo 'Total errors: ' . count($Errors);Написать свой PHP framework становиться всё проще и проще, ведь я публикую очередную статейку об этом. Сейчас мы поговорим об архитектуре web-приложений, а точнее об одном из основных аспектов организации обработок команд пользователя. Конечно же, я буду обговаривать некоторые шаблоны проектирования. Здесь мы затронем такие паттерны как модель-представление-контроллер (Model View Controller), контроллер страниц (Page Controller), контроллер запросов (Front Controller), команда (Command), сценарий транзакции (Transaction Script) и некоторые другие.
Итак, с чего начнем? А начнем мы с того, что все только и твердят об MVC (Model View Controller) хотя многие понятия не имеют, что это такое и как это работает. Не смотря на то, знаете ли вы или нет, что скрывается за этими словами, я все же попытаюсь кратко объяснить суть, так как на основе последней будет строиться дальнейшее изложение. Конечно же, в нашем контексте я говорю, что MVC представляет собой шаблон проектирования. Но так как шаблоны проектирования как таковые могут абстрагировать довольно различные по охвату мысли и масштабу реализации какого-то функционала, то сказать, что MVC просто шаблон проектирования равносильно тому, что говорить о компьютере, как о простом наборе микросхем. Паттерн MVC не просто типовое решение - он скрывает в себе один из фундаментальных принципов проектирования программного обеспечения - разделение основных частей приложения, благодаря которому приложение легче адаптируется, масштабируется, тестируется, сопровождается и конечно же реализуется. Разделение происходит именно в тех местах, где это наиболее выгодно для всех только что перечисленных действий. Конкретно происходит отделение интерфейсной части приложения от логической. В нашем же случае - случае web-приложения, происходит разделение последнего на три составляющие: модель, представление и контроллер. Модель представляет собой бизнес-логику приложения. Представление характеризуется внешним видом, которое непосредственно наблюдается пользователем. Ну а контроллер управляет всем этим делом. Сегодня речь пойдет о контроллере и вариантах его реализации, на одном из которых мы и остановимся.
На данный момент в 90 процентах случаев взаимодействие пользователя с web-приложением проходит посредством переходов по ссылкам. Посмотрите сейчас на адресную строку браузера - по этой ссылке вы получили этот текст. По другим ссылкам, например, находящимся справа на этой странице, вы получите другое содержимое. Таким образом, значение ссылки - комбинация символов в ней - определяет конкретную команду web-приложению. Надеюсь, вы уже успели заметить, что у разных сайтов могут быть совершенные разные форматы построения адресной строки. Где-то можно выделить явные названия файлов, где-то нельзя, а где-то - вообще ничего вроде и не меняется. Каждый формат может отображать архитектуру web-приложения. Хотя это и не всегда так, но в большинстве случаев это явный факт. К чему я веду? Я просто хочу показать на примере адресной строки различие двух подходов в реализации (сразу скажем это слово) контроллера. Допустим, имеется два варианта адресной строки, по которым показывается какой-то текст:
http://www.domain1.com/article.php?id=3
http://www.domain2.com/index.php?article=3
Соответственно имеются также два варианта для показа профиля пользователя:
http://www.domain1.com/user.php?id=4
http://www.domain2.com/index.php?user=4
Как видно из примеров, логика обращения к web-приложению является разной. Для сайта domain1.com каждый сценарий отвечает за выполнение определённой команды, а для сайта domain2.com все обращения происходят в одном сценарии index.php. То есть для первого случая каждая страница сервера выполняет конкретное действие на сайте, а для второго - все действия реализуются как бы одним скриптом. За реальными примерам далеко ходить не надо. Подход с множеством точек взаимодействия вы можете наблюдать на любом форуме с движком phpBB (например, www.php.ru/forum): просмотр форума происходит через сценарий viewforum.php, просмотр топика через viewtopic.php и т. д. Второй же подход, с доступом через один физический файл сценария, можно наблюдать на этом сайте: все обращения проходят через index.php. Чтобы просмотреть статью нужен адрес http://itdumka.com.ua/index.php?cmd=shownode&node=1, а чтобы, допустим, зарегистрироваться нужно пройти по ссылке http://itdumka.com.ua/index.php?cmd=showregisterform. Как видите, это совершенно два различных подхода, которые, как это ни странно, находят отражение в контексте наших с вами шаблонов проектирования. Первый подход характерен для шаблона контроллер страниц (Page Controller), а второй подход реализуется паттерном контроллер запросов (Front Controller).
Контроллер страниц хорошо применять для сайтов с достаточно простой логикой. Форум phpBB имеет очень простую логику, поэтому контроллер страниц хорошо подошел для его реализации. В свою очередь, контроллер запросов объединяет все действия по обработке запросов в одном месте, что даёт ему дополнительные возможности, благодаря которым можно реализовать более трудные задачи, чем обычно решаются контроллером страниц. Я не буду вдаваться в подробности реализации контроллера страниц, а скажу лишь, что в рамках нашей задачи в роли контроллера будет разработан именно контроллер запросов. Мы же делаем фреймоврк, который должен решать большинство задач, поэтому контроллер страниц, к сожалению, тут не уместен. Но, я думаю, мы еще затронем его.
Вернемся немного к архитектуре MVC. Что же будет делать наш контроллер запросов? В первую очередь, он будет просматривать параметры запроса пользователя, и исходя из их значений, обращаться к соответствующим моделям приложения. Существуют разные способы реализации контроллера запросов, но все они основаны на схеме, в которой взаимодействуют две сущности: обработчик и действие. Обработчик извлекает из URL необходимую информацию, после чего решает, какое действие необходимо инициировать. После выбора конкретного действия он передает ему управление. То есть, в большинстве случаев обработчик является довольно простой программой, функции которой заключаются в выборе нужного действия. Действие представляет собой чистой воды шаблон проектирования, реализуемый в виде сценария транзакции (Transaction Script) или команды (Command).
В чем отличие между сценарием транзакции (Transaction Script) и командой (Command)? Эти два типовых решения практически одинаковы и решают одну и ту же задачу - обеспечить "логическую изоляцию" логики работы приложения по процедурам. Но, в связи с различной структурой реализации, эти типовые решения начинают приобретать различные свойства, обеспечивающие им довольно сильные отличия друг от друга, которые способствуют различным вариантам применения. Фактически, происходит реализация логически завершенных процедур, но конструктивно разными способами. Сценарий транзакций подразумевает расположения каждого логического действия в отдельной процедуре, которые могут существовать как разрознено, так и в пределах одного класса. Это уже решает сам разработчик исходя из своих собственных убеждений. Можно объединить в одном классе сценарии транзакции, реализующие более родственные операции. Но это, еще раз повторюсь, решает конкретный разработчик. В рамках контроллера запросов web-приложения я обычно не решаю такие вопросы, а использую шаблон проектирования команда. В отличие от сценария транзакции каждая логическая процедура реализуется отдельным классом, в котором присутствует полиморфный метод запуска процедуры. Этот метод объявлен в абстрактном классе и реализуется, в наследниках:
Listing №1 (PHP)
/**
* Абстрактное действие
*/
abstract class Action {
/**
* Запускает действие
* с определенными параметрами
* @param ArrayList $Params
*/
abstract function Run(ArrayList $Params);
}/**
* Объявляем класс действия
* наследуемого от Action
*/
class regAction extends Action {
/**
* Реализуем логику модели в методе Run
* @param ArrayList $Params
* @return mixed
*/
public function Run(ArrayList $Params)
{
// Создаем контейнер ошибок
$Errors = new ArrayList();
// Проверяем новые логин и пароль
$Errors->LoginError = User::TestLogin(Request::Post('login'));
if(!count($Errors) && User::Exists('Login', Request::Post('login'))) {
$Errors->LoginError = 'Такой логин уже используется';
}
$Errors->PasswordError = User::TestPswd(Request::Post('password'));
// Если нет ошибок создаём нового пользователя
if(!count($Errors)) {
$User = new User();
$User->Login = Request::Post('login');
$User->Password = Request::Post('password');
// Пытаемся сохранить его
if ($User->Save()) {
// Если все прошло удачно,
// то возвращаем сообщение об
// успешной регистрации
$View = new View();
$View->SetTemplete('message');
$View->Message = $User->Login . ' зарегистрирован';
return $View;
}
else {
// Возвращаем ошибку если
// не можем сохранить пользователя
$Errors->UnknownError = 'Ошибка регистрации';
return $Errors;
}
}
else {
// Возвращаем ошибки входных данных
return $Errors;
}
}
}class Dispatcher {
/**
* Корневой путь
* к модулям
* @static
* @var string
*/
private static $_Path = '';
/**
* Задает путь к модулям приложения
* @static
* @param string $Path
*/
public static function Init($Path)
{
self::$_Path = $Path;
} /**
* Возвращает инстанцированный
* объект команды
* @param string $ActionName
* @param string $ModuleName
* @return Action
*/
private function _CreateAction($ActionName, $ModuleName)
{
//Формируем имя и путь подключаемого файла с классом команды
$FileName = self::$_Path . $ModuleName . DIRECTORY_SEPARATOR .
$ActionName . 'Action' . PHP_EXT;
if(is_file($FileName)) {
require_once $FileName;
$ActionClassName = $ActionName . 'Action';
return new $ActionClassName();
}
else {
//Генерируем исключение если файл не найден
throw new Exception('Action file "' . $FileName . '" does not exists');
}
}
/**
* Запускает команду и
* возвращает результат ее действия
* @param Action $Action
* @param ArrayList $Params
* @return mixed
*/
private function _RunAction(Action $Action, ArrayList $Params)
{
return $Action->Run($Params);
}class Dispatcher {
/**
* Зарегистрированные
* команды
* @var array
*/
private $_Actions;
public function __construct()
{
$this->_Actions = array();
}
/**
* Регистрирует команду модуля
* @param string $ActionName
* @param string $ModuleName
*/
public function RegisterAction($ActionName, $ModuleName)
{
//Приводим переменные к нижнему регистру
$ActionName = Str::Strtolower($ActionName);
$ModuleName = Str::Strtolower($ModuleName);
$this->_Actions[$ModuleName . ' ' . $ActionName] = TRUE;
}
/**
* Возвращает TRUE если команда зарегистрирована
* @param string $ActionName
* @param string $ModuleName
* @param bool $InDefault FALSE
* @return bool
*/
public function IsActionRegistered($ActionName, $ModuleName)
{
//Приводим переменные к нижнему регистру
$ActionName = Str::Strtolower($ActionName);
$ModuleName = Str::Strtolower($ModuleName);
return (isset($this->_Actions[$ModuleName . ' ' . $ActionName]));
}
/**
* Возвращает результат выполнения команды
* или NULL если команда не зарегистрирована
* @param string $ActionName
* @param string $ModuleName
* @param ArrayList|NULL $Params
* @return mixed
*/
public function GetResponse($ActionName, $ModuleName, $Params = NULL)
{
//Создаем пустые параметры команды если они не заданы
if(!$Params) {
$Params = new ArrayList();
}
//Приводим переменные к нижнему регистру
$ActionName = Str::Strtolower($ActionName);
$ModuleName = Str::Strtolower($ModuleName);
if($this->IsActionRegistered($ActionName, $ModuleName)) {
//Создаем неизвестный экземпляр команды
$Action = $this->_CreateAction($ActionName, $ModuleName);
//Запускаем команду и возвращаем результат
return $this->_RunAction($Action, $Params);
}
else {
return NULL;
}
}class Dispatcher {
/**
* Корневой путь
* к модулям
* @static
* @var string
*/
private static $_Path = '';
/**
* Зарегистрированные
* команды
* @var array
*/
private $_Actions;
/**
* Команды по умолчанию
* @var array
*/
private $_Default;
/**
* Модуль по умолчанию
* @var string
*/
private $_DefModule;
/**
* Адрес редиректа
* @var string
*/
private $_RedirectUrl;
/**
* Задает путь к модулям приложения
* @static
* @param string $Path
*/
public static function Init($Path)
{
self::$_Path = $Path;
}
/**
* @param string $DefModule
* @param string $RedirectUrl
*/
public function __construct($DefModule, $RedirectUrl = '/')
{
$this->_Actions = array();
$this->_Default = array();
$this->_RedirectUrl = Str::Strtolower($RedirectUrl);
$this->_DefModule = Str::Strtolower($DefModule);
}
/**
* Регистрирует команду модуля
* @param string $ActionName
* @param string $ModuleName
*/
public function RegisterAction($ActionName, $ModuleName)
{
//Приводим переменные к нижнему регистру
$ActionName = Str::Strtolower($ActionName);
$ModuleName = Str::Strtolower($ModuleName);
$this->_Actions[$ModuleName . ' ' . $ActionName] = TRUE;
}
/**
* Регистрирует команду по умолчанию для модуля
* @param string $ActionName
* @param string $ModuleName
*/
public function RegisterDefault($ActionName, $ModuleName)
{
//Приводим переменные к нижнему регистру
$ActionName = Str::Strtolower($ActionName);
$ModuleName = Str::Strtolower($ModuleName);
$this->_Default[$ModuleName] = $ActionName;
}
/**
* Возвращает TRUE если команда зарегистрирована
* @param string $ActionName
* @param string $ModuleName
* @param bool $InDefault FALSE
* @return bool
*/
public function IsActionRegistered($ActionName, $ModuleName)
{
//Приводим переменные к нижнему регистру
$ActionName = Str::Strtolower($ActionName);
$ModuleName = Str::Strtolower($ModuleName);
return (isset($this->_Actions[$ModuleName . ' ' . $ActionName]));
}
/**
* Возвращает команду по умолчанию для модуля
* или NULL если её нет
* @param string $ModuleName
* @return mixed
*/
public function GetDefaultAction($ModuleName)
{
//Приводим переменную к нижнему регистру
$ModuleName = Str::Strtolower($ModuleName);
return (isset($this->_Default[$ModuleName])) ? $this->_Default[$ModuleName] : NULL;
}
/**
* Возвращает результат выполнения команды
* @param string $ActionName
* @param string $ModuleName
* @param ArrayList|NULL $Params
* @return mixed
*/
public function GetResponse($ActionName, $ModuleName, $Params = NULL)
{
//Создаем пустые параметры команды если они не заданы
if(!$Params) {
$Params = new ArrayList();
}
//Если не указан модуль устанавливаем тот что по умолчанию
if(!$ModuleName) {
$ModuleName = $this->_DefModule;
}
//Ищем команду в зарегистрированных
if($this->IsActionRegistered($ActionName, $ModuleName)) {
//Создаем неизвестный экземпляр команды
$Action = $this->_CreateAction($ActionName, $ModuleName);
//Запускаем команду и возвращаем результат
return $this->_RunAction($Action, $Params);
}
//Ищем команду в зарегистрированных по умолчанию
elseif (NULL !== $ActionName = $this->GetDefaultAction($ModuleName)) {
//Создаем неизвестный экземпляр команды
$Action = $this->_CreateAction($ActionName, $ModuleName);
//Запускаем команду по умолчанию и возвращаем результат
return $this->_RunAction($Action, $Params);
}
else {
//Перенаправляем пользователя
header('Location: ' . $this->_RedirectUrl);
exit(0);
}
}
/**
* Возвращает инстанцированный
* объект команды
* @param string $ActionName
* @param string $ModuleName
* @return Action
*/
private function _CreateAction($ActionName, $ModuleName)
{
//Приводим переменные к нижнему регистру
$ActionName = Str::Strtolower($ActionName);
$ModuleName = Str::Strtolower($ModuleName);
//Формируем имя и путь подключаемого файла с классом команды
$FileName = self::$_Path . $ModuleName . DIRECTORY_SEPARATOR .
$ActionName . 'Action' . PHP_EXT;
if(is_file($FileName)) {
//Подключаем файл класса
require_once $FileName;
//Формируем имя класса команды (действия)
$ActionClassName = $ActionName . 'Action';
//Инстанцируем экземпляр команды и возвращаем его
return new $ActionClassName();
}
else {
//Генерируем исключение если файл не найден
throw new Exception('Action file "' . $FileName . '" does not exists');
}
}
/**
* Запускает команду и
* возвращает результат ее действия
* @param Action $Action
* @param ArrayList $Params
* @return mixed
*/
private function _RunAction(Action $Action, ArrayList $Params)
{
return $Action->Run($Params);
}
}// Константа расширения для файлов команд
// да и для всех интерпретируемых файлов
define('PHP_EXT', '.php');
require_once './framework/Action' . PHP_EXT;
require_once './framework/ArrayList' . PHP_EXT;
require_once './framework/Dispatcher' . PHP_EXT;
require_once './framework/Request' . PHP_EXT;
require_once './framework/Str' . PHP_EXT;
//Инициализируем классы
Str::Init(Str::MODE_MULTIBYTE, 'UTF-8');
Request::Init();
Dispatcher::Init('./actions/');
//Посылаем заголовок
header('Content-type: text/html; charset="' . Str::GetCharset() . '"');
//Создаем диспетчер
$Dispatcher = new Dispatcher('standart');
//Регистрируем команду "login" в модуле "standart"
$Dispatcher->RegisterAction('login', 'standart');
//Регистрируем команду "reg" в модуле "standart"
$Dispatcher->RegisterAction('reg', 'standart');
//Регистрируем команду "user" в модуле "standart"
$Dispatcher->RegisterAction('user', 'standart');
//Также регистрируем ее как команду по умолчанию
$Dispatcher->RegisterDefault('user', 'standart');
//Запускаем диспетчер
$Response = $Dispatcher->GetResponse(Request::Get('cmd'), Request::Get('module'));
//Выводим результат
echo $Response;<?php //авторизация: файл ./actions/standart/loginAction.php
class loginAction extends Action {
public function Run(ArrayList $Params)
{
return 'Запущена команда авторизации';
}
}
?>
<?php //регистрация: файл ./actions/standart/regAction.php
class regAction extends Action {
public function Run(ArrayList $Params)
{
return 'Запущена команда регистрации';
}
}
?>
<?php //пользователь: файл ./actions/standart/userAction.php
class userAction extends Action {
public function Run(ArrayList $Params)
{
return 'Запущена команда пользователя';
}
}
?>class ArrayList {}Продолжаем писать свой PHP framework дальше. Раз уж я решился написать вторую часть, значит, думаю, дело пойдёт. В этой статье, как бы это банально не звучало, мы закрепим обсуждаемый в прошлой статье шаблон проектирования фасад (Facade) и опишем на его основе еще одну нужную в любом движке сущность, называемую объектом запроса.
Как известно, PHP содержит посланные пользователем данные в различных супеглобальных переменных, основными из которых являются $_GET, $_POST и $_COOKIE. На основании этих данных приложение "понимает", что от него хочет пользователь и соответственно пользовательскому запросу выполняет необходимые действия. Что же дают эти суперглобальные массивы нам, как программистам? На основе структуризации этих данным мы может строить логику взаимодействия интерфейса пользователя и непосредственно приложения. Например, мы используем данные из $_GET массива, чтобы определить какую функциональность требует пользователь в данный момент:
Listing №1 (PHP)
if(isset($_GET['mode']) && ($_GET['mode'] === 'reg')) {
// Показываем форму регистрации...
}
else {
// Перенаправляем на index.php
header('Location: /index.php');
}class Request {
/**
* @static
* @param string $VarName
* @param string $Default NULL
* @return mixed
*/
public static function Post($VarName, $Default = NULL)
{
return isset($_POST[$VarName]) ? $_POST[$VarName] : $Default;
}
/**
* @static
* @param string $VarName
* @param string $Default NULL
* @return mixed
*/
public static function Get($VarName, $Default = NULL)
{
return isset($_GET[$VarName]) ? $_GET[$VarName] : $Default;
}
/**
* @static
* @param string $VarName
* @param string $Default NULL
* @return mixed
*/
public static function Cookie($VarName, $Default = NULL)
{
return isset($_COOKIE[$VarName]) ? $_COOKIE[$VarName] : $Default;
}class Request {
/**
* @static
* @param string $VarName
* @param string $Value
*/
public static function SetPost($VarName, $Value)
{
$_POST[$VarName] = $Value;
}
/**
* @static
* @param string $VarName
* @param string $Value
*/
public static function SetGet($VarName, $Value)
{
$_GET[$VarName] = $Value;
}
/**
* @static
* @param string $VarName
* @param string $Value
*/
public static function SetCookie($VarName, $Value)
{
$_COOKIE[$VarName] = $Value;
}class Request {
/**
* @static
* @param mixed &$Param
* @return mixed
*/
private static function _StripSlash(&$Param)
{
$Param = (is_array($Param)) ? array_map(array('self', '_StripSlash'), $Param) : trim(stripslashes($Param), " \x0B\0");
return $Param;
}
/**
* @static
*/
private static function _MagicFilter()
{
if (((function_exists('get_magic_quotes_gpc')) && get_magic_quotes_gpc()) ||
(ini_get('magic_quotes_sybase'))) {
self::_StripSlash($_GET);
self::_StripSlash($_POST);
self::_StripSlash($_COOKIE);
}
if (function_exists('get_magic_quotes_runtime') && get_magic_quotes_runtime()) {
set_magic_quotes_runtime(FALSE);
}
}
/**
* @static
*/
public static function Init()
{
self::_MagicFilter();
}Str::Init(Str::MODE_MULTIBYTE, 'UTF-8'); Request::Init();
Каждый из Web-программистов может прийти к такому моменту своей жизни, когда захочет сделать свой собственный движок, цивилизованными словами - framework, для каких-то своих разработок. Я хочу помочь вам в этом и отдать свой опыт разработки в ваше распоряжение. Возможно, вы почерпнёте из этого материала хороший кусок знаний, так как сама разработка будет основана на применении различных шаблонов проектирования. В этой статье я расскажу о применении шаблона проектирования фасад (Facade) в контексте класса обработки срок. Что же это за класс и зачем он нужен?
Допустим, вы пишете русскоязычный сайт. Естественно при этом вы пользуетесь различными строковыми функция, такими как substr, strlen и т.д., которые работают с однобайтовой кодировкой, например Windows-1251. Вот вы написали сайт, запустили его, он работает и радует ваш глаз. Но вдруг вам понадобилось расширить локализацию сайта языком, алфавит которого не может описаться одним байтом. Тут, конечно же, вам на помощь придёт кодировка UTF-8. Как известно PHP нормально работает с многобайтовой кодировкой посредством расширения mbstring. И вы задаёте себе вопрос: заменять все строковые функции в проекте на mb_*? Конечно же нет. Для таких случаев уже придумали фичу, которая заключается в регулировании параметра php.ini mbstring.func_overload. По умолчанию значение этого параметра равно нулю, но если вы установите его в двойку, то строковые функции, работающие с однобайтовыми символами, перегрузятся функциями из расширения mbstring. То есть вместо substr будет вызвана mb_substr, хотя написана будет именно первая. Этот очень хороший механизм пригодится, когда вам необходимо внедрить код, написанный с помощью стандартных строковых функций в приложение, работающее в многобайтном режиме. Класс, который я собираюсь вам показать, попросту абстрагирует приложение от параметра mbstring.func_overload. Конечно же, многим этот подход может не понравиться, вы даже можете сказать, что это вообще не нужно делать. Но я люблю быть независимым от подобных настроек, поэтому и вам рекомендую пользоваться таким подходом, раз уж разработчики PHP не были в состоянии сделать сразу всё как полагается.
Итак, какие задачи будет решать этот класс? Первое, что он должен сделать, это инкапсулировать в себе строковый функционал, запретив доступ программиста к стандартным строковым функциям, будь то substr или mb_substr. В это же время он предоставит один интерфейс, то есть одни методы доступа, для вызова различных функций. Как я уже сказал выше, это будет явный представитель типового решения фасад (Facade), так как для доступа к двум интерфейсам функций мы предоставим доступ через один унифицированный. Для удобства он преобразует свой интерфейс в интерфейсы вызываемых функций. Перечислим обязанности нашего "строкового фасада":
1. Выбор режимов функционирования: однобайтовый / многобайтовый.
2. Детектирование настроек окружения и выбор соответствующих вызовов.
3. Статический интерфейс, совместимый с интерфейсом функций.
Давайте определим некоторые свойства класса и напишем метод, который будет определять, какого типа функцию нужно вызвать фасаду.
Listing №1 (PHP)
class Str {
const MODE_ANSII = 0;
const MODE_MULTIBYTE = 1;
/**
* Текущий режим работы
* Принимает значения
* MODE_MULTIBYTE для работы в многобайтовом режиме
* или MODE_ANSII в однобайтовом
* По умолчанию в многобайтовом
* @static int
*/
private static $_Mode = 1;
/**
* Текущая кодировка
* По умолчанию UTF-8
* @static stirng
*/
private static $_Encoding = 'UTF-8';
/**
* Значение параметра mbstring.func_overload
* @static array
*/
private static $_OverloadBitmask = 0;
/**
* Возвращает TRUE если нужно вызвать mb_* функцию
* @param int $OvelroadBitmask - флаг выбора возможных перезагруженных функций
* @return bool
*/
private static function _NeedUseMBFuncs($OvelroadBitmask)
{
return (self::$_Mode && self::$_OverloadBitmask) ? (!($OvelroadBitmask & self::$_OverloadBitmask)) : self::$_Mode;
}/**
* @static
* @param int $Mode self::MODE_MULTIBYTE
* @param string $Encoding 'UTF-8'
*/
public static function Init($Mode = self::MODE_MULTIBYTE, $Encoding = 'UTF-8')
{
self::$_Mode = $Mode;
if (!function_exists('mb_substr')) {
self::$_Mode = self::MODE_ANSII;
}
else {
mb_internal_encoding($Encoding);
}
self::$_Encoding = $Encoding;
self::$_OverloadBitmask = ini_get('mbstring.func_overload');
}Str::Init(Str::MODE_MULTIBYTE, 'UTF-8');
class Str {
const MODE_ANSII = 0;
const MODE_MULTIBYTE = 1;
/**
* Текущий режим работы
* Принимает значения
* MODE_MULTIBYTE для работы в многобайтовом режиме
* или MODE_ANSII в однобайтовом
* По умолчанию в многобайтовом
* @static int
*/
private static $_Mode = 1;
/**
* Текущая кодировка
* По умолчанию UTF-8
* @static stirng
*/
private static $_Encoding = 'UTF-8';
/**
* Значение параметра mbstring.func_overload
* @static array
*/
private static $_OverloadBitmask = 0;
/**
* @static
* @param int $Mode self::MODE_MULTIBYTE
* @param string $Encoding 'UTF-8'
*/
public static function Init($Mode = self::MODE_MULTIBYTE, $Encoding = 'UTF-8')
{
self::$_Mode = $Mode;
if (!function_exists('mb_substr')) {
self::$_Mode = self::MODE_ANSII;
}
else {
mb_internal_encoding($Encoding);
}
self::$_Encoding = $Encoding;
self::$_OverloadBitmask = ini_get('mbstring.func_overload');
}
/**
* @static
* @return string
*/
public static function GetCharset()
{
return self::$_Encoding;
}
/**
* @static
* @param string $String
* @param int $Start
* @param int $Length
* @return string
*/
public static function Substr($String, $Start, $Length = NULL)
{
if (self::_NeedUseMBFuncs(2)) {
if (NULL === $Length) {
return mb_substr($String, $Start);
}
else {
return mb_substr($String, $Start, (int)$Length, self::$_Encoding);
}
}
else {
if (NULL === $Length) {
return substr($String, $String, $Length);
}
else {
return substr($String, $String);
}
}
}
/**
* @static
* @param string $String
* @return int
*/
public static function Strlen($String)
{
if (self::_NeedUseMBFuncs(2)) {
return mb_strlen($String, self::$_Encoding);
}
else {
return strlen($String);
}
}
/**
* @static
* @param string $String
* @return string
*/
public static function Strtolower($String)
{
return (self::_NeedUseMBFuncs(2)) ? mb_strtolower($String, self::$_Encoding) : strtolower($String);
}
/**
* @static
* @param string $String
* @return string
*/
public static function Strtoupper($String)
{
return (self::_NeedUseMBFuncs(2)) ? mb_strtoupper($String, self::$_Encoding) : strtoupper($String);
}
/**
* @static
* @param string $HayStack
* @param string $Needle
* @param int $Offset
* @return int
*/
public static function Strpos($Haystack, $Needle, $Offset = NULL)
{
if (self::_NeedUseMBFuncs(2)) {
return mb_strpos($Haystack, $Needle, $Offset, self::$_Encoding);
}
else {
return strpos($Haystack, $Needle, $Offset);
}
}
/**
* @static
* @param string $HayStack
* @param string $Needle
* @param int $Offset
* @return int
*/
public static function Strrpos($Haystack, $Needle, $Offset = NULL)
{
if (self::_NeedUseMBFuncs(2)) {
return mb_strrpos($Haystack, $Needle, $Offset, self::$_Encoding);
}
else {
return strrpos($Haystack, $Needle, $Offset);
}
}
/**
* @static
* @param string $Pattern
* @param string $String
* @param int $Limit = NULL
*/
public static function Split($Pattern, $String, $Limit = NULL)
{
if (self::_NeedUseMBFuncs(2)) {
if (NULL === $Limit) {
return mb_split($Pattern, $String);
}
else {
return mb_split($Pattern, $String, $Limit);
}
}
else {
if (NULL === $Limit) {
return split($Pattern, $String);
}
else {
return split($Pattern, $String, $Limit);
}
}
}
/**
* @static
* @param string $To
* @param string $Subject
* @param string $Message
* @param string $AdditionalHeaders = NULL
* @param string $AdditionalParameters
* @return bool
*/
public static function Mail($To, $Subject, $Message, $AdditionalHeaders = NULL, $AdditionalParameters = NULL)
{
if (self::_NeedUseMBFuncs(1)) {
if (NULL === $AdditionalHeaders) {
return mb_send_mail($To, $Subject, $Message);
}
else {
if (NULL === $AdditionalParameters) {
return mb_send_mail($To, $Subject, $Message, $AdditionalHeaders);
}
else {
return mb_send_mail($To, $Subject, $Message, $AdditionalHeaders, $AdditionalParameters);
}
}
}
else
{
if (NULL === $AdditionalHeaders) {
return mail($To, $Subject, $Message);
}
else {
if (NULL === $AdditionalParameters) {
return mail($To, $Subject, $Message, $AdditionalHeaders);
}
else {
return mail($To, $Subject, $Message, $AdditionalHeaders, $AdditionalParameters);
}
}
}
}
/**
* Возвращает TRUE если нужно вызвать mb_* функцию
* @param int $OvelroadBitmask - флаг выбора возможных перезагруженных функций
* @return bool
*/
private static function _NeedUseMBFuncs($OvelroadBitmask)
{
return (self::$_Mode && self::$_OverloadBitmask) ? (!($OvelroadBitmask & self::$_OverloadBitmask)) : self::$_Mode;
}
}$RequestString = Str::Strtolower($_SERVER['REQUEST_URI']);
Целью этой статьи является разработка простого шаблонизатора, который смог бы удовлетворить основные потребности разработчика в моём лице, да и не только в моём. Принцип работы его будет основан на native методе, который вместе с другим детальнее описан тут для тех, кто еще не в курсе.
Итак, какой функционал я хочу? Для начала, это должно быть довольно простое решение без всяких рюшиков типа "куча хелперов". Конечно, кому-то это нужно, но у меня обычно всё проще, поэтому обойдёмся. То есть, должно быть независимое решение в виде одного класса. Сразу скажу, что кеширование результатов тоже упустим, дабы не отвлекаться от сути, да и задача эта в большинстве случаев принадлежит другим. Кому надо, легко может его прикрутить как внутри так и снаружи, используя этот материал. Далее я также хочу спокойно выполнять такие вещи как:
Удобное извлечение переменных в шаблоне
Вставка шаблона в шаблон
Корректная работа с HTML кодом
Режим отладки
Этим списком, думаю, я буду полностью удовлетворен.
Начнем с переменных. Тут, как и везде далее все просто - магический метод __get нам поможет обращаться к переменной по ее имени. Всё начинает выглядеть примерно так:
Listing №1 (PHP)
class Template {
/**
* @var array
*/
private $_Vars;
/**
* @var string
*/
private $_Name;
/**
* @var string
*/
private $_Path;
public function __construct()
{
$this->_Name = '';
$this->_Vars = array();
$this->_Path = '';
}
/**
* @param string $VarName имя переменной
* @param string $VarValue значение переменной
* @return Template
*/
public function AddVar($VarName, $VarValue)
{
if ($VarName !== '') {
$this->_Vars[$VarName] = $VarValue;
}
return $this;
}
/**
* @param string $Varname имя переменной
* @return mixed
*/
public function __get($VarName)
{
if (array_key_exists($VarName, $this->_Vars)) {
if ($this->_Vars[$VarName] instanceof Template) {
return $this->_Vars[$VarName]->Prepare();
}
else{
return $this->_Vars[$VarName];
}
}
return NULL;
}
/**
* @var string $Name имя шаблона
*/
public function SetName($Name)
{
$this->_Name = $Name;
}
/**
* @var string $Path путь к шаблону(ам)
*/
public function SetPath($Path)
{
$this->_Path = $Path;
}
} /**
* @param mixed $Var
* @return mixed
*/
protected function EscapeHtml($Var)
{
if(is_array($Var)) {
foreach($Var as &$VarItem) {
$VarItem = $this->EscapeHtml($VarItem);
}
}
else {
$Var = htmlspecialchars($Var, ENT_QUOTES);
}
return $Var;
}
/**
* @param mixed $Var
* @return mixed
*/
protected function EscapeUrl($Var)
{
if(is_array($Var)) {
foreach($Var as &$VarItem) {
$VarItem = $this->EscapeUrl($Var);
}
}
else {
$Var = htmlentities($Var, ENT_QUOTES);
}
return $Var;
} /**
* Свойство хранящее текущий режим
* @static
* @var bool
*/
private static $_Debug = FALSE;
/**
* Вкл/выкл дебаг режим
* @static
* @param bool $TrueOrFalse TRUE
*/
public static function SetDebug($TrueOrFalse = TRUE)
{
self::$_Debug = $TrueOrFalse;
}
/**
* Возвращает $Text с удаленными "\t", "\n" и "\r"
* @param string $Text
* @return string
*/
private function _Zip($Text)
{
return (empty($Text)) ? $Text : str_replace(array("\t", "\n", "\r"), '', $Text);
} /**
* @return string
*/
public function Prepare()
{
if (file_exists($this->_Path . $this->_Name)) {
ob_start();
${__CLASS__} = $this;
include $this->_Path . $this->_Name;
unset(${__CLASS__});
return (self::$_Debug) ? ob_get_clean() : $this->_Zip(ob_get_clean());
}
else {
throw new Exception('Template file "' . $this->_Path . $this->_Name . '" does not exists');
}
}class Template {
/**
* @var string
*/
private $_Name;
/**
* @var array
*/
private $_Vars;
/**
* @var string
*/
private $_Path;
/**
* @static
* @var bool
*/
private static $_Debug = FALSE;
/**
* @static
* @param bool $TrueOrFalse TRUE
*/
public static function SetDebug($TrueOrFalse = TRUE)
{
self::$_Debug = $TrueOrFalse;
}
public function __construct()
{
$this->_Name = '';
$this->_Vars = array();
$this->_Path = '';
}
/**
* @param string $VarName
* @param string $VarValue
* @return Template
*/
public function AddVar($VarName, $VarValue)
{
if ($VarName !== '') {
$this->_Vars[$VarName] = $VarValue;
}
return $this;
}
/**
* @param string $Varname
* @return mixed
*/
public function __get($VarName)
{
if (array_key_exists($VarName, $this->_Vars)) {
if ($this->_Vars[$VarName] instanceof Template) {
return $this->_Vars[$VarName]->Prepare();
}
else{
return $this->_Vars[$VarName];
}
}
return NULL;
}
/**
* @var string $Name
*/
public function SetName($Name)
{
$this->_Name = $Name;
}
/**
* @var string $Path
*/
public function SetPath($Path)
{
$this->_Path = $Path;
}
/**
* @return string
*/
public function Prepare()
{
if (file_exists($this->_Path . $this->_Name)) {
ob_start();
${__CLASS__} = $this;
include $this->_Path . $this->_Name;
unset(${__CLASS__});
return (self::$_Debug) ? ob_get_clean() : $this->_Zip(ob_get_clean());
}
else {
throw new Exception('Template file "' . $this->_Path . $this->_Name . '" does not exists');
}
}
public function Display()
{
print ($this->Prepare());
}
/**
* @param mixed $Var
* @return mixed
*/
protected function EscapeHtml($Var)
{
if(is_array($Var)) {
foreach($Var as &$VarItem) {
$VarItem = $this->EscapeHtml($VarItem);
}
}
else {
$Var = htmlspecialchars($Var, ENT_QUOTES);
}
return $Var;
}
/**
* @param mixed $Var
* @return mixed
*/
protected function EscapeUrl($Var)
{
if(is_array($Var)) {
foreach($Var as &$VarItem) {
$VarItem = $this->EscapeUrl($Var);
}
}
else {
$Var = htmlentities($Var, ENT_QUOTES);
}
return $Var;
}
/**
* @param string $Text
* @return string
*/
private function _Zip($Text)
{
return (empty($Text)) ? $Text : str_replace(array("\t", "\n", "\r"), '', $Text);
}
}<div style="border: 1px solid #E0E3F3;"> <?php echo $Template->UserList;?> </div>
<div>
<?php foreach($Template->EscapeHtml($Template->UserNames) as $UserName) { ?>
<span style="color: #FF0000;"><?php echo $UserName;?></span><br>
<?php } ?>
</div>//Подключаем шаблонизатор
require 'Template.php';
//Устанавливаем режим отладки включенным для всех шаблонов
Template::SetDebug(TRUE);
//Инициализируем подшаблон списка пользователей
$UsersListView = new Template();
$UsersListView->SetName('userslist.html');
//Список пользователей
$UserNames = array('Den Rights©', 'Dasha Nasha', 'Samuil Kakojto');
$UsersListView->AddVar('UserNames', $UserNames);
//Инициализируем главный шаблон страницы
$PageView = new Template();
$PageView->SetName('page.html');
$PageView->AddVar('UserList', $UsersListView);
$PageView->AddVar('Link', 'http://www.itdumka.com.ua/index.php?cmd=shownode&node=1');
$PageView->Display();<div style="border: 1px solid #E0E3F3;">
<div>
<span style="color: #FF0000;">Den Rights©</span><br>
<span style="color: #FF0000;">Dasha Nasha</span><br>
<span style="color: #FF0000;">Samuil Kakojto</span><br>
</div>
</div>
<a href="http://www.itdumka.com.ua/index.php?cmd=shownode&node=1">About</a>В этой короткой статье я расскажу, как я убирал тривиальность из политики кеширования на файлах в PHP сценариях. В принципе это всё можно применить и не к "файловому" кешированию. Надеюсь, многим эта статейка принесет пользу.
Итак, что же мне не нравилось в моей тривиальности, и что я хотел бы улучшить?
Во-первых, меня очень напрягала обстановка, когда несколько килобайт кеша вдруг одновременно устаревало и могло довольно прилично нагрузить сервер. А во-вторых, мой мозг до некоторого момента все время мучила мысль о непосредственной работе с файлами, а точнее - создание и удаление.
Все эти беды происходили от выбранного мною тривиального подхода к управлению кешированием. Суть его заключалась в следующем.
Сначала я проверял актуальность кешированных данных и если они таковыми не являлись, то проводил операцию получения данных и записывал "свежий" кеш на диск. Иначе - если кеш был актуален - просто брал данные от туда. Вот пример из моего старенького шаблонизатора:
Listing №1 (PHP)
<?php
if ($this->_Cache->IsActual()) {
return $this->_Cache->Read();
}
else {
$Content = $this->Parse($File);
$this->_Cache->Write($Content);
return $Content;
}
?><?php
public function IsActual()
{
clearstatcache();
if (!file_exists($this->_Id)) {
return FALSE;
}
if (!($CreateTime = filemtime($this->_Id))) {
return FALSE;
}
if (($CreateTime + $this->_Expired) < time()) {
@unlink($this->_Id);
return FALSE;
}
return TRUE;
}
?><?php
interface Cachebackend {
/**
* @param string $Key
*/
public function GetCache($Key);
/**
* @param string $Key
* @param int $ExpiredPeriod
*/
public function IsActual($Key, $ExpiredPeriod);
/**
* @param string $Key
* @param int $ExpiredPeriod
* @param int $SoonPeriod
* @param string $SoonFuseKeyPostfix
*/
public function IsNeedCreateNewCache($Key, $ExpiredPeriod, $SoonPeriod, $SoonFuseKeyPostfix);
/**
* @param string $Key
* @param string $Data
*/
public function PutCache($Key, $Data);
/**
* @param stirng $PreparedKey
* @param string $RenameKey
* @return bool
*/
public function Rename($PreparedKey, $RenameKey);
}
?><?php
class Cacher {
/**
* @var Cachebackend
*/
private static $_Backend = NULL;
/**
* @var mixed
*/
private static $_CallbackSignature = array();
/**
* @var array
*/
private static $_CallbackArguments = array();
/**
* @static
* @param string $Tag
* @param string $Key
* @param int $ExpiredPeriod
* @param int $SoonPeriod
* @param sting $SoonFuseKeyPostfix '.next'
* @return mixed
*/
public static function GetData($Tag, $Key, $ExpiredPeriod, $SoonPeriod, $SoonFuseKeyPostfix = '.next')
{
$Key = CACHE_PATH . strtolower($Tag) . '_' . strtolower($Key) . CACHE_EXT;
if (NULL === self::$_Backend) {
self::$_Backend = new Cachefilebackend;
}
if (self::$_Backend->IsActual($Key, $ExpiredPeriod)) {
$Data = self::$_Backend->GetCache($Key);
if(self::$_Backend->IsNeedCreateNewCache($Key, $ExpiredPeriod, $SoonPeriod, $SoonFuseKeyPostfix)) {
$CallbackData = self::_GetCallbackResult();
self::$_Backend->PutCache($Key . $SoonFuseKeyPostfix, $CallbackData);
}
return $Data;
}
else {
if(self::$_Backend->IsActual($Key . $SoonFuseKeyPostfix, $ExpiredPeriod)) {
$Data = self::$_Backend->GetCache($Key . $SoonFuseKeyPostfix);
self::$_Backend->Rename($Key . $SoonFuseKeyPostfix, $Key);
return $Data;
}
else {
$CallbackData = self::_GetCallbackResult();
self::$_Backend->PutCache($Key, $CallbackData);
return $CallbackData;
}
}
}
/**
* @static
* @param string $CallbackFunction
* @param array $CallbackArguments
* @param mixed $CallbackObject NULL
*/
public static function SetCallback($CallbackFunction, $CallbackArguments, $CallbackObject = NULL)
{
if ($CallbackObject) {
self::$_CallbackSignature = array($CallbackObject, $CallbackFunction);
}
else {
self::$_CallbackSignature = $CallbackFunction;
}
self::$_CallbackArguments = $CallbackArguments;
}
/**
* @static
* @return mixed
*/
private static function _GetCallbackResult()
{
self::_CheckCallback();
return call_user_func_array(self::$_CallbackSignature, self::$_CallbackArguments);
}
/**
* @static
*/
private static function _CheckCallback()
{
if(!is_callable(self::$_CallbackSignature, FALSE, $CallableName)) {
throw new Exception($CallableName . ' is not correct callback');
}
if(!is_array(self::$_CallbackArguments)) {
throw new Exception('Callback arguments must be an array');
}
}
}
?><?php
class Cachefilebackend implements Cachebackend {
/**
* @see Cachebackend
*/
public function GetCache($Key)
{
clearstatcache();
if (file_exists($Key)) {
$Content = '';
$Content = @file_get_contents($Key);
return unserialize((string)$Content);
}
else {
throw new Exception('Cached keyfile "' . $Key. '" does not exists');
}
}
/**
* @see Cachebackend
*/
public function IsActual($Key, $ExpiredPeriod)
{
clearstatcache();
if (file_exists($Key)) {
if (time() - filemtime($Key) > $ExpiredPeriod) {
@unlink($Key);
return FALSE;
}
else {
return TRUE;
}
}
return FALSE;
}
/**
* @see Cachebackend
*/
public function IsNeedCreateNewCache($Key, $ExpiredPeriod, $SoonPeriod, $SoonFuseKeyPostfix)
{
clearstatcache();
if (file_exists($Key)) {
if ((time() - filemtime($Key) > $ExpiredPeriod - $SoonPeriod) && (
(!file_exists($Key . $SoonFuseKeyPostfix)))) {
return TRUE;
}
else {
return FALSE;
}
}
else {
return FALSE;
}
}
/**
* @see Cachebackend
*/
public function PutCache($Key, $Data)
{
if($Key) {
@file_put_contents($Key, serialize($Data));
}
else {
throw new Exception('Cache key is empty');
}
}
/**
* @see Cachebackend
*/
public function Rename($PreparedKey, $RenameKey)
{
return @rename($PreparedKey, $RenameKey);
}
}
?><?php
//Инициализируем пользователя
$User = new User();
//Инициализируем данные пользователя
$UserData= new UserData($User->Id);
//Настраиваем кеширование данных пользователя
Cacher::SetCallback('GetData', array($User->Id), $UserData);
//Получаем данные пользователя
$Response = Cacher::GetData('userdata', md5($User->Id), 24 * 60 * 60, 60 * 60);
//Настраиваем кеширование текста пользователя
Cacher::SetCallback('GetUserText', array($User->Id));
//Получаем текст пользователя
$Response .= Cacher::GetData('usertext', md5($User->Id), 24 * 60 * 60, 120 * 60);
//Отдаем результат
echo $Response;
?>Так получилось, что я задал себе этот вопрос и решил немножко покопаться в нём. Результаты не заставили себя долго ждать и привели к некоторым вполне обычным необычным выводам. Этот текст не является точкой отправки, он всего лишь ставит вопрос и оглашает некоторые фаты, относительно него. Поэтому комментарии были бы как никогда кстати. Ну что, суть вся в следующем.
Однажды в PHP появилось расширение под названием mysqli, которое пришло на замену привычному mysql и победило его словом Improved (улучшенный), расставив некоторые точки над i. С тех пор, если вы используете MySQL-сервер версии 4.1.3 и выше, разработчики PHP строго рекомендуют использовать mysqli-расширение. Основными аргументами в пользу последнего были улучшенный протокол обмена между клиентов и сервером, а так же поддержка следующих и не только фич:
Самое, что было крутое так это поддержка объектно-ориентированного интерфейса. Коннект вешался на объект - и все становились счастливыми. Мы можем наследовать от класса mysqli, перезагружать методы и т. д. Стало намного удобнее, не надо тягать за собой ресурс и всё такое.
Поддержка Multiple Statements - фактически можно выполнить несколько запросов за одно обращение. Да, это хорошо - уменьшение обращений к серверу открывает новые возможности обращения )). Это позволило открыть некоторым небольшую дыру, но у кого руки не крюки - у тех всё сработало.
Поддержка Prepared Statements - третья основная фича в mysqli. Надеюсь, вы знаете, что это за механизм prepared statements, а если не знаете, то, может быть, узнаете.
Конечно, было еще несколько нововведений, но я их упущу чтобы конкретнее остановиться на последнем пункте. Итак, сторона разработчиков оперирует следующими фактами об использовании механизма prepared statement. Следующие три абзаца - это почти прямое цитирование Харисона Фиска (ссылка на оригинал) .
Использование prepared statements дает множество преимуществ как для безопасности так и для производительности. Prepared statements могут способствовать повышению безопасности путём отделения логики SQL запроса, от данных, подставляемых в него. Такое разделение логики и данных может помочь предотвратить внедрение SQL-инъекций. Обычно, когда вы используете запросы, в которых используются данные пришедшие от пользователей, вы должны быть очень осторожными. Для этого вы используете функции, которые экранируют проблемные символы, такие как одинарные и двойные кавычки, обратный слешь. Эти операции не обязательно необходимо выполнять, когда вы будете использовать prepared statements. Отделение данных от логики запроса SQL позволяет MySQL автоматически обработать эти символы и не прибегать к помощи специальных функций.
Увеличение производительности при использовании prepared statements может быть связано со следующими некоторыми факторами. Во-первых разбор строки SQL запроса происходит лишь единожды. На начальном этапе prepared statements (этап подготовки параметров) MySQL обработает каждый параметр, для проверки синтаксиса, а затем создаст запрос для непосредственного запуска. Затем, если мы будет выполнять этот запрос много раз, это уже не будет требовать расходов на новый синтаксический анализ его логики. Такой предварительный анализ, приводит к увеличению скорости, если нужно выполнить один и тот же запрос много раз, например, когда производиться вставка нескольких строк с помощью INSERT. Во-вторых, при использовании механизма prepared statements применяется совсем другой, так называемый, двоичный протокол обмена. Стандартный протокол MySQL перед отправкой данных по сети всегда преобразует их в строки. Это означает, что клиент преобразует данные в строки, которые зачастую становятся больше чем исходные данные, отправляет их серверу, который, в свою очередь, декодирует пришедшие строки обратно в правильный тип данных. Двоичный протокол отправляет все данный в родной бинарной форме, тем самым уменьшая использование ресурса процессора на конвертации данных и, естественно, сокращает затраты на передачу по сети.
Использование prepared statements не носит общий характер, так как может не поддерживать как некоторые типы SQL, так и положение в логике SQL строки. По словам разработчиков, эти недочеты будут дорабатываться, чтобы prepared statements API носило более общий характер. Также использование prepared statements может быть медленнее, чем обычный запрос без prepared statements, при котором фактически происходит лишь одно обращение к серверу. При использовании prepared statements, клиент, упрощенно говоря, сперва готовить запрос, а потом запускает его с определенными параметрами. Поэтому, если запрос используется всего один раз, то тут необходимо решать спорный вопрос, что предпочтительнее быстродействие либо возможность использования prepared statements.
Я решил немного измерить процесс работы. Для теста я написал две функции - одна работает чисто без prepared statements, а другая только с prepared statements. Так же я взял "небольшие" несколько запросов и комбинировал их в разных вариациях друг с другом. Вот один из вариантов тестирования:
Listing №1 (PHP)
error_reporting(E_ALL);
function workWithoutPS(mysqli $db)
{
$startTime = microtime(TRUE);
/**
* Select 1
*/
$login = "mylogin'mm";
$email = "myemail%@em.com";
$login = "'" . $db->real_escape_string($login) . "'";
$email = "'" . $db->real_escape_string($email) . "'";
$result = $db->query('Select ID from USERS where LOGIN = ' . $login . ' and EMAIL = ' . $email);
while($row = $result->fetch_assoc()) {
$a = $row;
}
$result->close();
/**
* Select 2
*
$login = "mylogin'mm";
$email = "myemail%@em.com";
$login = "'" . $db->real_escape_string($login) . "'";
$email = "'" . $db->real_escape_string($email) . "'";
$result = $db->query('Select ID from USERS where LOGIN = '. $login . ' and EMAIL = ' . $email . ' LIMIT 10 OFFSET 100' );
while($row = $result->fetch_assoc()) {
$a = $row;
}
$result->close();
/**
* Select 3
*
$login = "mylogin'mm";
$login = "'" . $db->real_escape_string($login) . "'";
$result = $db->query('Select ID from USERS where LOGIN = '. $login . ' LIMIT 30 OFFSET 100');
while($row = $result->fetch_assoc()) {
$a = $row;
}
$result->close();*/
/**
* Insert 1
*/
$login = "mylogin'mm";
$email = "myemail%@em.com";
$login = "'" . $db->real_escape_string($login) . "'";
$email = "'" . $db->real_escape_string($email) . "'";
$db->query('Insert into USERS (LOGIN, EMAIL) values ('. $login . ',' . $email . ')');
/**
* Insert 2
*
$login = "mylogin'mm";
$email = "myemail%@em.com";
$login = "'" . $db->real_escape_string($login) . "'";
$email = "'" . $db->real_escape_string($email) . "'";
$db->query('Insert into USERS (LOGIN, EMAIL) values ('. $login . ',' . $email . ')');*/
return microtime(TRUE) - $startTime;
}
function workWithPS(mysqli $db)
{
$startTime = microtime(TRUE);
/**
* Select 1
*/
$login = "mylogin'mm";
$email = "myemail%@em.com";
$stmt = $db->prepare('Select ID from USERS where LOGIN = ? and EMAIL = ?');
$stmt->bind_param('ss', $login, $email);
$result = $stmt->execute();
$stmt->bind_result($id);
while($stmt->fetch()) {
$a = $id;
}
$stmt->close();
/**
* Select 2
*
$login = "mylogin'mm";
$email = "myemail%@em.com";
$stmt = $db->prepare('Select ID from USERS where LOGIN = ? and EMAIL = ? LIMIT 10 OFFSET 100');
$stmt->bind_param('ss', $login, $email);
$result = $stmt->execute();
$stmt->bind_result($id);
while($stmt->fetch()) {
$a = $id;
}
$stmt->close();
/**
* Select 3
*
$login = "mylogin'mm";
$stmt = $db->prepare('Select ID from USERS where LOGIN = ? LIMIT 30 OFFSET 100');
$stmt->bind_param('s', $login);
$result = $stmt->execute();
$stmt->bind_result($id);
while($stmt->fetch()) {
$a = $id;
}
$stmt->close();*/
/**
* Insert 1
*/
$login = "mylogin'mm";
$email = "myemail%@em.com";
$stmt = $db->prepare('Insert into USERS (LOGIN, EMAIL) values (?, ?)');
$stmt->bind_param('ss', $login, $email);
$result = $stmt->execute();
/**
* Insert 2
*
$login = "mylogin'mm";
$email = "myemail%@em.com";
$stmt->bind_param('ss', $login, $email);
$result = $stmt->execute();*/
return microtime(TRUE) - $startTime;
}
$db = new mysqli('localhost', 'test', 'test', 'test');
$count = 1000;
$time = 0;
for ($i = 0; $i < $count; $i++) {
$time += workWithoutPS($db); //workWithPS($db)
}
$time /= $count;
echo $time . '</br>';
Зачем все это? - возмущенно спросил Артём, широко раскрыв глаза. У нас и так все прекрасно работает! Я знаю, где мне надо поменять код, чтобы новости отображались в три колонки, а не в две. А Тане я потом покажу, где поменять теги...
Если вы понимаете, какие проблемы у Тани с Артёмом, то это уже хорошо. Как некоторые догадываются Артём - PHP программист, Таня - дизайнер-верстальщик. У них есть общая проблема - файл, который формирует ленту новостей. Редактируют они его по очереди и сам черт ногу сломит в нём. А все потому, что PHP код Артёма уже давно зависит от HTML кода Татьяны и наоборот. И нет им покоя длительное время, если что-то надо поменять в этом файле. Если бы Артём с Татьяной знали о представлении по шаблону, то они бы не тратили кучу нервов и времени на столь простую функцию.
Итак, что же такое шаблонизация, шаблонизаторы, наконец, Template View, зачем они нужны, как их делать и как ими пользоваться? Все очень просто...
Представление по шаблону (Template View) или шаблонизация - это механизм, позволяющий заменять в статической HTML странице ее динамические части, то есть, простым языком говоря, в Таниной HTML страничке заменять данные, генерируемые PHP кодом Артёма. При этом Артём работает со своим файлом, в котором не видит ни одного Таниного тега, а Таня со своим. С этим механизмом Артёму и Тане очень удобно. Им больше не режет глаза чужой листинг, и их идеи в построении кода не зависят друг от друга. Таким образом, выделим два основных, почти смежных, достоинства шаблонизации:
Разделение и удобство работы дизайнера и кодера над одним приложением.
Отделение представления интерфейса пользователя от логики работы программы.
Даже если вы сами работаете над приложением, вам, может быть, намного удобнее будет разделять работу над видом вашего сайта и его логикой работы.
Для начала маленький пример:
Listing №1 (PHP)
<?php
date_default_timezone_set('Europe/Kiev');
$UserMessage = 'Артём';
if (date('md') === '0509') {
$UserMessage .= ' С Днём Победы!';
}
?>
<html>
<head>
<title>Пример1</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
Привет, <?php echo $UserMessage; ?>
</body>
</html><?php
date_default_timezone_set('Europe/Kiev');
$UserMessage = 'Артём';
if (date('md') === '0509') {
$UserMessage .= ' С Днём Победы!';
}
include 'template.php';
?><html> <head> <title>Пример2</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> </head> <body> Привет, <?php echo $UserMessage; ?> </body> </html>
<?php
/**
* Класс шаблонизатор
*/
class View {
/**
* Файл шаблона
* @var string
*/
private $_File;
/**
* Массив переменных-маркеров шаблона
* @var array
*/
private $_Vars;
/**
* Конструктор, инициализируем свойства класса
*/
public function __construct()
{
$this->_File = '';
$this->_Vars = array();
}
/**
* Устанавливает файл шаблона
* @param string $File
*/
public function SetTemplate($File)
{
$this->_File = $File;
}
/**
* Связывает переменные скрипта с переменными-маркерами
* @param string $VarName
* @param string $VarValue
*/
public function AssignVar($VarName, $VarValue)
{
if ($VarName !== '') {
$this->_Vars[$VarName] = $VarValue;
}
}
/**
* Отображает шаблон
*/
public function Display()
{
if (file_exists($this->_File)) {
extract($this->_Vars, EXTR_PREFIX_ALL, '');
include $this->_File;
}
}
}
?><html> <head> <title>Пример3</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> </head> <body> Привет, <?php echo $_UserMessage; ?> </body> </html>
<?php
require_once 'class.view.php';
date_default_timezone_set('Europe/Kiev');
$UserMessage = 'Артём';
if (date('md') === '0509') {
$UserMessage .= ' С Днём Победы!';
}
//Создаем объект представления интерфейса
$Veiw = new View;
//Устанавливаем шаблон представления
$Veiw->SetTemplate('template.php');
//Установка значения переменной-маркера сообщения пользователю
$Veiw->AssignVar('UserMessage', $UserMessage);
//Отображение интерфейса
$Veiw->Display();
?><?php
/**
* Класс шаблонизатор
*/
class View {
/**
* Файл шаблона
* @var string
*/
private $_File;
/**
* Массив переменных-маркеров шаблона
* @var array
*/
private $_Vars;
/**
* Конструктор, инициализируем свойства класса
*/
public function __construct()
{
$this->_File = '';
$this->_Vars = array();
}
/**
* Устанавливает файл шаблона
* @param string $File
*/
public function SetTemplate($File)
{
$this->_File = $File;
}
/**
* Связывает переменные скрипта с переменными-маркерами
* @param string $VarName
* @param string $VarValue
*/
public function AssignVar($VarName, $VarValue)
{
if ($VarName !== '') {
$this->_Vars[$VarName] = $VarValue;
}
}
/**
* Отображает шаблон
*/
public function Display()
{
if (file_exists($this->_File)) {
include $this->_File;
}
}
/**
* Функция доступа к элементу массива
* переменных-маркеров шаблона
* @param string $Varname
* @return mixed
*/
public function __get($VarName)
{
if (array_key_exists($VarName, $this->_Vars)) {
return $this->_Vars[$VarName];
}
return NULL;
}
}
?><html> <head> <title>Пример4</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> </head> <body> Привет, <?php echo $this->UserMessage; ?> </body> </html>
<?php
/**
* Класс шаблонизатор
*/
class View {
/**
* Файл шаблона
* @var string
*/
private $_File;
/**
* Массив переменных маркеров шаблона
* @var array
*/
private $_Vars;
/**
* Конструктор, инициализируем свойства класса
*/
public function __construct()
{
$this->_File = '';
$this->_Vars = array();
}
/**
* Устанавливает файл шаблона
* @param string $File
*/
public function SetTemplate($File)
{
$this->_File = $File;
}
/**
* Связывает переменные скрипта с переменными-маркерами
* @param string $VarName
* @param string $VarValue
*/
public function AssignVar($VarName, $VarValue)
{
if ($VarName !== '') {
$this->_Vars[$VarName] = $VarValue;
}
}
/**
* Отображает шаблон
*/
public function Display()
{
echo $this->_Prepare();
}
/**
* Загружает шаблон и
* заменяет в нем маркеры замены
*/
private function _Prepare()
{
$Content = '';
if (file_exists($this->_File)) {
$Content = file_get_contents($this->_File);
foreach ($this->_Vars as $VarName=>$VarValue) {
$Content = str_replace('{' . $VarName . '}', $VarValue, $Content);
}
}
return $Content;
}
}
?><html>
<head>
<title>Пример5</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
Привет, {UserMessage}
</body>
</html>Вот набросал вариант глобального реестра. Это стандартная реализация паттерна Registry в виде единственного экземпляра, но еще и с некоторыми фишками. Одна из них, если это можно назвать фишкой, - возможность обращения к защищенным свойствам из статических методов. Не знаю на сколько это круто, но меня почему-то пропёрло.
Думаю, неплохо получилось, так что комментировать мне уже нечего. Но вы можете это сделать в комментариях.
Listing №1 (PHP)
<?php
//интерфейс базы данных
interface Db {
function foo();
}
//база данных
class RealDb implements Db {
function foo()
{
echo 'real db';
}
}
//фиктивная служба базы данных
class TestDb implements Db {
function foo()
{
echo 'test db';
}
}
//Реестр
class Registry {
private static $_instance = null;
protected $_db;
public static function init($instance)
{
self::$_instance = new $instance();
if (!self::$_instance instanceof Registry) {
throw new Exception('');
}
}
private static function getInstance()
{
if (!self::$_instance) {
self::init('Registry');
}
return self::$_instance;
}
public static function getDb()
{
return self::getInstance()->_db;
}
protected function __construct() {
$this->_db = new RealDb();
}
private function __clone() {}
}
//Реестр для тестирования
class TestRegistry extends Registry {
protected function __construct() {
$this->_db = new TestDb();
}
}
////Пример
//Инициализация для тестирования
//Registry::init('TestRegistry');
$db = Registry::getDb();
$db->foo();Всем привет! Сегодня наш урок будет посвящен использованию Excel'я в Delphi. А именно я рассажу как вывести табличку из Excel'я в компонент DbGrid.
Итак начинаем.....
Таблица содержит 3 столбика: Имя, Фамилия, Должность. Обратите внимание что таблицу я создал на листе с Именем Лист1 (в дальнейшем нам это пригодиться). Так далее, я добавил в неё первую запись (Олег, Иванов, Менеджер). Всё начальные приготовления завершены, осталось только сохранить наш документ, я сохраню его на диске C:\ с именем 2.xls
Теперь открываем Delphi и кидаем на форму 4 компонента: ADOConnection, ADOQuery с закладки ADO, DataSource с закладки Data Access и DBGrid с закладки DataControls. Для того что-бы вывести документ Excel в компонент DbGrid нам нужно с начала подключиться к этому документу, для этих целей будем использовать компонент ADOConnection. Выделяем его и в свойстве ConnectionString пишем вот такой код
Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\2.xls;Extended Properties=Excel 8.0;
procedure ConnectToExcel;
procedure TForm1.ConnectToExcel; begin end; end;
procedure TForm1.ConnectToExcel;
var strConn: widestring;
begin
strConn:='Provider=Microsoft.Jet.OLEDB.4.0;' +
'Data Source=' +Edit1.Text+ ';' +
'Extended Properties=Excel 8.0;';
AdoConnection1.Connected:=False;
AdoConnection1.ConnectionString:=strConn;
try
AdoConnection1.Open;
except
ShowMessage('Не могу соединиться с Excel книгой, которая расположена по адресу: '+Edit1.Text+' !');
raise;
end;
end; procedure FetchData;
procedure TForm1.FetchData; begin ConnectToExcel; AdoQuery1.Close; AdoQuery1.SQL.Text:=Edit2.Text; try AdoQuery1.Open; except ShowMessage( 'Не могу выполнить Sql запрос ' + Edit1.Text +'!'); raise; end; end;
SELECT * FROM [Лист1$]