Комментарии

Файл текущего контроля подписок
( 0 Голосов )

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

jimport('joomla.application.cli');
jimport('joomla.database.database');

/** * Приложение на платформе Joomla! для текущего контроля состояния подписок и выполнения необходимых действий. * @package Subscriptions * @since  1.0
*/
class SubscriptionMonitor extends JCli {
/** * @var JDatabase * Объект для установления связи приложения с базой данных. * @since 1.0
*/
protected $db;

Сначала в этой части исходного кода импортируются классы JCli и JDatabase, а затем объявляется класс SubscriptionMonitor в качестве подкласса, производного от класса JCli. И наконец, в этот класс вводится защищенное поле $db, в котором должен храниться объект типа JDat abase для данного класса.

Ниже приведена следующая часть исходного кода из файла subscriptionmonitor.php.

public  function construct(JInputCli $input - null, JRegistry $config = 0>null, JDispatcher $dispatcher = null, JDatabase $db = null) {

// вызвать конструктор родительского класса для основной установки объекта parent:: construct($input,   $config,   $dispatcher);
// воспользоваться объектом для связи с базой данных, если он задан if ($db instanceof JDatabase) { $this->db = $db; }
// установить соединение с базой данных, исходя из логики выполнения данного приложения else { $this->loadDatabase();

В сигнатуре вызова конструктора из приведенного выше кода присутствуют следующие четыре аргумента: $input, $config, $dispatcher и $db. И хотя в данном примере эти аргументы не требуются, они все же указаны на тот случай, если они понадобятся при использовании данного класса в другом контексте. В частности, первый аргумент позволяет передать данному конструктору объект типа JInputCli, имитирующий аргументы командной строки. А в качестве остальных аргументов при вызове данного конструктора можно указать собственный конфигурационный файл, класс диспетчера и класс базы данных.

Обратите также внимание на еще одно отличие в сигнатуре вызова данного конструктора по сравнению с тем, что встречалось в предыдущих примерах исходного кода. В данном случае перед каждым аргументом явно указывается класс (например, JInputCli $ input). Это языковое средство РНР называется явный указанием типов. Оно явным образом сообщает интерпретатору РНР (и, конечно, тому, что читает исходный код) о том, что данный аргумент должен быть объектом указанного типа. В противном случае при выполнении метода или конструктора возникнет неисправимая ошибка. Однако явное указание типов стало доступным лишь с версии РНР 5.3. В коде данного конструктора сначала вызывается конструктор родительского класса, а затем проверяется, установлено ли поле $db. Если этого не сделано, то для установки данного поля вызывается метод loadDatabase().

Далее в классе SubscriptionMonitor определяется метод doExecute (). Напомним, что этот метод вызывается в методе execute () из класса JCli. Ниже приведен исходный код этого метода.

protected function doExecute() {

// зарегистрировать начало программы JLog::add('Subscription monitoring started');
// получить список окончившихся подписок и удалить их $ended = $this->getEndedSubscriptions(); foreach ($ended as $sub) { $this->doEndSubscription($sub);
// получить список завершающихся подписок и послать уведомление $ending = $this->getEndingSubscriptions(); foreach ($ending as  $sub) { $this->doSendSubscriptionEndingNotification($sub); }
// зарегистрировать конец программы JLog::add('Subscription monitoring ended'); }

Именно в этом методе фактически осуществляется управление выполнением данного приложения. Сначала в нем делается запись в журнал регистрации о начале выполнения программы. Затем вызывается метод getEndedSubscriptions () для получения списка подписок, срок действия которых уже истек. Все они просматриваются в цикле, и для обработки каждой устаревшей подписки вызывается метод doEndSubscription (). Что касается применения циклов f oreach в данном контексте, то следует обратить внимание на одну интересную особенность. Вполне возможно, что устаревшие подписки не обнаружатся, и тогда массив $ended окажется пустым. В таком случае цикл foreach будет просто пропущен без каких-либо ошибок или замечаний.

После обработки устаревших подписок вызывается метод getEndingSubscrip-tions () для получения списка подписок, срок действия которых близок к истечению. И в этом случае каждая подписка обрабатывается в цикле f oreach, для чего вызывается метод doSendSubscriptionEndingNotification (). И в конце рассматриваемого здесь метода в журнале регистрации делается запись о завершении программы.

Далее в классе SubscriptionMonitor определяется метод getEndedSubscriptions (). В этом методе делается запрос базы данных на получение списка устаревших подписок. Ниже приведен исходный код этого метода:

protected function getEndedSubscriptions() {

// получить объект текущей даты $now = JFactory::getDate($this->get('execution.datetime'));
// получить класс для составления запроса из базы данных $query = $this->db->getQuery(true);
// составить запрос на выборку подписок с датой окончания, наступившей раньше текущей даты $query->select('*') ->from($this->db->qn('# joompro_sub_mapping')) ->where($this->db->qn('end_date') . ' < ' . "4>$this->db->q($now->toSQL() ) ) ->where($this->db->qn('end_date') . ' > ' . ^$this->db->q('0000-00-00 00:00:00'));
// установить объект запроса базы данных в качестве объекта для связи с базой данных $this->db->setQuery($query);
// получить все строки, возвращаемые из таблицы по запросу, в виде массива объектов $rows = $this->db->loadObjectList(); return $rows,

Сначала в данном методе получаются текущие дата и время, а затем составляется запрос на выборку всех строк из таблицы соответствий, в столбце enddate которых хранятся более ранние дата и время, чем текущие. Для получения текущих даты и времени служит приведенный ниже код.

($this->get('execution.datetime')

В этом коде вызывается метод get () из класса JCli. Ниже приведен исходный код данного метода.

public function get($key, $default = null) {
return $this->config->get($key, $default); }

В коде данного метода вызывается метод get () для поля config текущего класса. А где же устанавливается значение execution. datetime для текущих даты и времени? Это делается в конструкторе класса JCli, где имеется следующая строка кода:

$this->set('execution.datetime', gmdate('Y-m-d H:i:s'));

Результат выполнения метода gmdate (), вызываемого в методе set (), вводится в поле config текущего класса. Метод gmdate () возвращает текущее время по Гринвичу. А зачем сохранять это значение в поле config вместо того, чтобы вызывать метод gmdate () всякий раз, когда требуется текущее время? Дело в том, что при каждом вызове метода gmdate () из него будут возвращаться разные значения. Если же вызвать его один раз в конструкторе, то будет получено одно значение текущего времени, используемое в запросах, а следовательно, они будут всегда давать непротиворечивые результаты.

Кроме того, дата окончания подписки не должна быть нулевой, что проверяется специально. Напомним, что в системе Joomla CMS нулевой датой принято обозначать неопределенный срок действия. Добавление проверки на нулевую дату дает возможность применять такое же условное обозначение срока действия и для подписок.

Для составления запроса вызываемые методы связываются в цепочку, благодаря этому весь запрос формируется в одной строке кода, где результаты выполнения предыдущего метода передаются в качестве объекта следующему вызываемому методу. В противном случае методы select (), from () и дважды метод where () пришлось бы вызывать в отдельных строках кода. Как только запрос будет составлен, он передается объекту базы данных, а результаты его обработки возвращаются из метода loadObjectList (). Напомним, что этот метод возвращает массив объектов.

И наконец, в коде рассматриваемого здесь метода обращает на себя внимание еще одна интересная особенность. В нескольких местах этого кода вызывается метод qn () из класса JDatabase. Но если проанализировать исходный код объявления этого класса в файле libraries/joomla/database/database.php, то в нем не удастся обнаружить метод под названием qn (). А поскольку класс JDatabase не расширяет ни один из родительских классов, то где же находится определение метода qn (). Разгадка кроется в методе call () из класса JDatabase. Это один из нескольких "волшебных" методов языка РНР. Обращение к нему происходит всякий раз, когда вызывается метод, неопределенный в текущем классе. Чаще всего метод call () применяется для создания псевдонимов наиболее употребительных методов. Ниже приведен исходный код этого метода.

switch ($method) {
case 'q':
return $this->quote ($args[0], isset($args[1]) ? $args[l] : true);
break;
case 'nq':
case 'qn':
return $this->quoteName ($args[0]);
break; }

В этом методе по очереди проверяются имена методов q, nq и qn и затем вызывается метод quote () или quoteName (). Таким образом, метод qn () оказывается всего лишь псевдонимом метода quoteName (). Остается лишь обратить внимание на то, что при передаче аргументов (элементов массива $args) методам quote () и quoteName () необходимо соблюдать особую аккуратность.

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

protected  function getEndingSubscriptions()
{

// получить объекты двух дат: текущей даты и наступающей через несколько дней $now = JFactory:rgetDate($this->get('execution.datetime')); $future = JFactory::getDate($this->get('execution.datetime')); $future->add(new Datelnterval('P5D'));
// получить класс для составления запроса из базы данных $query = $this->db->getQuery(true);
// составить запрос на выборку подписок, срок действия которых истекает через пять дней от текущей даты $query->select('*') ->from($this->db->qn(*# joompro_sub_mapping')) ->where($this->db->qn('end_date') . ' < ' . > $this->db->q($future->toSQL())) ->where($this->db->qn('end_date*) . ' > ' . > $this->db->q($now->toMySQL()));
// установить объект запроса базы данных в качестве объекта для связи с базой данных $this->db->setQuery($query);
// получить все строки, возвращаемые из таблицы по запросу, в виде массива объектов $rows = $this->db->loadObjectList(); return $rows; }

Прежде всего в этом методе рассчитываются дата и время, наступающие в ближайшие несколько дней. Для этого создается объект текущей даты, сохраняемый в переменной $ future, а затем к этой дате прибавляются пять дней с помощью метода add (). Для получения пятидневного промежутка времени используется класс Datelnterval языка РНР, конструктор которого принимает аргумент в следующем формате:

Р + <число> + <промежуток времени>

В данном примере в качестве аргумента указывается значение P5D, обозначающее промежуток времени в пять дней. В конечном итоге в переменной $ future сохраняется значение даты и времени на пять дней позже, чем значение текущей даты и времени в переменной $now. Эти значения даты и времени затем используются для составления запроса на выборку любых подписок с датой окончания, наступающей позже текущей, но раньше, чем через пять дней. Как и прежде, все эти значения возвращаются в виде массива объектов.

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

Следующим в классе SubscriptionMonitor определяется метод doEndSub scriptions (), выполняющий несколько функций.

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

Ниже приведена первая часть исходного кода данного метода.

protected function doEndSubscription($subscriptionMapping) {
$subscription = $this->getSubscription(>$subscriptionMapping->subscription_id);
$user = $this->getUser($subscriptionMapping->user_id);

В этой части кода рассматриваемого здесь метода вызываются методы getSubscription () и getUser (). В них проверяется достоверность идентификаторов пользователя и подписки, а также получаются имя пользователя и название подписки для уведомления по электронной почте.

Ниже приведена следующая часть исходного кода из метода doEndSubscrip-tions ().

// составить запрос на исключение пользователя из группы
$query =  $this->db->getQuery(true); $query->delete()
->from($this->db->qn('# user_usergroup_map'))
->where($this->db->qn(fgroup_id') . ' = ' . (int) $subscription->group_id)
->where($this->db->qn('user_id') . ' = ' . (int) $subscriptionMapping->user_id);

//  установить  объект  запроса базы данных в  качестве объекта для связи с базой данных $this->db->setQuery($query);
// выполнить запрос $this->db->query();

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

Ниже приведена следующая часть исходного кода из рассматриваемого здесь метода.

// получить класс для  составления  запроса из  базы данных
$query =  $this->db->getQuery(true);

// составить зарос на удаление  строки из  таблицы соответствий пользователей и подписок $query->delete() ->from($this->db->qn('# joompro_sub_mapping')) ->where($this~>db->qn('subscription_id') . ' = ' . => (int) $subscriptionMapping->subscription_id) ->where($this->db->qn('user_id') . ' = ' . => (int) $subscriptionMapping->user_id);
//  установить  объект  запроса базы данных в качестве объекта для связи с базой данных $this->db->setQuery($query);
// выполнить запрос $this->db->query();

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

И наконец, ниже приведена последняя часть исходного кода из метода doEnd Subscriptions ().

// составить уведомление об окончании срока действия подписки для отправки пользователю по электронной почте
$subject = 'Your subscription has expired.';
$body[] = 'Hi, '.$user->name.',';
$body[] = ";
$body[] = 'Your subscription to '.$subscription->title.' has
^expired.';
$body[] = ";
$body[] = 'Regards, ';
$body[] = $this->get('fromname');

// отправить уведомление по электронной почте $this->sendNotificationEmail($user->email, $subject, implode("\n", $body)); JLog::add('Subscription removed for user='.$user->name.', title=(.$subscription->title) ; }

В этой части исходного кода сначала формируется содержимое уведомления, отправляемого пользователю по электронной почте. Затем вызывается метод sendNotificationEmail () для отправки составленного уведомления по электронной почте. И наконец, в журнале регистрации делается запись о том, что подписка данного пользователя удалена.

Далее в рассматриваемом здесь классе следует определение метода doSendSubscript ionEndingNotification (). Его исходный код приведен ниже.

protected function doSendSubscriptionEndingNotification($subscriptionMapping) {
$subscription = $this->getSubscription( ^$subscriptionMapping->subscription_id); $user =  $this->getUser($subscriptionMapping->user_id);
$subject = 'Your Subscription is ending soon.'; $body[] = 'Hi, '.$user->name.','; $body[] = ";
$body[] = 'Your subscription to '.$subscription-  >title. *bwill end on '.$subscriptionMapping->end_date.'.'; $body[] = " ; $body[] = 'Regards,'; $body[] = $this->get('fromname');

// отправить уведомление по электронной почте $this->sendNotificationEmail($user->email, $subject, implode("\n", ^>$body)) ;
// зарегистрировать уведомление в журнале JLog::add('Subscription ending notification sent for user='. $user->name.', title=(.$subscription->title); }

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

Ниже приведен код метода getSubscription (), определяемого далее в классе SubscriptionMonitor.

protected function getSubscription($subID) {

// составить запрос на получение идентификатора группы из записи о подписке  в базе данных $query = $this->db->getQuery(true); $query->select('*') ->from($this->db->qn('# joompro_subscriptions')) ->where($this->db->qn('id') . ' = ' . (int) $subID);
// установить объект запроса базы данных в качестве объекта для связи с базой данных $this->db->setQuery($query);
// проверить наличие подписки $subscription = $this->db->loadObject(); if (empty($subscription)) { throw new Exception('Invalid Subscription.'); } return $subscription; }

В этом методе формируется запрос на получение строки из таблицы подписок по идентификатору подписки. Напомним, что этот идентификатор должен быть однозначным, а следовательно, по данному запросу из таблицы должна быть возвращена единственная строка. Если подписка отсутствует по данному идентификатору, генерируется исключение для регистрации этого события в журнале. И в конце рассматриваемого здесь метода возвращается объект $subscription с данными из полученной строки таблицы.

Далее в классе SubscriptionMonitor определяется метод getUser (). Ниже приведен исходный код этого метода.

protected function getUser($userID) {

// составить запрос на получение объекта пользователя $query = $this->db->getQuery(true); $query->select('*') ->from($this->db->qn('# users1)) ->where($this->db->qn('id') . * = ' . (int) $userID)
// установить объект запроса базы данных в качестве объекта для связи с базой данных $this->db->setQuery($query);
// проверить наличие пользователя $user = $this->db->loadObject(); if (empty($user)) { throw new Exception('Invalid User.'); return $user;

Этот метод очень похож на предыдущий. В нем предпринимается попытка извлечь строку из таблицы пользователей по заданному идентификатору пользователя. Если это удастся сделать, то строка возвратится из таблицы в виде объекта. В противном случае генерируется исключение, указывающее на то, что идентификатор пользователя оказался недействительным.

Ниже приведен код метода sendNotif icationEmail (), определяемого далее в классе SubscriptionMonitor.

protected function  sendNotificationEmail($email,   $subject,   $body) {
JFactory::getMailer()->sendMail(
$this->get('mailfrom'),
$this->get('fromname'),
$email,
$subject,
$body >; }

В этом методе отправляется уведомление по электронной почте, для чего из класса JMail, объявляемого в файле libraries/joomla/mail/mail.php, вызывается метод sendMail (). Однако веб-сайт должен быть специально настроен на отправку сообщений по электронной почте. Так, если вы работаете на локальной машине, не настроенной на обмен сообщениями по электронной почте, то можете оставить этот метод пустым, но по-прежнему определенным в данном классе, закомментировав все строки кода в его теле. Это позволит вам проверить на выполнение остальную часть рассматриваемого здесь приложения, даже если у вас не установлена электронная почта.

И последним в данном классе определяется метод loadDatabase (). Напомним, что этот метод вызывается в конструкторе текущего класса для создания объекта базы данных с информацией, извлекаемой из конфигурационного файла. Ниже приведен исходный код этого метода.

protected function loadDatabase() {

// если возникает ошибка при установлении соединения с базой данных, то генерируется исключение $this->db = JDatabase:rgetlnstance( array( 'driver'   =>  $this->get('dbtype'), 'host'   =>  $this->get('host'), 'user'   =>  $this->get('user'), 'password'   =>  $this->get('password'), 'database'   =>  $this->get('db'), 'prefix'   =>  $this->get('dbprefix'), ) ); } }

// конец класса

В этом методе просто создается новый объект типа JDatabase, а его поля заполняются значениями из конфигурационного файла. Как упоминалось выше, для выборки этих значений из конфигурационного файла вызывается метод get () из класса JCli.


Понравился материал? Пригодилась информация? Плюсани в социалки!


 
Похожие новости