понедельник, 10 сентября 2012 г.

PHP расширение mysqli - Асинхронные запросы



Асинхронный, неблокирующий ввод-вывод это старая идея. Вот и в PHP расширении mysqli присутствует возможность асинхронных запросов. Эта возможность доступна только при использовании библиотеки mysqlnd (в противовес libmysql).

В обычной схеме, после отправки запроса к БД PHP-скрипт заблокирован до получения ответа (в самом распространённом варианте: до получения всех записей соответствующих запросу). При использовании асинхронности скрипт продолжает работу сразу же после отправки запроса, а затем уже можно производить опрос mysqli-объектов с целью получения ответов.

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

MySQL протокол клиент-сервер не имеет встроенной поддержки асинхронности, поэтому для её реализации в PHP необходимо отдельное соединение (объект mysqli) на каждый запрос.

Для отправка асинхронного запроса в метод mysqli::query() добавляется второй параметр «MYSQLI_ASYNC»:
$mysqli = new mysqli('host', 'user', 'pass', 'database');
$mysqli->query('...', MYSQLI_ASYNC);
При опросе соединения на предмет получения результата предлагается использовать статический метод mysqli::poll(). Но в PHP 5.3.3 (той, что у меня сейчас установлена) этот метод почему-то недоступен, поэтому я использую процедурный вариант, т.е. mysqli_poll():
mysqli_poll($read, $write, $error, 1);
Добавим проверку ошибок, вывод результатов и получим рабочий пример:
$mysqli = new mysqli('host', 'user', 'pass', 'database');
$mysqli->query('SELECT * FROM `test`', MYSQLI_ASYNC);
 
$all_links = array($mysqli);
$processed = 0;
 
do
{
    $read = $write = $error = array();
 
    foreach ($all_links as $link)
    {
        $read[] = $write[] = $error[] = $link;
    }
 
    if (!mysqli_poll($read, $write, $error, 1))
    {
        if (count($error))
            foreach ($error as $link)
            {
                echo $link->error . '<br />';
                $processed++;
        }
    }
 
    foreach ($read as $link)
    {
        if ($result = $link->reap_async_query())
        {
            if (is_object($result)) // for SELECT
            {
                print_r($result->fetch_row());
                $result->free();
 
            } else { // for INSERT / UPDATE / DELETE
 
                print_r($link);
            }
 
            $processed++;
        }
    }
}
while ($processed < count($all_links));
 
$mysqli->close();
Описанная выше функциональность плохо документирована, поэтому следующие ссылки могут пригодиться:

  1. PHP: parallel, background, asynchronous fetch
  2. PHP: How mysqlnd async queries help you with sharding!

понедельник, 3 сентября 2012 г.

PHP расширение mysqli



mysqli (MySQL Improved Extension) — это одно из трёх API предоставляемых расширениями PHP для работы с MySQL (другие два это: mysql и PDO).

mysqli и PDO являются рекомендованными для новых проектов, в отличии от mysql которая считается устаревшей.

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

mysqli по сравнению с mysql имеет следующие преимущества:
  1. ОО интерфейс (процедурный интерфейс тоже в наличии);
  2. поддержка неблокирующих, асинхронных запросов (с mysqlnd);
  3. поддержка подготовленных выражений (Prepared Statements) на стороне сервера;
  4. поддержка хранимых процедур;
  5. поддержка множественных запросов (Multiple Statements);
  6. поддержка транзакций;
  7. продвинутые возможности отладки;
  8. поддержка всей функциональности MySQL 5.1+.
В этой статье я рассмотрю только базовые возможности этого расширения.
Как уже упоминал, mysqli поддерживает и ОО, и процедурный интерфейс. Разницы в производительности нет и выбор зависит от предпочтений. Далее я буду использовать ОО подход, так как применяю его, по возможности, и в ежедневной работе.

Подключение

