2012/04/07

Run Zend cli scripts (yii style)


we moved here http://radzserg.com/2012/04/07/run-zend-cli-scripts-yii-style/


yii has a nice yiic tool - cli for *nix so you can run scripts in this way

./yiic mynicescript --param1=2 --param2=2

this is really cool feature is essentially just a bootstrap for for running cli yii scripts(commands). 


It's weird that Zend Framework(ZF) doesn't have something similar. I propose you my variant for ZF


There are 2 classes (btw some features are similar with Extended Yii CConsoleCommand :):

<?php

/**
 *
 * Implements function to work from CLI
 * @author radzserg
 *
 */
abstract class App_Script_Abstract
{
    const VERBOSE_ERROR = 'error';
    const VERBOSE_INFO = 'info';

    protected $_verbose;
    
    /**
     *
     * Return class name
     * @return string
     */
    public function getName() {
        return get_class($this);
    }
    
    /**
     * Get params from cli
     * myscript.php --param1=value --param2=value
     * @return array
     */
    public function getCliParams() {
        $params = isset($_SERVER['argv']) ? $_SERVER['argv'] : array();
        $resultParams = array();
        foreach ($params as $paramPair) {
            if (strpos($paramPair, '--') !== false && strpos($paramPair, '--') == 0) {
                $count = 1;
                $paramPair = str_replace('--', '', $paramPair, $count);
                $paramPair = explode('=', $paramPair, 2);
                $key = isset($paramPair[0]) ? $paramPair[0] : null;
                $value = isset($paramPair[1]) ? $paramPair[1] : '';
                if ($key) {
                    $resultParams[$key] = $value;
                }
            }
        }
        return $resultParams;
    }

    /**
     * Verbose info
     * @param $message
     * @param null $type
     */
    public function verbose($message, $type = null)
    {
        if ($this->_verbose === NULL) {
            $cliParams = $this->getCliParams();
            $this->_verbose = isset($cliParams['verbose']) ? true : false;
        }
        if ($this->_verbose) {
            if ($type == self::VERBOSE_ERROR) {
                // message in red
                echo date('H:i:s ') . "\033[31;1m" . $message . "\033[0m\n";
            } elseif ($type == self::VERBOSE_INFO) {
                // message in green
                echo date('H:i:s ') . "\033[32;1m" . $message . "\033[0m\n";
            } else {
                echo date('H:i:s ') . $message . "\n";    
            }
        }
    }
}
<?php

class App_Script_ScriptRunner extends App_Script_Abstract
{

    /**
     *
     * Run script
     */
    public function run()
    {
        $script = $this->_getScriptObject();
        $params = $this->getCliParams();
        if (isset($params['help'])) {
            $script->help();
        } else {
            $script->execute($params);    
        }
    }
    
    
    /**
     *
     * Return script nam that have to be run
     * Mandatory format
     *
     * cron_runner.php scriptName [--param1=value --param2=value]
     * @return string
     */
    public function getScriptName()
    {
        return isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : NULL;
    }

    
    /**
     *
     * Return script object instance of App_Exception_System
     * @return App_Script_Abstract
     * @throws App_Exception_System
     */
 protected function _getScriptObject()
 {
     $scriptName = $this->getScriptName();
        if (!$scriptName) {
            throw new App_Exception_System("Cron name is not defined. Server argv " . print_r($_SERVER['argv'], true));
        }
        
        $scriptName = "App_Script_Command_" . ucfirst($scriptName);
        $script = new $scriptName;
        
        return $script;
 }
    
}
Second class App_Script_ScriptRunner is a descendant class and you can extend it as you want. As you see in my example I extended _getScriptObject my scrips are in /App/Script/Command perhaps just /scripts is better place. For example  I use another one App_Script_CronRunner to run cron scripts that additionally adds some stat about script execution.

And finally you have to add entry script for CLI. I put it in /scripts folder.


/**
 * Run CLI scripts
 *
 * php cron_runner.php myCommandName --param1=value --param2=value
 * command should be located in App/Script/Command/
 */

$envention = file_get_contents(dirname(__FILE__) . '/cli_envention');
if (!$envention) {
    throw new Exception("CLI envention is not defined");
}
define('APPLICATION_ENV', $envention);
define('ROOT_PATH', realpath(dirname(__FILE__) . '/..'));

define('APPLICATION_PATH', ROOT_PATH . '/application');

set_include_path(implode(PATH_SEPARATOR, array(
    realpath(ROOT_PATH . '/library'),
    get_include_path(),
)));

if ('development' == APPLICATION_ENV) {
    error_reporting(E_ALL | E_NOTICE);
    ini_set('display_errors', 1);
}

require_once 'App/Application/Console.php';
// Create application, bootstrap, and run
$application = new App_Application_Console(
    APPLICATION_ENV,
    APPLICATION_PATH . '/configs/application.ini'
);
$application->bootstrap();

$scriptRunner = new App_Script_ScriptRunner();
$scriptRunner->run();

As you see it's very similar to index.php The last hack that I use here is App_Application_Console it's full copy of Zend_Application but uses his own Bootstrap class.

That's it now you can run your scripts in this way.

cd /scripts
php script_runner.php haveFun --param1=123 --verbose


1 comment:

  1. This is pretty useful script, It really helpful for me while coding on zend framework.
    PHP Zend Development | Offshore PHP Developers

    ReplyDelete