Skip to content

Commit 5eb9f74

Browse files
committed
chore(seeder): add Member and User seeders
The data fixtures can be loaded into the database using the `application:fixtures:load` command. All existing records are `TRUNCATE`d from the database to ensure a clean start. This also fixes some inconsistencies in the (sub)decision model with GEWISDB, somehow the possibility for these to be `null` got lost somewhere (and fixes for initialisation of `Collection`s). --- Unfortunately, adding the data fixtures for (sub)decisions has proved to be quite difficult. As such, these have been removed. The WIP can be found in GEWIS#1913. There is an issue with the "hydration" of the entities when they are added to the database. I have not seen this issue in GEWISDB, but the cause appears to be the usage of `BackedEnum`s as part of a composite key (which forms the foundation for our (sub)decision entities and relations). Either the enum cannot be cast to string while being saved to the database. Or when using custom mapping types (see the PR mentioned) above the value cannot be properly restored from the database. The latter can then also be fixed with another patch for ORM (see GEWIS/orm@8031547), however, this may break other things. This patch can probably also be applied in reverse, such that we do not need the custom mapping types. However, this has not (yet) been tested. As such, this has to be investigated more and potentially a bug report must be submitted to Doctrine ORM to get this fixed.
1 parent e997d06 commit 5eb9f74

File tree

13 files changed

+540
-8
lines changed

13 files changed

+540
-8
lines changed

Makefile

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,16 @@ rundev: builddev
4242
@make replenish
4343
@docker compose exec web rm -rf data/cache/module-config-cache.application.config.cache.php
4444

45+
migrate: replenish
46+
@docker compose exec -it web ./orm migrations:migrate
47+
4548
migration-list: replenish
4649
@docker compose exec -T web ./orm migrations:list
4750

4851
migration-diff: replenish
4952
@docker compose exec -T web ./orm migrations:diff
5053
@docker cp "$(shell docker compose ps -q web)":/code/module/Application/migrations ./module/Application/migrations
5154

52-
migration-migrate: replenish
53-
@docker compose exec -it web ./orm migrations:migrate
54-
5555
migration-up: replenish migration-list
5656
@read -p "Enter the migration version to execute (e.g., Application\\Migrations\\Version20241020212355 -- note escaping the backslashes is required): " version; \
5757
docker compose exec -it web ./orm migrations:execute --up $$version
@@ -60,6 +60,9 @@ migration-down: replenish migration-list
6060
@read -p "Enter the migration version to down (e.g., Application\\Migrations\\Version20241020212355 -- note escaping the backslashes is required): " version; \
6161
docker compose exec -it web ./orm migrations:execute --down $$version
6262

63+
seed: replenish
64+
@docker compose exec -T web ./web application:fixtures:load
65+
6366
exec:
6467
docker compose exec -it web $(cmd)
6568

module/Application/config/module.config.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Application;
66

7+
use Application\Command\LoadFixtures;
78
use Application\Controller\Factory\IndexControllerFactory;
89
use Application\Controller\IndexController;
910
use Application\View\Helper\BootstrapElementError;
@@ -148,6 +149,11 @@
148149
'message_separator_string' => '</li><li>',
149150
],
150151
],
152+
'laminas-cli' => [
153+
'commands' => [
154+
'application:fixtures:load' => LoadFixtures::class,
155+
],
156+
],
151157
'doctrine' => [
152158
'driver' => [
153159
__NAMESPACE__ . '_driver' => [
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Application\Command\Factory;
6+
7+
use Application\Command\LoadFixtures;
8+
use Doctrine\ORM\EntityManager;
9+
use Laminas\ServiceManager\Factory\FactoryInterface;
10+
use Psr\Container\ContainerInterface;
11+
12+
class LoadFixturesFactory implements FactoryInterface
13+
{
14+
/**
15+
* @param string $requestedName
16+
*/
17+
public function __invoke(
18+
ContainerInterface $container,
19+
$requestedName,
20+
?array $options = null,
21+
): LoadFixtures {
22+
/** @var EntityManager $entityManager */
23+
$entityManager = $container->get('doctrine.entitymanager.orm_default');
24+
25+
return new LoadFixtures($entityManager);
26+
}
27+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Application\Command;
6+
7+
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
8+
use Doctrine\Common\DataFixtures\Loader;
9+
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
10+
use Doctrine\ORM\EntityManager;
11+
use Symfony\Component\Console\Attribute\AsCommand;
12+
use Symfony\Component\Console\Command\Command;
13+
use Symfony\Component\Console\Input\InputInterface;
14+
use Symfony\Component\Console\Output\OutputInterface;
15+
use Throwable;
16+
17+
#[AsCommand(
18+
name: 'application:fixtures:load',
19+
description: 'Seed the database with data fixtures.',
20+
)]
21+
class LoadFixtures extends Command
22+
{
23+
private const array FIXTURES = [
24+
// './module/Activity/test/Seeder',
25+
// './module/Company/test/Seeder',
26+
'./module/Decision/test/Seeder',
27+
// './module/Education/test/Seeder',
28+
// './module/Frontpage/test/Seeder',
29+
// './module/Photo/test/Seeder',
30+
'./module/User/test/Seeder',
31+
];
32+
33+
public function __construct(private readonly EntityManager $entityManager)
34+
{
35+
parent::__construct();
36+
}
37+
38+
protected function execute(
39+
InputInterface $input,
40+
OutputInterface $output,
41+
): int {
42+
$loader = new Loader();
43+
$purger = new ORMPurger();
44+
$purger->setPurgeMode(ORMPurger::PURGE_MODE_TRUNCATE);
45+
$executor = new ORMExecutor($this->entityManager, $purger);
46+
47+
foreach ($this::FIXTURES as $fixture) {
48+
$loader->loadFromDirectory($fixture);
49+
}
50+
51+
$output->writeln('<info>Loading fixtures into the database...</info>');
52+
53+
$connection = $this->entityManager->getConnection();
54+
try {
55+
// Temporarily disable FK constraint checks. This is necessary because large parts of our database do not have
56+
// explicit CASCADEs set to prevent data loss when syncing with ReportDB (GEWISDB).
57+
// The try-catch is necessary to hide some error messages (because the executeStatement).
58+
$connection->executeStatement('SET FOREIGN_KEY_CHECKS = 0');
59+
$executor->execute($loader->getFixtures());
60+
$connection->executeStatement('SET FOREIGN_KEY_CHECKS = 1');
61+
} catch (Throwable) {
62+
}
63+
64+
$output->writeln('<info>Loaded fixtures!</info>');
65+
66+
return Command::SUCCESS;
67+
}
68+
}

module/Application/src/Module.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Application;
66

7+
use Application\Command\Factory\LoadFixturesFactory as LoadFixturesCommandFactory;
8+
use Application\Command\LoadFixtures as LoadFixturesCommand;
79
use Application\Extensions\CommonMark\CompanyImage\CompanyImageExtension;
810
use Application\Extensions\CommonMark\NoImage\NoImageExtension;
911
use Application\Extensions\CommonMark\VideoIframe\VideoIframeExtension;
@@ -266,6 +268,7 @@ public function generateSignature(
266268

267269
return new UrlBuilder($config['glide']['base_url'], $signature);
268270
},
271+
LoadFixturesCommand::class => LoadFixturesCommandFactory::class,
269272
],
270273
];
271274
}

