<?php
require_once 'Zend/Exception.php';
require_once 'Zend/Config/Xml.php';
/**
* SQL Server Integration Services (SSIS) package execution class.
* Allows for the configuration and execution of SSIS packages via a PHP script or batch file.
*
* @package lib
* @author DPassey
* @copyright 2011, Everett Public Schools
* @version 1.0, 02.14.2011
*/
/**
* Environment defined computer name.
* Allows the class to know where it is at and process the correct configuration.
*
* @var Const EPS_COMPUTER
*/
define('EPS_COMPUTER',strtolower($_ENV['COMPUTERNAME']));
/**
* SSIS command line executable.
*
* @var Const EXEC
*/
define('EXEC', 'dtexec');
define('DTSUCCESS','dtser_success');
class ZEND_EPS_DTEXEC
{
/**
* System temp directory.
*
* @var String
*/
private $tmpDir;
/**
* XML configuration file location.
* Path and name of the master configuration file.
* The config file should have this structure:
* <?xml version="1.0" encoding="UTF-8" ?>
* <config> [parent node named as you want]
* <env> [required for each environment]
* <computer> [required for each computer in that environment]
* <name>computer_name</name> [required]
* <package> [required for each package to define]
* <name>package_name</name> [required]
* <package_path>path_to_package_file</package_path> [required]
* <config_path>path_to_package_config</config_path> [required]
* <report_path></report_path> [optional]
* </computer>
* </env>
* </config>
*
* @var String
*/
private $configFile;
/**
* Package configuration file location.
* Path and name of the package configuration file.
*
* @var String
*/
private $pkgConfigFile;
/**
* Package file location.
* Path and name of the executable package file.
*
* @var String
*/
private $package;
/**
* XML config env node object.
* Holds all configurable information for each environment (dev, production, etc.) that the package will run in.
*
* @var Object
*/
private $envObj;
/**
* Report data.
* Data returned from the exec() regardless of success status.
*
* @var String
*/
private $report;
/**
* Check pointing flag.
* Set this to true to allow checkpointing.
*
* @var Boolean
*/
private $isCheckPointing = FALSE;
/**
* Validation only flag.
* Set this to true to only validate the package.
*
* @var Boolean
*/
private $isValidateOnly = FALSE;
/**
* Valid configurable reporting options.
* Index 0 is the default.
*
* @var Array
*/
private $reportConfigArray = array('N','V','E','W','I','C','D','P');
/**
* Report options array.
* Holds the valid reporting options which will be used during exec().
*
* @var Array
*/
private $reportOptions = array();
/**
* Reporting flag.
* Set via setReporting() so report data will be captured.
*
* @var Boolean
*/
private $isReporting = FALSE;
/**
* Reporting file path and name.
*
* @var String
*/
private $reportFilename;
/**
* Packages object.
* Holds all config information for each package defined in the config file.
*
* @var Object
*/
private $pkgsObject;
/**
* Exec statistics array.
* Used for indexing the statistics object.
*
* @var Array
*/
private $statsArray = array('started','finished','elapsed');
/**
* Statistics object.
* Holds the stats from the package execution.
* Indexed by the statsArray and a success index.
*
* @var Object
*/
public $statsObj;
/**
* Class constructor.
* Attempts to load in the configuration XML file and set configurables.
*
* @param String $configFile
* @uses setConfig()
* @throws Zend_Exception
*/
public function __construct($configFile = NULL)
{
try
{
if (empty($configFile)) throw new Zend_Exception(__FUNCTION__ . '::Config file path is missing.');
else
{
$this->tmpDir = sys_get_temp_dir();
$this->reportFilename = sprintf("%s.%s.txt",__CLASS__, date("YmdHis"));
$this->envObj = new Zend_Config_Xml($configFile,'env',TRUE);
if (is_object($this->envObj)) self::setConfig();
else throw new Zend_Exception(__FUNCTION__ . '::Environment nodes not found.');
}
}
catch (Zend_Exception $e)
{
exit($e->getMessage());
}
}
/**
* Class destructor.
* Writes the report file if reporting was turned on and there is report data to write.
*
* @throws Zend_Exception
*/
public function __destruct()
{
try
{
if ($this->isReporting && isset($this->report))
{
if (!@file_put_contents($this->reportFilename, $this->report)) throw new Zend_Exception(__FUNCTION__ . '::Failed to write report file.');
}
}
catch (Zend_Exception $e)
{
exit($e->getMessage());
}
}
/**
* Sets the package name to be executed.
* Validates the package file location, package config file location, reporting file location.
*
* @param String $pkg
* @uses setReportFile()
* @uses setConfigPath()
* @throws Zend_Exception
*/
public function setPackage($pkg = NULL)
{
try
{
if (empty($pkg)) throw new Zend_Exception (__FUNCTION__ . '::Package name is missing.');
elseif (empty($this->pkgsObject->$pkg)) throw new Zend_Exception(__FUNCTION__ . '::Package object does not exist.');
else
{
$path = $this->pkgsObject->$pkg->path;
$config = $this->pkgsObject->$pkg->config;
$report = $this->pkgsObject->$pkg->report;
}
if (!file_exists($path)) throw new Zend_Exception (__FUNCTION__ . '::Package file does not exist.');
if (empty($config)) throw new Zend_Exception(__FUNCTION__ . '::Missing "config_path" node in the config file.');
else self::setConfigPath($config);
if (!empty($report)) self::setReportFile($report,__FUNCTION__);
else self::setReportFile($this->tmpDir,$this->reportFilename);
$this->package = str_replace('//','/',str_replace('\\','/',$path));
}
catch (Zend_Exception $e)
{
exit($e->getMessage());
}
}
/**
* Executes the DTEXEC command with all of the configured options and generates report data.
* Throws an error if the package is not set.
*
* @uses setExecStats()
* @throws Zend_Exception
* @return Boolean
*/
public function exec()
{
try
{
if (isset($this->package))
{
if (empty($this->reportOptions)) $this->reportOptions[] = $this->reportConfigArray[0];
else
{
if (count($this->reportOptions) > 1 && (in_array($this->reportConfigArray[0], $this->reportOptions) || in_array($this->reportConfigArray[1], $this->reportOptions)))
{
throw new Zend_Exception(__FUNCTION__ . '::Reporting options "' . $this->reportConfigArray[0] . '" and "' . $this->reportConfigArray[1] . '" must be specified alone.');
}
}
$cmd = sprintf("%s -F %s -CONFIG %s -CHECKP %s -REP %s %s", EXEC, $this->package, $this->pkgConfigFile, ($this->isCheckPointing)?'ON':'OFF', implode('',$this->reportOptions), ($this->isValidateOnly)?'-VA':'');
$this->report = `$cmd`;
self::setExecStats();
}
else throw new Zend_Exception(__FUNCTION__ . '::Package is not set.');
}
catch (Zend_Exception $e)
{
exit($e->getMessage());
}
return $this->statsObj->success;
}
/**
* Sets the checkpoint option.
*
* @param Boolean $check
*/
public function setCheckPoint($check = FALSE)
{
$this->isCheckPointing = ($check) ? TRUE: FALSE;
}
/**
* Sets the reporting options.
* No reporting (N) and Verbose reporting (V) cannot be combined with any other options.
*
* @param Array $optionsArray
* @throws Zend_Exception
*/
public function setReportOptions($optionsArray = array())
{
try
{
if (is_array($optionsArray))
{
foreach ($optionsArray as $o)
{
$o = strtoupper($o);
if (!in_array($o, $this->reportConfigArray)) throw new Zend_Exception(__FUNCTION__ . '::' . $o . ' is not a valid reporting option.');
elseif (!in_array($o, $this->reportOptions)) $this->reportOptions[] = $o;
if ($o != $this->reportConfigArray[0]) $this->isReporting = TRUE;
}
}
else throw new Zend_Exception(__FUNCTION__ . '::Reporting options must be in an array.');
if (empty($this->reportOptions)) throw new Zend_Exception(__FUNCTION__ . '::Reporting options array is empty.');
}
catch (Zend_Exception $e)
{
exit($e->getMessage());
}
}
/**
* Sets the validation option so the package is not executed, but only validated.
*
*/
public function setValidation()
{
$this->isValidateOnly = TRUE;
}
/**
* Sets the report file path and filename.
* If $path does not a valid directory/file structure, then the default report file name is used.
*
* @param String $path
* @throws Zend_Exception
*/
public function setReportFile($path = NULL)
{
try
{
if (empty($path)) throw new Zend_Exception(__FUNCTION__ . '::File path is missing.');
if (!is_dir(dirname($path))) throw new Zend_Exception(__FUNCTION__ . '::File path directory is not valid.');
elseif (!is_writable(dirname($path))) throw new Zend_Exception(__FUNCTION__ . '::File directory is not writable.');
elseif (!is_dir($path)) $this->reportFilename = sprintf("%s/%s", dirname($path), basename($path));
else $this->reportFilename = sprintf("%s/%s", $path, $this->reportFilename);
}
catch (Zend_Exception $e)
{
exit($e->getMessage());
}
$this->reportFilename = str_replace('//','/',str_replace('\\','/',$this->reportFilename));
}
/**
* Private methods
*/
/**
* Sets the configurable items.
* Creates an array from the Config object and trims it down to the base element.
* Cycles through the array and processes configurables based upon the local computer.
*
* @uses setConfigPath()
* @uses setReportFile()
* @throws Zend_Exception
*/
private function setConfig()
{
try
{
$cpuArray[] = $this->envObj->computer->toArray();
$cpuArray = $cpuArray[0];
foreach ($cpuArray as $arr)
{
if (strtolower($arr['name']) == EPS_COMPUTER)
{
$this->pkgsObject = new stdClass();
foreach ($arr['package'] as $v)
{
$ndx = strtolower($v['name']);
$this->pkgsObject->$ndx = new stdClass();
$this->pkgsObject->$ndx->path = $v['package_path'];
$this->pkgsObject->$ndx->config = $v['config_path'];
$this->pkgsObject->$ndx->report = $v['report_path'];
}
}
}
}
catch (Zend_Exception $e)
{
exit($e->getMessage());
}
}
/**
* Sets the package configuration path.
*
* @param String $path
* @throws Zend_Exception
*/
private function setConfigPath($path)
{
try
{
if (file_exists($path)) $this->pkgConfigFile = $path;
else throw new Zend_Exception(__FUNCTION__ . '::Package config file does not exist.');
}
catch (Zend_Exception $e)
{
exit($e->getMessage());
}
}
/**
* Sets the statsObject which holds indexes of success and the statsArray indexes.
*
* @throws Zend_Exception
*/
private function setExecStats()
{
try
{
$tmp = array();
$arr = explode("\n", $this->report);
if (empty($arr)) throw new Zend_Exception(__FUNCTION__ . '::Report was not created.');
else
{
$arr = array_reverse($arr);
$str = EXEC;
foreach ($arr as $val)
{
if (preg_match("/^$str/i", $val) == 0) $tmp[] = $val;
else
{
$tmp[] = $val;
$arr = array_reverse($tmp);
break;
}
}
if (!empty($tmp))
{
$this->statsObj = new stdClass();
$this->statsObj->success = 0;
foreach ($arr as $val)
{
$split = explode(':',$val,2);
$ndx = trim(strtolower($split[0]));
if (in_array($ndx,$this->statsArray) || $ndx == EXEC) $this->statsObj->$ndx = trim($split[1]);
}
foreach (str_split(strrev($this->statsObj->$str)) as $c)
{
if ($c == '0')
{
$this->statsObj->success = 1;
break;
}
}
}
else unset($tmp);
}
}
catch (Zend_Exception $e)
{
exit($e->getMessage());
}
}
}
?>