Класс mysqli представляет собой подключение к БД, и среди свойств содержит следующие:
  • $affected_rows — количество строк в результате последнего запроса;
  • $connect_errno — код ошибки последнего подключения;
  • $connect_error — описание ошибки последнего подключения;
  • $errno — код ошибки последней вызванной функции;
  • $error — описание последней ошибки;
  • $insert_id — автоматически сгенерированный идентификатор использованный в последнем запросе.
Для подключения к базе используется конструктор:
new mysqli('host', 'user', 'pass', 'database', 'port');
Но, если необходимо установить какие-то опции подключения, то нужно использовать метод real_connect():
$mysqli = new mysqli();
$mysqli->options('option', 'value');
$mysqli->real_connect('host', 'user', 'pass', 'database');
Базовые запросы выполняются следующим образом:
$result = $mysqli->query('...');
$result->free();
$mysqli->close();

Результат запроса

Метод mysqli::query() при запросе, возвращающем значения, вернёт объект класса mysqli_result.

При помощи объекта mysqli_result можно получить записи которые выбрал запрос:
if ($result->num_rows)
    while ($row = $result->fetch_assoc())
    {
    }
 
$result->free();

Помимо классов mysqli и mysqli_result есть ещё:
  • mysqli_stmt - подготовленное выражение (Prepared Statements);
  • mysqli_driver – не документировано;
  • mysqli_warning – не документировано;
  • mysqli_sql_exception – не документировано.
В заключение полный пример базового использования mysqli:
$mysqli = new mysqli('localhost', 'root', '', 'test');
 
if ($mysqli->connect_error)
    exit('Connect error: "' . $mysqli->connect_error . '"');
 
$result = $mysqli->query('SELECT * FROM `test`') OR
    exit('Query error: "' . $mysqli->error . '"');
 
while ($row = $result->fetch_assoc())
{
    print_r($row); echo '<br />';
}
 
$result->free();
$mysqli->close();

понедельник, 27 августа 2012 г.

Социальная Flash-игра — Игровое поле


Этот этап занял некоторое время. Хорошо что нашёлся ещё один пример с открытым кодом от Adam Zwakk, что мне очень помогло:

Создать игровое поле было довольно просто, особенно имея в наличии примеры реализации. Но были и ошибки с которыми я столкнулся как новичок. Кроме того в примерах я не нашёл части нужной функциональности, которую пришлось доделывать самому.

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

Вот код примера: скачать. А дальше я расскажу об основных препятствиях.


Система координат


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

Изображение в As3Iso.lib строится следующим образом:
  • IsoView - {x = 0; y = 0} в центре экрана, положительные значения вверх и влево.
  • IsoScene – координат не содержит
  • IsoSprite - имеет доступ к двум системам координат:
    • 1. IsoSprite.container – (спрайт отображающий объекты IsoSprite) {x = 0; y = 0} в центре экрана, но положительные значения вправо и вниз.
    • 2. Изометрические координаты IsoSprite, о которых сейчас речь не идёт.
  • Sprite – {x = 0; y = 0} в центре экрана, положительные значения вправо и вниз.

Масштабирование и скроллинг


Масштабирование и скроллинг по отдельности устроены довольно просто. Но совмещение этих двух вещей с наложением ограничений на скроллинг может стать проблемой.

Чтобы ограничить скроллинг создаётся специальный спрайт, размером превышающий окно игры и прикреплённый к текущему IsoView следующим образом:

pan_limit_sp = new Sprite();
// задаётся размер и положение в пространстве
updatePanLimitSize(); 
view.backgroundContainer.addChild(pan_limit_sp);
 
view.rangeOfMotionTarget = pan_limit_sp;
view.limitRangeOfMotion  = true;

После этого скроллинг возможен до тех пор пока окно игры расположено в пределах «pan_limit_sprite».

При масштабировании «pan_limit_sprite» должен соотвественно менять размер и положение чтобы границы окна игры постоянно находились внутри него, иначе эта система не будет работать.

Полное решение можно посмотреть в коде примера (ссылка на скачивание выше). Масштабирование выполняется колесом мышки. Попробуйте увеличить спрайт до максимума, передвинуть до упора в любую из сторон, затем уменьшить до минимума.

