PEAR logo

PEAR_PackageFileManager : The Definitive Guide



Advanced installation with post install script

Giving parameters for PEAR installer
Scripting actions proceed after files copy
Introduction to a class pattern
Initializing environment
Proceeding post install task

If you want to do something else than just basic tasks (search and replace string, converting line ending), extreme customization is possible with <postinstallscript> tag. This tag define parameters that are used by PEAR installer to retrieve user input, while running a post-install script.

[Note] Note
This feature is only available for package.xml, version 2.0

Distribution of full web application, including a database, is one of common problem that <tasks:postinstallscript> may solve.

Giving parameters for PEAR installer

In this example we will install a MySQL database with the mysqlinstall script that resides in server sub-directory of target directory installation (see attribute baseinstalldir).

In package.xml, between <contents> tag we will find something like :

  1. <file baseinstalldir="HTML" name="server/mysqlinstall.php" role="php">
  2.  <tasks:postinstallscript>
  3.   <tasks:paramgroup>
  4.    <tasks:id>databaseSetup</tasks:id>
  5.    <tasks:param>
  6.     <tasks:name>database</tasks:name>
  7.     <tasks:prompt>Mysql database</tasks:prompt>
  8.     <tasks:type>string</tasks:type>
  9.     <tasks:default>peartest</tasks:default>
  10.    </tasks:param>
  11.    <tasks:param>
  12.     <tasks:name>user</tasks:name>
  13.     <tasks:prompt>Mysql Username (must have create permision)</tasks:prompt>
  14.     <tasks:type>string</tasks:type>
  15.     <tasks:default>root</tasks:default>
  16.    </tasks:param>
  17.    <tasks:param>
  18.     <tasks:name>password</tasks:name>
  19.     <tasks:prompt>Mysql password</tasks:prompt>
  20.     <tasks:type>string</tasks:type>
  21.     <tasks:default />
  22.    </tasks:param>
  23.    <tasks:param>
  24.     <tasks:name>dbhost</tasks:name>
  25.     <tasks:prompt>Database Host</tasks:prompt>
  26.     <tasks:type>string</tasks:type>
  27.     <tasks:default>localhost</tasks:default>
  28.    </tasks:param>
  29.   </tasks:paramgroup>
  30.  </tasks:postinstallscript>
  31. </file>

Generated by a script like this:

  1. <?php
  2. require_once 'PEAR/PackageFileManager2.php';
  3.  
  4. PEAR::setErrorHandling(PEAR_ERROR_DIE);
  5.  
  6. $p2 = new PEAR_PackageFileManager2();
  7. //...
  8. $task = & $p2->initPostinstallScript('server/mysqlinstall.php');
  9. $task->addParamGroup('databaseSetup', array(
  10.     $task->getParam('database', 'Mysql database', 'string', 'peartest'),
  11.     $task->getParam('user', 'Mysql Username (must have create permision)', 'string', 'root'),
  12.     $task->getParam('password', 'Mysql password', 'string', ''),
  13.     $task->getParam('dbhost', 'Database Host', 'string', 'localhost'),
  14.     ));
  15. $p2->addPostinstallTask($task, 'server/mysqlinstall.php');
  16. $p2->generateContents();
  17.  
  18. if (isset($_GET['make']) || (isset($_SERVER['argv']) && @$_SERVER['argv'][1] == 'make')) {
  19.     $p2->writePackageFile();
  20. } else {
  21.     $p2->debugPackageFile();
  22. }
  23. ?>

Adding a post install task <tasks:postinstallscript> requires to build a PEAR_Task_Postinstallscript_rw instance for the post install script ($task). This is made by PEAR_PackageFileManager::initPostinstallScript() method.

Next we can add as much as we want any paramgroup id, such as : <tasks:paramgroup><tasks:id>databaseSetup</tasks:id>. This is made by PEAR_Task_Postinstallscript_rw::addParamGroup() method.

Each paramgroup may have zero, one or more param. These params are added by an array of PEAR_Task_Postinstallscript_rw::getParam(), or false if there is no param.

Finally, post install script task is added to package xml by a call to PEAR_PackageFileManager::addPostinstallTask() method.

Scripting actions proceed after files copy

Introduction to a class pattern

Post-install script files can be named anything one desires, but the class within the file must be the same name as the file with all path separators replaced by underscores, and a fixed postfix "_postinstall". In other words, this postinstall script: server/mysqlinstall.php, must contain a class named server_mysqlinstall_postinstall.

API of a post install script always match, at least 2 functions :

init()

that initialize script environment with :

  • parameter #1 a reference to a PEAR_Config instance (current configuration used for installation).

  • parameter #2 a reference to the current PEAR_PackageFileManager instance.

  • parameter #3 the last version of this package that was installed. This is a very important parameter, as it is the only way to determine whether a package is being installed from scratch, or upgraded from a previous version. Using this parameter, it is possible to determine what incremental changes, if any, need to be performed.

run()

is called at the conclusion of each parameter group in order to process the user's responses to queries. This method carry two parameters :

  • parameter #1 is an array that will contain a list of successfully completed parameter group sections. This can be used to restore any system changes made by the installation script.

  • parameter #2 identify the most recent paramgroup or contains _undoOnError only triggered by the PEAR installer on rollback process.

