2011/12/27

Exteneded Yii CConsoleCommand


we moved here http://radzserg.com/2011/12/27/exteneded-yii-cconsolecommand/


Continue to write about yii and sharing my experience. Today I write about how do I exteneded CConsoleCommand.

First of all I wanna say that base class CConsoleCommand is all what you need to run console commands. And I extended it for my purposes. But everybody can want to do it in another way or add another functionality. So all of this is just an example of extending base class.

I've added few additional abilities

  • pretty verbose method (add ./yiic myCommand --verbose)
  • ability to run only one copy of script
  • if verbose is set, show script execution time


<?php

/**
 * Extended yii console command
 *
 * @author: radzserg
 * @date: 11.04.11
 */
class ConsoleCommand extends CConsoleCommand
{

    const VERBOSE_ERROR = 'error';
    const VERBOSE_INFO = 'info';
    const VERBOSE_SYSTEM = 'system';

    public $verbose;

    private $_lockFile;

    // if false this means that multiple scripts can work simultaneously

    protected $_isSingletonScript = false;

    // calculate time execution time

    protected $_timeStart;

    protected function _verbose($message, $level=null, $type=null)
    {
        if (!$this->verbose) {
            return ;
        }

        $level = (int)$level;
        $indent = str_repeat("\t", $level);
        if ($type == self::VERBOSE_ERROR) {
            // message in red
            $message = "\033[31;1m" . $message . "\033[0m\n";
        } elseif ($type == self::VERBOSE_INFO) {
            // message in green
            $message = "\033[32;1m" . $message . "\033[0m\n";
        } elseif ($type == self::VERBOSE_SYSTEM) {
            $message = "\033[33;1m" . $message . "\033[0m\n";
        }

        echo $indent . date('H:i:s ') . $message . "\n";
    }

    protected function beforeAction($action,$params)
    {
        $this->_verbose("Start execution of " . get_class($this), null, self::VERBOSE_SYSTEM);
        $this->_timeStart = $this->_microtimeFloat();
        if ($this->_isSingletonScript) {
            $lockDir = Yii::getPathOfAlias('application.commands.lock');
            if (!is_dir($lockDir)) {
                mkdir($lockDir);
            }
            $filePath = $lockDir . '/' . get_class($this) . '.lock';
            $this->_lockFile = fopen($filePath, "w");
            if (!flock($this->_lockFile, LOCK_EX | LOCK_NB)) {
                $this->_verbose("Another instance of this script is running");
                return false;
            }
        }
        return true;
    }



    protected function afterAction($action,$params)
    {
        if ($this->_lockFile) {
            flock($this->_lockFile, LOCK_UN);
        }
        $time = round($this->_microtimeFloat() - $this->_timeStart, 2);
        $this->_verbose("End (time: {$time} seconds)", null, self::VERBOSE_SYSTEM);
    }

    private function _microtimeFloat()
    {
        list($usec, $sec) = explode(" ", microtime());
        return ((float)$usec + (float)$sec);
    }
}


2011/12/21

Unit tests for yii modules

Testing yii modules 

we moved here http://radzserg.com/2011/12/21/unit-tests-for-yii-modules/


I was creating simple yii module (about which perhaps I will write later). Since last time I like TDD more and more I've decided to test it. But there is a problem I'd like to move all tests to [my_module]/tests and do not mix them with project tests.

I din't find any good tutorial on this topic and decided to do it by myself.
Please note: I added only unit tests. But I believe it's easy to add functional tests and you can do it by yourself. 

 Ok let's start first of all we create following test directory structure:

/config
    -- test.php
/tests
    -- /fixtures
    -- /unit
    -- bootstrap.php
    -- phpunit.xml

As you see this is the copy of standard yii directory structure except some files. In fixtures and unit folders we will put fixtures and tests files as we do for standard yii tests. phpunit.xml also will leave the same. Of course you can customize it as you need.

<phpunit bootstrap="bootstrap.php" colors="true" converterrorstoexceptions="true" convertnoticestoexceptions="true" convertwarningstoexceptions="true" stoponfailure="false">

    <testsuite name="all tests">
        <directory>./</directory>
    </testsuite>
</phpunit>


In bootstrap.php we will change paths and comment WebTestCase.php including
<?php

$appPath = realpath(dirname(__FILE__) . '/../../../');
$config = $appPath . '/modules/pct/config/test.php';

require_once($appPath . '/yii/framework/yiit.php');
//require_once(dirname(__FILE__).'/WebTestCase.php');

Yii::createWebApplication($config);


And finally config.php


<?php

$basePath = realpath(dirname(__FILE__) . '/../../..');

return array(
    'basePath' => $basePath,

     'import' => array(
        'application.modules.[my_module].models.*',
        'application.modules.[my_module].components.*',

        // any other includings
    ),

    'components'=>array(
        'fixture'=>array(
            'class'=>'system.test.CDbFixtureManager',
            'basePath' => realpath(dirname(__FILE__) . '/../tests/fixtures'),

             // redefine basePath for module tests
        ),
        'db'=>array(
            'connectionString' => 'mysql:host=127.0.0.1;dbname=[test_db_name]',
            'username' => '',
            'password' => '',
        ),
    ),
);


I define the whole config but you can use test config from main application by

return CMap::mergeArray(
require(dirname(__FILE__).'../../../config/test.php'),
        array()



That's it. Good testing for you guys and thank you.