-
Notifications
You must be signed in to change notification settings - Fork 180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Separate db tool and db backup/restore services for version 2.x #398
Changes from 7 commits
02e3518
fb40d39
c92e7e4
260d01c
5923bed
66e4390
831b101
817c8ee
1a71772
6379ac1
c355de3
7d2d90e
966681b
87d9f1a
b919a90
3cde299
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,15 @@ public function getConfigTreeBuilder(): TreeBuilder | |
$rootNode | ||
->children() | ||
->booleanNode('cache_sqlite_db')->defaultFalse()->end() | ||
->arrayNode('cache_db') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we then remove There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I'll remove it. |
||
->addDefaultsIfNotSet() | ||
->ignoreExtraKeys(false) | ||
->children() | ||
->scalarNode('sqlite') | ||
->defaultNull() | ||
->end() | ||
->end() | ||
->end() | ||
->scalarNode('command_verbosity')->defaultValue('normal')->end() | ||
->booleanNode('command_decoration')->defaultTrue()->end() | ||
->arrayNode('query') | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<?xml version="1.0" ?> | ||
|
||
<container xmlns="http://symfony.com/schema/dic/services" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> | ||
|
||
<services> | ||
<service id="liip_functional_test.services.fixtures_loader_factory" class="Liip\FunctionalTestBundle\Services\FixturesLoaderFactory" public="true"> | ||
<argument type="service" id="service_container" /> | ||
</service> | ||
|
||
<service id="liip_functional_test.services_database_backup.sqlite" class="Liip\FunctionalTestBundle\Services\DatabaseBackup\SqliteDatabaseBackup" public="true"> | ||
<argument type="service" id="service_container" /> | ||
<argument type="service" id="liip_functional_test.services.fixtures_loader_factory" /> | ||
</service> | ||
|
||
<service id="liip_functional_test.services_database_backup.mysql_custom" class="Liip\FunctionalTestBundle\Services\DatabaseBackup\MysqlCustomDatabaseBackup" public="true"> | ||
<argument type="service" id="service_container" /> | ||
<argument type="service" id="liip_functional_test.services.fixtures_loader_factory" /> | ||
</service> | ||
|
||
<service id="liip_functional_test.services_database_tools.ormdatabase_tool" class="Liip\FunctionalTestBundle\Services\DatabaseTools\ORMDatabaseTool" public="false"> | ||
<argument type="service" id="service_container" /> | ||
<argument type="service" id="liip_functional_test.services.fixtures_loader_factory" /> | ||
</service> | ||
<service id="liip_functional_test.services_database_tools.ormsqllite_database_tool" class="Liip\FunctionalTestBundle\Services\DatabaseTools\ORMSqlliteDatabaseTool" public="false"> | ||
<argument type="service" id="service_container" /> | ||
<argument type="service" id="liip_functional_test.services.fixtures_loader_factory" /> | ||
</service> | ||
<service id="liip_functional_test.services_database_tools.mongo_dbdatabase_tool" class="Liip\FunctionalTestBundle\Services\DatabaseTools\MongoDBDatabaseTool" public="false"> | ||
<argument type="service" id="service_container" /> | ||
<argument type="service" id="liip_functional_test.services.fixtures_loader_factory" /> | ||
</service> | ||
<service id="liip_functional_test.services_database_tools.phpcrdatabase_tool" class="Liip\FunctionalTestBundle\Services\DatabaseTools\PHPCRDatabaseTool" public="false"> | ||
<argument type="service" id="service_container" /> | ||
<argument type="service" id="liip_functional_test.services.fixtures_loader_factory" /> | ||
</service> | ||
<service id="liip_functional_test.services.database_tool_collection" class="Liip\FunctionalTestBundle\Services\DatabaseToolCollection" public="true"> | ||
<argument type="service" id="service_container" /> | ||
<argument type="service" id="liip_functional_test.services.fixtures_loader_factory" /> | ||
<call method="add"> | ||
<argument type="service" id="liip_functional_test.services_database_tools.ormdatabase_tool" /> | ||
</call> | ||
<call method="add"> | ||
<argument type="service" id="liip_functional_test.services_database_tools.ormsqllite_database_tool" /> | ||
</call> | ||
<call method="add"> | ||
<argument type="service" id="liip_functional_test.services_database_tools.mongo_dbdatabase_tool" /> | ||
</call> | ||
<call method="add"> | ||
<argument type="service" id="liip_functional_test.services_database_tools.phpcrdatabase_tool" /> | ||
</call> | ||
</service> | ||
</services> | ||
</container> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Liip/FunctionalTestBundle | ||
* | ||
* (c) Lukas Kahwe Smith <[email protected]> | ||
* | ||
* This source file is subject to the MIT license that is bundled | ||
* with this source code in the file LICENSE. | ||
*/ | ||
|
||
namespace Liip\FunctionalTestBundle\Services\DatabaseBackup; | ||
|
||
use Doctrine\Common\DataFixtures\Executor\AbstractExecutor; | ||
use Doctrine\DBAL\Connection; | ||
use Liip\FunctionalTestBundle\Services\FixturesLoaderFactory; | ||
use Symfony\Component\DependencyInjection\ContainerInterface; | ||
|
||
/** | ||
* @author Aleksey Tupichenkov <[email protected]> | ||
*/ | ||
abstract class AbstractDatabaseBackup | ||
{ | ||
protected $container; | ||
|
||
protected $fixturesLoaderFactory; | ||
|
||
/** | ||
* @var Connection | ||
*/ | ||
protected $connection; | ||
|
||
/** | ||
* @var array | ||
*/ | ||
protected $metadatas; | ||
|
||
/** | ||
* The fixture classnames. | ||
* | ||
* @var array | ||
*/ | ||
protected $classNames; | ||
|
||
public function __construct(ContainerInterface $container, FixturesLoaderFactory $fixturesLoaderFactory) | ||
{ | ||
$this->container = $container; | ||
$this->fixturesLoaderFactory = $fixturesLoaderFactory; | ||
} | ||
|
||
public function init(Connection $connection, array $metadatas, array $classNames): void | ||
{ | ||
$this->connection = $connection; | ||
$this->metadatas = $metadatas; | ||
$this->classNames = $classNames; | ||
} | ||
|
||
abstract public function getBackupName(): string; | ||
|
||
abstract public function isBackupActual(): bool; | ||
|
||
abstract public function backup(AbstractExecutor $executor): void; | ||
|
||
abstract public function restore(AbstractExecutor $executor): void; | ||
|
||
/** | ||
* Determine if the Fixtures that define a database backup have been | ||
* modified since the backup was made. | ||
* | ||
* @param string $backup The fixture backup SQLite database file path | ||
* | ||
* @return bool TRUE if the backup was made since the modifications to the | ||
* fixtures; FALSE otherwise | ||
*/ | ||
protected function isBackupUpToDate(string $backup): bool | ||
{ | ||
$backupLastModifiedDateTime = \DateTime::createFromFormat('U', filemtime($backup)); | ||
|
||
$loader = $this->fixturesLoaderFactory->getFixtureLoader($this->classNames); | ||
|
||
// Use loader in order to fetch all the dependencies fixtures. | ||
foreach ($loader->getFixtures() as $className) { | ||
$fixtureLastModifiedDateTime = $this->getFixtureLastModified($className); | ||
if ($backupLastModifiedDateTime < $fixtureLastModifiedDateTime) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* This function finds the time when the data blocks of a class definition | ||
* file were being written to, that is, the time when the content of the | ||
* file was changed. | ||
* | ||
* @param string $class The fully qualified class name of the fixture class to | ||
* check modification date on | ||
* | ||
* @return \DateTime|null | ||
*/ | ||
protected function getFixtureLastModified($class): ?\DateTime | ||
{ | ||
$lastModifiedDateTime = null; | ||
|
||
$reflClass = new \ReflectionClass($class); | ||
$classFileName = $reflClass->getFileName(); | ||
|
||
if (file_exists($classFileName)) { | ||
$lastModifiedDateTime = new \DateTime(); | ||
$lastModifiedDateTime->setTimestamp(filemtime($classFileName)); | ||
} | ||
|
||
return $lastModifiedDateTime; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Liip/FunctionalTestBundle | ||
* | ||
* (c) Lukas Kahwe Smith <[email protected]> | ||
* | ||
* This source file is subject to the MIT license that is bundled | ||
* with this source code in the file LICENSE. | ||
*/ | ||
|
||
namespace Liip\FunctionalTestBundle\Services\DatabaseBackup; | ||
|
||
use Doctrine\Common\DataFixtures\Executor\AbstractExecutor; | ||
use Doctrine\ORM\EntityManager; | ||
|
||
/** | ||
* @author Aleksey Tupichenkov <[email protected]> | ||
* | ||
* It's class created just for example that how to create database backup/restore service | ||
*/ | ||
class MysqlCustomDatabaseBackup extends AbstractDatabaseBackup | ||
{ | ||
protected static $referenceData; | ||
|
||
protected static $sql; | ||
|
||
protected static $metadata; | ||
|
||
public function getBackupName(): string | ||
{ | ||
return $this->container->getParameter('kernel.cache_dir').'/test_mysql_'.md5(serialize($this->metadatas).serialize($this->classNames)).'.sql'; | ||
} | ||
|
||
public function getReferenceBackupName(): string | ||
{ | ||
return $this->getBackupName().'.ser'; | ||
} | ||
|
||
protected function getBackup(): string | ||
{ | ||
if (empty(self::$sql)) { | ||
self::$sql = file_get_contents($this->getBackupName()); | ||
} | ||
|
||
return self::$sql; | ||
} | ||
|
||
protected function getReferenceBackup(): string | ||
{ | ||
if (empty(self::$referenceData)) { | ||
self::$referenceData = file_get_contents($this->getReferenceBackupName()); | ||
} | ||
|
||
return self::$referenceData; | ||
} | ||
|
||
public function isBackupActual(): bool | ||
{ | ||
return | ||
file_exists(file_exists($this->getBackupName())) && | ||
file_exists(file_exists($this->getReferenceBackupName())) && | ||
$this->isBackupUpToDate($this->getBackupName()); | ||
} | ||
|
||
public function backup(AbstractExecutor $executor): void | ||
{ | ||
$params = $this->connection->getParams(); | ||
if (isset($params['master'])) { | ||
$params = $params['master']; | ||
} | ||
|
||
$dbName = isset($params['dbname']) ? $params['dbname'] : ''; | ||
$dbHost = isset($params['host']) ? $params['host'] : ''; | ||
$dbPort = isset($params['port']) ? $params['port'] : ''; | ||
$dbUser = isset($params['user']) ? $params['user'] : ''; | ||
$dbPass = isset($params['password']) ? $params['password'] : ''; | ||
|
||
$executor->getReferenceRepository()->save($this->getBackupName()); | ||
/** @var EntityManager $em */ | ||
$em = $executor->getReferenceRepository()->getManager(); | ||
self::$metadata = $em->getMetadataFactory()->getLoadedMetadata(); | ||
|
||
exec("mysqldump -h $dbHost -u $dbUser -p$dbPass --no-create-db $dbName > {$this->getBackupName()}"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @alexislefebvre @lsmith77 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed .. Generally I think if we have to use a CLI command, then its not really a good fit for this Bundle, since it will not be applicable in too many cases. This library here might help however https://github.com/portphp/doctrine There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm... I added mysql class just for example Currently I rename service As a proposal, mysql (and other) can be implemented by a separate repo in github, what do you think about it? @alexislefebvre @lsmith77 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The method works on the current version of this bundle. I'm sorry but I don't understand why you don't use it in this PR. Could you please explain why you chose to perform a full export instead of using only the reference data? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @alexislefebvre I save reference too But it's not enough, I need backup both, DB and reference. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @lsmith77 about https://github.com/portphp/doctrine This lib really cool, but still not suitable, this lib saves only entities, but I need dump full db. I found some PHP libs to make backup like https://github.com/backup-manager/backup-manager or https://github.com/spatie/db-dumper There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for your explanation, I was confused by the fact that SQLite and MySQL need different ways to perform backups. I understand now. Thanks. |
||
} | ||
|
||
public function restore(AbstractExecutor $executor): void | ||
{ | ||
$this->connection->query('SET FOREIGN_KEY_CHECKS = 0;'); | ||
$tables = []; | ||
foreach ($this->metadatas as $classMetadata) { | ||
$tables[] = $classMetadata->table['name']; | ||
} | ||
$this->connection->query('DROP TABLE IF EXISTS '.implode(',', $tables)); | ||
$this->connection->query($this->getBackup()); | ||
$this->connection->query('SET FOREIGN_KEY_CHECKS = 1;'); | ||
|
||
/** @var EntityManager $em */ | ||
$em = $executor->getReferenceRepository()->getManager(); | ||
if (self::$metadata) { | ||
// it need for better performance | ||
foreach (self::$metadata as $class => $data) { | ||
$em->getMetadataFactory()->setMetadataFor($class, $data); | ||
} | ||
$executor->getReferenceRepository()->unserialize($this->getReferenceBackup()); | ||
} else { | ||
$executor->getReferenceRepository()->unserialize($this->getReferenceBackup()); | ||
self::$metadata = $em->getMetadataFactory()->getLoadedMetadata(); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sqlite
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's typo