понедельник, 20 августа 2012 г.

PHP - закругление углов у картинки

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

Для выполнения задания необходимо было обойтись по возможности штатными средствами PHP, потому что установка чего-то дополнительного — это отдельный процесс. Поэтому я стал искать решения в модуле GD.

Если вам, также как и мне, интересно что значит «GD», то вот ответ:

В GD 1.0, это означало «gif draw». После того как Unisys запатентовало алгоритм LZW сжатия, используемый в GIF и поддержка GIF была убрана, аббревиатура оффицально ничего не обознает, поэтому пусть просто будет «graphics draw» и всё. (поддержка GIF возвращена, благодаря тому что срок действия патента истёк, но GD может гораздо больше чем создание GIF)

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

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

Поэтому последовательность действий у меня следующая:
  1. на временной картинке рисую те части, которые должны стать прозрачными в результате;
  1. накладываю на исходное изображение;
  2. по нужным координатам заполняю прозрачным цветом (результат вверху).
Чтобы прозрачность работала, у исходной картинки необходимо установить соответствующий флаг при помощи функции:
imagesavealpha($img_res, true);
, а вот наличие альфа канала у временного изображения не важно (я использовал true color, потому что в этом случае на один вызов функции imagecolortransparent() меньше).

Решение:
class ImgUtils
{
    public static function imageToRes($img_file)
    {
        $img_res = false;
 
        list($tmp, $tmp, $type) = getimagesize($img_file);
 
        switch ($type)
        {
            case 2: // JPEG
 
                $img_res = imagecreatefromjpeg($img_file);
                break;
 
            case 1: // GIF
 
                $img_res = imagecreatefromgif($img_file);
                break;
 
            case 3: // PNG
 
                $img_res = imagecreatefrompng($img_file);
                break;
        }
 
        return $img_res;
    }
 
 
    public static function roundedCorners($img_res, $radius)
    {
        $width  = imagesx($img_res);
        $height = imagesy($img_res);
 
        imagesavealpha($img_res, true);
 
        // corners START
        $img_tmp = imagecreate($width, $height);
 
        $black  = imagecolorallocate($img_tmp, 0, 0, 0);
        $transp = imagecolorallocatealpha($img_tmp,
                                            0, 0, 0, 127);
 
        imagefill($img_tmp, $width / 2, $height / 2, $transp);
 
        // left upper
        imagearc($img_tmp,  $radius / 2 - 1,
                            $radius / 2 - 1,
                            $radius,
                            $radius, 180, 270, $black);
        imagefill($img_tmp, 0, 0, $black);
        // right upper
        imagearc($img_tmp,  $width - $radius / 2,
                            $radius / 2 - 1,
                            $radius,
                            $radius, 270, 0, $black);
        imagefill($img_tmp, $width - 1, 0, $black);
        // right lower
        imagearc($img_tmp,  $width - $radius / 2,
                            $height - $radius / 2,
                            $radius,
                            $radius, 0, 90, $black);
        imagefill($img_tmp, $width - 1, $height - 1, $black);
        // left lower
        imagearc($img_tmp,  $radius / 2 - 1,
                            $height - $radius / 2,
                            $radius,
                            $radius, 90, 180, $black);
        imagefill($img_tmp, 0, $height - 1, $black);
 
        // corners END
 
        imagecopyresampled($img_res, $img_tmp,
                            0, 0, 0, 0,
                            $width, $height, $width, $height);
 
        $transp = imagecolorallocatealpha($img_res,
                                            0, 0, 0, 127);
        imagefill($img_res, 0, 0, $transp);
        imagefill($img_res, $width - 1, 0, $transp);
        imagefill($img_res, $width - 1, $height - 1, $transp);
        imagefill($img_res, 0, $height - 1, $transp);
 
        imagedestroy($img_tmp);
 
        return $img_res;
    }
}
Изначально я пытался нарисовать «уголки» на временном изображении при помощи одного прямоугольника и одного эллипса наложенного поверх. Оказалось что в таких условиях трудно контролировать радиусь углов — тогда я стал рисовать на каждый угол по своей окружности.

