Pyrus plugins: custom file tasks

Introduction

If you are not familiar with how file tasks work, read the introduction to file tasks first.

Custom file tasks can implement a single task, and are defined by an xml file that is noted in package.xml with the customtask file role. The XML format is defined and validated by pyrus with the customtask-2.0.xsd XSchema file.

Here is a human-readable custom file task xml file definition. Optional elements are enclosed in [brackets].

<?xml version="1.0" encoding="UTF-8"?>
<task version="2.0" xmlns="http://pear2.php.net/dtd/customtask-2.0">
 <name>taskname</name>
 <class>MyPlugin_Taskname</class>
[<autoloadpath>relative/path/to/MyPlugin</autoloadpath>]
</task>

Telling Pyrus how to load your task: <class> and <autoloadpath>

This is the same method used for all plugins, and is documented here.

Defining the task: the task class

A custom file task must extend the \PEAR2\Pyrus\Task\Common class, and must implement a few methods:

  • validateXml()
  • startSession()

Optionally, __construct() may be used to do special pre-processing, storing of information or other setup tasks.

A task must also define two class constants, TYPE and PHASE. TYPE should be one of 'simple' or 'script'. 'script' tasks are post-install scripts, and are executed by the run-scripts command. 'simple' tasks are executed while installation is in progress.

PHASE should be one of:

  • \PEAR2\Pyrus\Task\Common::PACKAGE
  • \PEAR2\Pyrus\Task\Common::INSTALL
  • \PEAR2\Pyrus\Task\Common::PACKAGEANDINSTALL
  • \PEAR2\Pyrus\Task\Common::POSTINSTALL

\PEAR2\Pyrus\Task\Common::POSTINSTALL is only used by post-install scripts, the other 3 are used to determine at what time the task should be called. If your custom task only works at installation-time, use \PEAR2\Pyrus\Task\Common::INSTALL. If the task can perform only at package time (this is exceedingly rare), use \PEAR2\Pyrus\Task\Common::PACKAGE. Most tasks should use \PEAR2\Pyrus\Task\Common::PACKAGEANDINSTALL. If your task can perform its task at packaging time, then you should override the isPreProcessed() method and return true. Some tasks, such as the replace task, perform some of their actions at package time, and the rest at install time. The isPreProcessed() method should only return true if the XML of the specific task could be processed at package time.

The startSession() method is used to actually execute the task, and is passed a read/write file resource that is set to the beginning of the file being installed. The full path of the file's final installation location is also passed in, and can be used for throwing exceptions. All errors should be handled by throwing a PEAR2\Pyrus\Task\Exception or one of its descendants.

The validateXml() method should validate the XML as represented in an array format. The array uses associative arrays for tags, the attribs index for attributes, and for a tag with both attributes and content, the _content index contains the content. If present, the tasks: namespace is removed from the tags prior to passing to validateXml(). On encountering validation errors, the method should throw one of the four exceptions in the example below.

Example Custom Task

<?php
class MyPlugin_Taskname extends \PEAR2\Pyrus\Task\Common
{
    const 
TYPE 'simple';
    const 
PHASE = \PEAR2\Pyrus\Task\Common::PACKAGEANDINSTALL;

    
/**
     * Initialize a task instance with the parameters
     * @param array raw, parsed xml
     * @param array attributes from the <file> tag containing this task
     * @param string|null last installed version of this package
     */
    
function __construct($pkg$phase$xml$attribs$lastversion)
    {
        
parent::__construct($pkg$phase$xml$attribs$lastversion);
        
// do any special parsing of attributes/contents here
        // phase is one of \PEAR2\Pyrus\Task\Common::PACKAGE,
        // \PEAR2\Pyrus\Task\Common::INSTALL, or
        // \PEAR2\Pyrus\Task\Common::POSTINSTALL
    
}

    
/**
     * Validate the basic contents of a <taskname> tag
     * @param PEAR_Pyrus_IPackageFile
     * @param array
     * @param array the entire parsed <file> tag
     * @param string the filename of the package.xml
     * @throws \PEAR2\Pyrus\Task\Exception\InvalidTask
     * @throws \PEAR2\Pyrus\Task\Exception\NoAttributes
     * @throws \PEAR2\Pyrus\Task\Exception\MissingAttribute
     * @throws \PEAR2\Pyrus\Task\Exception\WrongAttributeValue
     */
    
static function validateXml(PEAR2\Pyrus\IPackage $pkg$xml$fileXml$file)
    {
        if (!isset(
$xml['attribs'])) {
            throw new \
PEAR2\Pyrus\Task\Exception\NoAttributes('taskname'$file);
        }
        if (!isset(
$xml['attribs']['attributename'])) {
            throw new \
PEAR2\Pyrus\Task\Exception\MissingAttribute('taskname',
                                                                  
$attrib$file);
        }
        if (
$xml['attribs']['attributename'] != 'hi'
            
&& $xml['attribs']['attributename'] != 'bye') {
            throw new \
PEAR2\Pyrus\Task\Exception\WrongAttributeValue('taskname',
                                                                     
'attributename',
                                                                     
$xml['attribs']['attributename'],
                                                                     
$file,
                                                                     array(
'hi''bye'));
            }
        }
        if (
MyPlugin_Other_Class::somecondition()) {
            throw new \
PEAR2\Pyrus\Task\Exception\InvalidTask(
                
'general validation errors should use this exception');
        }
        return 
true;
    }

    
/**
     * Perform the taskname task
     * @param \PEAR2\Pyrus\IPackage
     * @param resource open file pointer, set to the beginning of the file
     * @param string the eventual final file location (informational only)
     * @return string|false
     */
    
function startSession($fp$dest)
    {
        
// use \PEAR2\Pyrus\Logger::log() to send messages to the user
        
\PEAR2\Pyrus\Logger::log(3'doing stuff');
        \
PEAR2\Pyrus\Logger::log(0'doing more important stuff');
        
// operate directly on the file pointer
        
$contents stream_get_contents($fp);
        
$contents .= 'hi';
        
rewind($fp);
        
ftruncate($fp0);
        
fwrite($fp$contents);
        return 
true;
    }

    
/**
     * This is used to tell the installer to skip a task that has already been
     * executed.
     *
     * For example, package-info replacement tasks are performed at packaging
     * time, and do not need to be re-applied on installation
     * @return bool true if task has already been executed on the file at
     *              packaging time
     */
    
function isPreProcessed()
    {
        
// do not implement this method unless your task does its processing
        // at package-time!
        
return true;
    }
}
?>