Сайт Романа ПарпалакаБлогКлючевые словаPHP

PHP

Статьи по этой теме:
Миниатюры на PHP
Офлайн-версия сайта, или PDF и PHP
PHP и UTF-8
If-Modified-Since и кеширование
Управление зависимостями на примере composer


Как определить домен из PHP

Илья Бирман написал про баг в Эгее, когда сайт доступен по разным доменам, и RSS кешируется то с одним доменом, то с другим.

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

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

HTTP_HOST и SERVER_NAME

Для этих целей обычно проверяют серверную переменную HTTP_HOST. Но в ней всего лишь содержимое заголовка Host из http-запроса. Этот заголовок — часть стандарта HTTP/1.1, и в HTTP/1.0 он не обязателен. Правда, без этого заголовка не заработают виртуальные хосты — разные сайты на общем сервере. Но даже в таком случае среди сайтов есть сайт по умолчанию, открывающийся при заходе напрямую по IP. Так вот, когда устаревшие клиенты (в том числе нормальные браузеры за старыми или специально настроенными прокси) открывают сайт по умолчанию, переменная HTTP_HOST будет пустой.

Есть еще одна серверная переменная — SERVER_NAME. Обычно она содержит хост, определенный в конфигурации веб-сервера. Но на него тоже нельзя стопроцентно положиться. Например, в nginx хост по умолчанию задается конструкцией

server_name _;

Сайт будет прекрасно открываться, но при этом в SERVER_NAME окажется знак подчеркивания.

Подробности для дальнейшего чтения на стековерфлоу: HTTP_HOST vs. SERVER_NAME.

Параметр конфигурации

Если вы делаете распространяемый движок для работы на разных серверах, у вас нет гарантированного способа определить хост, по которому открыт сайт. В моем движке S2 я скопировал способ из PunBB. В нем установочный скрипт «угадывает» адрес сайта (протокол + домен + порт + подпапка) в том числе на основе HTTP_HOST, дает возможность этот адрес отредактировать и сохраняет результат в конфигурационный файл. Затем именно этот адрес используется для генерации ссылок.

Как альтернативу Илья советует настроить редиректы. Это правильно, но, опять же, не всегда выполнимо. Например, вы настроили на сервере https, но не хотите делать редирект с http на https (вы хотите поддерживать старые браузеры, но у вас нет отдельного IP-адреса на каждый домен).

Когда одна и та же страница открывается по разным адресам, Гугл рекомендует в явном виде указывать canonical-адреса:

<link rel="canonical" href="https://example.com/some/url" />

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

Кстати, давно хотел написать о том, что https — это новый www. Он вынуждает совершать дополнительные бессмысленные действия при настройке сайта вроде редиректов с www. Ради https мне пришлось сделать в S2 поддержку тега link rel="canonical".

14 августа 2017 года, 23:12     www · PHP · S2     Оставить комментарий

Пишем объектно-ориентированный код в PhpStorm

В прошлом посте я разрушал мифы о среде разработки PhpStorm. В продолжение я записал скринкаст о том, как в ней писать объектно-ориентированный код.

Скринкаст рассчитан на людей, не владеющих уверенно ООП. На записи я перевожу фрагмент кода из процедурного стиля в объектно-ориентированный, объясняю пользу от преобразования и одновременно показываю приемы работы в PhpStorm.

Содержание:
00:19 Процедурный стиль vs. объектно-ориентированный стиль
01:11 PHP не для процедурного программирования
02:22 ООП в PHP: много рутины
03:05 Задача: показать не только приемы работы в PhpStorm, но и пользу от ООП
04:08 Выбираем код для рефакторинга
05:10 Создаем класс: пространство имен; методы; константы
10:36 Автозагрузка классов через composer
13:28 Разбираем проблемы кода
15:34 Возвращаем вместо массива объект (DTO)
24:29 Избавляемся от глобальных переменных по принципу инверсии зависимостей (dependency inversion)
29:29 Наполняем DTO логикой: __toString
33:16 Рефакторинг
35:14 Наполняем DTO логикой: валидация в конструкторе
39:40 Получился код по принципам SOLID
40:24 Проблема создания сервисов
41:04 Решение с помощью контейнеров зависимостей; подключение Pimple через composer
46:01 Обзор изменений, привнесенных объектно-ориентированным подходом
48:09 Дополнение: подключаем библиотеку поиска Rose, описывая сервисы в контейнере
01:01:38 Подведение итогов

8 мая 2017 года, 16:13     PHP · веб-разработка · видео     Оставить комментарий

Оптимизация памяти в PHP и функция serialize

Хорошая статья на Хабре про особенности выделения памяти в PHP. Обычно на расход памяти в php-скриптах никто не обращает внимания. Но иногда бывают ситуации, когда стандартных 16 мегабайт оказывается недостаточно. Тогда приходится изобретать различные трюки.

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

Я немного поколдовал с кодом и в итоге сократил потребление памяти более чем в два раза. Раньше для индексации этого сайта нужно было 32 мегабайта памяти, а теперь достаточно и 16. Кроме методов из статьи, я применил запись чисел в системе счисления по основанию 36 (перевод осуществляется функцией base_convert()) и оптимизировал функцию serialize(). Опишу последний метод подробнее.

Дело в том, что функция serialize() расходует много памяти, и об этом написано в документации. Если ей передать большой массив, доступная скрипту память может закончиться. Относительно простой выход из положения существует, когда массив нужно сохранить в файл. Функцию serialize() можно применять к отдельным элементам массива, как в следующем фрагменте кода:

file_put_contents($filename, 'a:'.count($array).':{');
$buffer = '';
$length = 0;
foreach ($array as $word => $data)
{
    $chunk = serialize($word).serialize($data);
    $length += strlen($chunk);
    $buffer .= $chunk;
    if ($length > 100000)
    {
        file_put_contents($filename, $buffer, FILE_APPEND);
        $buffer = '';
        $length = 0;
    }
}
file_put_contents($filename, $buffer.'}', FILE_APPEND);

Запись происходит порциями размером около 100 килобайт. Этот код подходит для сохранения в файл массива с большим количеством элементов среднего размера и решает проблему перерасхода памяти функцией serialize().

23 декабря 2011 года, 01:43     PHP     Комментарии (2)

Загадка специалистам по PHP

Как вы думаете, что выведут следующие операторы?

<?php

echo preg_match('#тес#iu', 'Такой Вот Тест');
echo preg_match('#Тес#Siu', 'Такой Вот Тест');
echo preg_match('#тес#Siu', 'Такой Вот Тест');
echo preg_match('#во#Siu', 'Такой Вот Тест');

Логика подсказывает, что 1111, а на опыте оказалось 1101. Причем и в Windows, и в Linux (Debian, PHP 5.2.6). Я подумал, что комбинация модификаторов Siu несовместима (и даже убрал в отлаживаемом коде модификатор S). Но почему тогда последнее регулярное выражение срабатывает правильно?

Кто подскажет, в чем тут дело?

16 января 2011 года, 12:53     PHP     Комментарии (3)

UTF-8 bad chars

Вопрос о «плохих» данных в UTF-8. Иногда такое знание оказывается полезным. Например, в корректной UTF-8 строке не могут встретиться байты 0xC0, 0xC1. Это может пригодиться при обработке строк для экранировки неизменяемых последовательностей символов (таких, как html-теги). Экранируемые подстроки вырезаются из строки, на их место ставятся символы с кодом 0xC0, строка обрабатывается, после чего подстроки возвращаются назад, вместо 0xC0.

10 апреля 2010 года, 01:36     PHP     Оставить комментарий

#.*#/u

У регулярных выражений PHP есть специальный модификатор u для работы со строками в кодировке UTF-8. Оказывается, вставлять этот модификатор во все подряд регулярные выражения не только бессмысленно, но и вредно. Если шаблон может работать после удаления модификатора u, то он будет работать без него быстрее, зачастую существенно быстрее.

Вообще-то, знакомства с устройством кодировки UTF-8 достаточно, чтобы понять, почему строки в этой кодировке обрабатываются медленнее. Однако я не сопоставил этот факт с тем, что неоправданное употребление модификатора u может сильно замедлить регулярное выражение, и обнаружил такое замедление случайно.

Когда же модификатор u необходим? Только тогда, когда в регулярном выражении указывается количество символов или в квадратных скобках присутствуют символы, не входящие в нижнюю половину таблицы ASCII.

В процессе оптимизации можно попытаться изменить регулярное выражение и убрать из него модификатор u.

Как всегда, лучше проверять на практике необходимость модификатора u в каждом конкретном регулярном выражении и его влияние на время выполнения скрипта.

27 января 2010 года, 19:27     PHP     Оставить комментарий

PHP: навигация

Некоторое время назад Илья Бирман написал про подсветку ключевых слов. В комментариях после моего замечания о возможности использовать функцию preg_replace развязалась небольшая дискуссия о том, как правильно нужно генерировать подобные вещи. Вот что писал Илья:

*_replace — это вообще не наш метод, надо сразу всё правильно генерировать, а не резать по живому потом.

...

А генерировать неправильный контент, чтобы потом его героически исправить — это левак, нужно сразу генерировать правильный.

Рассмотрим достоинства и недостатки различных подходов к генерации контента на простом примере навигационных ссылок.

Использование preg_replace позволяет сделать код коротким и понятным.

$cur_url = 'item2.htm';

$menu = '<a href="item1.htm">item1</a><br />
<a href="item2.htm">item2</a><br />
<a href="item3.htm">item3</a><br />
<a href="item4.htm">item4</a><br />
<a href="item5.htm">item5</a>';

$menu = preg_replace(
	'#<a href="'.$cur_url.'">([^<]*)</a>#',
	'<span>\\1</span>',
	$menu);

Однако на мой взгляд этот код может быть расценен в соответствии с цитатой как «левак». Я не знаю, какой способ является правильным в этой ситуации с точки зрения Ильи, но могу предположить, что он должен быть примерно таким:

$cur_url = 'item2.htm';

$menu_array = array(
	'item1.htm' => 'item1',
	'item2.htm' => 'item2',
	'item3.htm' => 'item3',
	'item4.htm' => 'item4',
	'item5.htm' => 'item5'
);

$menu = '';
foreach ($menu_array as $url => $link) {
	if ($url != $cur_url)
		$menu .= '<a href="'.$url.'">'.$link.'</a><br />';
	else
		$menu .= '<span>'.$link.'</span><br />';
}

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

Можно применить и третий способ:

$cur_url = 'item2.htm';

if ($url != 'item1.htm')
	$menu = '<a href="item1.htm">item1</a><br />';
else
	$menu = '<span>item1</span><br />';

if ($url != 'item2.htm')
	$menu .= '<a href="item2.htm">item2</a><br />';
else
	$menu .= '<span>item2</span><br />';

if ($url != 'item3.htm')
	$menu .= '<a href="item3.htm">item3</a><br />';
else
	$menu .= '<span>item3</span><br />';

if ($url != 'item4.htm')
	$menu .= '<a href="item4.htm">item4</a><br />';
else
	$menu .= '<span>item4</span><br />';

if ($url != 'item5.htm')
	$menu .= '<a href="item5.htm">item5</a>';
else
	$menu .= '<span>item5</span>';

Он еще более громоздкий, да еще и избыточный. Хотя данный способ в полтора раза быстрее первого, в подобной ситуации я отдаю предпочтение использованию preg_replace.

4 августа 2008 года, 23:57     PHP     Комментарии (6)

PHP и timestamp

На мой взгляд, функции time(), mktime(), date(), gmmktime(), gmdate() недостаточно хорошо описаны в документации. Легко запутаться при попытках понять, что же происходит в разных часовых поясах. Вот доходчивое объяснение (правда, на английском). Вкратце его суть в следующем. Метка времени (timestamp) фиксированного момента одна и та же для всех часовых поясов. Функции date() и mktime() преобразуют timestamp ко времени в часовом поясе, установленном на сервере, и обратно. Функции gmdate() и gmmktime() делают то же самое, но только для гринвичского времени.

24 июля 2008 года, 18:18     PHP     Оставить комментарий

PHP: mkdir

Сегодня потратил немало времени в попытках понять, почему права у директории dir после выполнения функции mkdir('dir', 0777); не выставляются в 777. А ведь в документации написано:

На аргумент mode также влияет текущее значение umask, которое можно изменить при помощи umask().

Тема umask осталась нераскрытой. В общем, про второй параметр у функции mkdir() можно забыть, а правильный код выглядит так:

mkdir('dir');
chmod('dir', 0777);
3 июля 2008 года, 18:09     PHP     Комментарии (6)
Поделиться
Записи