Skip to content

Commit

Permalink
Fix fetching users linked to companies
Browse files Browse the repository at this point in the history
  • Loading branch information
pierredup committed Apr 12, 2023
1 parent c3585f0 commit 0e2b457
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 144 deletions.
116 changes: 72 additions & 44 deletions migrations/Version20201.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,17 @@ public function up(Schema $schema): void
}
}

$this->migrate('users');
$this->schema->getTable('user_company')
->addForeignKeyConstraint('users', ['user_id'], ['id']);

$this->persistChanges();

$this->migrate('users', false);

$this->schema->getTable('user_company')
->dropPrimaryKey();
$this->schema->getTable('user_company')
->setPrimaryKey(['company_id', 'user_id']);

$this->persistChanges();
}
Expand Down Expand Up @@ -142,37 +152,22 @@ public function down(Schema $schema): void
$extLogEntries->addOption('row_format', 'DYNAMIC');
}

private function persistChanges(): void
{
foreach (
$this->platform
->getAlterSchemaSQL(
$this
->connection
->createSchemaManager()
->createComparator()
->compareSchemas($this->fromSchema, $this->schema)
) as $sql
) {
$this->logger->log(LogLevel::DEBUG, '{query}', ['query' => $sql]);
$this->connection->executeQuery($sql);
}
}

