четверг, 31 июля 2008 г.

Команды (Tasks). Создание

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

Для создания своей команды, можно воспользоваться генерацией каркаса команды. Например создадим команду, которая выводит "Привет, Мир!" и назовем ее "hello". Определим ее в пространстве имен "example"


       ./symfony generate:task example:hello

Теперь для того чтобы убедиться, что каркас был создан введем команду:


       ./symfony list example
   
и если мы на выходе получим примерно следующее:

       Available tasks for the "example" namespace:
         :hello
   
значит каркас был успешно создан и в директории "lib/task" можно найти файл "exampleHelloTask.class.php".

В этом файле и создан класс-каркас нашей команды. В котором определены два необходимых метода "configure" и "execute", которые конфигурируют и запускают команду соответственно.


   class exampleHelloTask extends sfBaseTask
   {
       protected function configure()
       {
           // ...
       }

       protected function execute($arguments = array(), $options = array())
       {
           // ...
       }
   }
   

По умолчанию, команда "generate:task" создаст каркас в директории "lib/task", но можно указать путь, где создать каркас, например при создании своей команды в плагине:


     ./symfony generate:task --dir=plugins/spExamplePlugin/lib/task example:hello
   

В классе команды должны быть определены два метода, метод описания и конфигурации команды - "configure" и метод в котором выполняются действия команды - "execute". Метод configure для нашей команды может выглядеть примерно так:


   protected function configure()
    {
        $this->namespace = 'example'; // пространство имен
        $this->name      = 'hello'; // название команды 
        // короткое описание 
        // выводимое по команде ./symfony list
        $this->briefDescription = ''; 
        // подробное описание 
        // выводимое по команде help, в этом описании 
        // форматируя особым образом текст можно добиться 
        // необходимой подсветки.
        // например [text|INFO] выведет text зеленым цветом, а
        // [text|COMMENT] выведет text коричневым цветом
        $this->detailedDescription = '
        Команда [example:hello|INFO] просто привествует мир.
        Запускайте ее так:

          [./symfony example:hello|INFO]

        ';
        $this->aliases = array(); // массив синонимов
    }

   
Теперь можно выполнить команду:

     ./symfony help example:hello
   
и убедиться, что справочная информация выводится так как и было задумано.

Теперь добавим действий команде, дополнив код метода "execute":


   protected function execute($arguments = array(), $options = array())
   {
       $this->logSection("example", "Привет, Мир!");
   }
   
Метод "logSection" печатает отформатированное сообщение на стандартный вывод. Запускаем:

     ./symfony example:hello
   
и получаем:

   >>example   Привет, Мир!
   

Зачастую команды должны быть конфигурируемы, например выполнять действия в указанном окружении, или для определенного приложения. А так же должны уметь принимать аргументы как имя автора в команде "configure:author" или название приложения в команде "generate:app".
Эти задачи реализуются за счет описания опций и аргументов в методе "configure".
В качестве примера добавим нашей команде способность привествовать не только мир, но и разработчика вызвавшего ее. Для этого добавим аргумент "name", в котором будем принимать имя разработчика и немного изменим подробное описание команды, продемонстрировав еще одну возможность расскраски выводимого текста:


    protected function configure()
    {
        $this->namespace = 'example'; // пространство имен
        $this->name      = 'hello'; // название команды 
        // короткое описание 
        // выводимое по команде ./symfony list
        $this->briefDescription = ''; 
        // подробное описание 
        // выводимое по команде help, в этом описании 
        // форматируя особым образом текст можно добиться 
        // необходимой подсветки.
        // например [text|INFO] выведет text зеленым цветом, а
        // [text|COMMENT] выведет text коричневым цветом
        $this->detailedDescription = '
        Команда [example:hello|INFO] просто привествует мир.
        Запускайте ее так:

          [./symfony example:hello|INFO]

        Команда может привествовать не только мир, но и вас лично,
        для этого используйте аргумент [name|COMMENT]:

          [./symfony example:hello name|INFO]

        ';
        $this->aliases = array(); // массив синонимов
        $this->addArgument
        (
            'name', // название аргумента
            sfCommandArgument::OPTIONAL, // тип аргумента
            'Ваше имя, о величайший создатель', // справка
            'Мир' // значение по умолчанию
        )
    }
   
Если теперь выполнить команду:

     ./symfony help example:hello
   
то можно увидеть, что описание этого аргумента добавилось в вывод справки нашей команды. Теперь изменим код команды:

   protected function execute($arguments = array(), $options = array())
   {
       $this->logSection('example', 'Привет, '.$arguments['name'].'!');
   }
   
и запустим в двух вариантах:

     ./symfony example:hello
     >>example    Привет, Мир!
     ./symfony example:hello Сергей
     >>example    Привет, Сергей!
   

Рассмотрим подробнее описание аргумента.
Для описания используется метод:


   /**
    * name - имя аргумента
    * mode - свойства аргумента, возможные значения
    *         sfCommandArgument::OPTIONAL,
    *         sfCommandArgument::REQUIRED,
    *         sfCommandArgument::IS_ARRAY
    * help - справка
    * default - значение по умолчанию, нельзя указать при REQUIRED
    */
   addArgument($name, $mode = null, $help = '', $default = null);
   