Фотографию предоставила Анастасия Мельникова (StacyLV).

вторник, 14 августа 2012 г.

FlashDevelop - опыт использования

Изучая примеры Flash-игр я начал пользоваться FlashDevelop просто потому, что примеры, которыми я распологал, были созданы в нём. Пользуюсь я им уже около года, без всякого предварительного обучения - всё что мне нужно было для работы с AS 3.0-проектами я смог найти методом тыка (тем более, что документация не поражает глубиной описания).

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

FlashDevelop (FD) – это IDE (Integrated Development Environment) для вэб-разработчиков, с поддержкой ActionScript (AS) 2 и 3 (и не только AS), с автодополнением, компиляцией и отладкой, шаблонами проектов и много чем другим.

Речь пойдёт о FlashDevelop версии 4.0.4 RTM (RTM? Wikipedia подсказывает, что возможно это "release to manufacturing" или "release to marketing").

Общие заметки

После установки FD полностью готов к работе: можно создать AS-проект, скомпилировать и запустить.

По умолчанию для компиляции AS FD использует бесплатную Flex SDK, которая устанавливается вместе с IDE. Это тот вариант, которым я всё время пользовался.

Основные комбинации клавишь, которые я использовал в работе с FD:
  1. F5 – компиляции;
  2. F8 – тестовый запуск;
  3. Shift+F5 - принудительная остановка отладчика;
  4. и, наверное, ещё ESC - закрытие подсказки автодополнения.
На данный момент моей основной задачей с AS было разбираться в устройстве чужих проектов. Поэтому наиболее востребованной функциональностью являлись:
  1. Всплывающие подсказки при наведение на название класса;
  2. Возможность кликнуть правой кнопкой на названии класса и перейти к его определению;
  3. Закладка «Outline» содержащая интерактивное дерево текущего класса + импортируемые и родительский классы.

Автодополнение работает хорошо и почти не мешается. Особенно нравится автдополнение в директиве import: можно написать только название самого класса, а FD уже выдаст список с полными путями.

Особенности FlashDevelop

По-умолчанию все файлы (*.as и .as3proj) открываются в одном и том же окне программы. Если нужно запускать несколько окон FD, то в корневой папке программы нужно создать файл .multi и перезапустить программу. После этого каждый файл, ассоциированный с FD, запускает свою отдельную версию - это учит пользоваться встроенным Project explorer (можно ещё использовать drag`n`drop на нужном окне).

Также при закрытии FD не запоминает проекты и вкладки всех окон, а только последнего.

При отладке тоже есть свой нюанс (но это скорее относится к самому Flash Player, чем к FD). Если при тестовом запуске приложение не может запуститься из-за ошибки, окно Flash Player  вашего приложения остаётся позади всех остальных окон и не появляется в task bar даже после принудительной остановки отладчика (через меню Debug или Shift+F5). Если это окно не найти и не закрыть, то последующие попытки запустить компиляцию приведут к ошибке:

The process cannot access the file 'D:\Dev\Flash\MyProject\bin\ MyProject .swf' because it is being used by another process.

По не знанию может быть трудно догадаться о причине этого сообщения.

Другая функциональность

FD предлагает большое количество настроек:
  1. Расположения окон интерфейса;
  2. Изменения всевозможных цветов (подсветки синтаксиса и фона окон);
  3. Автодополнения и шаблонов классов;
  4. Изменения пунктов главного меню через XML-файлы;
  5. Некоторых комбинаций клавишь.
Также есть поддержка макросов — внутренних команд («запустить программу», «открыть файл», «сообщение плагину» и др.) с набором аргументов («имя файла/программы» и др.).

Помимо этого, существуют ещё и плагины - скрипты использующие синтаксис C#.

У меня потребность в плагинах не возникала, за исключением одного: Apache Ant. Этот плагин выглядит как дополнительная панель и предоставляет графический интерфейс к одноимённой программе.

