Cache Manager
Я почему-то думал, что давно уже выложил в блоге свои классы опубликованные на phpclasses.org, а когда недавно хотел посмотреть один из них, оказалось, что их нет. А вот сейчас я собираюсь исправить этот недосмотр и начну с класса для кэширования страниц.
Это мой любимый класс, я очень много раз его переписывал и переделывал, особенно функцию замены ссылок на странице. Итак код, а комментарии потом.
class cache {
/**
* @author CTAPbIu_MABP
* @version 1.4
* @license GNU General Public License
*
*/
protected $cache = NULL;
protected $handler = array();
/**
* Стартует кеширование с makeGZip(),
* если у браузера присутствует соотвецтвующий заголовок
*
* @param bool $gzip
*/
public function __construct($gzip=TRUE){
//принимает ли браузер сжатие?
if (@strpos($_SERVER['HTTP_ACCEPT_ENCODING'],'gzip')!==false && $gzip) // @ отключает NOTICE если заголовок HTTP_ACCEPT_ENCODING не пришел вообще
// второй параметр это степень буфферизации 0 > 9
$this->start('makeGZip', array(5));
}
/**
* Начинает новую буферизацию и записывает ее обработчик
*
* @param string $func
* @param array $argv
*/
public function start($func, $argv=NULL){
@ob_start();
// notice: надо передавать именно NULL, а не FALSE тогда при array_merge элемент удалится
$this->handler[] = array($func, $argv);
}
/**
* Очищает буфер $loop раз и выталкивает последние $loop обработчиков
*
* @param int $loop
*/
public function free($loop=0){
$loop = $loop ? $loop : count($this->handler);
for($i=0;$i< =$loop;$i++){
@ob_end_clean();
array_pop($this->handler);
}
}
/**
* Парсит последние $loop буфферов
*
* @param integer $loop
* @return mixed
*/
public function flush($loop=0){
$loop = $loop ? $loop : count($this->handler);
for ($i=$loop-1;$i>=0;$i--){
// узнаем номер последнего хендлера
$x = count($this->handler)-1;
// получаем последний буффер для работы
$this->cache = @ob_get_contents();
// создаем правильный массив параметров
$params = array_merge((array)$this->cache, (array)$this->handler[$x][1]);
// проверяем принадлежит ли функция объекту или она пользовательская
$handler = method_exists($this,$this->handler[$x][0]) ? array($this,$this->handler[$x][0]) : $this->handler[$x][0];
// вызываем нужную функцию с параметрами
$this->cache = call_user_func_array($handler, $params);
// удаляем последний. отработаный, элемент массива
array_pop($this->handler);
// отключаем буферизацию
@ob_end_clean();
// выбрасываем назад содержимое буффера
echo $this->cache;
}
return $this->cache;
}
/**
* Gzip'ирует буффер и возвращает в поток или в файл,
* если имя файла и расширение указано
*
* @param mixed $buf
* @param integer $ratio
* @param string $name
* @param string $extention
* @return mixed
*/
private function & makeGZip(&$buf, $ratio=0, $name='', $extention=''){
if ($ratio === 0) return $buf;
$bufziped = gzcompress($buf, $ratio);
$bufziped = pack('cccccccc',0x1f,0x8b,0x08,0x00,0x00,0x00,0x00,0x00)
.substr($bufziped, 0, -4)
.pack('V',crc32($buf))
.pack('V',strlen($buf));
header('Content-Encoding: gzip');
if ($name && $extention){
header('Content-description: File Transfer');
header('Content-type: application/x-gzip');
header('Content-length: '.strlen($bufziped));
header('Content-Disposition: attachment; filename='.$name.'.'.$extention.'.gz');
}
return $bufziped;
}
/**
* Парсит буффер в поисках ссылок и заменяет их
*
* @param mixed $buf
* @return mixed
*/
private function & makeURL(&$buf){
$search = "~([actionhrefsrclocationbackground]) = [\"|'] /? ([/.a-z0-9_-]+) . (w+) ?? (S*?)? (#[^'\"]*)? [\"|']~six";
$replace = array($this, "_makeURL");
return preg_replace_callback($search, $replace, $buf);
}
/**
* Пересобирает ссылки из полученного массива call_back_func
*
* @return string
*/
private final function _makeURL($url){
$string = $url[1]."="http://".$_SERVER['SERVER_NAME'];
$string .= $_SERVER['SERVER_PORT']!= 80 ? ":".$_SERVER['SERVER_PORT']."/" : "/";
$string .= ($url[2]!="index") ? $url[2] : "";
$string .= ($url[3]!="php") ? ".".$url[3] : (($url[2]!="index") ? "/" : "");
parse_str($url[4],$query);
foreach ($query as $val)
$string .= $val ? $val."/" : "";
$string .= $url[5]."\"";
return $string;
}
/**
* Подсвечивает текст
*
* @param mixed $buf
* @param array|string $matches
* @param string $tag
* @param string $color
* @return mixed
*/
private function & makeHighlight(&$buf, $matches, $tag = 'b', $color = 'ffff00'){
if (!$matches) return $buf;
$matches = (array)$matches;
foreach ($matches as $match)
if($match)
$array[] = preg_quote($match);
if (!$array) return $buf;
return preg_replace('#(?!<.*)(?<!w)('.implode('|',$array).')(?!w|[^<>]*>)#i', '<'.$tag.' style="background-color:#'.$color.'">1</'.$tag.'>', $buf);
}
/**
* парсит и выводит буфер
*
*/
public function __destruct(){
$this->flush();
}
}
Из исходного кода видно что класс помимо разруливания буферизации и обработки буфера callback функциями имеет три базовые метода для обработки страницы.
Первый из них это gzip сжатие. Оно включается по умолчанию при создании класса, если пользователь прислал соответствующий заголовок в запросе, это сделано, потому что нельзя сжать только половину страницы, а остальное оставить как есть. Сам алгоритм сжатия прост как три копейки и тысячу раз описан в интернете, а заголовки посылаються автоматически. Приведу маленький пример использования:
$cach = new cache();
echo "Hello World!";
$cach->flush();
Этот кусок кода вернет браузеру всем уже порядком надоевшую gzip-ированую строку "Hello World!". Если же нужно вернуть не html страницу а например файл, то пример придется немного переделать.
$cache = new cache(false);
$cache->start("makeGZip", array(5,"file","txt"));
echo "Hello World!";
$cach->flush();
Надо заметить что функция $cache->start() начинает кэширование с выводом в файл. При этом она принимает параметрами имя callback функции и массив ее параметров: степень сжатия, имя файла и его расширение. В результате мы получим файл file.txt.gz, с все той же надоевшей надписью про "Хэлоу Ворлд".
Все страницы этого сайта в том числе и rss-поток новостей сжаты этой функцией, а реальный пример сжатия в файл можно посмотреть на Google Site Map для сайта.
Второй идет функция для переписывания ссылок в человекочитаемую форму, например: "/page.php?foo=bar" превратится в "example.com/page/bar/". Я долгое время искал оптимальный вариант регулярного выражения для замены ссылок, придумывая самые разные комбинации, которые бы могли покрыть все виды ссылок, как например "/file.php?foo[0]=bar".
$search = "~(action|href|src|location|background)=(\"|') (/)? ([/.a-z0-9_-]+) .(w+) ?? (?: (w+) = (w+?))? (?: & (w+) = (w+?))? (?: & (S*?))? (#[-a-zа-я0-9_ ]*)?(\"|')~six";
$search = "~(action|href|src|location|background)=(\"|') (/)? ([/.a-z0-9_-]+) .(w+) ?? (?: ([a-z0-9[]]+) = (w+?))? (?: & ([a-z0-9[]]+) = (w+?))? (?: & (S*?))? (#[-a-zа-я0-9_ ]*)?(\"|')~six";
$search = "~(action|href|src|location|background)=(\"|') (/)? ([/.a-z0-9_-]+) .(w+) ?? (?: ([a-z0-9[]]+) = (w+?))? (?: & ([a-z0-9[]]+) = (w+?) (?: & ([a-z0-9[]]+) = (w+?) (?: & ([a-z0-9[]]+) = (w+?) (?: & ([a-z0-9[]]+) = (w+?)?)?)?)?)? (#[-a-zа-я0-9_ ]*)?(\"|')~six";
Но все они были недостаточно хороши, одни не покрывали массивы, другие могли распарсить ограниченное количество пар key=value в query string, а третьи были очень медлительны. А потом я решил, что занимаюсь не тем, регулярки не должны парсить урлы, они должны их только находить! и я решил переложить все на call-back функцию, в результате чего получился вот такой код, который был размазан для удобочитаемости
private function & makeURL(&$buf){
return preg_replace_callback("~([actionhrefsrclocationbackground]) = [\"|'] /? ([/.a-z0-9_-]+) . (w+) ?? (S*?)? (#[^'\"]*)? [\"|']~six", array($this, "_makeURL"), $buf);
}
Я думаю надо объяснить эту функцию на примере, так как все мои знакомые программисты не смогли понять ее смысла и красоты. Функция ищет внутренние ссылки и отравляет результат другой функции, которая собственно над ними издевается, как хочет. Вторая функция принимает массив из 5 элементов. Попробую описать это таблицей на примере ссылки <a href = "/page.php?name=foo¶m[0]=bar#anchor">link</a> и картинки <img src = "/path/to/image.pic.jpg">
| № | Патерн | Link | Image |
|---|---|---|---|
| 1 | ([actionhrefsrclocationbackground]) | href | src |
| 2 | ([/.a-z0-9_-]+) | page | /path/to/image.pic |
| 3 | (w+) | php | jpg |
| 4 | (S*?) | name=foo¶m[0]=bar | |
| 5 | (#[^'"]*) | #anchor |
Тут есть одно замечание - actionhrefsrclocationbackground на самом деле не что иное как описание местонахождения ссылки action, href, src, location, background, все это можно было бы написать через палку | но так работает быстрее. Более того если убрать action то он все равно будет заменяться так как слово location содержит все буквы слова action. Учитывая, что повторяющиеся буквы можно убрать и поставить в алфавитном порядке abcdefghiklnorstu все равно будет работать, но удобочитаемость пропадет напрочь!
Функция _makeURL получает массив и начинает его склеивать в нужном порядке, самую большую ценность тут представляет парсинг query string и его склейку через слеш, собственно то из-за чего все и затевалось. Можно склеивать не только через слеш и приводить к виду директории, а например приводить к виду "page/bar.html"
В результате получаем <a href="http://example.com:8080/page/foo/bar#anchor">link</a> и <img src="http://example.com:8080/path/to/image.pic.jpg">
Еще одна маленькая деталь для того чтобы все это работало нужно написать правила в .htaccess по приведению ссылок в нормальный вид, тут уже я не в силах помочь, каждый должен будет сам для себя писать, единственное могу показать как он выглядит для моего сайта
#rewrite engine
Options FollowSymLinks -Indexes -Multiviews
RewriteEngine on
RewriteBase /
#RegExp have only 9 back-references
RewriteRule ^(content)(/([0-9]+)(/([0-9]+)(/([0-9]+)(/([a-z_]+))?)?)?)?/?$ index.php?act=$1&year=$3&month=$5&day=$7&name=$9 [NC,L]
RewriteRule ^(content)(/([a-z0-9_-]+)(/([a-z_]+))?)?/?$ index.php?act=$1&cat=$3&name=$5 [NC,L]
RewriteRule ^(user)(/([a-z0-9_-]+)(/([a-z0-9_-]+)(/([a-z0-9_-]+)(/([a-z0-9_-]+))?)?)?)?/?$ index.php?act=$1&name=$3 [NC,L]
RewriteRule ^(message)(/([a-z0-9_-]+))?/?$ index.php?act=$1&msg=$3 [NC,L]
RewriteRule ^(xml)(/([a-z]+)(/([a-z0-9_-]+))?)?/?$ shell.php?act=$1&type=$3&name=$5 [NC,L]
RewriteRule ^(plugins)(/([a-z]+)(/([a-z]+))?)?/?$ shell.php?act=$1&name=$3∂=$5 [NC,L]
Ну и наконец третья функция, она служит для подсветки слов в тексте. Честно признаюсь, регулярное выражение для нее я нарыл в интернете. Функция подсвечивает слова на странице, причем следит, чтобы эти слова не были частью html кода. Приведу пример:
$cache = new cache();
$cache->start("makeHighlight",array(array('class','div'),"i"));
echo "<div class='highlighted'>this div has class 'highlighted'</div>";
В результате получим вот такой html код, естественно сжатый gzip'ом, его же никто не отменял
<div class='highlighted'>this <i style="background-color:#ffff00">div</i> has <i style="background-color:#ffff00">class</i> 'highlighted'</div>
Но это плохой пример, можно смастерить что-то более правдоподобное
$cache = new cache();
$url = parse_url($_SERVER['HTTP_REFERER']);
if (strpos($url['host'],'google') !== false){
parse_str($url['query'],$query);
$cache->start("makeHighlight",array(explode(' ',$query['q'])));
}
Если на ваш сайт перейдут с гугла, этот код подсветит на странице слова из поискового запроса. Посмотреть, как в реальности работает такой код, можно перейдя с Google по любой ссылке, слово jQuery должно быть, выделено жиром и подсвечено желтым
И на последок приведу пример как вставить в класс свою функцию обработки текста, например вы хотите заменять строки по словарю и у вас есть два массива $search и $replace. Попробуем написать простенькую функцию simle_replace которая это будет делать.
$cache = new cache();
$search = array("CSS","PHP","JS");
$replace = array("<acronym title='Cascading Style Sheets'>CSS</acronym>",
"<acronym title='PHP Hypertext Preprocessor'>PHP</acronym>",
"<acronym title='JavaScript'>JS</acronym>",
);
// $buf is always first argument
function & simle_replace(&$buf,$search,$replace){
return str_replace($search,$replace,$buf);
}
$cache->start("simle_replace",array($search,$replace));
Как видите ничего сложного! Приятной работы!
Свежие комментарии