Всем привет.
Хочу поделиться очень простым и легким способом организации e-mail очереди с помощью Zend_Mail. Примеры в статье намеренно сделаны максимально простыми и не привязаны к фреймворку, т.к. цель статьи показать способ, а не конкретную реализацию. К тому же данное решение не обязательно должно быть использовано только в рамках Zend Framework, оно легко впишется в любой проект.
Сразу к делу. Нам потребуется:
— таблица в БД;
— класс транспорта;
— файл реализующий отправку сообщений (запускается по крону).
Сперва определим таблицу БД:
Далее, создадим класс нашего транспорта:
Всё что делает этот класс, это — получает уже подготовленные данные из Zend_Mail и сохраняет их в БД. Eсли создать запись в таблице не удалось, выбрасывает исключение Zend_Mail_Transport_Exception.
Ну и файл реализующий отправку сообщений, предположим, что файл лежит в отдельной папке, в корне приложения:
Использование очереди:
Соответственно, если нужно отправить сообщение без очереди, через mail() — не передайте транспорт либо передайте null.
P.S. Возможно кто-то посчитает более правильным использовать Zend_Queue, и возможно будет прав. Я ни в коем случае не утверждаю, что способ описанный в статье является единственным верным решением. Решений всегда множество, я описал всего лишь один из них.
P.P.S. Отдельное спасибо за дельные замечания пользователям markPnk и gen.
Хочу поделиться очень простым и легким способом организации e-mail очереди с помощью Zend_Mail. Примеры в статье намеренно сделаны максимально простыми и не привязаны к фреймворку, т.к. цель статьи показать способ, а не конкретную реализацию. К тому же данное решение не обязательно должно быть использовано только в рамках Zend Framework, оно легко впишется в любой проект.
Сразу к делу. Нам потребуется:
— таблица в БД;
— класс транспорта;
— файл реализующий отправку сообщений (запускается по крону).
Сперва определим таблицу БД:
CREATE TABLE email_queue (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
recipients TEXT NOT NULL,
subject CHAR(255) NOT NULL,
message TEXT NOT NULL,
header TEXT NOT NULL,
parameters TEXT,
max_attempts TINYINT UNSIGNED NOT NULL DEFAULT 3,
attempts TINYINT UNSIGNED NOT NULL DEFAULT 0,
is_false TINYINT(1) UNSIGNED NOT NULL DEFAULT 0,
in_process INT UNSIGNED DEFAULT NULL DEFAULT 0,
time_last_attempt INT UNSIGNED DEFAULT NULL,
create_time INT UNSIGNED NOT NULL
);
Далее, создадим класс нашего транспорта:
class EmailQueueTransport extends Zend_Mail_Transport_Sendmail
{
/**
* Send mail using EmailQueue
*
* @access public
* @return void
* @throws Zend_Mail_Transport_Exception If parameters is set but not a string
* @throws Zend_Mail_Transport_Exception If failed to add a message in queue
*/
public function _sendMail()
{
if ($this->parameters !== null && !is_string($this->parameters)) {
/**
* Exception is thrown here because $parameters is a public property
*/
throw new Zend_Mail_Transport_Exception('Parameters were set but are not a string');
}
$db = Zend_Db_Table_Abstract::getDefaultAdapter();
$statement = $db->prepare('
INSERT email_queue
SET recipients = :recipients,
subject = :subject,
message = :message,
header = :header,
parameters = :parameters,
create_time = :create_time
');
$result = $statement->execute(array(
'recipients' => $this->recipients,
'subject' => $this->_mail->getSubject(),
'message' => $this->body,
'header' => $this->header,
'parameters' => $this->parameters,
'create_time' => time()
));
if (!$result) {
throw new Zend_Mail_Transport_Exception(
'Failed to add a message in queue.');
}
}
}
Всё что делает этот класс, это — получает уже подготовленные данные из Zend_Mail и сохраняет их в БД. Eсли создать запись в таблице не удалось, выбрасывает исключение Zend_Mail_Transport_Exception.
Ну и файл реализующий отправку сообщений, предположим, что файл лежит в отдельной папке, в корне приложения:
<?php
/**
* Add the messages in the log
*
* @param type $message
* @return void
*/
function set_log($message)
{
$message = date('H:i d.m.Y ', time()) . $message . "\r\n";
$logFile = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'logs' . DIRECTORY_SEPARATOR . basename(__FILE__, '.php') . '.log';
error_log($message, 3, $logFile);
}
try {
$config = include realpath(dirname(__FILE__) . '/../') . '/application/config/config.php'; // Path to config file
$configDb = $config['db']['params'];
$db = new PDO(
'mysql:host=' . $configDb['host'] . ';dbname=' . $configDb['dbname'],
$configDb['username'],
$configDb['password'],
array(
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'",
'profiler' => false,
)
);
} catch (PDOException $e) {
set_log('Connection error: ' . $e->getMessage());
}
$limit = 100; // limit rows
$processLimitTime = 600; // 10 minutes
$statement = $db->query('
SELECT *
FROM email_queue
WHERE attempts < max_attempts
AND in_process < ' . (time() - $processLimitTime) . '
ORDER BY id ASC
LIMIT ' . $limit
);
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
$db->beginTransaction();
$result = $db->exec('
UPDATE email_queue
SET in_process = ' . time() . '
WHERE id = ' . $row['id']
);
if (!$result) {
set_log('Error when updating record from the table "email_queue". Id queue is ' . $row['id'] . '.');
continue;
}
$isSent = mail(
$row['recipients'],
$row['subject'],
$row['message'],
$row['header'],
$row['parameters']
);
$db->commit();
if ($isSent) {
$result = $db->exec('DELETE from email_queue WHERE id = ' . $row['id']);
if (!$result) {
set_log('Error when deleting record from the table "email_queue". Id queue is ' . $row['id'] . '.');
}
} else {
$result = $db->exec('
UPDATE email_queue
SET is_false = 1,
in_process = 0,
attempts = ' . ($row['attempts'] + 1) . '
WHERE id = ' . $row['id']
);
if (!$result) {
set_log('Error when updating record from the table "email_queue". Id queue is ' . $row['id'] . '.');
}
set_log('Error when sending messages to e-mail. Id queue is ' . $row['id'] . ', e-mails is ' . $row['recipients'] . '.');
}
}
Использование очереди:
$mail = new Zend_Mail();
$mail->setFrom($from);
$mail->addTo($to);
$mail->setBodyHtml($body);
$mail->setSubject($subject);
$transport = new \EmailQueueTransport();
$mail->send($transport);
Соответственно, если нужно отправить сообщение без очереди, через mail() — не передайте транспорт либо передайте null.
P.S. Возможно кто-то посчитает более правильным использовать Zend_Queue, и возможно будет прав. Я ни в коем случае не утверждаю, что способ описанный в статье является единственным верным решением. Решений всегда множество, я описал всего лишь один из них.
P.P.S. Отдельное спасибо за дельные замечания пользователям markPnk и gen.