In our example, we should have a class pattern as this one :

  1. <?php
  2. class server_mysqlinstall_postinstall
  3. {
  4.     var $_config;
  5.     var $_ui;
  6.     var $_pkg;
  7.     var $lastversion;
  8.  
  9.     function init(&$config, &$pkg, $lastversion)
  10.     {
  11.         $this->_config = &$config;
  12.         $this->_ui = &PEAR_Frontend::singleton();
  13.         $this->_pkg = &$pkg;
  14.         $this->lastversion = $lastversion;
  15.         return true;
  16.     }
  17.  
  18.     function run($answers, $phase)
  19.     {
  20.     }
  21. }
  22. ?>

Initializing environment

This phase is important and allow script to communicate with a backend logger ($this->_ui), get information from your PEAR installation copy ($this->_config), get information on package release you are trying to install/upgrade ($this->_pkg) and the previous version installed (if any).

You may also add other data you think it's necessary to task proceed. For example a database existence indicator ($this->databaseExists), and what kind of driver we could use: either mysql or mysqli ($this->mysqliAvailable).

  1. <?php
  2. class server_mysqlinstall_postinstall
  3. {
  4. //...
  5.     var $databaseExists;
  6.     var $mysqliAvailable;
  7.  
  8.     function init(&$config, &$pkg, $lastversion)
  9.     {
  10.         //...
  11.         $this->databaseExists  = false;
  12.         $this->mysqliAvailable = extension_loaded('mysqli');
  13.  
  14.     }
  15. //...
  16. }
  17. ?>

Proceeding post install task

Deeper inside class server_mysqlinstall_postfix and run() method, we find :

  • a way to commit action (each paramgroup id).

  • and another to revert action : _undoOnError

  1. <?php
  2. class server_mysqlinstall_postinstall
  3. {
  4. //...
  5.  
  6.     function run($answers, $phase)
  7.     {
  8.         switch ($phase) {
  9.             case 'databaseSetup' :
  10.                 return $this->createDatabase($answers);
  11.                 break;
  12.             case '_undoOnError' :
  13.                 // answers contains paramgroups that succeeded in reverse order
  14.                 foreach ($answers as $group) {
  15.                     switch ($group) {
  16.                         case 'databaseCreate' :
  17.                             if ($this->lastversion || $this->databaseExists) {
  18.                                 // don't uninstall the database
  19.                                 break;
  20.                             }
  21.                             // code to drop the database
  22.                         // ... other case ...
  23.                     }
  24.                 }
  25.                 // ...
  26.                 break;
  27.         }
  28.     }
  29. }
  30. ?>

Explore in details one action (paramgroup id): databaseCreate specialized to creating new database (default is peartest) given by param name database of this paramgroup.

  1. <?php
  2. class server_mysqlinstall_postinstall
  3. {
  4.     var $dbhost;
  5.     var $db;
  6.     var $user;
  7.     var $password;
  8. //...
  9.  
  10.     function createDatabase($answers)
  11.     {
  12.         $this->dbhost   = $answers['dbhost'];
  13.         $this->db       = $answers['database'];
  14.         $this->user     = $answers['user'];
  15.         $this->password = $answers['password'];
  16.  
  17.         // Try to connect to database
  18.         $conn = $this->getDBConnection();
  19.         if (!$conn) {
  20.             return false;
  21.         }
  22.  
  23.         if ($this->mysqliAvailable) {
  24.             $query = mysqli_select_db($conn, $this->db);
  25.         } else {
  26.             $query = mysql_select_db($this->db, $conn);
  27.         }
  28.         if ($query) {
  29.             // upgrading database schema/data ?
  30.             $this->databaseExists = true;
  31.             // ...
  32.         } else {
  33.             // create database
  34.             $this->databaseExists = false;
  35.  
  36.             if ($this->mysqliAvailable) {
  37.                 $query = mysqli_query($conn, 'CREATE DATABASE ' . $this->db);
  38.                 if ($query) {
  39.                     $query = mysqli_select_db($conn, $this->db);
  40.                 }
  41.             } else {
  42.                 $query = mysql_query('CREATE DATABASE ' . $this->db, $conn);
  43.                 if ($query) {
  44.                     $query = mysql_select_db($this->db, $conn);
  45.                 }
  46.             }
  47.  
  48.             if (!$query) {
  49.                 if ($this->mysqliAvailable) {
  50.                     $err = mysqli_error($conn);
  51.                 } else {
  52.                     $err = mysql_error();
  53.                 }
  54.                 $this->_ui->outputData('Database creation failed: ' . $err);
  55.                 $this->closeDB($conn);
  56.                 return false;
  57.             }
  58.         }
  59.         $this->closeDB($conn);
  60.         return true;
  61.     }
  62.  
  63.     function getDBConnection()
  64.     {
  65.         if ($this->mysqliAvailable) {
  66.             $conn = mysqli_connect($this->dbhost, $this->user, $this->password);
  67.         } else {
  68.             $conn = mysql_connect($this->dbhost, $this->user, $this->password);
  69.         }
  70.         if (!$conn) {
  71.             $this->_ui->outputData('Connection to mysql server failed');
  72.             return false;
  73.         }
  74.         return $conn;
  75.     }
  76.  
  77.     function closeDB($conn)
  78.     {
  79.         if ($this->mysqliAvailable) {
  80.             mysqli_close($conn);
  81.         } else {
  82.             mysql_close($conn);
  83.         }
  84.     }
  85. }
  86. ?>
[Important] Important
In case of problem, don't forget to provide a way to revert action already applied. PEAR installer will then trigger _undoOnError phase run.
PEAR_PackageFileManager : The Definitive Guide v 1.6.0 : November 17, 2006