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: protected $_initialized = false;
39:
40: 41: 42: 43: 44:
45: protected $_loaded = [];
46:
47: 48: 49: 50: 51:
52: protected $_fixtureMap = [];
53:
54: 55: 56: 57: 58:
59: protected $_insertionMap = [];
60:
61: 62: 63: 64: 65:
66: protected $_processed = [];
67:
68: 69: 70: 71: 72: 73:
74: protected $_debug = false;
75:
76: 77: 78: 79: 80: 81:
82: public function setDebug($debug)
83: {
84: $this->_debug = $debug;
85: }
86:
87: 88: 89: 90: 91: 92:
93: public function fixturize($test)
94: {
95: $this->_initDb();
96: if (empty($test->fixtures) || !empty($this->_processed[get_class($test)])) {
97: return;
98: }
99: if (!is_array($test->fixtures)) {
100: $test->fixtures = array_map('trim', explode(',', $test->fixtures));
101: }
102: $this->_loadFixtures($test);
103: $this->_processed[get_class($test)] = true;
104: }
105:
106: 107: 108: 109: 110:
111: public function loaded()
112: {
113: return $this->_loaded;
114: }
115:
116: 117: 118: 119: 120: 121: 122: 123:
124: protected function _aliasConnections()
125: {
126: $connections = ConnectionManager::configured();
127: ConnectionManager::alias('test', 'default');
128: $map = [];
129: foreach ($connections as $connection) {
130: if ($connection === 'test' || $connection === 'default') {
131: continue;
132: }
133: if (isset($map[$connection])) {
134: continue;
135: }
136: if (strpos($connection, 'test_') === 0) {
137: $map[$connection] = substr($connection, 5);
138: } else {
139: $map['test_' . $connection] = $connection;
140: }
141: }
142: foreach ($map as $testConnection => $normal) {
143: ConnectionManager::alias($testConnection, $normal);
144: }
145: }
146:
147: 148: 149: 150: 151:
152: protected function _initDb()
153: {
154: if ($this->_initialized) {
155: return;
156: }
157: $this->_aliasConnections();
158: $this->_initialized = true;
159: }
160:
161: 162: 163: 164: 165: 166: 167:
168: protected function _loadFixtures($test)
169: {
170: if (empty($test->fixtures)) {
171: return;
172: }
173: foreach ($test->fixtures as $fixture) {
174: if (isset($this->_loaded[$fixture])) {
175: continue;
176: }
177:
178: if (strpos($fixture, '.')) {
179: list($type, $pathName) = explode('.', $fixture, 2);
180: $path = explode('/', $pathName);
181: $name = array_pop($path);
182: $additionalPath = implode('\\', $path);
183:
184: if ($type === 'core') {
185: $baseNamespace = 'Cake';
186: } elseif ($type === 'app') {
187: $baseNamespace = Configure::read('App.namespace');
188: } elseif ($type === 'plugin') {
189: list($plugin, $name) = explode('.', $pathName);
190:
191: $path = str_replace('/', '\\', $plugin);
192: $uninflected = $path;
193: $baseNamespace = Inflector::camelize(str_replace('\\', '\ ', $path));
194: if ($baseNamespace !== $uninflected) {
195: deprecationWarning(sprintf(
196: 'Declaring fixtures in underscored format in TestCase::$fixtures is deprecated.' . "\n" .
197: 'Expected "%s" instead in "%s".',
198: str_replace('\\', '/', $baseNamespace),
199: get_class($test)
200: ));
201: }
202: $additionalPath = null;
203: } else {
204: $baseNamespace = '';
205: $name = $fixture;
206: }
207:
208: $uninflected = $name;
209:
210: if (strpos($name, '/') > 0) {
211: $name = str_replace('/', '\\', $name);
212: $uninflected = $name;
213: $name = str_replace('\\', '\ ', $name);
214: }
215:
216: $name = Inflector::camelize($name);
217: if ($name !== $uninflected) {
218: deprecationWarning(sprintf(
219: 'Declaring fixtures in underscored format in TestCase::$fixtures is deprecated.' . "\n" .
220: 'Found "%s.%s" in "%s". Expected "%s.%s" instead.',
221: $type,
222: $uninflected,
223: get_class($test),
224: $type,
225: str_replace('\\', '/', $name)
226: ));
227: }
228:
229: $nameSegments = [
230: $baseNamespace,
231: 'Test\Fixture',
232: $additionalPath,
233: $name . 'Fixture'
234: ];
235: $className = implode('\\', array_filter($nameSegments));
236: } else {
237: $className = $fixture;
238: $name = preg_replace('/Fixture\z/', '', substr(strrchr($fixture, '\\'), 1));
239: }
240:
241: if (class_exists($className)) {
242: $this->_loaded[$fixture] = new $className();
243: $this->_fixtureMap[$name] = $this->_loaded[$fixture];
244: } else {
245: $msg = sprintf(
246: 'Referenced fixture class "%s" not found. Fixture "%s" was referenced in test case "%s".',
247: $className,
248: $fixture,
249: get_class($test)
250: );
251: throw new UnexpectedValueException($msg);
252: }
253: }
254: }
255:
256: 257: 258: 259: 260: 261: 262: 263: 264:
265: protected function _setupTable($fixture, $db, array $sources, $drop = true)
266: {
267: $configName = $db->configName();
268: $isFixtureSetup = $this->isFixtureSetup($configName, $fixture);
269: if ($isFixtureSetup) {
270: return;
271: }
272:
273: $table = $fixture->sourceName();
274: $exists = in_array($table, $sources);
275:
276: $hasSchema = $fixture instanceof TableSchemaAwareInterface && $fixture->getTableSchema() instanceof TableSchema;
277:
278: if (($drop && $exists) || ($exists && !$isFixtureSetup && $hasSchema)) {
279: $fixture->drop($db);
280: $fixture->create($db);
281: } elseif (!$exists) {
282: $fixture->create($db);
283: } else {
284: $fixture->truncate($db);
285: }
286:
287: $this->_insertionMap[$configName][] = $fixture;
288: }
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 $name) {
435: if (!empty($this->_loaded[$name])) {
436: $fixture = $this->_loaded[$name];
437: $dbs[$fixture->connection()][$name] = $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: