2012/04/18

Zend_Test_PHPUnit_DatabaseTestCase with multidb


we moved here http://radzserg.com/2012/04/18/zend_test_phpunit_databasetestcase-with-multidb/

Today I'll tell you how you can run tests for multiple databases with PHPUnit_DatabaseTestCase at the same time.

There are 2 ways. First is very simple you can use in your fixtures something like this
<second_db_scheme.accounts id="123"
It will work if only you have enough rights it's good for development/local db. But as a rule on even dev servers it won't work.

Second approach is to extend PHPUnitsetUp
I've changed some names you can use more clear names in your projects.
<?php

abstract class App_Test_PHPUnit_DatabaseTestCase extends Zend_Test_PHPUnit_DatabaseTestCase
{

    protected $_connectionMock;
    protected $_secondDbConnectionMock;

    protected $backupGlobalsBlacklist = array('application');  // btw this hack will speed up your tests

    /**
     * @return Zend_Test_PHPUnit_Db_Connection
     */
    protected function getConnection()
    {
        if ($this->_connectionMock == null) {
            $multiDb = Zend_Registry::get('multidb');

            $connection = $multiDb->getDb();

            $this->_connectionMock = $this->createZendDbConnection(
                $connection, ''
            );

            Zend_Db_Table_Abstract::setDefaultAdapter($connection);
        }
        return $this->_connectionMock;
    }

    protected function getSecondDbConnection()
    {
        if ($this->_dnsConnectionMock == null) {
            $multiDb = Zend_Registry::get('multidb');

            $connection = $multiDb->getDb('second_db');

            $this->_secondDbConnectionMock = $this->createZendDbConnection(
                $connection, ''
            );
        }

        return $this->_dnsConnectionMock;
    }

    protected function setUp()
    {
        parent::setUp();

        $this->databaseTester = NULL;

        $this->getDatabaseTester()->setSetUpOperation($this->getSetUpOperation());
        $this->getDatabaseTester()->setDataSet($this->getDataSet());
        $this->getDatabaseTester()->onSetUp();

        $secondDataSet = $this->getDataSetForSecondDb();
        if ($dnsDataSet) {
            // create data set for second db
            $secondDataTester = new PHPUnit_Extensions_Database_DefaultTester($this->getSecondDbConnection());
            $secondDataTester->setSetUpOperation($this->getSetUpOperation());
            $secondDataTester->setDataSet($secondDataSet);
            $secondDataTester->onSetUp();
        }

    }

    protected function getDataSetForSecondDb()
    {
        return null;
    }

}
 
as you see we just check does method getDataSetForSecondDb return data. You have to override it in child classes. If we get dataSet we will set up operations for second Db.

Everything is quite simple but in the fullness of time it made me to  look inside PHPUnit_* classes. So I hope I can save you time with that stuff. 

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