Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
94.20% |
65 / 69 |
|
40.00% |
2 / 5 |
CRAP | |
0.00% |
0 / 1 |
| ApieAuditLogForMigrationCommand | |
94.20% |
65 / 69 |
|
40.00% |
2 / 5 |
15.04 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| configure | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
| skipBoundedContext | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
| skipResource | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
3.03 | |||
| execute | |
96.23% |
51 / 53 |
|
0.00% |
0 / 1 |
8 | |||
| 1 | <?php |
| 2 | namespace Apie\Common\Command; |
| 3 | |
| 4 | use Apie\Common\Other\Audit\AuditMigration; |
| 5 | use Apie\Common\Other\AuditLog; |
| 6 | use Apie\Core\Attributes\Auditable; |
| 7 | use Apie\Core\BoundedContext\BoundedContext; |
| 8 | use Apie\Core\BoundedContext\BoundedContextHashmap; |
| 9 | use Apie\Core\BoundedContext\BoundedContextId; |
| 10 | use Apie\Core\ContextBuilders\ContextBuilderFactory; |
| 11 | use Apie\Core\ContextConstants; |
| 12 | use Apie\Core\Datalayers\ApieDatalayer; |
| 13 | use Apie\Core\Datalayers\Search\QuerySearch; |
| 14 | use Apie\Core\Entities\EntityInterface; |
| 15 | use Apie\Core\Entities\RequiresRecalculatingInterface; |
| 16 | use Apie\Core\Enums\ConsoleCommand; |
| 17 | use Apie\Core\Lists\StringHashmap; |
| 18 | use Apie\Core\ValueObjects\IdFriendlyEntityReference; |
| 19 | use Apie\Core\ValueObjects\NonEmptyString; |
| 20 | use Apie\Core\ValueObjects\Utils; |
| 21 | use Apie\Serializer\ValueObjects\SerializedPhpObject; |
| 22 | use ReflectionClass; |
| 23 | use Symfony\Component\Console\Command\Command; |
| 24 | use Symfony\Component\Console\Input\InputInterface; |
| 25 | use Symfony\Component\Console\Input\InputOption; |
| 26 | use Symfony\Component\Console\Output\OutputInterface; |
| 27 | |
| 28 | class ApieAuditLogForMigrationCommand extends Command |
| 29 | { |
| 30 | private const CHUNKSIZE = 2000; |
| 31 | public function __construct( |
| 32 | private readonly BoundedContextHashmap $boundedContextHashmap, |
| 33 | private readonly ApieDatalayer $apieDatalayer, |
| 34 | private readonly ContextBuilderFactory $contextBuilderFactory |
| 35 | ) { |
| 36 | parent::__construct('apie:audit-log-for-migration'); |
| 37 | } |
| 38 | protected function configure(): void |
| 39 | { |
| 40 | $this->setDescription('This command will create audit log entries for all resources that are being updated by a database migration.'); |
| 41 | $this->addOption('limit', 'l', InputOption::VALUE_OPTIONAL, 'limit number of resources to check'); |
| 42 | $this->addOption('resource', 'r', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'only check this resource'); |
| 43 | $this->addOption('bounded-context', 'b', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'only check this bounded context'); |
| 44 | } |
| 45 | |
| 46 | private function skipBoundedContext(InputInterface $input, string $contextId): bool |
| 47 | { |
| 48 | $boundedContexts = $input->getOption('bounded-context'); |
| 49 | if (empty($boundedContexts)) { |
| 50 | return false; |
| 51 | } |
| 52 | return !in_array($contextId, $boundedContexts); |
| 53 | } |
| 54 | |
| 55 | /** |
| 56 | * @param ReflectionClass<EntityInterface> $resource |
| 57 | */ |
| 58 | private function skipResource(InputInterface $input, ReflectionClass $resource): bool |
| 59 | { |
| 60 | $resources = $input->getOption('resource'); |
| 61 | $attributes = $resource->getAttributes(Auditable::class); |
| 62 | if (empty($attributes)) { |
| 63 | return true; |
| 64 | } |
| 65 | if (empty($resources)) { |
| 66 | return false; |
| 67 | } |
| 68 | return !in_array($resource->getShortName(), $resources); |
| 69 | } |
| 70 | |
| 71 | protected function execute(InputInterface $input, OutputInterface $output): int |
| 72 | { |
| 73 | $limit = $input->getOption('limit'); |
| 74 | if ($limit !== null) { |
| 75 | $limit = (int) $limit; |
| 76 | } |
| 77 | $apieContext = $this->contextBuilderFactory->createGeneralContext([ |
| 78 | ConsoleCommand::class => ConsoleCommand::CONSOLE_COMMAND, |
| 79 | ConsoleCommand::CONSOLE_COMMAND->value => true, |
| 80 | Command::class => $this, |
| 81 | ContextConstants::DISABLE_CONTEXT_FILTER => true, |
| 82 | ]); |
| 83 | /** @var BoundedContext $boundedContext */ |
| 84 | foreach ($this->boundedContextHashmap as $contextId => $boundedContext) { |
| 85 | if ($this->skipBoundedContext($input, $contextId)) { |
| 86 | continue; |
| 87 | } |
| 88 | $subApieContext = $apieContext->registerInstance($boundedContext) |
| 89 | ->withContext(ContextConstants::BOUNDED_CONTEXT_ID, $contextId) |
| 90 | ->registerInstance(new BoundedContextId($contextId)); |
| 91 | /** @var ReflectionClass<EntityInterface> $resource */ |
| 92 | foreach ($boundedContext->resources as $resource) { |
| 93 | if ($this->skipResource($input, $resource)) { |
| 94 | continue; |
| 95 | } |
| 96 | $offset = 0; |
| 97 | $boundedContextId = new BoundedContextId($contextId); |
| 98 | $output->writeln($resource->getShortName() . ' (' . $boundedContextId . ')'); |
| 99 | $list = $this->apieDatalayer->all($resource, $boundedContextId); |
| 100 | do { |
| 101 | $chunk = $list->toPaginatedResult( |
| 102 | new QuerySearch( |
| 103 | $offset, |
| 104 | $limit ?? self::CHUNKSIZE, |
| 105 | textSearch: null, |
| 106 | searches: null, |
| 107 | orderBy: new StringHashmap(['timestamp' => 'ASC']), |
| 108 | apieContext: $subApieContext |
| 109 | ) |
| 110 | ); |
| 111 | $offset++; |
| 112 | $stop = true; |
| 113 | foreach ($chunk as $item) { |
| 114 | $output->write(sprintf('%40s', Utils::toString($item->getId()))); |
| 115 | $stop = false; |
| 116 | /** @var EntityInterface $item */ |
| 117 | $createdResource = $this->apieDatalayer->persistExisting($item, $boundedContextId); |
| 118 | $this->apieDatalayer->persistNew( |
| 119 | new AuditLog( |
| 120 | new IdFriendlyEntityReference( |
| 121 | $boundedContextId, |
| 122 | NonEmptyString::fromNative($resource->getShortName()), |
| 123 | NonEmptyString::fromNative($createdResource->getId())), |
| 124 | SerializedPhpObject::createFromPhpObject($createdResource), |
| 125 | new AuditMigration(), |
| 126 | null |
| 127 | ), |
| 128 | $boundedContextId |
| 129 | ); |
| 130 | $output->writeln(' Done'); |
| 131 | } |
| 132 | } while (!$stop || $limit !== null); |
| 133 | } |
| 134 | } |
| 135 | return Command::SUCCESS; |
| 136 | } |
| 137 | } |