Предназначение параметров вроде понятно из их названий, а вот о "mode" немного дополню.
sfCommandArgument::OPTIONAL - говорит о том, что аргумент является не обязательным
sfCommandArgument::REQUIRED - напртив, говорит о том, что аргумент является обязательным. При указании sfCommandArgument::REQUIRED нельзя задать значение по умолчанию для аргумента.
sfCommandArgument::IS_ARRAY - говорит о том, что аргумент является массивом. В этом случае в методе "execute" он будет доступен именно как массив и в значении по умолчанию его тоже надо задавать в виде массива. При вызове команды из консоли массивы передаются так:

     ./symfony example:hello {Сергей,Мир}
     // преобразуется в массив array('Сергей', 'Мир');
     ./symfony example:hello Сергей Мир
     // преобразуется в массив, если у аргумента указан IS_ARRAY
   
"mode" можно задать и так, объединив свойства аргумента:

   $this->addArgument
   (
       'hello',
       sfCommandArgument::OPTIONAL | sfCommandArgument::IS_ARRAY
   );
   
Для добавления нескольких аргументов за раз, можно воспользоваться методом "addArguments":

   $this->addArguments
   (
       new sfCommandArgument
       (
           'hello',
           sfCommandArgument::OPTIONAL,
           'help',
           'default'
       ),
       new sfCommandArgument
       (
           'bye',
           sfCommandArgument::OPTIONAL,
           'help',
           'default'
       )
       //, ...
   );
   

Опции команде можно добавить используя методы "addOption" или "addOptions".


   /**
    * Описывает принимаемую опцию
    *
    * name - имя опции
    * shortname - короткое имя опции
    * mode - свойства параметра опции
    * help - справка
    * default - значение по умолчанию
    */
   addOption($name, $shortname = null, $mode = null, $help = '', $default = null);
   

Добавим нашей команде возможность указания способа привествия, для этого опишем опцию "greeting" в методе "configure":


   protected function configure()
   {
       // ... пропускаю то, что сделали выше

       // обязателен только первый параметр
       $this->addOption
       (
           'greeting', // имя опции
           'g', // короткое имя опции
           sfCommandOption::PARAMETER_OPTIONAL, // mode
           'Как попривествовать', // справка
           'Привет', // значение по умолчанию
       )
   }

   protected function execute($arguments = array(), $options = array())
   {
       $this->logSection('example', $options['greeting'].', '.$arguments['name'].'!');
   }
   
И запускаем в двух вариантах:

     ./symfony example:hello -g Здравствуй
     >> example    Здравствуй, Мир!
     ./symfony example:hello --greeting=Здравствуй
     >> example    Здравствуй, Мир!
   
Предназначение параметров понятны из названия и комментариев.
"mode" может иметь следующие значения или их комбинации:
sfCommandOption::PARAMETER_NONE - если опция не нуждается в параметре, т.е. например:

     ./symfony task_name --debug
   
в метод "execute" в "$options['debug']" будет передано либо "true" если опция указана, либо "false" если опция не указана.
sfCommandOption::PARAMETER_REQUIRED - параметр у опции обязателен
sfCommandOption::PARAMETER_OPTIONAL - параметр у опции не обязателен
sfCommandOption::IS_ARRAY - параметр у опции массив, можно указать несколько раз:

     ./symfony task_name --option='param1' --option='param2'
     // преобразуется в array('param1', 'param2')
   
Ну и вариант для добавления нескольких опций за раз:

   protected function configure()
   {
       $this->addOptions
       (
           new sfCommandOption
           (
               'greeting',
               'g',
               sfCommandOption::PARAMETER_REQUIRED,
               'help',
               'Привет'
           ),
           new sfCommandOption
           (
               'env'
           )
           //, ...
       );
   }
   

Несколько полезных советов

Вызов команды внутри другой команды.


   protected function execute($arguments = array(), $options = array())
   {
       $task = new myOtherTask($this->dispatcher, $this->formatter);
       $task->run
       (
           $arguments = array
           (
               'foo' => 'bar'
           ),
           $options = array
           (
               'bar' => 'foo'
           )
       );
   }
   

Использование базы данных Если в команде используется подключение к базе данных, то необходимо в методе "configure" добавить аргумент "appliction" и опцию "--env" (опция не обязательна) для указания какие данные использовать для подключения к базе данных. После этого необходимо в методе "execute" выполнить инициализацию соединения с базой данных.


   protected function configure()
   {
       $this->addArgument('application', sfCommandArgument::REQUIRED, 'Имя приложения');
       $this->addOption('env', null, sfCommandOption::PARAMETER_REQUIRED, 'Окружение', 'dev');
       $this->addOption('connection', null, sfCommandOption::PARAMETER_REQUIRED, 'Имя соединения', 'doctrine')
       // ...
   }
   protected function execute($arguments = array(), $options = array())
   {
       $dbManager = new sfDatabaseManager($this->configuration);
       $connection = Doctrine_Manager::connection();
       // ...
   }
   
Можно и при создании каркаса команды указать опцию "--use-database", но потом поправить некоторые значения если используется "Doctrine", а в частности:
В методе "configure" при описании опции "connection" изменить имя по умолчанию.
В метод "execute":

   // заменить
   $connection = Propel::getConnection($options['connection'] ? $options['connection'] : '');
   // на
   $connection = Doctrine_Manager::connection();
   

[ читать дальше ]