Дополнительно из интересного:
  1. Встроенный профайлер;
  2. Закладки (Bookmarks) - напоминают точки останова в отладчике, с возможностью просмотра списка закладок и перехода по нему (причём для точек останова, есть похожая панель, только кликать там надо на колонку с номером строки);
  3. Список задач (Task List) — по исходному коду ведётся отслеживание меток (по умолчанию это - TODO, FIXME, BUG). В панели Tasks выводится список найденных меток с указанием файли и строки;
  4. Автоматическое построение документации (интерфейс к ASDoc);
  5. Анализ кода на потенциальные ошибки;
  6. Проверка синтаксиса. Запускается автоматически, когда начинаешь редактировать код, из-за этого FD может «задумываться» на несколько секунд, но это случается изредка;
  7. Просмотр типов проекта (Type Explorer) – дерево всех классов с их открытыми методами. Практически тоже самое, что и закладка Outline только глобально.
Заключение

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

Единственная бесплатная альтернатива FD, которую мне удалось найти, и которая не заброшена разработчиками это FDT 5 Free. Нужно попробовать её в работе, тогда будет что сравнить с FD.

понедельник, 6 августа 2012 г.

PHP Sockets - отправка файла

Это продолжение записки "PHP модуль Sockets для HTTP запросов", здесь будет дополнение к ранее написанному и я не буду приводить весь код класса.

Уже при написании HttpSocket мне было интересно узнать, как составить HTTP заголовок для отправки файла. Поэтому я решил разобраться что и как, и дописать эту функциональность.

Как указано в RFC 1867, при отправке файла методом POST, заголовок Content-Type должен принять значение «multipart/form-data». Также Content-Type должен содержать определение boundary:

Content-Type: multipart/form-data; boundary=ABCuniq_idABC

Более того теперь тело запроса разделено на несколько частей начинающихся, разделённых и заканчивающихся строкой указанной в boundary. Каждая часть содержит свой «подзаголовок» и выглядит следующим образом:

--ABCuniq_idABC
Content-Disposition: form-data; name="var_name1"

value1
--ABCuniq_idABC
Content-Disposition: form-data; name="var_name2"

value2
--ABCuniq_idABC
Content-Disposition: form-data; name="img_file"; filename="IMG_20120305_102159.jpg"
Content-Type: image/jpeg

<содержимое файла>
--ABCuniq_idABC--

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

$_FILES = Array(
[img_file] => Array(
[name] => IMG_20120305_102159.jpg
[type] => image/jpeg
[tmp_name] => C:\WINDOWS\Temp\php5DF.tmp
[error] => 0
[size] => 450055
)
)

В дополнение к сделанному, я думал что лучше будет отправлять файл закодированным в base64, чтобы данные не исказились, да и при выводе заголовка он выглядит лучше. Я исследовал такие поля как Content-Encoding, Transfer-Encoding и даже Content-Transfer-Encoding.

Как выяснилось, протокол HTTP принимает двоичные данные (содержимое файла) без проблем и кодирование не нужно. Поля Content-Encoding, Transfer-Encoding нужны соотвественно для сжатия и передачи содержимого по частям, а поле Content-Transfer-Encoding не поддерживается HTTP и используется в почтовых протоколах.

Если с заголовком всё понятно, то написание метода не создаст проблем. Но перед этим, одно «но»: на данный момент в PHP нет надёжного встроенного способа определить MIME-тип произвольного файла. Зато есть простой способ определить MIME-тип изображений. Поэтому я буду отправлять только изображения.

Получившийся у меня код метода класса HttpSocket:

