├── tests
├── Integration
│ ├── database
│ │ └── .gitkeep
│ ├── Entity
│ │ ├── Product
│ │ │ └── TestProduct.php
│ │ └── Category
│ │ │ └── TestCategory.php
│ ├── Dictionary
│ │ ├── SqliteDictionary.php
│ │ └── MySqlDictionary.php
│ ├── PurgerTest.php
│ ├── MySqlBackupTest.php
│ ├── SqliteBackupTest.php
│ ├── BasicPHPUnitUsageExampleTest.php
│ ├── AdvancedPHPUnitUsageExampleTest.php
│ └── IntegrationTestCase.php
├── bootstrap.php
├── Command
│ ├── MysqldumpDummyCommand.php
│ └── MysqldumpCommandTest.php
├── PurgerFactoryTest.php
├── Storage
│ ├── InMemoryStorageTest.php
│ └── LocalStorageTest.php
├── Backup
│ ├── SqliteBackupTest.php
│ └── MySqlBackupTest.php
└── BackupFactoryTest.php
├── .gitignore
├── src
├── Command
│ ├── Command.php
│ └── MysqldumpCommand.php
├── PurgerFactory.php
├── Storage
│ ├── Storage.php
│ ├── LocalStorage.php
│ └── InMemoryStorage.php
├── Backup
│ ├── Backup.php
│ ├── SqliteBackup.php
│ └── MySqlBackup.php
├── DoctrineDatabaseBackup.php
├── BackupFactory.php
└── Purger.php
├── .travis.yml
├── CHANGELOG.md
├── phpunit.xml.dist
├── composer.json
└── README.md
/tests/Integration/database/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | .idea/
3 | composer.lock
4 | composer.phar
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ./tests
7 | ./tests/Integration
8 |
9 |
10 |
11 | ./tests/Integration
12 |
13 |
14 |
15 |
16 |
17 | ./
18 |
19 | ./tests
20 | ./vendor
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lzakrzewski/doctrine-database-backup",
3 | "type": "library",
4 | "description": "Doctrine Database Backup",
5 | "require": {
6 | "php": ">=5.4",
7 | "doctrine/orm": "~2.3",
8 | "symfony/process": "~2.3|~3.0"
9 | },
10 | "license": "MIT",
11 | "authors": [
12 | {
13 | "name": "lzakrzewski",
14 | "email": "contact@lzakrzewski.com"
15 | }
16 | ],
17 | "autoload": {
18 | "psr-4": {
19 | "Lzakrzewski\\DoctrineDatabaseBackup\\": "src"
20 | }
21 | },
22 | "autoload-dev": {
23 | "psr-4" : { "Lzakrzewski\\DoctrineDatabaseBackup\\tests\\" : "tests" }
24 | },
25 | "require-dev": {
26 | "phpunit/phpunit" : "~4.8",
27 | "mikey179/vfsStream": "~1",
28 | "satooshi/php-coveralls": "~1.0"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Storage/LocalStorage.php:
--------------------------------------------------------------------------------
1 | name = $name;
37 | $this->price = $price;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/PurgerFactoryTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf('\Lzakrzewski\DoctrineDatabaseBackup\Purger', PurgerFactory::instance($this->entityManager->reveal()));
18 | }
19 |
20 | /** {@inheritdoc} */
21 | protected function setUp()
22 | {
23 | $this->entityManager = $this->prophesize('\Doctrine\ORM\EntityManager');
24 | }
25 |
26 | /** {@inheritdoc} */
27 | protected function tearDown()
28 | {
29 | $this->entityManager = null;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/Integration/Dictionary/SqliteDictionary.php:
--------------------------------------------------------------------------------
1 | 'pdo_sqlite',
17 | 'user' => 'root',
18 | 'password' => '',
19 | 'path' => __DIR__.'/../database/sqlite.db',
20 | ];
21 | }
22 |
23 | protected function setupDatabase()
24 | {
25 | $params = $this->getParams();
26 |
27 | $tmpConnection = DriverManager::getConnection($params);
28 | $tmpConnection->getSchemaManager()->createDatabase($params['path']);
29 |
30 | $schemaTool = new SchemaTool($this->entityManager);
31 | $schemaTool->dropDatabase();
32 |
33 | $productClass = $this->productClass();
34 | $categoryClass = $this->categoryClass();
35 |
36 | $schemaTool->createSchema([
37 | $this->entityManager->getClassMetadata($productClass),
38 | $this->entityManager->getClassMetadata($categoryClass),
39 | ]);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Storage/InMemoryStorage.php:
--------------------------------------------------------------------------------
1 | registry[$key] = $value;
29 | }
30 |
31 | /** {@inheritdoc} */
32 | public function read($key)
33 | {
34 | if (!$this->has($key)) {
35 | throw new \RuntimeException(sprintf('There is no object "%s" in memory', $key));
36 | }
37 |
38 | return $this->registry[$key];
39 | }
40 |
41 | /** {@inheritdoc} */
42 | public function has($key)
43 | {
44 | return array_key_exists($key, $this->registry);
45 | }
46 |
47 | public function clear()
48 | {
49 | $this->registry = [];
50 | }
51 |
52 | private function __construct()
53 | {
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tests/Integration/Entity/Category/TestCategory.php:
--------------------------------------------------------------------------------
1 | products = new ArrayCollection($products);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/Integration/Dictionary/MySqlDictionary.php:
--------------------------------------------------------------------------------
1 | 'pdo_mysql',
17 | 'user' => 'root',
18 | 'password' => '',
19 | 'dbname' => 'doctrine-database-test',
20 | ];
21 | }
22 |
23 | protected function setupDatabase()
24 | {
25 | $params = $this->getParams();
26 | $name = $params['dbname'];
27 |
28 | unset($params['dbname']);
29 |
30 | $tmpConnection = DriverManager::getConnection($params);
31 | $nameEscaped = $tmpConnection->getDatabasePlatform()->quoteSingleIdentifier($name);
32 |
33 | if (in_array($name, $tmpConnection->getSchemaManager()->listDatabases())) {
34 | $tmpConnection->getSchemaManager()->dropDatabase($nameEscaped);
35 | }
36 |
37 | $tmpConnection->getSchemaManager()->createDatabase($nameEscaped);
38 |
39 | $productClass = $this->productClass();
40 | $categoryClass = $this->categoryClass();
41 |
42 | $schemaTool = new SchemaTool($this->entityManager);
43 | $schemaTool->createSchema([
44 | $this->entityManager->getClassMetadata($productClass),
45 | $this->entityManager->getClassMetadata($categoryClass),
46 | ]);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/DoctrineDatabaseBackup.php:
--------------------------------------------------------------------------------
1 | entityManager = $entityManager;
26 |
27 | $this->backup = BackupFactory::instance($entityManager);
28 | $this->purger = PurgerFactory::instance($entityManager);
29 |
30 | $this->purgeDB = $purgeDB;
31 | }
32 |
33 | public function restore(callable $setupDatabaseCallback = null)
34 | {
35 | if (!$this->backup->isBackupCreated()) {
36 |
37 | if ($this->purgeDB) {
38 | $this->purger->purge();
39 | }
40 |
41 | if (null !== $setupDatabaseCallback) {
42 | $setupDatabaseCallback($this->entityManager);
43 | }
44 |
45 | $this->backup->create();
46 | }
47 |
48 | $this->backup->restore();
49 | }
50 |
51 | /**
52 | * @return Backup
53 | */
54 | public function getBackup()
55 | {
56 | return $this->backup;
57 | }
58 |
59 | /**
60 | * @return Purger
61 | */
62 | public function getPurger()
63 | {
64 | return $this->purger;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/tests/Integration/PurgerTest.php:
--------------------------------------------------------------------------------
1 | givenDatabaseContainsProducts(5);
20 |
21 | $this->purger->purge();
22 |
23 | $this->assertThatDatabaseIsClear();
24 | }
25 |
26 | /** @test */
27 | public function it_purges_database_twice_with_cached_sql()
28 | {
29 | $this->givenDatabaseContainsProducts(5);
30 |
31 | $this->purger->purge();
32 |
33 | $this->addProduct();
34 |
35 | $this->purger->purge();
36 |
37 | $this->assertThatDatabaseIsClear();
38 | }
39 |
40 | /** @test */
41 | public function it_purges_database_with_related_entities()
42 | {
43 | $this->givenDatabaseContainsProducts(5);
44 | $this->givenDatabaseContainsCategories(5);
45 |
46 | $this->purger->purge();
47 |
48 | $this->assertThatDatabaseIsClear();
49 | }
50 |
51 | /**
52 | * {@inheritdoc}
53 | */
54 | protected function setUp()
55 | {
56 | parent::setUp();
57 |
58 | InMemoryStorage::instance()->clear();
59 |
60 | $this->purger = new Purger($this->entityManager, InMemoryStorage::instance());
61 | }
62 |
63 | /**
64 | * {@inheritdoc}
65 | */
66 | protected function tearDown()
67 | {
68 | $this->purger = null;
69 |
70 | parent::tearDown();
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Backup/SqliteBackup.php:
--------------------------------------------------------------------------------
1 | sourcePath = $sourcePath;
27 | $this->memoryStorage = $memoryStorage;
28 | $this->localStorage = $localStorage;
29 | }
30 |
31 | /** {@inheritdoc} */
32 | public function create()
33 | {
34 | $sourcePath = $this->sourcePath;
35 |
36 | if (!$this->localStorage->has($sourcePath)) {
37 | throw new \RuntimeException(sprintf("Source database '%s' should exists.", $sourcePath));
38 | }
39 |
40 | $this->memoryStorage->put(self::BACKUP_KEY, $this->localStorage->read($sourcePath));
41 | }
42 |
43 | /** {@inheritdoc} */
44 | public function restore()
45 | {
46 | if (!$this->isBackupCreated()) {
47 | throw new \RuntimeException('Backup file should be created before restore database.');
48 | }
49 |
50 | $this->localStorage->put($this->sourcePath, $this->memoryStorage->read(self::BACKUP_KEY));
51 | }
52 |
53 | /** {@inheritdoc} */
54 | public function isBackupCreated()
55 | {
56 | return $this->memoryStorage->has(self::BACKUP_KEY);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/Command/MysqldumpCommandTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(
13 | "mysqldump 'dbname' --no-create-info --host='host' --port='3306' --user='user' --password='password'",
14 | $command->run()
15 | );
16 | }
17 |
18 | /** @test */
19 | public function it_can_call_command_to_dump_database_without_password()
20 | {
21 | $command = new MysqldumpDummyCommand('dbname', 'host', '3306', 'user');
22 |
23 | $this->assertEquals(
24 | "mysqldump 'dbname' --no-create-info --host='host' --port='3306' --user='user'",
25 | $command->run()
26 | );
27 | }
28 |
29 | /** @test */
30 | public function it_can_call_command_to_dump_database_without_password_and_user()
31 | {
32 | $command = new MysqldumpDummyCommand('dbname', 'host', '3306');
33 |
34 | $this->assertEquals(
35 | "mysqldump 'dbname' --no-create-info --host='host' --port='3306'",
36 | $command->run()
37 | );
38 | }
39 |
40 | /** @test */
41 | public function it_can_call_command_to_dump_database_without_password_and_user_and_port()
42 | {
43 | $command = new MysqldumpDummyCommand('dbname', 'host');
44 |
45 | $this->assertEquals(
46 | "mysqldump 'dbname' --no-create-info --host='host'",
47 | $command->run()
48 | );
49 | }
50 |
51 | /** @test */
52 | public function it_can_call_command_to_dump_database_without_password_user_and_host_and_port()
53 | {
54 | $command = new MysqldumpDummyCommand('dbname');
55 |
56 | $this->assertEquals(
57 | "mysqldump 'dbname' --no-create-info ",
58 | $command->run()
59 | );
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Command/MysqldumpCommand.php:
--------------------------------------------------------------------------------
1 | dbname = $dbname;
30 | $this->host = $host;
31 | $this->port = $port;
32 | $this->user = $user;
33 | $this->password = $password;
34 | }
35 |
36 | /** {@inheritdoc} */
37 | public function run()
38 | {
39 | $command = sprintf('mysqldump %s --no-create-info ', escapeshellarg($this->dbname));
40 |
41 | if (null !== $this->host && strlen($this->host)) {
42 | $command .= sprintf(' --host=%s', escapeshellarg($this->host));
43 | }
44 |
45 | if (null !== $this->port && strlen($this->port)) {
46 | $command .= sprintf(' --port=%s', escapeshellarg($this->port));
47 | }
48 |
49 | if (null !== $this->user && strlen($this->user)) {
50 | $command .= sprintf(' --user=%s', escapeshellarg($this->user));
51 | }
52 |
53 | if (null !== $this->password && strlen($this->password)) {
54 | $command .= sprintf(' --password=%s', escapeshellarg($this->password));
55 | }
56 |
57 | return $this->execute($command);
58 | }
59 |
60 | protected function execute($command)
61 | {
62 | $process = new Process($command);
63 | $process->run();
64 |
65 | if (!$process->isSuccessful()) {
66 | throw new \RuntimeException($process->getErrorOutput());
67 | }
68 |
69 | return $process->getOutput();
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Backup/MySqlBackup.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
32 | $this->memoryStorage = $memoryStorage;
33 | $this->purger = $purger;
34 | $this->command = $command;
35 | }
36 |
37 | /** {@inheritdoc} */
38 | public function create()
39 | {
40 | $this->memoryStorage->put(self::BACKUP_KEY, $this->dataSql());
41 | }
42 |
43 | /** {@inheritdoc} */
44 | public function restore()
45 | {
46 | if (!$this->isBackupCreated()) {
47 | throw new \RuntimeException('Backup should be created before restore database.');
48 | }
49 |
50 | $this->purger->purge();
51 |
52 | if (null !== $dataSql = $this->memoryStorage->read(self::BACKUP_KEY)) {
53 | $this->execute($dataSql);
54 | }
55 | }
56 |
57 | /** {@inheritdoc} */
58 | public function isBackupCreated()
59 | {
60 | return $this->memoryStorage->has(self::BACKUP_KEY);
61 | }
62 |
63 | private function execute($sql)
64 | {
65 | $this->connection->beginTransaction();
66 | $this->connection->exec($sql);
67 |
68 | $this->connection->commit();
69 | }
70 |
71 | private function dataSql()
72 | {
73 | $output = $this->command->run();
74 |
75 | if (false === stripos($output, 'INSERT')) {
76 | return;
77 | }
78 |
79 | return $output;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/tests/Storage/InMemoryStorageTest.php:
--------------------------------------------------------------------------------
1 | assertSame(InMemoryStorage::instance(), $this->storage);
16 | }
17 |
18 | /** @test */
19 | public function it_reads()
20 | {
21 | $value = new \stdClass();
22 |
23 | $this->storage->put('test', $value);
24 |
25 | $this->assertSame($value, $this->storage->read('test'));
26 | }
27 |
28 | /**
29 | * @test
30 | * @expectedException \RuntimeException
31 | */
32 | public function it_fails_when_try_to_read_not_existing_value()
33 | {
34 | $this->storage->read('test');
35 | }
36 |
37 | /** @test */
38 | public function it_has_value()
39 | {
40 | $value = new \stdClass();
41 |
42 | $this->storage->put('test', $value);
43 |
44 | $this->assertTrue($this->storage->has('test'));
45 | }
46 |
47 | /** @test */
48 | public function it_has_not_value()
49 | {
50 | $this->assertFalse($this->storage->has('test'));
51 | }
52 |
53 | /** @test */
54 | public function it_has_null()
55 | {
56 | $this->storage->put('test', null);
57 |
58 | $this->assertTrue($this->storage->has('test'));
59 | }
60 |
61 | /** @test */
62 | public function it_has_not_shared_value_by_default()
63 | {
64 | $this->assertFalse($this->storage->has('test'));
65 | }
66 |
67 | /**
68 | * @test
69 | * @expectedException \RuntimeException
70 | */
71 | public function it_can_be_cleared()
72 | {
73 | $value = new \stdClass();
74 |
75 | $this->storage->put('test', $value);
76 | $this->storage->clear();
77 |
78 | $this->storage->read('test');
79 | }
80 |
81 | /** {@inheritdoc} */
82 | protected function setUp()
83 | {
84 | $this->storage = InMemoryStorage::instance();
85 | $this->storage->clear();
86 | }
87 |
88 | /** {@inheritdoc} */
89 | protected function tearDown()
90 | {
91 | $this->storage = null;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/BackupFactory.php:
--------------------------------------------------------------------------------
1 | getConnection();
19 |
20 | if ($connection->getDatabasePlatform() instanceof SqlitePlatform) {
21 | return self::sqliteBackup($entityManager);
22 | }
23 |
24 | if ($connection->getDatabasePlatform() instanceof MySqlPlatform) {
25 | return self::mySqlBackup($entityManager);
26 | }
27 |
28 | throw new \RuntimeException('Unsupported database platform. Currently "SqlitePlatform" is supported.');
29 | }
30 |
31 | private static function sqliteBackup(EntityManager $entityManager)
32 | {
33 | $params = $entityManager->getConnection()->getParams();
34 |
35 | if (false === isset($params['path']) || $params['path'] == ':memory:') {
36 | throw new \RuntimeException('Backup for Sqlite "in_memory" is not supported.');
37 | }
38 |
39 | return new SqliteBackup($params['path'], InMemoryStorage::instance(), new LocalStorage());
40 | }
41 |
42 | private static function mySqlBackup(EntityManager $entityManager)
43 | {
44 | $params = $entityManager->getConnection()->getParams();
45 |
46 | if (false === isset($params['dbname'])) {
47 | throw new \RuntimeException('Database name should be provided');
48 | }
49 |
50 | $host = (isset($params['host'])) ? $params['host'] : null;
51 | $port = (isset($params['port'])) ? $params['port'] : null;
52 | $user = (isset($params['user'])) ? $params['user'] : null;
53 | $password = (isset($params['password'])) ? $params['password'] : null;
54 |
55 | $purger = PurgerFactory::instance($entityManager);
56 | $command = new MysqldumpCommand($params['dbname'], $host, $port, $user, $password);
57 |
58 | return new MySqlBackup($entityManager->getConnection(), InMemoryStorage::instance(), $purger, $command);
59 | }
60 |
61 | private function __construct()
62 | {
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tests/Storage/LocalStorageTest.php:
--------------------------------------------------------------------------------
1 | givenFileExists('vfs://project/source.db', 'contents');
17 |
18 | $this->assertEquals('contents', $this->storage->read('vfs://project/source.db'));
19 | }
20 |
21 | /**
22 | * @test
23 | *
24 | * @expectedException \RuntimeException
25 | */
26 | public function it_fails_during_reading_when_file_does_not_exists()
27 | {
28 | $this->storage->read('vfs://project/source.db');
29 | }
30 |
31 | /** @test */
32 | public function it_can_write_new_file()
33 | {
34 | $this->storage->put('vfs://project/source.db', 'contents');
35 |
36 | $this->assertEquals('contents', $this->storage->read('vfs://project/source.db'));
37 | }
38 |
39 | /** @test */
40 | public function it_can_write_existing_file()
41 | {
42 | $this->givenFileExists('vfs://project/source.db', 'old-contents');
43 |
44 | $this->storage->put('vfs://project/source.db', 'contents');
45 |
46 | $this->assertEquals('contents', $this->storage->read('vfs://project/source.db'));
47 | }
48 |
49 | /**
50 | * @test
51 | *
52 | * @expectedException \RuntimeException
53 | */
54 | public function it_can_not_write_file_to_not_existing_directory()
55 | {
56 | $this->storage->put('vfs://project/not-existing/source.db', 'contents');
57 | }
58 |
59 | /** @test */
60 | public function it_checks_if_file_exists()
61 | {
62 | $this->givenFileExists('vfs://project/source.db');
63 |
64 | $this->assertTrue($this->storage->has('vfs://project/source.db'));
65 | }
66 |
67 | /** @test */
68 | public function it_checks_if_file_does_not_exists()
69 | {
70 | $this->assertFalse($this->storage->has('vfs://project/source.db'));
71 | }
72 |
73 | /**
74 | * {@inheritdoc}
75 | */
76 | protected function setUp()
77 | {
78 | vfsStream::setup('project');
79 |
80 | $this->storage = new LocalStorage();
81 | }
82 |
83 | /**
84 | * {@inheritdoc}
85 | */
86 | protected function tearDown()
87 | {
88 | $this->storage = null;
89 | }
90 |
91 | private function givenFileExists($fileName, $contents = 'contents')
92 | {
93 | file_put_contents($fileName, $contents);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/tests/Integration/MySqlBackupTest.php:
--------------------------------------------------------------------------------
1 | givenDatabaseIsClear();
21 |
22 | $this->backup->getBackup()->create();
23 | $this->addProduct();
24 |
25 | $this->backup->restore();
26 |
27 | $this->assertThatDatabaseIsClear();
28 | }
29 |
30 | /** @test */
31 | public function it_can_restore_database_with_data()
32 | {
33 | $this->givenDatabaseContainsProducts(5);
34 |
35 | $this->backup->getBackup()->create();
36 | $this->addProduct();
37 |
38 | $this->backup->getBackup()->restore();
39 |
40 | $this->assertThatDatabaseContainProducts(5);
41 | }
42 |
43 | /** @test */
44 | public function it_can_restore_database_with_callback()
45 | {
46 | $this->givenDatabaseIsClear();
47 |
48 | $this->backup->restore(function (EntityManager $entityManager) {
49 | $product1 = $this->productInstance();
50 | $product2 = $this->productInstance();
51 |
52 | $entityManager->persist($product1);
53 | $entityManager->persist($product2);
54 |
55 | $entityManager->flush();
56 | });
57 |
58 | $this->assertThatDatabaseContainProducts(2);
59 | }
60 |
61 | /** @test */
62 | public function it_can_clear_database()
63 | {
64 | $this->givenDatabaseContainsProducts(5);
65 |
66 | $this->backup->restore();
67 |
68 | $this->assertThatDatabaseIsClear();
69 | }
70 |
71 | /** @test */
72 | public function it_confirms_that_backup_was_created()
73 | {
74 | $this->backup->getBackup()->create();
75 |
76 | $this->assertTrue($this->backup->getBackup()->isBackupCreated());
77 | }
78 |
79 | /** @test */
80 | public function it_confirms_that_backup_was_not_created()
81 | {
82 | $this->assertFalse($this->backup->getBackup()->isBackupCreated());
83 | }
84 |
85 | /** {@inheritdoc} */
86 | protected function setUp()
87 | {
88 | parent::setUp();
89 |
90 | $this->backup = new DoctrineDatabaseBackup($this->entityManager);
91 |
92 | $this->givenMemoryIsClear();
93 | }
94 |
95 | /** {@inheritdoc} */
96 | protected function tearDown()
97 | {
98 | $this->backup = null;
99 |
100 | parent::tearDown();
101 | }
102 |
103 | private function givenMemoryIsClear()
104 | {
105 | InMemoryStorage::instance()->clear();
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/tests/Integration/SqliteBackupTest.php:
--------------------------------------------------------------------------------
1 | givenDatabaseIsClear();
21 |
22 | $this->backup->getBackup()->create();
23 | $this->addProduct();
24 |
25 | $this->backup->restore();
26 |
27 | $this->assertThatDatabaseIsClear();
28 | }
29 |
30 | /** @test */
31 | public function it_can_restore_database_with_data()
32 | {
33 | $this->givenDatabaseContainsProducts(5);
34 |
35 | $this->backup->getBackup()->create();
36 | $this->addProduct();
37 |
38 | $this->backup->getBackup()->restore();
39 |
40 | $this->assertThatDatabaseContainProducts(5);
41 | }
42 |
43 | /** @test */
44 | public function it_can_restore_database_with_callback()
45 | {
46 | $this->givenDatabaseIsClear();
47 |
48 | $this->backup->restore(function (EntityManager $entityManager) {
49 | $product1 = $this->productInstance();
50 | $product2 = $this->productInstance();
51 |
52 | $entityManager->persist($product1);
53 | $entityManager->persist($product2);
54 |
55 | $entityManager->flush();
56 | });
57 |
58 | $this->assertThatDatabaseContainProducts(2);
59 | }
60 |
61 | /** @test */
62 | public function it_can_clear_database()
63 | {
64 | $this->givenDatabaseContainsProducts(5);
65 |
66 | $this->backup->restore();
67 |
68 | $this->assertThatDatabaseIsClear();
69 | }
70 |
71 | /** @test */
72 | public function it_confirms_that_backup_was_created()
73 | {
74 | $this->backup->getBackup()->create();
75 |
76 | $this->assertTrue($this->backup->getBackup()->isBackupCreated());
77 | }
78 |
79 | /** @test */
80 | public function it_confirms_that_backup_was_not_created()
81 | {
82 | $this->assertFalse($this->backup->getBackup()->isBackupCreated());
83 | }
84 |
85 | /** {@inheritdoc} */
86 | protected function setUp()
87 | {
88 | parent::setUp();
89 |
90 | $this->backup = new DoctrineDatabaseBackup($this->entityManager);
91 |
92 | $this->givenMemoryIsClear();
93 | }
94 |
95 | /** {@inheritdoc} */
96 | protected function tearDown()
97 | {
98 | $this->backup = null;
99 |
100 | parent::tearDown();
101 | }
102 |
103 | private function givenMemoryIsClear()
104 | {
105 | InMemoryStorage::instance()->clear();
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DoctrineDatabaseBackup
2 |
3 | [](https://travis-ci.org/lzakrzewski/DoctrineDatabaseBackup) [](https://packagist.org/packages/lucaszz/doctrine-database-backup) [](https://packagist.org/packages/lucaszz/doctrine-database-backup) [](https://coveralls.io/github/lzakrzewski/DoctrineDatabaseBackup?branch=master)
4 |
5 | DoctrineDatabaseBackup is simple library for speed up tests in your app.
6 | It could be used for **PHPUnit** tests or **Behat** tests running from command line.
7 | My target was to avoid wasting time for dropping/creating or purging database for each test, so I optimized it.
8 |
9 | This library puts contents of database to memory and share it between every tests.
10 |
11 | **Notice:** I don't recommend to use this library with large fixtures because it can cause huge memory usage.
12 | I prefer to run tests with minimal database setup because it is more readable for me and it have better performance.
13 |
14 | Requirements
15 | ------------
16 | ```json
17 | "require": {
18 | "php": ">=5.4",
19 | "doctrine/orm": "~2.3",
20 | "symfony/process": "~2.3"
21 | },
22 | ```
23 |
24 | Features
25 | --------
26 | - It supports **SqlitePlatform** and **MySqlPlatform**,
27 | - It can create database backup per PHP process,
28 | - It can purge database in fast way,
29 | - It can restore database from backup before every test,
30 | - It can restore clear database before every test.
31 |
32 | Installation
33 | --------
34 | Require the library with composer:
35 |
36 | ```sh
37 | composer require lzakrzewski/doctrine-database-backup "~1.2"
38 | ```
39 |
40 | Basic usage (PHPUnit example)
41 | --------
42 | ```php
43 | /** {@inheritdoc} */
44 | protected function setUp()
45 | {
46 | parent::setUp();
47 |
48 | $this->entityManager = $this->createEntityManager();
49 |
50 | $backup = new DoctrineDatabaseBackup($this->entityManager);
51 | $backup->restore();
52 | }
53 | ```
54 |
55 | This database setup prepares clear database before every test.
56 | [Full working example](https://github.com/lzakrzewski/DoctrineDatabaseBackup/blob/master/tests/Integration/BasicPHPUnitUsageExampleTest.php).
57 |
58 | Advanced usage (PHPUnit example)
59 | --------
60 | ```php
61 | /** {@inheritdoc} */
62 | protected function setUp()
63 | {
64 | parent::setUp();
65 |
66 | $this->entityManager = $this->createEntityManager();
67 | $backup = new DoctrineDatabaseBackup($this->entityManager);
68 |
69 | $backup->restore(function (EntityManager $entityManager) {
70 | //your fixtures
71 | $entityManager->persist(new TestProduct('Iron', 99));
72 | $entityManager->flush();
73 | });
74 | }
75 | ```
76 |
77 | This database setup database with your fixtures before every test.
78 | [Full working example](https://github.com/lzakrzewski/DoctrineDatabaseBackup/blob/master/tests/Integration/AdvancedPHPUnitUsageExampleTest.php).
79 |
80 | **Notice:** that before first test of PHP process database should be created.
81 |
82 | Behat example
83 | --------
84 | ```php
85 | /** @BeforeScenario*/
86 | public function restoreDatabase()
87 | {
88 | // "getEntityManager" is your own getter for EntityManager
89 | $backup = new DoctrineDatabaseBackup($this->getEntityManager());
90 | $backup->restore();
91 | }
92 | ```
93 |
--------------------------------------------------------------------------------
/tests/Backup/SqliteBackupTest.php:
--------------------------------------------------------------------------------
1 | givenDatabaseFileDoesNotExists();
27 |
28 | $this->backup->create();
29 | }
30 |
31 | /** @test */
32 | public function it_creates_database_backup()
33 | {
34 | $this->givenDatabaseFileExists();
35 |
36 | $this->backup->create();
37 |
38 | $this->memoryStorage->put(SqliteBackup::BACKUP_KEY, 'contents')->shouldBeCalled();
39 | }
40 |
41 | /** @test */
42 | public function it_restores_database_from_backup()
43 | {
44 | $this->givenMemoryBackupExists();
45 |
46 | $this->backup->restore();
47 |
48 | $this->localStorage->put('/var/www/project/database/sqlite.db', 'contents')->shouldBeCalled();
49 | }
50 |
51 | /** @test */
52 | public function it_confirms_that_backup_was_created()
53 | {
54 | $this->givenMemoryBackupExists();
55 |
56 | $this->assertTrue($this->backup->isBackupCreated());
57 | }
58 |
59 | /** @test */
60 | public function it_confirms_that_backup_was_not_created()
61 | {
62 | $this->givenMemoryBackupDoesNotExists();
63 |
64 | $this->assertFalse($this->backup->isBackupCreated());
65 | }
66 |
67 | /** {@inheritdoc} */
68 | protected function setUp()
69 | {
70 | $this->memoryStorage = $this->prophesize('Lzakrzewski\DoctrineDatabaseBackup\Storage\InMemoryStorage');
71 | $this->localStorage = $this->prophesize('Lzakrzewski\DoctrineDatabaseBackup\Storage\LocalStorage');
72 |
73 | $this->backup = new SqliteBackup(
74 | '/var/www/project/database/sqlite.db',
75 | $this->memoryStorage->reveal(),
76 | $this->localStorage->reveal()
77 | );
78 | }
79 |
80 | /** {@inheritdoc} */
81 | protected function tearDown()
82 | {
83 | $this->memoryStorage = $this->prophesize('Lzakrzewski\DoctrineDatabaseBackup\Storage\InMemoryStorage');
84 | $this->localStorage = $this->prophesize('Lzakrzewski\DoctrineDatabaseBackup\Storage\LocalStorage');
85 |
86 | $this->backup = null;
87 | }
88 |
89 | private function givenDatabaseFileDoesNotExists()
90 | {
91 | $this->localStorage->has('/var/www/project/database/sqlite.db')->willReturn(false);
92 | }
93 |
94 | private function givenDatabaseFileExists()
95 | {
96 | $this->localStorage->has('/var/www/project/database/sqlite.db')->willReturn(true);
97 | $this->localStorage->read('/var/www/project/database/sqlite.db')->willReturn('contents');
98 | }
99 |
100 | private function givenMemoryBackupExists()
101 | {
102 | $this->memoryStorage->has(SqliteBackup::BACKUP_KEY)->willReturn(true);
103 | $this->memoryStorage->read(SqliteBackup::BACKUP_KEY)->willReturn('contents');
104 | }
105 |
106 | private function givenMemoryBackupDoesNotExists()
107 | {
108 | $this->memoryStorage->has(SqliteBackup::BACKUP_KEY)->willReturn(false);
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/tests/Integration/BasicPHPUnitUsageExampleTest.php:
--------------------------------------------------------------------------------
1 | entityManager->persist(new TestProduct('Teapot', 25));
24 | $this->entityManager->flush();
25 |
26 | $this->assertCount(1, $this->entityManager->getRepository('\Lzakrzewski\DoctrineDatabaseBackup\tests\Integration\Entity\Product\TestProduct')->findAll());
27 | }
28 |
29 | public function testThatDatabaseIsClear()
30 | {
31 | $this->assertCount(0, $this->entityManager->getRepository('\Lzakrzewski\DoctrineDatabaseBackup\tests\Integration\Entity\Product\TestProduct')->findAll());
32 | }
33 |
34 | /**
35 | * Before first test of PHP process database should be created.
36 | */
37 | public static function setUpBeforeClass()
38 | {
39 | $entityManager = self::createEntityManager();
40 | self::setupDatabase($entityManager);
41 |
42 | //Should be called only if another test in current PHP process created backup.
43 | InMemoryStorage::instance()->clear();
44 | }
45 |
46 | /** {@inheritdoc} */
47 | protected function setUp()
48 | {
49 | parent::setUp();
50 |
51 | $this->entityManager = $this->createEntityManager();
52 |
53 | $backup = new DoctrineDatabaseBackup($this->entityManager);
54 | $backup->restore();
55 | }
56 |
57 | /** {@inheritdoc} */
58 | protected function tearDown()
59 | {
60 | $this->entityManager = null;
61 | }
62 |
63 | /**
64 | * Example of creating EntityManager.
65 | */
66 | private static function createEntityManager()
67 | {
68 | $entityPath = [__DIR__.'/Entity/Product'];
69 |
70 | $config = Setup::createAnnotationMetadataConfiguration($entityPath, false);
71 | $driver = new AnnotationDriver(new AnnotationReader(), $entityPath);
72 | AnnotationRegistry::registerLoader('class_exists');
73 | $config->setMetadataDriverImpl($driver);
74 |
75 | return EntityManager::create(self::getParams(), $config);
76 | }
77 |
78 | /**
79 | * Example of setup database before test.
80 | */
81 | private static function setupDatabase(EntityManager $entityManager)
82 | {
83 | $params = self::getParams();
84 |
85 | $tmpConnection = DriverManager::getConnection($params);
86 | $tmpConnection->getSchemaManager()->createDatabase($params['path']);
87 |
88 | $schemaTool = new SchemaTool($entityManager);
89 | $schemaTool->dropDatabase();
90 |
91 | $class = 'Lzakrzewski\DoctrineDatabaseBackup\tests\Integration\Entity\Product\TestProduct';
92 |
93 | $schemaTool->createSchema([$entityManager->getClassMetadata($class)]);
94 | }
95 |
96 | /**
97 | * Example of Database connection parameters.
98 | */
99 | private static function getParams()
100 | {
101 | return [
102 | 'driver' => 'pdo_sqlite',
103 | 'user' => 'root',
104 | 'password' => '',
105 | 'path' => __DIR__.'/database/sqlite.db',
106 | ];
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/tests/Integration/AdvancedPHPUnitUsageExampleTest.php:
--------------------------------------------------------------------------------
1 | entityManager->persist(new TestProduct('Teapot', 25));
24 | $this->entityManager->flush();
25 |
26 | $this->assertCount(2, $this->entityManager->getRepository('\Lzakrzewski\DoctrineDatabaseBackup\tests\Integration\Entity\Product\TestProduct')->findAll());
27 | }
28 |
29 | public function testThatDatabaseContainsFixtures()
30 | {
31 | $this->assertCount(1, $this->entityManager->getRepository('\Lzakrzewski\DoctrineDatabaseBackup\tests\Integration\Entity\Product\TestProduct')->findAll());
32 | }
33 |
34 | /**
35 | * Before first test of PHP process database should be created.
36 | */
37 | public static function setUpBeforeClass()
38 | {
39 | $entityManager = self::createEntityManager();
40 | self::setupDatabase($entityManager);
41 |
42 | //Should be called only if another test in current PHP process created backup.
43 | InMemoryStorage::instance()->clear();
44 | }
45 |
46 | /** {@inheritdoc} */
47 | protected function setUp()
48 | {
49 | parent::setUp();
50 |
51 | $this->entityManager = $this->createEntityManager();
52 | $backup = new DoctrineDatabaseBackup($this->entityManager);
53 |
54 | $backup->restore(function (EntityManager $entityManager) {
55 | //your fixtures
56 | $entityManager->persist(new TestProduct('Iron', 99));
57 | $entityManager->flush();
58 | });
59 | }
60 |
61 | /** {@inheritdoc} */
62 | protected function tearDown()
63 | {
64 | $this->entityManager = null;
65 | }
66 |
67 | /**
68 | * Example of creating EntityManager.
69 | */
70 | private static function createEntityManager()
71 | {
72 | $entityPath = [__DIR__.'/Entity/Product'];
73 |
74 | $config = Setup::createAnnotationMetadataConfiguration($entityPath, false);
75 | $driver = new AnnotationDriver(new AnnotationReader(), $entityPath);
76 | AnnotationRegistry::registerLoader('class_exists');
77 | $config->setMetadataDriverImpl($driver);
78 |
79 | return EntityManager::create(self::getParams(), $config);
80 | }
81 |
82 | /**
83 | * Example of setup database before test.
84 | */
85 | private static function setupDatabase(EntityManager $entityManager)
86 | {
87 | $params = self::getParams();
88 |
89 | $tmpConnection = DriverManager::getConnection($params);
90 | $tmpConnection->getSchemaManager()->createDatabase($params['path']);
91 |
92 | $schemaTool = new SchemaTool($entityManager);
93 | $schemaTool->dropDatabase();
94 |
95 | $class = 'Lzakrzewski\DoctrineDatabaseBackup\tests\Integration\Entity\Product\TestProduct';
96 |
97 | $schemaTool->createSchema([$entityManager->getClassMetadata($class)]);
98 | }
99 |
100 | /**
101 | * Example of Database connection parameters.
102 | */
103 | private static function getParams()
104 | {
105 | return [
106 | 'driver' => 'pdo_sqlite',
107 | 'user' => 'root',
108 | 'password' => '',
109 | 'path' => __DIR__.'/database/sqlite.db',
110 | ];
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/tests/Integration/IntegrationTestCase.php:
--------------------------------------------------------------------------------
1 | entityManager = $this->createEntityManager();
29 |
30 | $this->setupDatabase();
31 |
32 | $this->products = $this->entityManager->getRepository($this->productClass());
33 | $this->categories = $this->entityManager->getRepository($this->categoryClass());
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | */
39 | protected function tearDown()
40 | {
41 | $this->entityManager = null;
42 | $this->products = null;
43 | $this->categories = null;
44 | }
45 |
46 | /**
47 | * @return EntityManager
48 | */
49 | private function createEntityManager()
50 | {
51 | $entityPath = [__DIR__.'/Entity'];
52 |
53 | $config = Setup::createAnnotationMetadataConfiguration($entityPath, false);
54 | $driver = new AnnotationDriver(new AnnotationReader(), $entityPath);
55 | AnnotationRegistry::registerLoader('class_exists');
56 | $config->setMetadataDriverImpl($driver);
57 |
58 | return EntityManager::create($this->getParams(), $config);
59 | }
60 |
61 | /**
62 | * @return TestProduct
63 | */
64 | protected function productInstance()
65 | {
66 | return new TestProduct(uniqid(), rand(1, 1000) / 3);
67 | }
68 |
69 | /**
70 | * @return TestCategory
71 | */
72 | protected function categoryInstance()
73 | {
74 | return new TestCategory([]);
75 | }
76 |
77 | /**
78 | * @return string
79 | */
80 | protected function productClass()
81 | {
82 | return get_class($this->productInstance());
83 | }
84 |
85 | /**
86 | * @return string
87 | */
88 | protected function categoryClass()
89 | {
90 | return get_class($this->categoryInstance());
91 | }
92 |
93 | protected function addProduct()
94 | {
95 | $this->entityManager->persist($this->productInstance());
96 | $this->entityManager->flush();
97 | }
98 |
99 | protected function givenDatabaseIsClear()
100 | {
101 | }
102 |
103 | protected function givenDatabaseContainsProducts($productsCount)
104 | {
105 | $this->givenDatabaseIsClear();
106 |
107 | for ($productCount = 1; $productCount <= $productsCount; ++$productCount) {
108 | $this->addProduct();
109 | }
110 | }
111 |
112 | protected function givenDatabaseContainsCategories($categoriesCount)
113 | {
114 | for ($categoryCount = 1; $categoryCount <= $categoriesCount; ++$categoryCount) {
115 | $this->entityManager->persist(new TestCategory($this->products->findAll()));
116 | $this->entityManager->flush();
117 | }
118 | }
119 |
120 | protected function assertThatDatabaseContainProducts($expectedProductsCount)
121 | {
122 | $this->assertCount($expectedProductsCount, $this->products->findAll());
123 | }
124 |
125 | protected function assertThatDatabaseIsClear()
126 | {
127 | $this->assertEmpty($this->products->findAll());
128 | $this->assertEmpty($this->categories->findAll());
129 | }
130 |
131 | /**
132 | * @return array
133 | */
134 | abstract protected function getParams();
135 |
136 | abstract protected function setupDatabase();
137 | }
138 |
--------------------------------------------------------------------------------
/tests/BackupFactoryTest.php:
--------------------------------------------------------------------------------
1 | givenSqliteDatabasePlatformWasEnabled();
21 |
22 | $backup = BackupFactory::instance($this->entityManager->reveal());
23 |
24 | $this->assertInstanceOf('\Lzakrzewski\DoctrineDatabaseBackup\Backup\SqliteBackup', $backup);
25 | }
26 |
27 | /** @test */
28 | public function it_can_create_instance_of_mysql_backup()
29 | {
30 | $this->givenMySqlDatabasePlatformWasEnabled();
31 |
32 | $backup = BackupFactory::instance($this->entityManager->reveal());
33 |
34 | $this->assertInstanceOf('\Lzakrzewski\DoctrineDatabaseBackup\Backup\MySqlBackup', $backup);
35 | }
36 |
37 | /**
38 | * @test
39 | * @expectedException \RuntimeException
40 | */
41 | public function it_can_not_create_instance_of_backup_with_unsupported_platform()
42 | {
43 | $this->givenUnsupportedDatabasePlatformWasEnabled();
44 |
45 | BackupFactory::instance($this->entityManager->reveal());
46 | }
47 |
48 | /**
49 | * @test
50 | * @expectedException \RuntimeException
51 | */
52 | public function it_can_not_instance_of_sqlite_in_memory_backup()
53 | {
54 | $this->givenSqliteInMemoryDatabasePlatformWasEnabled();
55 |
56 | BackupFactory::instance($this->entityManager->reveal());
57 | }
58 |
59 | /**
60 | * @test
61 | * @expectedException \RuntimeException
62 | */
63 | public function it_can_not_create_backup_with_mysql_platform_without_dbname_provided()
64 | {
65 | $this->givenMySqlWithoutDbnameDatabasePlatformWasEnabled();
66 |
67 | BackupFactory::instance($this->entityManager->reveal());
68 | }
69 |
70 | /** {@inheritdoc} */
71 | protected function setUp()
72 | {
73 | $this->entityManager = $this->prophesize('\Doctrine\ORM\EntityManager');
74 | $this->connection = $this->prophesize('\Doctrine\DBAL\Connection');
75 |
76 | $this->entityManager->getConnection()->willReturn($this->connection->reveal());
77 | }
78 |
79 | /** {@inheritdoc} */
80 | protected function tearDown()
81 | {
82 | $this->entityManager = null;
83 | $this->connection = null;
84 | }
85 |
86 | private function givenSqliteDatabasePlatformWasEnabled()
87 | {
88 | $sqlitePlatform = $this->prophesize('\Doctrine\DBAL\Platforms\SqlitePlatform');
89 |
90 | $this->connection->getDatabasePlatform()->willReturn($sqlitePlatform->reveal());
91 | $this->connection->getParams()->willReturn(['path' => '/path/to/database.db']);
92 | }
93 |
94 | private function givenMySqlDatabasePlatformWasEnabled()
95 | {
96 | $mySqlPlatform = $this->prophesize('\Doctrine\DBAL\Platforms\MySqlPlatform');
97 |
98 | $this->connection->getDatabasePlatform()->willReturn($mySqlPlatform->reveal());
99 | $this->connection->getParams()->willReturn(['dbname' => 'test', 'host' => 'localhost', 'port' => '3306', 'user' => 'johndoe', 'password' => 'testing1']);
100 | }
101 |
102 | private function givenUnsupportedDatabasePlatformWasEnabled()
103 | {
104 | $unsupportedPlatform = $this->prophesize('\Doctrine\DBAL\Platforms\OraclePlatform');
105 |
106 | $this->connection->getDatabasePlatform()->willReturn($unsupportedPlatform->reveal());
107 | }
108 |
109 | private function givenSqliteInMemoryDatabasePlatformWasEnabled()
110 | {
111 | $this->givenSqliteDatabasePlatformWasEnabled();
112 |
113 | $this->connection->getParams()->willReturn(['path' => ':memory:']);
114 | }
115 |
116 | private function givenMySqlWithoutDbnameDatabasePlatformWasEnabled()
117 | {
118 | $this->givenMySqlDatabasePlatformWasEnabled();
119 |
120 | $this->connection->getParams()->willReturn(['host' => 'localhost']);
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/tests/Backup/MySqlBackupTest.php:
--------------------------------------------------------------------------------
1 | command->run()->willReturn(null);
29 |
30 | $this->backup->create();
31 |
32 | $this->memoryStorage->put(MySqlBackup::BACKUP_KEY, null)->shouldBeCalled();
33 | }
34 |
35 | /** @test */
36 | public function it_creates_backup_with_data()
37 | {
38 | $this->command->run()->willReturn('INSERT INTO table VALUES (1, 2, 3, 4)');
39 |
40 | $this->backup->create();
41 |
42 | $this->memoryStorage->put(MySqlBackup::BACKUP_KEY, 'INSERT INTO table VALUES (1, 2, 3, 4)')->shouldBeCalled();
43 | }
44 |
45 | /** @test */
46 | public function it_restores_database()
47 | {
48 | $this->givenMemoryBackupExists();
49 |
50 | $this->backup->restore();
51 |
52 | $this->purger->purge()->shouldBeCalled();
53 | $this->memoryStorage->read(MySqlBackup::BACKUP_KEY)->shouldBeCalled();
54 | }
55 |
56 | /** @test */
57 | public function it_restores_database_with_data()
58 | {
59 | $this->givenMemoryBackupWithDataExists();
60 |
61 | $this->backup->restore();
62 |
63 | $this->purger->purge()->shouldBeCalled();
64 | $this->connection->beginTransaction()->shouldBeCalled();
65 | $this->connection->exec('INSERT INTO table VALUES (1, 2, 3, 4)')->shouldBeCalled();
66 | $this->connection->commit()->shouldBeCalled();
67 | }
68 |
69 | /** @test */
70 | public function it_confirms_that_backup_was_created()
71 | {
72 | $this->givenMemoryBackupExists();
73 |
74 | $this->assertTrue($this->backup->isBackupCreated());
75 | }
76 |
77 | /** @test */
78 | public function it_confirms_that_backup_was_not_created()
79 | {
80 | $this->givenMemoryBackupDoesNotExists();
81 |
82 | $this->assertFalse($this->backup->isBackupCreated());
83 | }
84 |
85 | /**
86 | * @test
87 | * @expectedException \RuntimeException
88 | */
89 | public function it_fails_during_restoring_database_without_backup()
90 | {
91 | $this->backup->restore();
92 | }
93 |
94 | /** {@inheritdoc} */
95 | protected function setUp()
96 | {
97 | $this->connection = $this->prophesize('\Doctrine\DBAL\Connection');
98 | $this->memoryStorage = $this->prophesize('\Lzakrzewski\DoctrineDatabaseBackup\Storage\InMemoryStorage');
99 | $this->purger = $this->prophesize('\Lzakrzewski\DoctrineDatabaseBackup\Purger');
100 | $this->command = $this->prophesize('\Lzakrzewski\DoctrineDatabaseBackup\Command\Command');
101 |
102 | $this->backup = new MySqlBackup($this->connection->reveal(), $this->memoryStorage->reveal(), $this->purger->reveal(), $this->command->reveal());
103 | }
104 |
105 | /** {@inheritdoc} */
106 | protected function tearDown()
107 | {
108 | $this->connection = null;
109 | $this->memoryStorage = null;
110 | $this->purger = null;
111 | $this->command = null;
112 |
113 | $this->backup = null;
114 | }
115 |
116 | private function givenMemoryBackupExists()
117 | {
118 | $this->memoryStorage->has(MySqlBackup::BACKUP_KEY)->willReturn(true);
119 | $this->memoryStorage->read(MySqlBackup::BACKUP_KEY)->willReturn('contents');
120 | }
121 |
122 | private function givenMemoryBackupDoesNotExists()
123 | {
124 | $this->memoryStorage->has(MySqlBackup::BACKUP_KEY)->willReturn(false);
125 | }
126 |
127 | private function givenMemoryBackupWithDataExists()
128 | {
129 | $this->memoryStorage->has(MySqlBackup::BACKUP_KEY)->willReturn(true);
130 | $this->memoryStorage->read(MySqlBackup::BACKUP_KEY)->willReturn('INSERT INTO table VALUES (1, 2, 3, 4)');
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/Purger.php:
--------------------------------------------------------------------------------
1 |
10 | * and Jonathan H. Wage , Benjamin Eberlei
11 | */
12 |
13 | namespace Lzakrzewski\DoctrineDatabaseBackup;
14 |
15 | use Doctrine\DBAL\Platforms\AbstractPlatform;
16 | use Doctrine\ORM\EntityManager;
17 | use Doctrine\ORM\Internal\CommitOrderCalculator;
18 | use Doctrine\ORM\Mapping\ClassMetadata;
19 | use Lzakrzewski\DoctrineDatabaseBackup\Storage\InMemoryStorage;
20 |
21 | class Purger
22 | {
23 | const PURGER_KEY = 'purger';
24 |
25 | /** @var EntityManager */
26 | private $entityManager;
27 | /** @var InMemoryStorage */
28 | private $memoryStorage;
29 |
30 | /**
31 | * @param EntityManager $entityManager
32 | * @param InMemoryStorage $memoryStorage
33 | */
34 | public function __construct(EntityManager $entityManager, InMemoryStorage $memoryStorage)
35 | {
36 | $this->entityManager = $entityManager;
37 | $this->memoryStorage = $memoryStorage;
38 | }
39 |
40 | public function purge()
41 | {
42 | if (false === $this->memoryStorage->has(self::PURGER_KEY)) {
43 | $this->memoryStorage->put(self::PURGER_KEY, $this->purgeSql());
44 | }
45 |
46 | $this->execute($this->memoryStorage->read(self::PURGER_KEY));
47 | }
48 |
49 | private function execute($sql)
50 | {
51 | $connection = $this->entityManager->getConnection();
52 | $connection->beginTransaction();
53 |
54 | $connection->exec($sql);
55 |
56 | $connection->commit();
57 | }
58 |
59 | private function purgeSql()
60 | {
61 | $sql = '';
62 | $classes = [];
63 | $metadatas = $this->entityManager->getMetadataFactory()->getAllMetadata();
64 |
65 | foreach ($metadatas as $metadata) {
66 | if (!$metadata->isMappedSuperclass && !(isset($metadata->isEmbeddedClass) && $metadata->isEmbeddedClass)) {
67 | $classes[] = $metadata;
68 | }
69 | }
70 |
71 | $commitOrder = $this->getCommitOrder($classes);
72 |
73 | // Get platform parameters
74 | $platform = $this->entityManager->getConnection()->getDatabasePlatform();
75 |
76 | // Drop association tables first
77 | $orderedTables = $this->getAssociationTables($commitOrder, $platform);
78 |
79 | // Drop tables in reverse commit order
80 | for ($i = count($commitOrder) - 1; $i >= 0; --$i) {
81 | $class = $commitOrder[$i];
82 |
83 | if (($class->isInheritanceTypeSingleTable() && $class->name != $class->rootEntityName) ||
84 | (isset($class->isEmbeddedClass) && $class->isEmbeddedClass) ||
85 | $class->isMappedSuperclass
86 | ) {
87 | continue;
88 | }
89 |
90 | $orderedTables[] = $this->getTableName($class, $platform);
91 | }
92 |
93 | foreach ($orderedTables as $tbl) {
94 | $sql .= sprintf('DELETE FROM %s;', $tbl);
95 | }
96 |
97 | return $sql;
98 | }
99 |
100 | private function getCommitOrder(array $classes)
101 | {
102 | $calc = new CommitOrderCalculator();
103 |
104 | foreach ($classes as $class) {
105 | $calc->addClass($class);
106 |
107 | // $class before its parents
108 | foreach ($class->parentClasses as $parentClass) {
109 | $parentClass = $this->entityManager->getClassMetadata($parentClass);
110 |
111 | if (!$calc->hasClass($parentClass->name)) {
112 | $calc->addClass($parentClass);
113 | }
114 |
115 | $calc->addDependency($class, $parentClass);
116 | }
117 |
118 | foreach ($class->associationMappings as $assoc) {
119 | if ($assoc['isOwningSide']) {
120 | $targetClass = $this->entityManager->getClassMetadata($assoc['targetEntity']);
121 |
122 | if (!$calc->hasClass($targetClass->name)) {
123 | $calc->addClass($targetClass);
124 | }
125 |
126 | // add dependency ($targetClass before $class)
127 | $calc->addDependency($targetClass, $class);
128 |
129 | // parents of $targetClass before $class, too
130 | foreach ($targetClass->parentClasses as $parentClass) {
131 | $parentClass = $this->entityManager->getClassMetadata($parentClass);
132 |
133 | if (!$calc->hasClass($parentClass->name)) {
134 | $calc->addClass($parentClass);
135 | }
136 |
137 | $calc->addDependency($parentClass, $class);
138 | }
139 | }
140 | }
141 | }
142 |
143 | return $calc->getCommitOrder();
144 | }
145 |
146 | private function getAssociationTables(array $classes, AbstractPlatform $platform)
147 | {
148 | $associationTables = [];
149 |
150 | foreach ($classes as $class) {
151 | foreach ($class->associationMappings as $assoc) {
152 | if ($assoc['isOwningSide'] && $assoc['type'] == ClassMetadata::MANY_TO_MANY) {
153 | $associationTables[] = $this->getJoinTableName($assoc, $class, $platform);
154 | }
155 | }
156 | }
157 |
158 | return $associationTables;
159 | }
160 |
161 | private function getTableName($class, $platform)
162 | {
163 | if (isset($class->table['schema']) && !method_exists($class, 'getSchemaName')) {
164 | return $class->table['schema'].'.'.$this->entityManager->getConfiguration()->getQuoteStrategy()->getTableName($class, $platform);
165 | }
166 |
167 | return $this->entityManager->getConfiguration()->getQuoteStrategy()->getTableName($class, $platform);
168 | }
169 |
170 | private function getJoinTableName($assoc, $class, $platform)
171 | {
172 | if (isset($assoc['joinTable']['schema']) && !method_exists($class, 'getSchemaName')) {
173 | return $assoc['joinTable']['schema'].'.'.$this->entityManager->getConfiguration()->getQuoteStrategy()->getJoinTableName($assoc, $class, $platform);
174 | }
175 |
176 | return $this->entityManager->getConfiguration()->getQuoteStrategy()->getJoinTableName($assoc, $class, $platform);
177 | }
178 | }
179 |
--------------------------------------------------------------------------------