/**
* @throws Exception|RuntimeException|\Exception
*/
public function migrate(string $tableName, string $uuidColumnName = '__uuid__'): void
public function migrate(string $tableName, bool $linkCompany = true): void
{
$uuidColumnName = '__uuid__';

$this->write('Migrating ' . $tableName . '.id to UUIDs...');
$foreignKeys = $this->getTableForeignKeys($tableName);
$this->addUuidFields($tableName, $uuidColumnName, $foreignKeys);

$this->persistChanges();

$uuids = $this->generateUuidsToReplaceIds($tableName, $uuidColumnName);
$uuids = $this->generateUuidsToReplaceIds($tableName, $uuidColumnName, $linkCompany);

$this->addUuidsToTablesWithFK($foreignKeys, $uuids);
$this->addUuidsToTablesWithFK($foreignKeys, $uuids, $linkCompany);
$this->deletePreviousFKs($foreignKeys);

$this->persistChanges();
Expand Down Expand Up @@ -268,12 +263,18 @@ private function addUuidFields(string $tableName, string $uuidColumnName, array
* @return array<string, array<UuidInterface>>
* @throws \Exception
*/
private function generateUuidsToReplaceIds(string $tableName, string $uuidColumnName): array
private function generateUuidsToReplaceIds(string $tableName, string $uuidColumnName, bool $linkCompany = true): array
{
$idGenerator = new UuidOrderedTimeGenerator();

$fields = ['id'];

if ($linkCompany) {
$fields[] = 'company_id';
}

$records = $this->connection->createQueryBuilder()
->select('id', 'company_id')
->select(...$fields)
->from($tableName)
->fetchAllAssociative();

Expand All @@ -283,13 +284,21 @@ private function generateUuidsToReplaceIds(string $tableName, string $uuidColumn

foreach ($records as $record) {
$id = $record['id'];
$companyId = $record['company_id'];
$uuid = $idGenerator->generateId($this->getEntityManager(), null);
$idToUuidMap[$companyId][$id] = $uuid;

if ($linkCompany) {
$companyId = $record['company_id'];
$idToUuidMap[$companyId][$id] = $uuid;
$updateCriteria = ['id' => $id, 'company_id' => $companyId];
} else {
$idToUuidMap[$id] = $uuid;
$updateCriteria = ['id' => $id];
}

$this->connection->update(
$tableName,
[$uuidColumnName => $uuid],
['id' => $id, 'company_id' => $companyId],
$updateCriteria,
[$uuidColumnName => 'uuid_binary_ordered_time']
);
}
Expand All @@ -302,15 +311,21 @@ private function generateUuidsToReplaceIds(string $tableName, string $uuidColumn
* @param array<string, array<UuidInterface>> $idToUuidMap
* @throws Exception
*/
private function addUuidsToTablesWithFK(array $foreignKeys, array $idToUuidMap): void
private function addUuidsToTablesWithFK(array $foreignKeys, array $idToUuidMap, bool $linkCompany = true): void
{
$this->write('-> Adding UUIDs to tables with foreign keys...');
foreach ($foreignKeys as $fk) {
$selectPk = implode(',', $fk['primaryKey']);

try {
$fieldsSelect = [$selectPk . ', ' . $fk['key'], $fk['key']];

if ($linkCompany) {
$fieldsSelect[] = 'company_id';
}

$records = $this->connection->createQueryBuilder()
->select($selectPk . ', ' . $fk['key'], 'company_id')
->select(...$fieldsSelect)
->from($fk['table'])
->fetchAllAssociative();
} catch (\Exception $e) {
Expand All @@ -322,25 +337,38 @@ private function addUuidsToTablesWithFK(array $foreignKeys, array $idToUuidMap):
$this->write(' * Adding ' . count($records) . ' UUIDs to "' . $fk['table'] . '.' . $fk['key'] . '"');

foreach ($records as $record) {
if ($record[$fk['key']] && Uuid::fromBytes($record['company_id'])->toString() !== '00000000-0000-0000-0000-000000000000') {
$queryPk = array_flip($fk['primaryKey']);
foreach ($queryPk as $key => $value) {
$queryPk[$key] = $record[$key];
}
if (!$record[$fk['key']]) {
continue;
}

/** @var UuidInterface $uuid */
if ($linkCompany && Uuid::fromBytes($record['company_id'])->toString() === '00000000-0000-0000-0000-000000000000') {
continue;
}

$queryPk = array_flip($fk['primaryKey']);
foreach ($queryPk as $key => $value) {
$queryPk[$key] = $record[$key];
}

if ($linkCompany) {
$uuid = $idToUuidMap[$record['company_id']][$record[$fk['key']]];
$this->connection->update(
$fk['table'],
[
$fk['tmpKey'] => $uuid->toString() !== '00000000-0000-0000-0000-000000000000' ? $uuid : null,
],
$queryPk + ['company_id' => $record['company_id']],
[
$fk['tmpKey'] => 'uuid_binary_ordered_time',
]
);
$queryPk['company_id'] = $record['company_id'];
} else {
$uuid = $idToUuidMap[$record[$fk['key']]];
}

/** @var UuidInterface $uuid */

$this->connection->update(
$fk['table'],
[
$fk['tmpKey'] => $uuid->toString() !== '00000000-0000-0000-0000-000000000000' ? $uuid : null,
],
$queryPk,
[
$fk['tmpKey'] => 'uuid_binary_ordered_time',
]
);
}
}
}
Expand Down
5 changes: 0 additions & 5 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1700,11 +1700,6 @@ parameters:
count: 1
path: src/UserBundle/Entity/User.php

-
message: "#^Return type \\(void\\) of method SolidInvoice\\\\UserBundle\\\\Entity\\\\User\\:\\:getSalt\\(\\) should be compatible with return type \\(string\\|null\\) of method Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\:\\:getSalt\\(\\)$#"
count: 1
path: src/UserBundle/Entity/User.php

-
message: "#^Parameter \\#2 \\$default of method SolidWorx\\\\FormHandler\\\\Options\\:\\:get\\(\\) expects null, string given\\.$#"
count: 1
Expand Down
32 changes: 7 additions & 25 deletions src/CoreBundle/Doctrine/Filter/CompanyFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,44 +13,26 @@

namespace SolidInvoice\CoreBundle\Doctrine\Filter;

use Doctrine\DBAL\Exception;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Filter\SQLFilter;
use SolidInvoice\UserBundle\Entity\User;
use function count;

class CompanyFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
{
if (User::class === $targetEntity->getName() && $this->hasParameter('companyId')) {
try {
$users = $this
return sprintf(
'%s.id IN (%s)',
$targetTableAlias,
$this
->getConnection()
->createQueryBuilder()
->select('user_id', 'company_id')
->select('user_id')
->from('user_company')
->where('company_id = ' . $this->getParameter('companyId'))
->fetchAllAssociative();
} catch (Exception $e) {
return '';
}

if (count($users) > 0) {
return sprintf(
'%s.id IN (%s)',
$targetTableAlias,
implode(
',',
array_map(
fn (string $companyId) => $this->getConnection()->quote($companyId),
array_column($users, 'user_id')
)
)
);
}

return '';
->getSQL()
);
}

if (! $targetEntity->hasAssociation('company')) {
Expand Down
14 changes: 7 additions & 7 deletions src/InstallBundle/Test/EnsureApplicationInstalled.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,21 @@ trait EnsureApplicationInstalled
{
use SymfonyKernelTrait;

private Company $company;

/**
* @before
*/
public function installApplication(): void
{
self::bootKernel();

$this->company = static::getContainer()->get('doctrine')
->getRepository(Company::class)
->findOneBy([]);

// @phpstan-ignore-next-line Ignore this line in PHPStan, since it sees the CompanySelector service as private
static::getContainer()->get(CompanySelector::class)
->switchCompany(
static::getContainer()->get('doctrine')
->getRepository(Company::class)
->findOneBy([])
->getId()
);
static::getContainer()->get(CompanySelector::class)->switchCompany($this->company->getId());

$_SERVER['locale'] = $_ENV['locale'] = 'en_US';
$_SERVER['installed'] = $_ENV['installed'] = date(DateTimeInterface::ATOM);
Expand Down
48 changes: 3 additions & 45 deletions src/UserBundle/Repository/UserRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Ramsey\Uuid\Codec\OrderedTimeCodec;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidFactory;
use Ramsey\Uuid\UuidInterface;
use SolidInvoice\UserBundle\Entity\User;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
Expand Down Expand Up @@ -80,7 +76,9 @@ public function refreshUser(UserInterface $user): UserInterface
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $class));
}

return $this->loadUserByUsername($user->getUsername());
assert($user instanceof User);

return $this->loadUserByUsername($user->getEmail());
}

public function supportsClass(string $class): bool
Expand All @@ -106,46 +104,6 @@ public function save(UserInterface $user): void
$em->flush();
}

/**
* @param list<string|User|UuidInterface> $users
*
* @return float|int|mixed|string
*/
public function deleteUsers(array $users)
{
$factory = clone Uuid::getFactory();
assert($factory instanceof UuidFactory);

$codec = new OrderedTimeCodec($factory->getUuidBuilder());

$ids = [];

foreach ($users as $user) {
if ($user instanceof User) {
foreach ($user->getCompanies() as $company) {
$user->removeCompany($company);
}

$ids[] = $codec->encodeBinary($user->getId());
} elseif ($user instanceof UuidInterface) {
$ids[] = $codec->encodeBinary($user);
} else {
$ids[] = $codec->encodeBinary(Uuid::fromString($user));
}
}

$this->getEntityManager()->flush();

$qb = $this->createQueryBuilder('u');

$qb->delete()
->where('u.id IN (:users)')
->setParameter('users', $ids);

return $qb->getQuery()
->execute();
}

public function clearUserConfirmationToken(User $user): void
{
$user->setConfirmationToken(null)
Expand Down
Loading

0 comments on commit 0e2b457

Please sign in to comment.