Небольшие хитрости с Yii CGridView, или как избавиться от написания кода в строке для eval.

Всем нам не раз досаждала такая пакость, как необходимость писать большущие строки в представлении столбца в CGridView. А для чего? А чтобы не потерять переменные $data и (в меньшей степени) $row. Так вот, в нашу эпоху трайтов и анонимных функций (спасибо PHP 5.4+) можно больше не городить чего-то вроде:

array(
'name' => 'quantity',
'header' => 'Количество',
'type' => 'raw',
'value' => 'что-тосовершенноадское с $data->value $data->quantity etc',
),

А воспользоваться более элегантными вещами, через анонимную функцию и трайт, например.


//....

$controller = $this;

// ...

array(
'name' => 'quantity',
'header' => 'Количество',
'type' => 'raw',
'value' => function($data, $row) use ($controller) {
    return $controller->renderPartial('basketCols/__quantity', array('data' => $data), true);
},
),

Что происходит? А всё просто — мы можем для ячеек использовать свои собственные представления, из отдельных файлов, в которые спокойно передаем ту же $data, и там с ней работаем как с обычной переменной в шаблоне. При этом $data может являться, например, моделью, полученной для CGridView из CArrayDataProvider (или CActiveDataProvider). Если вам там надо просто сформировать какую-то дикую ссылку, то не надо даже извращения с $controller — просто юзайте статический вызов CController::createUrl(‘some paths’, array(‘param’ => ‘some param’)).

При этом внутри анонимной функции может быть сколь угодно сложный код, для которого не теряется читаемость, подсветка, и прочее, как это происходило бы в случае стандартной строки для eval. И более того, вы больше не будете вынуждены выискивать ошибку, которую вы допустили в процессе написания кода внутри строки.

 

Резка строк в UTF-8 в PHP

