1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
15: namespace Cake\TestSuite\Fixture;
16:
17: loadPHPUnitAliases();
18:
19: use Cake\Core\Configure;
20: use Cake\Core\Exception\Exception;
21: use Cake\Database\Schema\TableSchema;
22: use Cake\Database\Schema\TableSchemaAwareInterface;
23: use Cake\Datasource\ConnectionManager;
24: use Cake\Utility\Inflector;
25: use PDOException;
26: use UnexpectedValueException;
27:
28: 29: 30:
31: class FixtureManager
32: {
33:
34: 35: 36: 37: 38:
39: protected $_initialized = false;
40:
41: 42: 43: 44: 45:
46: protected $_loaded = [];
47:
48: 49: 50: 51: 52:
53: protected $_fixtureMap = [];
54:
55: 56: 57: 58: 59:
60: protected $_insertionMap = [];
61:
62: 63: 64: 65: 66:
67: protected $_processed = [];
68:
69: 70: 71: 72: 73: 74:
75: protected $_debug = false;
76:
77: 78: 79: 80: 81: 82:
83: public function setDebug($debug)
84: {
85: $this->_debug = $debug;
86: }
87:
88: 89: 90: 91: 92: 93:
94: public function fixturize($test)
95: {
96: $this->_initDb();
97: if (empty($test->fixtures) || !empty($this->_processed[get_class($test)])) {
98: return;
99: }
100: if (!is_array($test->fixtures)) {
101: $test->fixtures = array_map('trim', explode(',', $test->fixtures));
102: }
103: $this->_loadFixtures($test);
104: $this->_processed[get_class($test)] = true;
105: }
106:
107: 108: 109: 110: 111:
112: public function loaded()
113: {
114: return $this->_loaded;
115: }
116:
117: 118: 119: 120: 121: 122: 123: 124:
125: protected function _aliasConnections()
126: {
127: $connections = ConnectionManager::configured();
128: ConnectionManager::alias('test', 'default');
129: $map = [];
130: foreach ($connections as $connection) {
131: if ($connection === 'test' || $connection === 'default') {
132: continue;
133: }
134: if (isset($map[$connection])) {
135: continue;
136: }
137: if (strpos($connection, 'test_') === 0) {
138: $map[$connection] = substr($connection, 5);
139: } else {
140: $map['test_' . $connection] = $connection;
141: }
142: }
143: foreach ($map as $testConnection => $normal) {
144: ConnectionManager::alias($testConnection, $normal);
145: }
146: }
147:
148: 149: 150: 151: 152:
153: protected function _initDb()
154: {
155: if ($this->_initialized) {
156: return;
157: }
158: $this->_aliasConnections();
159: $this->_initialized = true;
160: }
161:
162: 163: 164: 165: 166: 167: 168:
169: protected function _loadFixtures($test)
170: {
171: if (empty($test->fixtures)) {
172: return;
173: }
174: foreach ($test->fixtures as $fixture) {
175: if (isset($this->_loaded[$fixture])) {
176: continue;
177: }
178:
179: if (strpos($fixture, '.')) {
180: list($type, $pathName) = explode('.', $fixture, 2);
181: $path = explode('/', $pathName);
182: $name = array_pop($path);
183: $additionalPath = implode('\\', $path);
184:
185: if ($type === 'core') {
186: $baseNamespace = 'Cake';
187: } elseif ($type === 'app') {
188: $baseNamespace = Configure::read('App.namespace');
189: } elseif ($type === 'plugin') {
190: list($plugin, $name) = explode('.', $pathName);
191:
192: $path = str_replace('/', '\\', $plugin);
193: $uninflected = $path;
194: $baseNamespace = Inflector::camelize(str_replace('\\', '\ ', $path));
195: if ($baseNamespace !== $uninflected) {
196: deprecationWarning(sprintf(
197: 'Declaring fixtures in underscored format in TestCase::$fixtures is deprecated.' . "\n" .
198: 'Expected "%s" instead in "%s".',
199: str_replace('\\', '/', $baseNamespace),
200: get_class($test)
201: ));
202: }
203: $additionalPath = null;
204: } else {
205: $baseNamespace = '';
206: $name = $fixture;
207: }
208:
209: $uninflected = $name;
210:
211: if (strpos($name, '/') > 0) {
212: $name = str_replace('/', '\\', $name);
213: $uninflected = $name;
214: $name = str_replace('\\', '\ ', $name);
215: }
216:
217: $name = Inflector::camelize($name);
218: if ($name !== $uninflected) {
219: deprecationWarning(sprintf(
220: 'Declaring fixtures in underscored format in TestCase::$fixtures is deprecated.' . "\n" .
221: 'Found "%s.%s" in "%s". Expected "%s.%s" instead.',
222: $type,
223: $uninflected,
224: get_class($test),
225: $type,
226: str_replace('\\', '/', $name)
227: ));
228: }
229:
230: $nameSegments = [
231: $baseNamespace,
232: 'Test\Fixture',
233: $additionalPath,
234: $name . 'Fixture'
235: ];
236: $className = implode('\\', array_filter($nameSegments));
237: } else {
238: $className = $fixture;
239: $name = preg_replace('/Fixture\z/', '', substr(strrchr($fixture, '\\'), 1));
240: }
241:
242: if (class_exists($className)) {
243: $this->_loaded[$fixture] = new $className();
244: $this->_fixtureMap[$name] = $this->_loaded[$fixture];
245: } else {
246: $msg = sprintf(
247: 'Referenced fixture class "%s" not found. Fixture "%s" was referenced in test case "%s".',
248: $className,
249: $fixture,
250: get_class($test)
251: );
252: throw new UnexpectedValueException($msg);
253: }
254: }
255: }
256:
257: 258: 259: 260: 261: 262: 263: 264: 265:
266: protected function _setupTable($fixture, $db, array $sources, $drop = true)
267: {
268: $configName = $db->configName();
269: $isFixtureSetup = $this->isFixtureSetup($configName, $fixture);
270: if ($isFixtureSetup) {
271: return;
272: }
273:
274: $table = $fixture->sourceName();
275: $exists = in_array($table, $sources);
276:
277: $hasSchema = $fixture instanceof TableSchemaAwareInterface && $fixture->getTableSchema() instanceof TableSchema;
278:
279: if (($drop && $exists) || ($exists && !$isFixtureSetup && $hasSchema)) {
280: $fixture->drop($db);
281: $fixture->create($db);
282: } elseif (!$exists) {
283: $fixture->create($db);
284: } else {
285: $fixture->truncate($db);
286: }
287:
288: $this->_insertionMap[$configName][] = $fixture;
289: }
290:
291: 292: 293: 294: 295: 296: 297:
298: public function load($test)
299: {
300: if (empty($test->fixtures)) {
301: return;
302: }
303:
304: $fixtures = $test->fixtures;
305: if (empty($fixtures) || !$test->autoFixtures) {
306: return;
307: }
308:
309: try {
310: $createTables = function ($db, $fixtures) use ($test) {
311: $tables = $db->getSchemaCollection()->listTables();
312: $configName = $db->configName();
313: if (!isset($this->_insertionMap[$configName])) {
314: $this->_insertionMap[$configName] = [];
315: }
316:
317: foreach ($fixtures as $fixture) {
318: if (in_array($fixture->table, $tables)) {
319: try {
320: $fixture->dropConstraints($db);
321: } catch (PDOException $e) {
322: $msg = sprintf(
323: 'Unable to drop constraints for fixture "%s" in "%s" test case: ' . "\n" . '%s',
324: get_class($fixture),
325: get_class($test),
326: $e->getMessage()
327: );
328: throw new Exception($msg, null, $e);
329: }
330: }
331: }
332:
333: foreach ($fixtures as $fixture) {
334: if (!in_array($fixture, $this->_insertionMap[$configName])) {
335: $this->_setupTable($fixture, $db, $tables, $test->dropTables);
336: } else {
337: $fixture->truncate($db);
338: }
339: }
340:
341: foreach ($fixtures as $fixture) {
342: try {
343: $fixture->createConstraints($db);
344: } catch (PDOException $e) {
345: $msg = sprintf(
346: 'Unable to create constraints for fixture "%s" in "%s" test case: ' . "\n" . '%s',
347: get_class($fixture),
348: get_class($test),
349: $e->getMessage()
350: );
351: throw new Exception($msg, null, $e);
352: }
353: }
354: };
355: $this->_runOperation($fixtures, $createTables);
356:
357:
358: $insert = function ($db, $fixtures) use ($test) {
359: foreach ($fixtures as $fixture) {
360: try {
361: $fixture->insert($db);
362: } catch (PDOException $e) {
363: $msg = sprintf(
364: 'Unable to insert fixture "%s" in "%s" test case: ' . "\n" . '%s',
365: get_class($fixture),
366: get_class($test),
367: $e->getMessage()
368: );
369: throw new Exception($msg, null, $e);
370: }
371: }
372: };
373: $this->_runOperation($fixtures, $insert);
374: } catch (PDOException $e) {
375: $msg = sprintf(
376: 'Unable to insert fixtures for "%s" test case. %s',
377: get_class($test),
378: $e->getMessage()
379: );
380: throw new Exception($msg, null, $e);
381: }
382: }
383:
384: 385: 386: 387: 388: 389: 390:
391: protected function _runOperation($fixtures, $operation)
392: {
393: $dbs = $this->_fixtureConnections($fixtures);
394: foreach ($dbs as $connection => $fixtures) {
395: $db = ConnectionManager::get($connection);
396: $newMethods = method_exists($db, 'isQueryLoggingEnabled');
397: if ($newMethods) {
398: $logQueries = $db->isQueryLoggingEnabled();
399: } else {
400: $logQueries = $db->logQueries();
401: }
402:
403: if ($logQueries && !$this->_debug) {
404: if ($newMethods) {
405: $db->disableQueryLogging();
406: } else {
407: $db->logQueries(false);
408: }
409: }
410: $db->transactional(function ($db) use ($fixtures, $operation) {
411: $db->disableConstraints(function ($db) use ($fixtures, $operation) {
412: $operation($db, $fixtures);
413: });
414: });
415: if ($logQueries) {
416: if ($newMethods) {
417: $db->enableQueryLogging(true);
418: } else {
419: $db->logQueries(true);
420: }
421: }
422: }
423: }
424:
425: 426: 427: 428: 429: 430:
431: protected function _fixtureConnections($fixtures)
432: {
433: $dbs = [];
434: foreach ($fixtures as $f) {
435: if (!empty($this->_loaded[$f])) {
436: $fixture = $this->_loaded[$f];
437: $dbs[$fixture->connection()][$f] = $fixture;
438: }
439: }
440:
441: return $dbs;
442: }
443:
444: 445: 446: 447: 448: 449:
450: public function unload($test)
451: {
452: if (empty($test->fixtures)) {
453: return;
454: }
455: $truncate = function ($db, $fixtures) {
456: $configName = $db->configName();
457:
458: foreach ($fixtures as $name => $fixture) {
459: if ($this->isFixtureSetup($configName, $fixture)) {
460: $fixture->dropConstraints($db);
461: }
462: }
463:
464: foreach ($fixtures as $fixture) {
465: if ($this->isFixtureSetup($configName, $fixture)) {
466: $fixture->truncate($db);
467: }
468: }
469: };
470: $this->_runOperation($test->fixtures, $truncate);
471: }
472:
473: 474: 475: 476: 477: 478: 479: 480: 481:
482: public function loadSingle($name, $db = null, $dropTables = true)
483: {
484: if (!isset($this->_fixtureMap[$name])) {
485: throw new UnexpectedValueException(sprintf('Referenced fixture class %s not found', $name));
486: }
487:
488: $fixture = $this->_fixtureMap[$name];
489: if (!$db) {
490: $db = ConnectionManager::get($fixture->connection());
491: }
492:
493: if (!$this->isFixtureSetup($db->configName(), $fixture)) {
494: $sources = $db->getSchemaCollection()->listTables();
495: $this->_setupTable($fixture, $db, $sources, $dropTables);
496: }
497:
498: if (!$dropTables) {
499: $fixture->dropConstraints($db);
500: $fixture->truncate($db);
501: }
502:
503: $fixture->createConstraints($db);
504: $fixture->insert($db);
505: }
506:
507: 508: 509: 510: 511:
512: public function shutDown()
513: {
514: $shutdown = function ($db, $fixtures) {
515: $connection = $db->configName();
516: foreach ($fixtures as $fixture) {
517: if ($this->isFixtureSetup($connection, $fixture)) {
518: $fixture->drop($db);
519: $index = array_search($fixture, $this->_insertionMap[$connection]);
520: unset($this->_insertionMap[$connection][$index]);
521: }
522: }
523: };
524: $this->_runOperation(array_keys($this->_loaded), $shutdown);
525: }
526:
527: 528: 529: 530: 531: 532: 533:
534: public function isFixtureSetup($connection, $fixture)
535: {
536: return isset($this->_insertionMap[$connection]) && in_array($fixture, $this->_insertionMap[$connection]);
537: }
538: }
539: