FixtureTrait.php 6.57 KB
Newer Older
Qiang Xue committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<?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;

/**
 * FixtureTrait provides functionalities for loading, unloading and accessing fixtures for a test case.
 *
16 17 18 19 20 21
 * By using FixtureTrait, a test class will be able to specify which fixtures to load by overriding
 * the [[fixtures()]] method. It can then load and unload the fixtures using [[loadFixtures()]] and [[unloadFixtures()]].
 * Once a fixture is loaded, it can be accessed like an object property, thanks to the PHP `__get()` magic method.
 * Also, if the fixture is an instance of [[ActiveFixture]], you will be able to access AR models
 * through the syntax `$this->fixtureName('model name')`.
 *
Qiang Xue committed
22 23 24 25 26
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
trait FixtureTrait
{
27 28 29 30 31 32 33 34
	/**
	 * @var array the list of fixture objects available for the current test.
	 * The array keys are the corresponding fixture class names.
	 * The fixtures are listed in their dependency order. That is, fixture A is listed before B
	 * if B depends on A.
	 */
	private $_fixtures;

Qiang Xue committed
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
	/**
	 * Declares the fixtures that are needed by the current test case.
	 * The return value of this method must be an array of fixture configurations. For example,
	 *
	 * ```php
	 * [
	 *     // anonymous fixture
	 *     PostFixture::className(),
	 *     // "users" fixture
	 *     'users' => UserFixture::className(),
	 *     // "cache" fixture with configuration
	 *     'cache' => [
	 *          'class' => CacheFixture::className(),
	 *          'host' => 'xxx',
	 *     ],
	 * ]
	 * ```
	 *
Qiang Xue committed
53 54 55
	 * Note that the actual fixtures used for a test case will include both [[globalFixtures()]]
	 * and [[fixtures()]].
	 *
Qiang Xue committed
56 57
	 * @return array the fixtures needed by the current test case
	 */
Qiang Xue committed
58
	public function fixtures()
Qiang Xue committed
59 60 61 62
	{
		return [];
	}

Qiang Xue committed
63 64 65 66 67 68 69
	/**
	 * Declares the fixtures shared required by different test cases.
	 * The return value should be similar to that of [[fixtures()]].
	 * You should usually override this method in a base class.
	 * @return array the fixtures shared and required by different test cases.
	 * @see fixtures()
	 */
Qiang Xue committed
70
	public function globalFixtures()
Qiang Xue committed
71 72 73 74
	{
		return [];
	}

Qiang Xue committed
75
	/**
76 77 78 79
	 * Loads the specified fixtures.
	 * This method will call [[Fixture::load()]] for every fixture object.
	 * @param Fixture[] $fixtures the fixtures to be loaded. If this parameter is not specified,
	 * the return value of [[getFixtures()]] will be used.
Qiang Xue committed
80
	 */
81
	public function loadFixtures($fixtures = null)
Qiang Xue committed
82 83
	{
		if ($fixtures === null) {
84
			$fixtures = $this->getFixtures();
Qiang Xue committed
85 86 87
		}

		/** @var Fixture $fixture */
88
		foreach ($fixtures as $fixture) {
Qiang Xue committed
89 90
			$fixture->beforeLoad();
		}
91
		foreach ($fixtures as $fixture) {
Qiang Xue committed
92 93
			$fixture->load();
		}
94
		foreach (array_reverse($fixtures) as $fixture) {
Qiang Xue committed
95 96 97 98 99
			$fixture->afterLoad();
		}
	}

	/**
100 101 102 103
	 * Unloads the specified fixtures.
	 * This method will call [[Fixture::unload()]] for every fixture object.
	 * @param Fixture[] $fixtures the fixtures to be loaded. If this parameter is not specified,
	 * the return value of [[getFixtures()]] will be used.
Qiang Xue committed
104
	 */
105
	public function unloadFixtures($fixtures = null)
Qiang Xue committed
106
	{
107 108 109 110
		if ($fixtures === null) {
			$fixtures = $this->getFixtures();
		}

Qiang Xue committed
111
		/** @var Fixture $fixture */
112 113 114 115 116
		foreach ($fixtures as $fixture) {
			$fixture->beforeUnload();
		}
		$fixtures = array_reverse($fixtures);
		foreach ($fixtures as $fixture) {
Qiang Xue committed
117 118
			$fixture->unload();
		}
119 120 121
		foreach ($fixtures as $fixture) {
			$fixture->afterUnload();
		}
Qiang Xue committed
122 123 124
	}

	/**
125 126
	 * Returns the fixture objects as specified in [[globalFixtures()]] and [[fixtures()]].
	 * @return Fixture[] the loaded fixtures for the current test case
Qiang Xue committed
127
	 */
Qiang Xue committed
128
	public function getFixtures()
Qiang Xue committed
129
	{
130 131 132
		if ($this->_fixtures === null) {
			$this->_fixtures = $this->createFixtures(array_merge($this->globalFixtures(), $this->fixtures()));
		}
Qiang Xue committed
133 134 135 136 137
		return $this->_fixtures;
	}

	/**
	 * Returns the named fixture.
138
	 * @param string $name the fixture name. This can be either the fixture alias name, or the class name if the alias is not used.
Qiang Xue committed
139 140
	 * @return Fixture the fixture object, or null if the named fixture does not exist.
	 */
Qiang Xue committed
141
	public function getFixture($name)
Qiang Xue committed
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 195 196 197 198 199 200 201 202 203
		if ($this->_fixtures === null) {
			$this->_fixtures = $this->createFixtures(array_merge($this->globalFixtures(), $this->fixtures()));
		}
		$name = ltrim($name, '\\');
		return isset($this->_fixtures[$name]) ? $this->_fixtures[$name] : null;
	}

	/**
	 * Creates the specified fixture instances.
	 * All dependent fixtures will also be created.
	 * @param array $fixtures the fixtures to be created. You may provide fixture names or fixture configurations.
	 * If this parameter is not provided, the fixtures specified in [[globalFixtures()]] and [[fixtures()]] will be created.
	 * @return Fixture[] the created fixture instances
	 * @throws InvalidConfigException if fixtures are not properly configured or if a circular dependency among
	 * the fixtures is detected.
	 */
	protected function createFixtures(array $fixtures)
	{
		// normalize fixture configurations
		$config = [];  // configuration provided in test case
		$aliases = [];  // class name => alias or class name
		foreach ($fixtures as $name => $fixture) {
			if (!is_array($fixture)) {
				$class = ltrim($fixture, '\\');
				$fixtures[$name] = ['class' => $class];
				$aliases[$class] = is_integer($name) ? $class : $name;
			} elseif (isset($fixture['class'])) {
				$class = ltrim($fixture['class'], '\\');
				$config[$class] = $fixture;
				$aliases[$class] = $name;
			} else {
				throw new InvalidConfigException("You must specify 'class' for the fixture '$name'.");
			}
		}

		// create fixture instances
		$instances = [];
		$stack = array_reverse($fixtures);
		while (($fixture = array_pop($stack)) !== null) {
			if ($fixture instanceof Fixture) {
				$class = get_class($fixture);
				$name = isset($aliases[$class]) ? $aliases[$class] : $class;
				unset($instances[$name]);  // unset so that the fixture is added to the last in the next line
				$instances[$name] = $fixture;
			} else {
				$class = ltrim($fixture['class'], '\\');
				$name = isset($aliases[$class]) ? $aliases[$class] : $class;
				if (!isset($instances[$name])) {
					$instances[$name] = false;
					$stack[] = $fixture = Yii::createObject($fixture);
					foreach ($fixture->depends as $dep) {
						// need to use the configuration provided in test case
						$stack[] = isset($config[$dep]) ? $config[$dep] : ['class' => $dep];
					}
				} elseif ($instances[$name] === false) {
					throw new InvalidConfigException("A circular dependency is detected for fixture '$class'.");
				}
			}
		}

		return $instances;
Qiang Xue committed
204 205
	}
}