Время от времени в программизме для веб возникает задача обрезать строчку скажем на 1000 символов — чтобы было нечто типа «строка…» и «Читать далее» (как например сделано в этом блоге на слишком длинных заметках. Проблемы начинаются с резкой UTF-8 строк, потому что стандартная функция substr в этом отношении демотивирует — байта на каждый символ два, а она режет по одному… В результате может от какой-нибудь буквы «ё», остаться неприглядный однобайтовый служебный символ.

Если у вас есть расширение php_mbstring — нет проблем, там есть функция mb_substr, которая корректно обрабатывает юникод. А вот если нету (скажем, на высоконагруженных проектах интерпретатор PHP как правило максимально облегчён — без всяких лишних расширений) то уже сложнее. Однако, выход есть — не обязательно резать по буквам, можно резать по словам. Этот метод я случайно обнаружил в одном из рабочих проектов и просто поразился простоте и эффективности решения такой уже тривиальной задачи. Собственно, сабж:

$arr = explode(' ', $text);
$arr = array_slice($arr, 50);
$text = implode(' ', $arr[0]).'...';
unset($arr);

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

О кэшировании в Web

День добрый, дражайший читатель.

Вообще, я не имею уж очень большого опыта в работе с высоконагруженными системами. Обычно проекты, которые мне доводилось делать, были рассчитаны на посещаемость в 20000 человек в сутки. Ну или около того. Однако, однако… Программисты народ странный. Мы иногда делаем нечто «на будущее». Ну вдруг потребуется. Но вначале немного теории. Как строится высоконагруженная система? Основной её компонент — это балансировщик нагрузки. Фактически он просто обеспечивает обработку запроса пользователя одним из нескольких back-end’ов. Например один балансирощик (и лично мне доводилось слышать, что в его роли может выступать например nginx), раскидывает запросы по нескольким PHP-сборщикам (или бросается смотреть в кэш — нет ли уже того, что требуется). Те, соответственно формируют контент, и отдают его обратно. Это очень упрощённо, прямо скажем. Потому что я всё-таки не администратор веб-серверов, и всех тонкостей не знаю.

Так вот, это я к тому, что кэширование — один из действенных способов снижения нагрузки. Зачем выполнять кучу кода для формирования HTML-контента, если такая работа уже была сделана ранее, и с тех пор ничего не поменялось? Правильно, незачем. Так не логичнее ли сохранить страницу в кэш, и отдавать её каждый раз быстро и без мучений? Логичнее, все так и делают. И вот тут есть выбор — где размещать кэш. Существует много вариантов, но если свести к основным, это либо жёсткий диск, либо оперативная память. Чувствуете разницу? Скорость обращения к жёсткому диску в разы ниже, чем к оперативке, но при этом кэширование на жёстком диске подходит практически для любого хостинга, в том числе и виртуального. Однако важно понимать, что высоконагруженные проекты никогда не размещаются на виртуальном хосте. Его мощностей и возможностей просто не хватит для обработки такого потока данных. Потому для просто посещаемых проектов это минимум VPS. А для проектов с высокой нагрузкой необходимо строить распределённую систему с балансировкой, отдельным сервером для БД, для кэшировщика, для контент-сборщиков и так далее. Это дорого, но как правило ресурс окупает подобные затраты.

Читать далее О кэшировании в Web

Внедрение OpenID на PHP

Логотип OpenID. Взято с Википедии.
Логотип OpenID. Взято с Википедии.

Таки здравствуй, дражайший читатель.

Вообще, не люблю я писать о работе (хотя статистика количества записей в рубрике соответствующей и ставит под сомнение данное утверждение). Но тут прямо не мог не написать. Все вы, коллеги-PHP-шники, разумеется слышали про такую адову приблуду, как OpenID. Если вкратце, то оная позволяет авторизовать пользователей на разных сайтах под одной учётной записью. Например у меня есть почтовый ящик Google, и с его помощью я могу быть авторизован на сайтах, поддерживающих такой вид авторизации. Т.е. мне не надо придумывать пароль/логин, для того, чтобы войти на сайт. При этом сам ресурс не узнаёт лишнего, он знает только то, что я — это я (ибо доверенный openid сервер это подтверждает). Так вот, вроде бы идея то, конечно, хорошая… Однако на пути внедрения оной становятся несколько проблем. Первая — отсутствие более или менее подробной информации по протоколу/стандарту на русском языке. Вторая — большой набор библиотек, позволяющих (теоретически) проводить безболезненное внедрение OpenID авторизации у себя на сайте. Третья — это наличие нескольких стандартов openid (хотя само по себе это не проблема). И четвёртая — это разная степень развитости этих библиотек, напрямую вытекающая из третьей. Есть ещё и пятая — библиотеки используют разные способы общения с серверами авторизации, самый распространённый — это curl-запросы, которые априори требуют наличия в установленном на сервере PHP расширения curl или компиляции, с опцией —with-curl. Вообще на нормальных хостингах он должен быть включен, однако… как показывает практика — не везде он есть. В общем, все дальнейшие рассуждения основываются на том, что curl таки есть, либо есть возможность его поставить. Просто найти библиотеку, которая бы его не использовала — на самом деле очень сложно.

Читать далее Внедрение OpenID на PHP

Водяной знак на PHP+GD

Собственно, совсем недавно была завершена некоторая работа — она была связана с программированием конвейера сборки большого числа изображений из отдельных картинок «конструктора». Для этого, само собой, пришлось накладывать полупрозрачные слои. При теоретических изысканиях данного вопроса — я обнаружил интересную особенность — при запросе «Водяной знак на PHP + GD» в сети нет нормальной, практической информации по этому поводу. Вас сначала погружают в дебри теоретических изысканий, вместо того, чтобы дать самодокументированный код. Я решил пойти иначе. Внизу — функция обеспечивающая наложение водяного знака на изображение. Из недостатков — то, что она работает только с изображениями формата PNG. Впрочем, программисту это исправить не проблема.


function watermark($_source_path, $_watermark_path, $_file='')
{
	/* Проверка - подключена ли библиотека GD - если её нет, вам необходимо либо самому подключить эту библиотеку
	* (В файле php.ini, секция extensions, необходимо прописать либо раскомментировать строку:
	*   extension=php_gd2.dll - в windows. */
	if (!extension_loaded('gd'))
	{
		return false;
	}

	if (!empty($_source_path) && !empty($_watermark_path))
	{
		$i_source    = imagecreatefrompng($_source_path);
		$i_watermark = imagecreatefrompng($_watermark_path);
		$result = imagecreatetruecolor(imagesx($i_source), imagesy($i_source));

		/* Координаты 0,0,0,0 - задают координаты соединения изображений.
		*  Поигравшись с ними можно изменить положение водяного знака (да и исходного изображения)
		*/
		imagecopy($result, $i_source, 0, 0, 0, 0, imagesx($i_source), imagesy($i_source));
		imagecopy($result, $i_watermark, 0, 0, 0, 0, imagesx($i_watermark), imagesy($i_watermark));

		imagedestroy($i_source);
		imagedestroy($i_watermark);

		if (!empty($_file))
		{
			imagepng($result, $_file, 4);
		}
		else
		{
			return $result;
		}
	}
}

Библиотечко для сохранения файлов по 300 штук в папке

Собственно говоря, посидел я тут на досуге, поразмышлял о нелёгкой судьбине веб-программиста. Часто приходится думать не только лишь о том, чтобы пообщаться с заливаемым файлом (ну или генерируемым — какова будет воля начальства либо заказчика), но и о правильном сохранении оного. Поясню, что я имею ввиду.

Как вам известно, просмотр одной папки с содержимым из 40 — 50 ТЫСЯЧ файлов любого формата операция достаточно ресурсоёмкая. В системные причины оного я не вникал, но подозреваю следующее. Перед выдачей файла на запрос, сервер лезет в папку указанную в адресе, и делает что-то вроде ls, ну или dir, что одно и то же. Просмотрев весь список файлов и найдя необходимый он выдаёт его юзеру. Тут впору загнуться и статическому веб-сайту, состоящему из одних html страниц. Так или иначе, решение было найдено многими поколениями программистов. В целевой папке (ну, допустим, uploads) создавать много подпапок, например по-номерам (1,2,3), и сохранять в них по фиксированному количеству файлов. Чаще всего по 300 штук.

Проблема по-большей части состоит в том, что каждый раз приходится сей алгоритм реализовать по-новой. Что совсем некошерно. Я в принципе реализовал его в виде универсального класса. Его достоинства в том, что там только два значимых метода: конструктор и функция save.

Работа тривиальна, создаём экземпляр класса FileSaver, в параметрах __construct() передаем:

1. Адрес родительской папки, к примеру /usr/bla/bla/domain.ru/www/uploads/, 2. максимальное количество файлов в подпапках, 3. создавать ли директорию, если она не существует, и 4. права на создаваемую папку.

Затем — каждый раз, когда нам надо сохранить в эту папку файл, мы вызываем метод save(), параметрами которого являются: 1. путь к перемещаемому файлу, должен содержать имя существующего файла, иначе метод вернёт false.

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

Третий параметр указывает на то, как формировать имя файла. При значении false он считает sha1-хэш файла, и именем файла ставит его, + расширение файла, если оно было. Этим мы обеспечиваем 1. уникальность имён, 2. уникальность файлов (если кто-то попробует поместить туда идентичный по содержимому файл, то хэши совпадут, и при условии сохранения в одну и ту же подпапку — ничего не выйдет, файл останется на месте). По умолчанию этот режим отключен, и имя файла формируется из текущего значения времени в unix timestamp + миллисекунды + случайное число из диапазона 1-10 + расширение оригинального файла.

ВНИМАНИЕ! Метод save осуществляет ПЕРЕМЕЩЕНИЕ файла со старого места в новое, если вам оно не надо, найдите в методе save участок (помечен комментарием), и измените там rename на copy. Возможно, в будущих версиях я сделаю более гибкую настройку модуля, но пока так.

Метод Save возвращает полный путь по которому был сохранён файл. Если вам надо иначе — в методе есть участок с комментарием, иллюстрирующим, как это сделать.

Вот, собственно, и всё. По мере наполнения структура директорий будет выглядеть так:

/uploads/

— /1/

— /2/

— /3/

И так далее. По мере заполнения директории с крайнем индексом, создаётся новая, со следующим, и вперёд, с песней… Я сохранял туда 100 000 файлов, и потом благополучно делал обращение. Ничего, апач не загибался. Если попробовать сделать то же самое, когда они лежат в одной папке кучей — можно и не дождаться результата.

В заключение: ссылка на архив с модулем.

Обновлён движок (CMS) сайта.

Ну таки здравствуйте.

Сегодня был обновлён движок для сайта «Шпаргалко», сиречь, этого сайта. Прошу любить и жаловать :) В целом он, как и всё в процессе пуско-наладки, сырой. Зато теперь наконец то сделан не просто набор скриптов, а вполне себе модульная система. Если, скажем, захочется добавить какой-то ещё функционал, кроме блога, то это сделать будет уже значительно проще, чем на старой версии (скажу по секрету, весь двжиок был полностью переписан, равно как и структура БД — она значительно изменилась).

Изменения в пользовательской части, в принципе, минимальны. Разве что теперь сайт (имеется ввиду вёрстка) поддерживает W3C стандарт, и валидатор проходит нормально.

Также напоминаю, что тут есть дохлый форум )) На нём 4 пользователя и 7 постов в данный момент.

Пока, собсна, всё :)

Как преобразовать (изменить) кодировку в PHP

Читал тут в сети кучу опусов на тему определения, преобразования, конвертации кодировок в PHP. Нет, оно всё работает, правда я не понимаю, нахер оно надо, если есть расширение iconv ? Описывать как с ним работать я не буду, ибо там всё элементарно.

Сслыка здесь: http://ru.php.net/manual/en/ref.iconv.php

Регулярное выражение для проверки email-адреса

Короче сегодня сидел ебался с поиском RegEx — выражения для проверки корректности введённого мыла. После того как потратил 15 минут на поиск в сети, сказал «а ну его на хуй», и поискал в своих старых исходниках. Нашёл. Нате, может кому пригодится :)

// проверка email на корректность
function valid_email($email)
{ 
    return eregi("^[a-zA-Z0-9._-]+@[a-z0-9._-]+.[a-z]{2,4}$", $email);
}

Расчёт расстояний между двумя точками на поверхности Земли

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

Есть два пути решения этой задачи. Первый — самый простой. Тупо перебрать массив и на каждом шаге вычислять радиус между двумя точками: опорной и текущей. Если радиус в пределах — выносим его в буфер, а после прохода — возвращаем сам буфер.

Второй — выяснить, какой шаг по географическим координатам соответствует шагу реальному, допустим, в 10 метров? К примеру это 0.00005. Вычисляем сдвиг, и по нему делаем выборку из БД (обычно всё это там хранится) по условию. Получится, правда не радиус, а квадратик. Но то не беда.

По некоторым причинам решили сделать по-тупому, т.е. получить все записи, а потом по ним пройтись.Целый день я ел себе мозг, пытаясь перевести какую-то хуйню и недоразумение (cos(d)) в нормальное число d в радианах, а затем и в километрах. После долгой и пиздецкой ебли, наконец таки нашёл формулу, которая выдаёт РАБОЧЕЕ значение в километрах. Короче вот кусок кода, отвечающий непосредственно за расчёт:

$dist_km = 111.2 * acos( abs(sin($db_latitude)*sin($_latitude) + 
cos($db_latitude)*cos($_latitude)*cos( abs($db_longitude - $_longitude) ) ) );