public function reqPostFile($addr,
                            $var_arr = array(),
                            $file_path = null)
{
    $this->parseAddr($addr);
 
    $boundary = '>>============' . uniqid() . '<<';
    $req_body = '';
 
    foreach ($var_arr as $var_name => $var_val)
    {
        $req_body .= '--' . $boundary . NL;
        $req_body .= 'Content-Disposition: form-data;' .
            ' name="' . $var_name . '"' . DNL;
 
        $req_body .= $var_val . NL;
    }
 
    if (!is_null($file_path))
    {
        if (($mime = getimagesize($file_path)) !== false)
        {
            $mime = $mime['mime'];
 
            $req_body .= '--' . $boundary . NL;
            $req_body .= 'Content-Disposition: form-data;' .
                ' name="img_file";' .
                ' filename="' . basename($file_path) . '"' . NL;
 
            $req_body .= 'Content-Type: ' . $mime . DNL;
            $req_body .= file_get_contents($file_path) . NL;
 
        } else {
 
            trigger_error('Mime type of image 
                "' . $file_path . '" can`t be detected',
                E_USER_ERROR);
        }
    }
 
    $req_body .= '--' . $boundary . '--' . NL;
 
 
    $header = 'POST ' . $this->query_str . ' HTTP/1.1' . NL;
    $header .= 'Host: ' . $this->host_str . NL;
    $header .= 'Content-Type: multipart/form-data;' .
        ' boundary=' . $boundary . NL;
 
    $header .= 'Content-Length: ' . strlen($req_body) . NL;
    $header .= 'Connection: Close' . DNL;
 
    $header .= $req_body;
 
    return $this->doQuery($header);
}

понедельник, 30 июля 2012 г.

Smarty и рассылка писем

"Smarty это - движок шаблонов (шаблонизатор) для PHP. Точнее, он предоставляет легко управляемый способ отделения логики и содержания приложения от его внешнего вида." ©документация.

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

Установка Smarty подробно описана в документации. Только отмечу, что гораздо удобнее использовать продвинутую установку, т.е. создать свой класс, расширяющий класс Smarty и задающий начальные установки:

require_once('Smarty-X.X.XX/libs/Smarty.class.php');


class MySmarty extends Smarty
{
 function __construct()
 {
  parent::__construct();

  $this->template_dir = CWD . '/modules/Smarty-X.X.XX/templates/';
  $this->compile_dir = CWD . '/modules/Smarty-X.X.XX/templates_c/';
  $this->config_dir = CWD . '/modules/Smarty-X.X.XX/configs/';
  $this->cache_dir = CWD . '/modules/Smarty-X.X.XX/cache/';

  //$this->caching  = Smarty::CACHING_OFF;
  //$this->debugging  = true;
  //$this->error_reporting = E_ALL;
 }
}

$Smarty = new MySmarty();

При обычном использовании шаблоны Smarty отображаются функцией display() :

$Smarty->assign('lang_code', 'RU');
$Smarty->assign('curr_date', date('d.m.Y') );

$Smarty->display('test_templ.tpl');

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

Я решил попробовать использовать Smarty для этой цели. Как оказалось функции display() и fetch() используют понятие ресурс: в первом параметре, перед основным значением можно вставить строку "string: " и тогда остальное значение будет воспринято как текст шаблона вместо пути к файлу. И в сочетании с функцией fetch(), возвращающей результат в виде строки, это всё что нужно для моей задачи.

В этом схематичном примере сначала Smarty переменные в тексте заменяются нужными значениями, затем текст вставляется в подготовленный заранее шаблон с HTML:

set_time_limit(0);

require_once(CWD . '/modules/Smarty.php');

while (true)
{
 $res = db_query('...');

 while ($row = db_fetch($res))
 {
  $Smarty->assign('FirstName',  $row['first_name']);
  $Smarty->assign('LastName',  $row['last_name']);

  $text_ready  = $Smarty->fetch('string: ' . $row['text']);
  $Smarty->assign('text',  $text_ready);

  $mail_body  = $Smarty->fetch(CWD . 'mail/generic_templ.tpl');


  mail($row['to'], $row['subj'], $mail_body);

  db_query('...'); // set mail as sent

  $Smarty->clearAllAssign();
 }
}

Помимо "string: " для работы со строками есть ещё "eval: " отличающийся только тем, что шаблон перекомпилируется каждый раз заново.