ActiveFixture.php 6.08 KB
Newer Older
Qiang Xue committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\test;

use Yii;
use yii\base\InvalidConfigException;
use yii\db\Connection;
use yii\db\TableSchema;

/**
 * ActiveFixture represents a fixture backed up by a [[modelClass|ActiveRecord class]] or a [[tableName|database table]].
 *
 * Either [[modelClass]] or [[tableName]] must be set. When loading an ActiveFixture, the corresponding
 * database table will be [[resetTable()|reset]] first. It will then be populated with the data loaded by [[loadData()]].
 *
 * You can access the loaded data via the [[rows]] property. If you set [[modelClass]], you will also be able
 * to retrieve an instance of [[modelClass]] with the populated data via [[getModel()]].
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
class ActiveFixture extends Fixture
{
	/**
	 * @inheritdoc
	 */
	public $depends = ['yii\test\DbFixture'];
	/**
	 * @var Connection|string the DB connection object or the application component ID of the DB connection.
	 * After the ActiveFixture object is created, if you want to change this property, you should only assign it
	 * with a DB connection object.
	 */
	public $db = 'db';
	/**
	 * @var string the AR model class associated with this fixture.
	 * @see tableName
	 */
	public $modelClass;
	/**
	 * @var string the name of the database table that this fixture is about. If this property is not set,
	 * the table name will be determined via [[modelClass]].
	 * @see modelClass
	 */
	public $tableName;
	/**
	 * @var array the data rows. Each array element represents one row of data (column name => column value).
	 */
	public $rows;
	/**
	 * @var TableSchema the table schema for the table associated with this fixture
	 */
	private $_table;
	/**
	 * @var \yii\db\ActiveRecord[] the loaded AR models
	 */
	private $_models;

	/**
	 * @inheritdoc
	 */
	public function init()
	{
		parent::init();
		if (!isset($this->modelClass) && !isset($this->tableName)) {
			throw new InvalidConfigException('Either "modelClass" or "tableName" must be set.');
		}
		if (is_string($this->db)) {
			$this->db = Yii::$app->getComponent($this->db);
		}
		if (!$this->db instanceof Connection) {
			throw new InvalidConfigException("The 'db' property must be either a DB connection instance or the application component ID of a DB connection.");
		}
	}

	/**
	 * @inheritdoc
	 */
	public function load()
	{
		$table = $this->getTableSchema();
		$this->resetTable($table);

		$this->rows = [];
		foreach ($this->loadData() as $alias => $row) {
			$this->db->createCommand()->insert($table->fullName, $row)->execute();
			if ($table->sequenceName !== null) {
				foreach ($table->primaryKey as $pk) {
					if (!isset($row[$pk])) {
						$row[$pk] = $this->db->getLastInsertID($table->sequenceName);
						break;
					}
				}
			}
			$this->rows[$alias] = $row;
		}
	}

	/**
	 * Returns the AR model by the specified model name.
	 * A model name is the key of the corresponding data row returned by [[loadData()]].
	 * @param string $name the model name.
	 * @return null|\yii\db\ActiveRecord the AR model, or null if the model cannot be found in the database
	 * @throws \yii\base\InvalidConfigException if [[modelClass]] is not set.
	 */
	public function getModel($name)
	{
		if (!isset($this->rows[$name])) {
			return null;
		}
		if (array_key_exists($name, $this->_models)) {
			return $this->_models[$name];
		}

		if ($this->modelClass === null) {
			throw new InvalidConfigException('The "modelClass" property must be set.');
		}
		$row = $this->rows[$name];
		/** @var \yii\db\ActiveRecord $modelClass */
		$modelClass = $this->modelClass;
		/** @var \yii\db\ActiveRecord $model */
		$model = new $modelClass;
		$keys = [];
		foreach ($model->primaryKey() as $key) {
			$keys[$key] = isset($row[$key]) ? $row[$key] : null;
		}
		return $this->_models[$name] = $modelClass::find($keys);
	}

	/**
	 * @return TableSchema the schema information of the database table associated with this fixture.
	 * @throws \yii\base\InvalidConfigException if the table does not exist
	 */
	public function getTableSchema()
	{
		if ($this->_table !== null) {
			return $this->_table;
		}

		$db = $this->db;
		$tableName = $this->tableName;
		if ($tableName === null) {
			/** @var \yii\db\ActiveRecord $modelClass */
			$modelClass = $this->modelClass;
			$tableName = $modelClass::tableName();
		}

		$this->_table = $db->getSchema()->getTableSchema($tableName);
		if ($this->_table === null) {
			throw new InvalidConfigException("Table does not exist: {$tableName}");
		}

		return $this->_table;
	}

	/**
	 * Loads fixture data.
	 *
	 * The default implementation will look for a file under the `data` sub-directory of the directory containing
	 * the fixture class. The file name is assumed to be the same as the table name (with schema prefix if necessary).
	 * For example, the `tbl_user` table corresponds to the `data/tbl_user.php` file; the `test.tbl_post` table (`test`
	 * is the non-default schema name) corresponds to `data/test.tbl_user.php`. The file should return an array
	 * of data rows (column name => column value), each corresponding to a row in the table.
	 *
	 * If the data file does not exist, an empty array will be returned.
	 *
	 * You may override this method if you want to change the default way of data loading.
	 *
	 * @return array the data rows to be inserted into the database table.
	 */
	protected function loadData()
	{
		$class = new \ReflectionClass($this);
		$dataFile = dirname($class->getFileName()) . '/data/' . $this->getTableSchema()->fullName . '.php';
		return is_file($dataFile) ? require($dataFile) : [];
	}

	/**
	 * Removes all existing data from the specified table and resets sequence number if any.
	 * This method is called before populating fixture data into the table associated with this fixture.
	 * @param TableSchema $table the table to be reset.
	 */
	protected function resetTable($table)
	{
		$this->db->createCommand()->delete($table->fullName)->execute();
		if ($table->sequenceName !== null) {
			$this->db->createCommand()->resetSequence($table->fullName, 1)->execute();
		}
	}
}