module/Decision/src/Model/AssociationYear.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ class AssociationYear
1515
/**
1616
* A GEWIS association year starts 01-07.
1717
*/
18-
public const ASSOCIATION_YEAR_START_MONTH = 7;
19-
public const ASSOCIATION_YEAR_START_DAY = 1;
18+
public const int ASSOCIATION_YEAR_START_MONTH = 7;
19+
public const int ASSOCIATION_YEAR_START_DAY = 1;
2020

2121
/** @var int the first calendar year of the association year */
2222
protected int $firstYear;

module/Decision/src/Model/Decision.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Decision\Model\Enums\MeetingTypes;
88
use Decision\Model\SubDecision\Annulment;
9+
use Doctrine\Common\Collections\ArrayCollection;
910
use Doctrine\Common\Collections\Collection;
1011
use Doctrine\ORM\Mapping\Column;
1112
use Doctrine\ORM\Mapping\Entity;
@@ -52,6 +53,7 @@ class Decision
5253
enumType: MeetingTypes::class,
5354
)]
5455
protected MeetingTypes $meeting_type;
56+
5557
/**
5658
* Meeting number.
5759
*
@@ -103,7 +105,12 @@ enumType: MeetingTypes::class,
103105
targetEntity: Annulment::class,
104106
mappedBy: 'target',
105107
)]
106-
protected Annulment $annulledBy;
108+
protected ?Annulment $annulledBy = null;
109+
110+
public function __construct()
111+
{
112+
$this->subdecisions = new ArrayCollection();
113+
}
107114

108115
/**
109116
* Set the meeting.

module/Decision/src/Model/SubDecision/Installation.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class Installation extends FoundationReference
5858
targetEntity: Discharge::class,
5959
mappedBy: 'installation',
6060
)]
61-
protected Discharge $discharge;
61+
protected ?Discharge $discharge = null;
6262

6363
/**
6464
* The organmember reference.
@@ -114,7 +114,7 @@ public function getReappointments(): Collection
114114
/**
115115
* Get the discharge, if it exists.
116116
*/
117-
public function getDischarge(): Discharge
117+
public function getDischarge(): ?Discharge
118118
{
119119
return $this->discharge;
120120
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DecisionTest\Seeder;
6+
7+
use DateTime;
8+
use Decision\Model\AssociationYear;
9+
use Decision\Model\Enums\MeetingTypes;
10+
use Decision\Model\Meeting;
11+
use Doctrine\Common\DataFixtures\AbstractFixture;
12+
use Doctrine\Persistence\ObjectManager;
13+
14+
use function range;
15+
16+
class MeetingFixture extends AbstractFixture
17+
{
18+
public function load(ObjectManager $manager): void
19+
{
20+
$today = new DateTime();
21+
22+
foreach (MeetingTypes::cases() as $meetingType) {
23+
foreach (range(0, 3) as $meetingNumber) {
24+
$meeting = new Meeting();
25+
$meeting->setType($meetingType);
26+
$meeting->setNumber($meetingNumber);
27+
28+
// 2 meetings in the past, 1 today, and 1 in the future.
29+
if (3 > $meetingNumber) {
30+
$meetingDate = (clone $today)->modify('-' . (2 - $meetingNumber) . ' days');
31+
} else {
32+
$meetingDate = AssociationYear::fromDate($today)->getEndDate();
33+
}
34+
35+
$meeting->setDate($meetingDate);
36+
37+
$manager->persist($meeting);
38+
$this->addReference('meeting-' . $meetingType->value . '-' . $meetingNumber, $meeting);
39+
}
40+
41+
$manager->flush();
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)