10 ноября 2011 г.

Переводим cron-скрипты на Zend

Часто бывает, что cron-скрипты Zend приложений работают с теми же ресурсами, что и само приложение. Само собой, хочется перенести эти скрипты в привычное окружение зенда. Один из вариантов этой процедуры я и приведу.

Сразу оговорюсь - представленная реализация наверняка не самая лучшая и имеет множество изъянов, да и некоторые моменты у меня самого вызывают вопросы (просто когда понадобилось перенести все крон скрипты на зенд времени на детальное рассмотрение небыло совершенно). Поэтому буду рад всем фидбеками и замечаниям по коду =)

Итак, для начала нам понадобится аналог фронтендового index.php для всех cron-скриптов. Назовём его zfcli.php. Поскольку внешний доступ к нему нам важно ограничить - я кладу его внутрь привычного APPLICATION_PATH, рядом с Bootstrap.php.


Листинг zfcli.php
Copy Source | Copy HTML
  1. <?php

  2. define('APPLICATION_ENV','cli');

  3. define('APPLICATION_PATH', realpath(dirname(__FILE__)));

  4. define('ZEND_PATH', realpath(dirname(__FILE__) . '/../../Zend'));

  5. define('WWW_PATH', realpath(dirname(__FILE__) . '/../www') );


  6. set_include_path(implode(PATH_SEPARATOR, array(

  7.     ZEND_PATH,

  8.     get_include_path()

  9. )));


  10. // Zend_Application

  11. require_once 'Zend/Application.php';


  12. $application = new Zend_Application(

  13.     APPLICATION_ENV,

  14.     APPLICATION_PATH . '/configs/application.ini'

  15. );


  16. try

  17. {

  18.     $opts = new Zend_Console_Getopt(

  19.         array(

  20.             'help|h' => 'Displays this information.',

  21.             'action|a=s' => 'onlinestat | csv | scavenger',

  22.         )

  23.     );

  24.     $opts->parse();


  25. }catch (Zend_Console_Getopt_Exception $e) {

  26.     exit($e->getMessage() ."\n\n". $e->getUsageMessage());

  27. }


  28. if(isset($opts->h))

  29.     exit($opts->getUsageMessage());



  30. require_once realpath(APPLICATION_PATH.'/lib/MyCliRouter.php');


  31. switch ($opts->a)

  32. {

  33.     //сборщик мусора и ротатор статистики

  34.     case 'scavenger':

  35.         $request = new Zend_Controller_Request_Simple('scavenger','cli');

  36.     break;


  37.     //сбор статистики по онлайну

  38.     case 'onlinestat':

  39.         $request = new Zend_Controller_Request_Simple('onlinestat','cli');

  40.     break;


  41.     //обновление собственных csv

  42.     case 'csv':

  43.         $request = new Zend_Controller_Request_Simple('csv','cli');

  44.     break;


  45. }


  46. if(!isset($request))

  47.     exit('Запрос не составлен (Cli error)');



  48. $front = $application->getBootstrap()

  49.         ->bootstrap('frontController')

  50.         ->getResource('frontController');


  51. $front->setRequest($request)

  52.       ->setResponse(new Zend_Controller_Response_Cli())

  53.       ->setRouter(new MyCliRouter())

  54.       ->throwExceptions(true);


  55. $application->bootstrap()

  56.             ->run();

В целом код очень напоминает обычный фронтконтроллер зенда (index.php), за некоторыми необходимыми правками.
В самом начале константе APPLICATION_ENV присваивается значение cli. Это сделано для отделения всех настроек приложения, необходимых для cron скриптов, в отдельную секцию application.ini и просто для удобства.
Далее мы задаём настройки Zend_Console_Getopt и тут же пытаемся их распарсить, попутно обрабатывая ошибки. Подробно останавливаться на этой части я не буду, поскольку официальная документация по данному компоненту отвечает на большинство возникающих вопросов. Если в кратце - скрипт будет требовать один обязательный параметр --action (или -a для краткости) обозначающий имя запускаемого действия (onlinestat, csv или scavenger) нашего Cli контроллера (будет рассмотрен далее).
Если было запрошено одно из доступных действий создаётся объект одиночного запроса, представленного в зенде классом Zend_Controller_Request_Simple (тут снова посыл в доку =) ), и созданный объект указывается как входящий для фронтконтроллера.
С фронтконтроллером связано ещё несколько действий, который и вызывают больше всего вопросов:
  • В качестве объекта ответа мы берём Zend_Controller_Response_Cli. Это отнюдь не обязательно для работы, но рекомендуется многими не глупыми людьми, поэтому я решил не удалять эту строку.
  • В качестве роутера необходимо указать заглушку, в нашем случае это объект класса MyCliRouter (будет рассмотрен далее). Эта заглушка призвана реализовывать стандартный интерфейс роутера и отмалчиваться на все запросы, поскольку мы берём диспетчеризацию на себя.

Ну а дальше следует обычный бутстрап и запуск приложения.


Листинг MyCliRouter
Copy Source | Copy HTML
  1. <?php

  2. class MyCliRouter

  3.      extends Zend_Controller_Router_Abstract

  4.      implements Zend_Controller_Router_Interface

  5. {

  6.         public function assemble($userParams, $name = null, $reset = false, $encode = true)

  7.             {}

  8.         public function route(Zend_Controller_Request_Abstract $dispatcher)

  9.             {}

  10. }
Как видите, задача заглушки просто "глушить" запросы нескольких методов роутера.


Теперь рассмотрим упомянутый выше CliController. В целом это обычный контроллер зендовского приложения, отличается он разве что проверкой на доступ в Cli контексте (помните указанное в самом начале zfcli значение константы APPLICATION_ENV?).
Листинг CliController
Copy Source | Copy HTML
  1. <?php

  2. class CliController extends Zend_Controller_Action

  3. {   

  4.     public function init()

  5.     {

  6.         if ( APPLICATION_ENV != 'cli' )

  7.             throw new Exception('Access denied');       

  8.     }

  9.     public function scavengerAction()

  10.     {       

  11.         echo "scavenger";

  12.     }

  13.     public function onlinestatAction()

  14.     {       

  15.         echo "onlinestat";

  16.     }

  17.     public function csvAction()

  18.     {       

  19.         echo "csv";

  20.     }

  21. }



Вот и всё =) Теперь вы можете заполнить действия логикой своих cron-скриптов и использовать в них все плюшки зенда.

Несколько примеров использования:
Вывести мануал
php /var/www/dseye/app/zfcli.php -h

Запустить действие csv
php /var/www/dseye/app/zfcli.php -a csv

Тоже самое без кратких аргументов
php /var/www/dseye/app/zfcli.php --action=csv


PS: В данном посте я не привёл содержимое упомянутой секции Cli в нашем application.ini. Это сделано потому, что содержимое (да и само наличие) данной секции очень сильно зависит от используемых вами ресурсов. Могу только посоветовать не наследовать cli от production, а, если необходимо, наследовать оные от базовой секции. Ещё посоветую создать для Cli отдельный бутстрап, который и указать через application.ini.

Комментариев нет: