CDbConnection persists in CActiveRecord

general description:

If i create a CApplication with a db component, then create a table entry with an CActiveRecord object, i destory the application and create a new one with a different db connection. the old db connection in the CActiveRecord persists.

when does it matter:

I wrote a codeception integration test, which in setUp, creates an yii application and starts a transaction.

in tearDown the transaction gets rolled back and the yii application gets destroy.

after the first testcase: everything gets cleaned up

after the second testcase: the transaction does not rollback, because the activerecords are not created with the newly genereted CDBConnection. the cdbconnection from the first testcase gets used.

is there any solution beside overwriting the CActiveRecord::getDbConnection method?

What are the drawbacks? Why is CActiveRecord::$db static?

How are you setting up and destroying the application on every test case run?

As for why $db is static, my guess would be so that objects attached to the same CDbConnection can re-use an open connection.

I’m using codeception to run my unit tests. basicly its a wrapper for phpunit

I’ve got a Helper, which runs before and after every testcase:




public function _before() {

    ...

    Yii::setApplication(null);

    Yii::createWebApplication(array(

        'components' => array(

            'db' => array( ... )

        ), ...

    ));


    $this->transaction = Yii::app()->db->beginTransaction();

}


public function _after() {

    $this->transaction->rollBack();

}



i reset the application because that way any added/modified component (Yii::app()->setComponent) will be reset.

the problem i’m currently facing is, that because of the static $db property in CActiveRecord the new database connection gets reset.

posible solutions i’ve tried so far:

  • modify CActiveRecord, to return \Yii::app()->db in getDBConnection, instead of using the static $db property. this may have other sideaffects, and may result in update problems.

  • FAILED: make a "workaround" in the test with two variables for the transaction. one for the cactiverecord db connections and one for any generated command (Yii::app()->db->createCommand).




<?php

namespace Helper;


class Integration extends \Codeception\Module

{

	protected $transaction_for_static_db;

	protected $transaction;

	protected static $db;


	public function _before(\Codeception\TestCase $test) {

            ...

            Yii::setApplication(null);

            Yii::createWebApplication(array(

                'components' => array(

                    'db' => array( ... )

                ), ...

            ));


            if (!isset(self::$db)) {

                self::$db = \Yii::app()->db;

            } else {

                $this->transaction = \Yii::app()->db->beginTransaction();

            }


            $this->transaction_for_static_db = self::$db->beginTransaction();

	}


	public function _after(\Codeception\TestCase $test) {

		$this->transaction_for_static_db->rollBack();

		if ($this->transaction && $this->transaction->getActive()) {

			$this->transaction->rollBack();

		}


		$this->getModule('Helper\Yii')->destroyYiiApplication();

	}

}



Why it failed: lets assume that in our first testcase we have an CActiveRecord A and in a second testcase an CActiveRecord B. in the first TestCase $db will be set with the first generated CDBConnection (which also gets stored in the _before method). In the second TestCase $db will be the second generated CDbConnection, which will not be stored. so it will fail later.

  • Using Reflection: the current working solution



<?php

namespace Helper;


class Integration extends \Codeception\Module

{

	protected $transaction;


	public function _before(\Codeception\TestCase $test) {

            ...

            Yii::setApplication(null);

            Yii::createWebApplication(array(

                'components' => array(

                    'db' => array( ... )

                ), ...

            ));


            $this->transaction = \Yii::app()->db->beginTransaction();

	}


	public function _after(\Codeception\TestCase $test) {


		$this->transaction->rollBack();


		foreach (get_declared_classes() as $class) {

			$reflection = new \ReflectionClass($class);


			if ($reflection->isSubclassOf('\CActiveRecord')) {

				$reflection->setStaticPropertyValue('db', null);

			}

		}


                Yii::setApplication(null);

	}

}