1 | <?php |
||
2 | |||
3 | namespace CoordinativeDev\Fixture\Traits; |
||
4 | |||
5 | use CoordinativeDev\Fixture\Exceptions\InvalidConfigException; |
||
6 | use CoordinativeDev\Fixture\Fixture; |
||
7 | |||
8 | /** |
||
9 | * FixtureTrait provides functionality for loading, unloading and accessing fixtures for a test case. |
||
10 | * |
||
11 | * By using FixtureTrait, a test class will be able to specify which fixtures to load by overriding |
||
12 | * the [[fixtures()]] method. It can then load and unload the fixtures using [[loadFixtures()]] and [[unloadFixtures()]]. |
||
13 | */ |
||
14 | trait FixtureTrait |
||
15 | { |
||
16 | /** |
||
17 | * @var array the list of fixture objects available for the current test. |
||
18 | * The array keys are the corresponding fixture class names. |
||
19 | * The fixtures are listed in their dependency order. That is, fixture A is listed before B |
||
20 | * if B depends on A. |
||
21 | */ |
||
22 | private $_fixtures; |
||
23 | |||
24 | /** |
||
25 | * Declares the fixtures that are needed by the current test case. |
||
26 | * |
||
27 | * The return value of this method must be an array of fixture configurations. For example, |
||
28 | * |
||
29 | * ```php |
||
30 | * [ |
||
31 | * // anonymous fixture |
||
32 | * ArticleFixture::class, |
||
33 | * // "articles" fixture |
||
34 | * 'articles' => ArticleFixture::class, |
||
35 | * ] |
||
36 | * ``` |
||
37 | * |
||
38 | * @return array the fixtures needed by the current test case |
||
39 | */ |
||
40 | public function fixtures(): array |
||
41 | { |
||
42 | return []; |
||
43 | } |
||
44 | |||
45 | /** |
||
46 | * Loads the specified fixtures. |
||
47 | * This method will call [[Fixture::load()]] for every fixture object. |
||
48 | * |
||
49 | * @param Fixture[] $fixtures the fixtures to be loaded. If this parameter is not specified, |
||
50 | * the return value of [[getFixtures()]] will be used. |
||
51 | * |
||
52 | * @throws InvalidConfigException |
||
53 | */ |
||
54 | public function loadFixtures($fixtures = null): void |
||
55 | { |
||
56 | if ($fixtures === null) { |
||
57 | $fixtures = $this->getFixtures(); |
||
58 | } |
||
59 | |||
60 | /* @var $fixture Fixture */ |
||
61 | foreach ($fixtures as $fixture) { |
||
62 | $fixture->beforeLoad(); |
||
63 | } |
||
64 | |||
65 | foreach ($fixtures as $fixture) { |
||
66 | $fixture->load(); |
||
67 | } |
||
68 | |||
69 | foreach (array_reverse($fixtures) as $fixture) { |
||
70 | $fixture->afterLoad(); |
||
71 | } |
||
72 | } |
||
73 | |||
74 | /** |
||
75 | * Unloads the specified fixtures. |
||
76 | * This method will call [[Fixture::unload()]] for every fixture object. |
||
77 | * |
||
78 | * @param Fixture[] $fixtures the fixtures to be loaded. If this parameter is not specified, |
||
79 | * the return value of [[getFixtures()]] will be used. |
||
80 | * |
||
81 | * @throws InvalidConfigException |
||
82 | */ |
||
83 | public function unloadFixtures($fixtures = null): void |
||
84 | { |
||
85 | if ($fixtures === null) { |
||
86 | $fixtures = $this->getFixtures(); |
||
87 | } |
||
88 | |||
89 | /* @var $fixture Fixture */ |
||
90 | foreach ($fixtures as $fixture) { |
||
91 | $fixture->beforeUnload(); |
||
92 | } |
||
93 | |||
94 | $fixtures = array_reverse($fixtures); |
||
95 | |||
96 | foreach ($fixtures as $fixture) { |
||
97 | $fixture->unload(); |
||
98 | } |
||
99 | |||
100 | foreach ($fixtures as $fixture) { |
||
101 | $fixture->afterUnload(); |
||
102 | } |
||
103 | } |
||
104 | |||
105 | /** |
||
106 | * Initialize the fixtures |
||
107 | * |
||
108 | * @throws InvalidConfigException |
||
109 | */ |
||
110 | public function initFixtures(): void |
||
111 | { |
||
112 | $this->unloadFixtures(); |
||
113 | $this->loadFixtures(); |
||
114 | } |
||
115 | |||
116 | /** |
||
117 | * Returns the fixture objects as specified in [[globalFixtures()]] and [[fixtures()]]. |
||
118 | * |
||
119 | * @throws InvalidConfigException |
||
120 | * |
||
121 | * @return Fixture[] the loaded fixtures for the current test case |
||
122 | */ |
||
123 | public function getFixtures() |
||
124 | { |
||
125 | if ($this->_fixtures === null) { |
||
126 | $this->_fixtures = $this->createFixtures($this->fixtures()); |
||
127 | } |
||
128 | |||
129 | return $this->_fixtures; |
||
130 | } |
||
131 | |||
132 | /** |
||
133 | * Returns the named fixture. |
||
134 | * |
||
135 | * @param $name |
||
136 | * |
||
137 | * @return Fixture|null the fixture object, or null if the named fixture does not exist |
||
138 | * |
||
139 | * @throws InvalidConfigException |
||
140 | */ |
||
141 | public function getFixture(string $name): Fixture |
||
142 | { |
||
143 | if ($this->_fixtures === null) { |
||
144 | $this->_fixtures = $this->createFixtures($this->fixtures()); |
||
145 | } |
||
146 | |||
147 | $name = ltrim($name, '\\'); |
||
148 | |||
149 | return $this->_fixtures[$name] ?? null; |
||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||
150 | } |
||
151 | |||
152 | /** |
||
153 | * Creates the specified fixture instances. |
||
154 | * All dependent fixtures will also be created. |
||
155 | * |
||
156 | * @param array $fixtures the fixtures to be created. You may provide fixture names or fixture configurations. |
||
157 | * If this parameter is not provided, the fixtures specified in [[globalFixtures()]] and [[fixtures()]] will be created. |
||
158 | * |
||
159 | * @throws InvalidConfigException if fixtures are not properly configured or if a circular dependency among |
||
160 | * the fixtures is detected |
||
161 | * |
||
162 | * @return Fixture[] the created fixture instances |
||
163 | */ |
||
164 | protected function createFixtures(array $fixtures) |
||
165 | { |
||
166 | // normalize fixture configurations |
||
167 | $config = []; // configuration provided in test case |
||
168 | $aliases = []; // class name => alias or class name |
||
169 | |||
170 | foreach ($fixtures as $name => $fixture) { |
||
171 | if (!is_array($fixture)) { |
||
172 | $class = ltrim($fixture, '\\'); |
||
173 | $fixtures[$name] = ['class' => $class]; |
||
174 | $aliases[$class] = is_int($name) ? $class : $name; |
||
175 | } elseif (isset($fixture['class'])) { |
||
176 | $class = ltrim($fixture['class'], '\\'); |
||
177 | $config[$class] = $fixture; |
||
178 | $aliases[$class] = $name; |
||
179 | } else { |
||
180 | throw new InvalidConfigException("You must specify 'class' for the fixture '$name'."); |
||
181 | } |
||
182 | } |
||
183 | |||
184 | // create fixture instances |
||
185 | $instances = []; |
||
186 | $stack = array_reverse($fixtures); |
||
187 | |||
188 | while (($fixture = array_pop($stack)) !== null) { |
||
189 | if ($fixture instanceof Fixture) { |
||
190 | $class = get_class($fixture); |
||
191 | $name = isset($aliases[$class]) ? $aliases[$class] : $class; |
||
192 | unset($instances[$name]); // unset so that the fixture is added to the last in the next line |
||
193 | $instances[$name] = $fixture; |
||
194 | } else { |
||
195 | $class = ltrim($fixture['class'], '\\'); |
||
196 | $name = isset($aliases[$class]) ? $aliases[$class] : $class; |
||
197 | if (!isset($instances[$name])) { |
||
198 | $instances[$name] = false; |
||
199 | |||
200 | $stack[] = $fixture = app()->make($fixture['class']); |
||
201 | foreach ($fixture->depends as $dep) { |
||
202 | // need to use the configuration provided in test case |
||
203 | $stack[] = isset($config[$dep]) ? $config[$dep] : ['class' => $dep]; |
||
204 | } |
||
205 | } elseif ($instances[$name] === false) { |
||
206 | throw new InvalidConfigException("A circular dependency is detected for fixture '$class'."); |
||
207 | } |
||
208 | } |
||
209 | } |
||
210 | |||
211 | return $instances; |
||
212 | } |
||
213 | } |
||
214 |