mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-27 12:18:54 +02:00
Reset autoincrement in a custom purger not in DataFixtures.
This makes things a lot prettier in the DataFixtures.
This commit is contained in:
parent
bd4c20765a
commit
63065a8b58
8 changed files with 326 additions and 10 deletions
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
@ -92,7 +92,7 @@ jobs:
|
|||
run: php bin/console --env test doctrine:migrations:migrate -n
|
||||
|
||||
- name: Load fixtures
|
||||
run: php bin/console --env test doctrine:fixtures:load -n
|
||||
run: php bin/console --env test doctrine:fixtures:load -n --purger reset_autoincrement_purger
|
||||
|
||||
- name: Run PHPunit and generate coverage
|
||||
run: ./bin/phpunit --coverage-clover=coverage.xml
|
||||
|
|
|
@ -42,6 +42,7 @@ services:
|
|||
Doctrine\Migrations\DependencyFactory:
|
||||
alias: 'doctrine.migrations.dependency_factory'
|
||||
|
||||
|
||||
####################################################################################################################
|
||||
# Email
|
||||
####################################################################################################################
|
||||
|
@ -210,3 +211,7 @@ services:
|
|||
arguments:
|
||||
$default_locale: '%partdb.locale%'
|
||||
$enforce_index_php: '%env(bool:NO_URL_REWRITE_AVAILABLE)%'
|
||||
|
||||
App\Doctrine\Purger\ResetAutoIncrementPurgerFactory:
|
||||
tags:
|
||||
- { name: 'doctrine.fixtures.purger_factory', alias: 'reset_autoincrement_purger' }
|
||||
|
|
|
@ -93,9 +93,6 @@ class DataStructureFixtures extends Fixture
|
|||
throw new InvalidArgumentException('$class must be a StructuralDBElement!');
|
||||
}
|
||||
|
||||
$table_name = $this->em->getClassMetadata($class)->getTableName();
|
||||
$this->em->getConnection()->exec("ALTER TABLE `${table_name}` AUTO_INCREMENT = 1;");
|
||||
|
||||
/** @var AbstractStructuralDBElement $node1 */
|
||||
$node1 = new $class();
|
||||
$node1->setName('Node 1');
|
||||
|
|
|
@ -40,7 +40,6 @@ class LabelProfileFixtures extends Fixture
|
|||
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
$this->em->getConnection()->exec('ALTER TABLE `label_profiles` AUTO_INCREMENT = 1;');
|
||||
|
||||
$profile1 = new LabelProfile();
|
||||
$profile1->setName('Profile 1');
|
||||
|
|
|
@ -50,8 +50,6 @@ class PartFixtures extends Fixture
|
|||
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
$table_name = $this->em->getClassMetadata(Part::class)->getTableName();
|
||||
$this->em->getConnection()->exec("ALTER TABLE `${table_name}` AUTO_INCREMENT = 1;");
|
||||
|
||||
/** Simple part */
|
||||
$part = new Part();
|
||||
|
|
|
@ -61,9 +61,6 @@ class UserFixtures extends Fixture
|
|||
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
//Reset autoincrement
|
||||
$this->em->getConnection()->exec('ALTER TABLE `users` AUTO_INCREMENT = 1;');
|
||||
|
||||
$anonymous = new User();
|
||||
$anonymous->setName('anonymous');
|
||||
$anonymous->setGroup($this->getReference(GroupFixtures::READONLY));
|
||||
|
|
300
src/Doctrine/Purger/ResetAutoIncrementORMPurger.php
Normal file
300
src/Doctrine/Purger/ResetAutoIncrementORMPurger.php
Normal file
|
@ -0,0 +1,300 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Doctrine\Purger;
|
||||
|
||||
use Doctrine\Common\DataFixtures\Purger\ORMPurgerInterface;
|
||||
use Doctrine\Common\DataFixtures\Purger\PurgerInterface;
|
||||
use Doctrine\Common\DataFixtures\Sorter\TopologicalSorter;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Schema\Identifier;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
|
||||
use function array_reverse;
|
||||
use function array_search;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function is_callable;
|
||||
use function method_exists;
|
||||
use function preg_match;
|
||||
|
||||
/**
|
||||
* Class responsible for purging databases of data before reloading data fixtures.
|
||||
*
|
||||
* Based on Doctrine\Common\DataFixtures\Purger\ORMPurger
|
||||
*/
|
||||
class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface
|
||||
{
|
||||
public const PURGE_MODE_DELETE = 1;
|
||||
public const PURGE_MODE_TRUNCATE = 2;
|
||||
|
||||
/** @var EntityManagerInterface|null */
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* If the purge should be done through DELETE or TRUNCATE statements
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $purgeMode = self::PURGE_MODE_DELETE;
|
||||
|
||||
/**
|
||||
* Table/view names to be excluded from purge
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $excluded;
|
||||
|
||||
/**
|
||||
* Construct new purger instance.
|
||||
*
|
||||
* @param EntityManagerInterface $em EntityManagerInterface instance used for persistence.
|
||||
* @param string[] $excluded array of table/view names to be excluded from purge
|
||||
*/
|
||||
public function __construct(?EntityManagerInterface $em = null, array $excluded = [])
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->excluded = $excluded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the purge mode
|
||||
*
|
||||
* @param int $mode
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPurgeMode($mode)
|
||||
{
|
||||
$this->purgeMode = $mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the purge mode
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getPurgeMode()
|
||||
{
|
||||
return $this->purgeMode;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function setEntityManager(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the EntityManagerInterface instance this purger instance is using.
|
||||
*
|
||||
* @return EntityManagerInterface
|
||||
*/
|
||||
public function getObjectManager()
|
||||
{
|
||||
return $this->em;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function purge()
|
||||
{
|
||||
$classes = [];
|
||||
|
||||
foreach ($this->em->getMetadataFactory()->getAllMetadata() as $metadata) {
|
||||
if ($metadata->isMappedSuperclass || (isset($metadata->isEmbeddedClass) && $metadata->isEmbeddedClass)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$classes[] = $metadata;
|
||||
}
|
||||
|
||||
$commitOrder = $this->getCommitOrder($this->em, $classes);
|
||||
|
||||
// Get platform parameters
|
||||
$platform = $this->em->getConnection()->getDatabasePlatform();
|
||||
|
||||
// Drop association tables first
|
||||
$orderedTables = $this->getAssociationTables($commitOrder, $platform);
|
||||
|
||||
// Drop tables in reverse commit order
|
||||
for ($i = count($commitOrder) - 1; $i >= 0; --$i) {
|
||||
$class = $commitOrder[$i];
|
||||
|
||||
if (
|
||||
(isset($class->isEmbeddedClass) && $class->isEmbeddedClass) ||
|
||||
$class->isMappedSuperclass ||
|
||||
($class->isInheritanceTypeSingleTable() && $class->name !== $class->rootEntityName)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$orderedTables[] = $this->getTableName($class, $platform);
|
||||
}
|
||||
|
||||
$connection = $this->em->getConnection();
|
||||
$filterExpr = $connection->getConfiguration()->getFilterSchemaAssetsExpression();
|
||||
$emptyFilterExpression = empty($filterExpr);
|
||||
|
||||
$schemaAssetsFilter = method_exists($connection->getConfiguration(), 'getSchemaAssetsFilter') ? $connection->getConfiguration()->getSchemaAssetsFilter() : null;
|
||||
|
||||
//Disable foreign key checks
|
||||
if($platform->getName() === 'mysql') {
|
||||
$connection->executeQuery('SET foreign_key_checks = 0;');
|
||||
}
|
||||
|
||||
foreach ($orderedTables as $tbl) {
|
||||
// If we have a filter expression, check it and skip if necessary
|
||||
if (! $emptyFilterExpression && ! preg_match($filterExpr, $tbl)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the table is excluded, skip it as well
|
||||
if (array_search($tbl, $this->excluded) !== false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Support schema asset filters as presented in
|
||||
if (is_callable($schemaAssetsFilter) && ! $schemaAssetsFilter($tbl)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->purgeMode === self::PURGE_MODE_DELETE) {
|
||||
$connection->executeUpdate($this->getDeleteFromTableSQL($tbl, $platform));
|
||||
} else {
|
||||
$connection->executeUpdate($platform->getTruncateTableSQL($tbl, true));
|
||||
}
|
||||
|
||||
//Reseting autoincrement is only supported on MySQL platforms
|
||||
if ($platform->getName() === 'mysql') {
|
||||
$connection->beginTransaction();
|
||||
$connection->executeQuery($this->getResetAutoIncrementSQL($tbl, $platform));
|
||||
}
|
||||
}
|
||||
|
||||
//Reenable foreign key checks
|
||||
if($platform->getName() === 'mysql') {
|
||||
$connection->executeQuery('SET foreign_key_checks = 1;');
|
||||
}
|
||||
}
|
||||
|
||||
private function getResetAutoIncrementSQL(string $tableName, AbstractPlatform $platform): string
|
||||
{
|
||||
$tableIdentifier = new Identifier($tableName);
|
||||
|
||||
return 'ALTER TABLE '. $tableIdentifier->getQuotedName($platform) .' AUTO_INCREMENT = 1;';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMetadata[] $classes
|
||||
*
|
||||
* @return ClassMetadata[]
|
||||
*/
|
||||
private function getCommitOrder(EntityManagerInterface $em, array $classes)
|
||||
{
|
||||
$sorter = new TopologicalSorter();
|
||||
|
||||
foreach ($classes as $class) {
|
||||
if (! $sorter->hasNode($class->name)) {
|
||||
$sorter->addNode($class->name, $class);
|
||||
}
|
||||
|
||||
// $class before its parents
|
||||
foreach ($class->parentClasses as $parentClass) {
|
||||
$parentClass = $em->getClassMetadata($parentClass);
|
||||
$parentClassName = $parentClass->getName();
|
||||
|
||||
if (! $sorter->hasNode($parentClassName)) {
|
||||
$sorter->addNode($parentClassName, $parentClass);
|
||||
}
|
||||
|
||||
$sorter->addDependency($class->name, $parentClassName);
|
||||
}
|
||||
|
||||
foreach ($class->associationMappings as $assoc) {
|
||||
if (! $assoc['isOwningSide']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetClass = $em->getClassMetadata($assoc['targetEntity']);
|
||||
assert($targetClass instanceof ClassMetadata);
|
||||
$targetClassName = $targetClass->getName();
|
||||
|
||||
if (! $sorter->hasNode($targetClassName)) {
|
||||
$sorter->addNode($targetClassName, $targetClass);
|
||||
}
|
||||
|
||||
// add dependency ($targetClass before $class)
|
||||
$sorter->addDependency($targetClassName, $class->name);
|
||||
|
||||
// parents of $targetClass before $class, too
|
||||
foreach ($targetClass->parentClasses as $parentClass) {
|
||||
$parentClass = $em->getClassMetadata($parentClass);
|
||||
$parentClassName = $parentClass->getName();
|
||||
|
||||
if (! $sorter->hasNode($parentClassName)) {
|
||||
$sorter->addNode($parentClassName, $parentClass);
|
||||
}
|
||||
|
||||
$sorter->addDependency($parentClassName, $class->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_reverse($sorter->sort());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $classes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getAssociationTables(array $classes, AbstractPlatform $platform)
|
||||
{
|
||||
$associationTables = [];
|
||||
|
||||
foreach ($classes as $class) {
|
||||
foreach ($class->associationMappings as $assoc) {
|
||||
if (! $assoc['isOwningSide'] || $assoc['type'] !== ClassMetadata::MANY_TO_MANY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$associationTables[] = $this->getJoinTableName($assoc, $class, $platform);
|
||||
}
|
||||
}
|
||||
|
||||
return $associationTables;
|
||||
}
|
||||
|
||||
private function getTableName(ClassMetadata $class, AbstractPlatform $platform): string
|
||||
{
|
||||
if (isset($class->table['schema']) && ! method_exists($class, 'getSchemaName')) {
|
||||
return $class->table['schema'] . '.' . $this->em->getConfiguration()->getQuoteStrategy()->getTableName($class, $platform);
|
||||
}
|
||||
|
||||
return $this->em->getConfiguration()->getQuoteStrategy()->getTableName($class, $platform);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $assoc
|
||||
*/
|
||||
private function getJoinTableName(
|
||||
array $assoc,
|
||||
ClassMetadata $class,
|
||||
AbstractPlatform $platform
|
||||
): string {
|
||||
if (isset($assoc['joinTable']['schema']) && ! method_exists($class, 'getSchemaName')) {
|
||||
return $assoc['joinTable']['schema'] . '.' . $this->em->getConfiguration()->getQuoteStrategy()->getJoinTableName($assoc, $class, $platform);
|
||||
}
|
||||
|
||||
return $this->em->getConfiguration()->getQuoteStrategy()->getJoinTableName($assoc, $class, $platform);
|
||||
}
|
||||
|
||||
private function getDeleteFromTableSQL(string $tableName, AbstractPlatform $platform): string
|
||||
{
|
||||
$tableIdentifier = new Identifier($tableName);
|
||||
|
||||
return 'DELETE FROM ' . $tableIdentifier->getQuotedName($platform);
|
||||
}
|
||||
}
|
20
src/Doctrine/Purger/ResetAutoIncrementPurgerFactory.php
Normal file
20
src/Doctrine/Purger/ResetAutoIncrementPurgerFactory.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace App\Doctrine\Purger;
|
||||
|
||||
use Doctrine\Bundle\FixturesBundle\Purger\PurgerFactory;
|
||||
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
|
||||
use Doctrine\Common\DataFixtures\Purger\PurgerInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class ResetAutoIncrementPurgerFactory implements PurgerFactory
|
||||
{
|
||||
|
||||
public function createForEntityManager(?string $emName, EntityManagerInterface $em, array $excluded = [], bool $purgeWithTruncate = false) : PurgerInterface
|
||||
{
|
||||
$purger = new ResetAutoIncrementORMPurger($em, $excluded);
|
||||
$purger->setPurgeMode($purgeWithTruncate ? ORMPurger::PURGE_MODE_TRUNCATE : ORMPurger::PURGE_MODE_DELETE);
|
||||
|
||||
return $purger;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue