From 8ddb44f6b119be346e64b8bedbefaea03d325c9c Mon Sep 17 00:00:00 2001 From: kozhilya Date: Wed, 11 Dec 2024 09:53:41 +0400 Subject: [PATCH] Work, work, work~~~ --- composer.json | 2 +- migrations/Version20241210083555.php | 42 ++++ migrations/Version20241210084313.php | 41 ++++ migrations/Version20241210085754.php | 32 +++ migrations/Version20241210102535.php | 35 +++ migrations/Version20241210103436.php | 38 ++++ migrations/Version20241210103547.php | 34 +++ migrations/Version20241210103818.php | 42 ++++ src/Command/Pf2eGenerateCommand.php | 133 ++++++------ src/Command/Pf2eImportCommand.php | 202 +++++++++++++++++- .../Pf2eSchemaGenerator/EntitySchemas.php | 74 +++++++ src/Command/Pf2eSchemaGenerator/Schema.php | 96 --------- .../Pf2eSchemaGenerator/SchemaItem.php | 72 ------- .../SchemaType/AbstractSchemaType.php | 101 +++++++++ .../SchemaType/ArraySchemaType.php | 121 +++++++++++ .../SchemaType/EmptySchemaType.php | 49 +++++ .../SchemaType/MixedSchemaType.php | 105 +++++++++ .../SchemaType/ObjectSchemaType.php | 101 +++++++++ .../SchemaType/StaticSchemaType.php | 86 ++++++++ src/Command/Pf2eTypesCommand.php | 2 + src/Entity/Item.php | 17 +- src/Entity/ItemLocalization.php | 30 +++ src/Entity/Pf2e/Source.php | 91 ++++++++ src/Entity/Pf2e/Tpait.php | 10 +- src/Repository/Pf2e/SourceRepository.php | 43 ++++ src/Service/AonprdConnectionService.php | 71 +++++- 26 files changed, 1416 insertions(+), 254 deletions(-) create mode 100644 migrations/Version20241210083555.php create mode 100644 migrations/Version20241210084313.php create mode 100644 migrations/Version20241210085754.php create mode 100644 migrations/Version20241210102535.php create mode 100644 migrations/Version20241210103436.php create mode 100644 migrations/Version20241210103547.php create mode 100644 migrations/Version20241210103818.php create mode 100644 src/Command/Pf2eSchemaGenerator/EntitySchemas.php delete mode 100644 src/Command/Pf2eSchemaGenerator/Schema.php delete mode 100644 src/Command/Pf2eSchemaGenerator/SchemaItem.php create mode 100644 src/Command/Pf2eSchemaGenerator/SchemaType/AbstractSchemaType.php create mode 100644 src/Command/Pf2eSchemaGenerator/SchemaType/ArraySchemaType.php create mode 100644 src/Command/Pf2eSchemaGenerator/SchemaType/EmptySchemaType.php create mode 100644 src/Command/Pf2eSchemaGenerator/SchemaType/MixedSchemaType.php create mode 100644 src/Command/Pf2eSchemaGenerator/SchemaType/ObjectSchemaType.php create mode 100644 src/Command/Pf2eSchemaGenerator/SchemaType/StaticSchemaType.php create mode 100644 src/Entity/Pf2e/Source.php create mode 100644 src/Repository/Pf2e/SourceRepository.php diff --git a/composer.json b/composer.json index e7aede1..ab51c3f 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "minimum-stability": "stable", "prefer-stable": true, "require": { - "php": ">=8.2", + "php": ">=8.3", "ext-ctype": "*", "ext-iconv": "*", "doctrine/dbal": "^3", diff --git a/migrations/Version20241210083555.php b/migrations/Version20241210083555.php new file mode 100644 index 0000000..cbea20b --- /dev/null +++ b/migrations/Version20241210083555.php @@ -0,0 +1,42 @@ +addSql('DROP SEQUENCE tpait_id_seq CASCADE'); + $this->addSql('ALTER TABLE item ADD aonprd_id VARCHAR(255) NOT NULL'); + $this->addSql('ALTER TABLE item_localization ADD locale VARCHAR(10) NOT NULL'); + $this->addSql('ALTER TABLE tpait ALTER id TYPE VARCHAR(255)'); + $this->addSql('ALTER TABLE tpait ALTER id DROP DEFAULT'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('CREATE SEQUENCE tpait_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('ALTER TABLE item_localization DROP locale'); + $this->addSql('ALTER TABLE item DROP aonprd_id'); + $this->addSql('ALTER TABLE tpait ALTER id TYPE INT'); + $this->addSql('CREATE SEQUENCE tpait_id_seq'); + $this->addSql('SELECT setval(\'tpait_id_seq\', (SELECT MAX(id) FROM tpait))'); + $this->addSql('ALTER TABLE tpait ALTER id SET DEFAULT nextval(\'tpait_id_seq\')'); + } +} diff --git a/migrations/Version20241210084313.php b/migrations/Version20241210084313.php new file mode 100644 index 0000000..3634108 --- /dev/null +++ b/migrations/Version20241210084313.php @@ -0,0 +1,41 @@ +addSql('DROP INDEX uniq_1f1b251ed17f50a6'); + $this->addSql('ALTER TABLE item DROP uuid'); + $this->addSql('CREATE SEQUENCE item_id_seq'); + $this->addSql('SELECT setval(\'item_id_seq\', (SELECT MAX(id) FROM item))'); + $this->addSql('ALTER TABLE item ALTER id SET DEFAULT nextval(\'item_id_seq\')'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_1F1B251EDA27C6FE ON item (aonprd_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('DROP INDEX UNIQ_1F1B251EDA27C6FE'); + $this->addSql('ALTER TABLE item ADD uuid UUID NOT NULL'); + $this->addSql('ALTER TABLE item ALTER id DROP DEFAULT'); + $this->addSql('COMMENT ON COLUMN item.uuid IS \'(DC2Type:uuid)\''); + $this->addSql('CREATE UNIQUE INDEX uniq_1f1b251ed17f50a6 ON item (uuid)'); + } +} diff --git a/migrations/Version20241210085754.php b/migrations/Version20241210085754.php new file mode 100644 index 0000000..664d4b8 --- /dev/null +++ b/migrations/Version20241210085754.php @@ -0,0 +1,32 @@ +addSql('ALTER TABLE item_localization ADD type VARCHAR(255) NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE item_localization DROP type'); + } +} diff --git a/migrations/Version20241210102535.php b/migrations/Version20241210102535.php new file mode 100644 index 0000000..a34fdd0 --- /dev/null +++ b/migrations/Version20241210102535.php @@ -0,0 +1,35 @@ +addSql('CREATE TABLE source (id SERIAL NOT NULL, item_id INT NOT NULL, rarity INT NOT NULL, primary_source_category VARCHAR(255) NOT NULL, primary_source_group VARCHAR(255) NOT NULL, source_category JSON NOT NULL, source_group JSON NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_5F8A7F73126F525E ON source (item_id)'); + $this->addSql('ALTER TABLE source ADD CONSTRAINT FK_5F8A7F73126F525E FOREIGN KEY (item_id) REFERENCES item (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE source DROP CONSTRAINT FK_5F8A7F73126F525E'); + $this->addSql('DROP TABLE source'); + } +} diff --git a/migrations/Version20241210103436.php b/migrations/Version20241210103436.php new file mode 100644 index 0000000..c3a48d3 --- /dev/null +++ b/migrations/Version20241210103436.php @@ -0,0 +1,38 @@ +addSql('DROP SEQUENCE source_id_seq CASCADE'); + $this->addSql('ALTER TABLE source ALTER id TYPE VARCHAR(255)'); + $this->addSql('ALTER TABLE source ALTER id DROP DEFAULT'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('CREATE SEQUENCE source_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('ALTER TABLE source ALTER id TYPE INT'); + $this->addSql('CREATE SEQUENCE source_id_seq'); + $this->addSql('SELECT setval(\'source_id_seq\', (SELECT MAX(id) FROM source))'); + $this->addSql('ALTER TABLE source ALTER id SET DEFAULT nextval(\'source_id_seq\')'); + } +} diff --git a/migrations/Version20241210103547.php b/migrations/Version20241210103547.php new file mode 100644 index 0000000..1cf5f41 --- /dev/null +++ b/migrations/Version20241210103547.php @@ -0,0 +1,34 @@ +addSql('ALTER TABLE source ALTER primary_source_category DROP NOT NULL'); + $this->addSql('ALTER TABLE source ALTER primary_source_group DROP NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE source ALTER primary_source_category SET NOT NULL'); + $this->addSql('ALTER TABLE source ALTER primary_source_group SET NOT NULL'); + } +} diff --git a/migrations/Version20241210103818.php b/migrations/Version20241210103818.php new file mode 100644 index 0000000..d35a289 --- /dev/null +++ b/migrations/Version20241210103818.php @@ -0,0 +1,42 @@ +addSql('ALTER TABLE source DROP primary_source_category'); + $this->addSql('ALTER TABLE source DROP primary_source_group'); + $this->addSql('ALTER TABLE source ALTER source_category TYPE VARCHAR(255)'); + $this->addSql('ALTER TABLE source ALTER source_category DROP NOT NULL'); + $this->addSql('ALTER TABLE source ALTER source_group TYPE VARCHAR(255)'); + $this->addSql('ALTER TABLE source ALTER source_group DROP NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE source ADD primary_source_category VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE source ADD primary_source_group VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE source ALTER source_category TYPE JSON'); + $this->addSql('ALTER TABLE source ALTER source_category SET NOT NULL'); + $this->addSql('ALTER TABLE source ALTER source_group TYPE JSON'); + $this->addSql('ALTER TABLE source ALTER source_group SET NOT NULL'); + } +} diff --git a/src/Command/Pf2eGenerateCommand.php b/src/Command/Pf2eGenerateCommand.php index 9228444..5f0efc3 100644 --- a/src/Command/Pf2eGenerateCommand.php +++ b/src/Command/Pf2eGenerateCommand.php @@ -2,11 +2,12 @@ namespace App\Command; -use App\Command\Pf2eSchemaGenerator\Schema; -use App\Command\Pf2eSchemaGenerator\SchemaItem; +use App\Command\Pf2eSchemaGenerator\SchemaType\AbstractSchemaType; +use App\Command\Pf2eSchemaGenerator\SchemaType\ObjectSchemaType; use App\Service\AonprdConnectionService; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -14,7 +15,7 @@ use Symfony\Component\Console\Style\SymfonyStyle; #[AsCommand( name: 'pf2e:generate', - description: 'Add a short description for your command', + description: 'Get schemas of data', )] class Pf2eGenerateCommand extends Command { @@ -29,57 +30,48 @@ class Pf2eGenerateCommand extends Command ->addOption("revision", null, InputOption::VALUE_REQUIRED, "Load index of specific revision. Default value - latest revision.") ->addOption('type', null, InputOption::VALUE_REQUIRED, 'Specify types to import schema') ->addOption('all', null, InputOption::VALUE_NONE, 'Generate schema for all types') + ->addOption('limit', null, InputOption::VALUE_REQUIRED, 'Specify number of elements can be listed as possible values') ; } - private int $revision; - /** * @var string[] */ private array $types; + private array $typePages; + /** - * @var array + * @var array */ private array $schemas = []; + + private OutputInterface $output; + protected function execute(InputInterface $input, OutputInterface $output): int { + $this->output = $output; + $io = new SymfonyStyle($input, $output); - $this->revision = $input->getOption('revision') ?? $this->connectionService->getRevision(); + if (!is_null($input->getOption('revision'))) { + $this->connectionService->setRevision($input->getOption('revision')); + } + + if (!is_null($input->getOption('limit'))) { + AbstractSchemaType::$elementLimit = $input->getOption('limit'); + } + + $this->types = $this->processTypes($input); + $this->typePages = $this->connectionService->getIndexPageListByType($this->types); if ($input->getOption('all')) { - $this->types = []; - } - else if (is_null($input->getOption('type'))) { -// $io->error("You must specify --type=[TYPE] or --all"); -// return Command::INVALID; - $this->types = ["class"]; - } - else { - $this->types = explode(",", $input->getOption('type')); + $this->types = array_keys($this->typePages); } - $typePages = $this->loadTypePages(); - - if ($input->getOption('all')) { - $this->types = array_keys($typePages); - } - - $pages = []; - - foreach ($this->types as $type) { - $pages = [...$pages, ...$typePages[$type]]; - $this->schemas[$type] = new Schema(); - } - - $pages = array_unique($pages); - - foreach ($pages as $page) { - $this->loadPage($page); - } + $this->progressBar = new ProgressBar($output); + $this->processPages(); foreach ($this->schemas as $type => $schema) { $io->write("{$type}"); @@ -90,45 +82,60 @@ class Pf2eGenerateCommand extends Command return Command::SUCCESS; } - protected function loadTypePages(): array + public function processTypes(InputInterface $input): array { - $typePages = []; + if ($input->getOption('all')) { + return []; + } + else if (is_null($input->getOption('type'))) { +// throw new \Error("You must specify --type=[TYPE] or --all"); - $index = $this->connectionService->loadCurrentIndex($this->revision); + return ["class"]; + } + else { + return explode(",", $input->getOption('type')); + } + } + + public function processPages(): void + { + $pages = []; + + foreach ($this->types as $type) { + $pages = [...$pages, ...$this->typePages[$type]]; + $this->schemas[$type] = new ObjectSchemaType(); + } + + $pages = array_unique($pages); + + $countPages = count($pages); + $i = 0; + + foreach ($pages as $page) { + $i++; + + $elements = $this->connectionService->loadIndexPage($page, $this->types); + + $progressBar = new ProgressBar($this->output, count($elements)); + $progressBar->setFormat("debug"); + $progressBar->setMessage("Page {$i}/{$countPages}"); + $progressBar->start(); - foreach ($index as $page => $elements) { foreach ($elements as $element) { - preg_match("#^([a-z\\-]+?)-(\\d.*)$#", $element, $matches); - $type = $matches[1]; - - if (!empty($targetType) && !in_array($type, $this->types)) { + if (array_key_exists("remaster_id", $element)) { + $progressBar->advance(); continue; } - if (!isset($typePages[$type])) { - $typePages[$type] = []; - } + $type = AonprdConnectionService::splitItemFullId($element["id"])["type"]; + $this->schemas[$type] = $this->schemas[$type]->adapt($element); + $this->schemas[$type]->addValue($element); - if (!in_array($page, $typePages[$type])) { - $typePages[$type][] = $page; - } - } - } - - return $typePages; - } - - protected function loadPage(string $page): void { - $data = $this->connectionService->load("/json-data/{$page}.json"); - - foreach ($data as $element) { - $type = $element["category"]; - - if (!in_array($type, $this->types)) { - continue; + $progressBar->advance(); } - $this->schemas[$type]->process($element); + $progressBar->finish(); } + } } \ No newline at end of file diff --git a/src/Command/Pf2eImportCommand.php b/src/Command/Pf2eImportCommand.php index 6a48499..ec9239b 100644 --- a/src/Command/Pf2eImportCommand.php +++ b/src/Command/Pf2eImportCommand.php @@ -2,6 +2,13 @@ namespace App\Command; +use App\Entity\Item; +use App\Entity\ItemLocalization; +use App\Entity\ItemTypeEnum; +use App\Service\AonprdConnectionService; +use Doctrine\ORM\EntityManagerInterface; +use ReflectionClass; +use ReflectionEnum; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -9,14 +16,18 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\PropertyAccess\PropertyAccess; #[AsCommand( name: 'pf2e:import', - description: 'Add a short description for your command', + description: 'Import data', )] class Pf2eImportCommand extends Command { - public function __construct() + public function __construct( + private AonprdConnectionService $connectionService, + private EntityManagerInterface $em + ) { parent::__construct(); } @@ -30,28 +41,199 @@ class Pf2eImportCommand extends Command ; } - private int $revision; - /** * @var string[] */ private array $types; + private array $typePages; + + private array $convertSchemas; + protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); - $arg1 = $input->getArgument('arg1'); - if ($arg1) { - $io->note(sprintf('You passed an argument: %s', $arg1)); + if (!is_null($input->getOption('revision'))) { + $this->connectionService->setRevision($input->getOption('revision')); } - if ($input->getOption('option1')) { - // ... + $this->convertSchemas = include(__DIR__ . "/Pf2eSchemaGenerator/EntitySchemas.php"); + + $this->types = $this->processTypes($input); + $this->typePages = $this->connectionService->getIndexPageListByType($this->types); + + $this->em->beginTransaction(); + + try { + $this->cleanUp(); + $this->processPages(); + } + catch (\Exception $e) { + $io->error($e->getMessage()); + $io->writeln($e->getTraceAsString()); +// dump($e->getTrace()); + return Command::FAILURE; } - $io->success('You have a new command! Now make it your own! Pass --help to see your options.'); + $this->em->commit(); return Command::SUCCESS; } + + + public function processTypes(InputInterface $input): array + { + if ($input->getOption('all')) { + return []; + } + else if (is_null($input->getOption('type'))) { +// throw new \Error("You must specify --type=[TYPE] or --all"); + + return ["class"]; + } + else { + return explode(",", $input->getOption('type')); + } + } + + public function cleanUp(): void + { + $connection = $this->em->getConnection(); + +// $connection->executeQuery('SET FOREIGN_KEY_CHECKS=0'); + + foreach ($this->types as $type) { + $connection->executeQuery("DELETE FROM ".$this->getTableName($this->convertSchemas[$type]["class"])); + + $qb = $this->em->createQueryBuilder(); + $qb->delete(ItemLocalization::class, 'e') + ->andWhere('e.type = :type') + ->setParameter('type', $type); + $qb->getQuery()->execute(); + + $qb = $this->em->createQueryBuilder(); + $qb->delete(Item::class, 'e') + ->andWhere('e.type = :type') + ->setParameter('type', $type); + $qb->getQuery()->execute(); + } + +// $connection->executeQuery('SET FOREIGN_KEY_CHECKS=1'); + } + + private function getTableName(string $entityClass): string + { + $cmd = $this->em->getClassMetadata($entityClass); + return $cmd->getTableName(); + } + + public function processPages(): void + { + $pages = []; + + foreach ($this->types as $type) { + $pages = [...$pages, ...$this->typePages[$type]]; + } + + $pages = array_unique($pages); + + foreach ($pages as $page) { + $elements = $this->connectionService->loadIndexPage($page, $this->types); + + foreach ($elements as $element) { + $this->processElement($element); + +// return; + } +// return; + } + } + + public function processElement(array $element): void + { + if (array_key_exists("remaster_id", $element)) { + return; + } + + $type = AonprdConnectionService::splitItemFullId($element["id"])["type"]; + $schema = $this->convertSchemas[$type]; + + $propertyAccessor = PropertyAccess::createPropertyAccessor(); + $reflection = new ReflectionClass($schema["class"]); + + // Item + $item = new Item(); + $item->setName($element["name"]) + ->setType(static::getEnum(ItemTypeEnum::class, $type)) + ->setAonprdId($element["id"]); + + // Entity + $entity = $reflection->newInstance(); + $propertyAccessor->setValue($entity, "id", $element["id"]); + $propertyAccessor->setValue($entity, "item", $item); + + foreach ($schema["schema"] as $field => $elementSchema) { + $value = $this->getValue($element, $field, $elementSchema); + $propertyAccessor->setValue($entity, $field, $value); + } + + // Localization + $localization = new ItemLocalization(); + $localization->setLocale("en") + ->setItem($item) + ->setType($item->getType()); + + foreach ($schema["localization"] as $field => $elementSchema) { + $value = $this->getValue($element, $field, $elementSchema); + $propertyAccessor->setValue($localization, $field, $value); + } + +// dump($item); +// dump($entity); +// dump($localization); + + $this->em->persist($item); + $this->em->persist($entity); + $this->em->persist($localization); + + $this->em->flush(); + } + + public function getValue(mixed $element, string $field, mixed $schema) + { + if ($schema === true) { + $schema = $field; + } + + if (is_string($schema)) { + $schema = [ + "method" => "value", + "field" => $schema, + ]; + } + + switch ($schema["method"]) { + case "enum": + return static::getEnum($schema["enum"], $element[$schema["field"]]); + + default: + return $element[$schema["field"]] ?? null; + } + } + + private static function getEnum(string $enum, mixed $value): mixed + { + $reflection = new ReflectionEnum($enum); + + $cases = $reflection->getCases(); + + foreach ($cases as $case) { + if ($case->getBackingValue() === $value) { + return $case->getValue(); + } + } + + throw new \Error(sprintf('Class "%s" does not have value "%s"', $enum, $value)); + } } diff --git a/src/Command/Pf2eSchemaGenerator/EntitySchemas.php b/src/Command/Pf2eSchemaGenerator/EntitySchemas.php new file mode 100644 index 0000000..8e498cd --- /dev/null +++ b/src/Command/Pf2eSchemaGenerator/EntitySchemas.php @@ -0,0 +1,74 @@ + "index_field", + +return [ + "trait" => [ + "class" => \App\Entity\Pf2e\Tpait::class, + "schema" => [ + "rarity" => [ + "method" => "enum", + "field" => "rarity_id", + "enum" => \App\Entity\Rarity::class, + ], + ], + "localization" => [ + "title" => "name", + "markdown" => "markdown", + "url" => "url" + ] + ], + "source" => [ + "class" => \App\Entity\Pf2e\Source::class, + "schema" => [ + "rarity" => [ + "method" => "enum", + "field" => "rarity_id", + "enum" => \App\Entity\Rarity::class, + ], + "source_category" => "primary_source_category", + "source_group" => "primary_source_group", + + ], + "localization" => [ + "title" => "name", + "markdown" => "markdown", + "url" => "url" + ] + ], + "ancestry" => [ +// "class" => , + "schema" => [ + "rarity" => [ + "method" => "enum", + "field" => "rarity_id", + "enum" => \App\Entity\Rarity::class, + ], + + ], + "localization" => [ + "title" => "name", + "markdown" => "markdown", + "url" => "url" + ] + ], +]; + +/* + "" => [ + "class" => , + "schema" => [ + "rarity" => [ + "method" => "enum", + "field" => "rarity_id", + "enum" => \App\Entity\Rarity::class, + ], + + ], + "localization" => [ + "title" => "name", + "markdown" => "markdown", + "url" => "url" + ] + ], + */ \ No newline at end of file diff --git a/src/Command/Pf2eSchemaGenerator/Schema.php b/src/Command/Pf2eSchemaGenerator/Schema.php deleted file mode 100644 index 6f7aba5..0000000 --- a/src/Command/Pf2eSchemaGenerator/Schema.php +++ /dev/null @@ -1,96 +0,0 @@ -items as $key => $item) { - if (!in_array($key, $keys)) { - $item->disableRequired(); - } - } - - foreach ($keys as $key) { - if (!array_key_exists($key, $this->items)) { - $this->items[$key] = new SchemaItem($key); - } - - $this->items[$key]->adapt($element[$key]); - } - } - - public static function resolveType(mixed $value): string - { - $type = gettype($value); - - if ($type === "array") { - if (empty($value)) { - return "empty[]"; - } - - if (array_is_list($value)) { - $elementType = ""; - foreach ($value as $element) { - $elementType = self::mergeTypes($elementType, self::resolveType($element)); - } - - if (str_contains("|", $elementType)) { - return "mixed[]"; - } - - return $elementType . "[]"; - } - - return "object"; - } - - return $type; - } - - public static function mergeTypes(string $typeList, string $newType): string { - if (empty($typeList)) { - return $newType; - } - - else if (str_contains("|", $typeList)) { - $types = explode("|", $typeList); - - if (!in_array($newType, $types, true)) { - $types[] = $newType; - } - - return implode("|", $types); - } - else if ($newType !== $typeList) { - return $typeList . "|" . $newType; - } - - return $typeList; - } - - public function __toString(): string - { - $rows = []; - - foreach ($this->items as $item) { - $rows = [...$rows, ...$item->strings()]; - } - - return implode(PHP_EOL, $rows); - } - -} \ No newline at end of file diff --git a/src/Command/Pf2eSchemaGenerator/SchemaItem.php b/src/Command/Pf2eSchemaGenerator/SchemaItem.php deleted file mode 100644 index a64473d..0000000 --- a/src/Command/Pf2eSchemaGenerator/SchemaItem.php +++ /dev/null @@ -1,72 +0,0 @@ -name = $name; - } - - public function disableRequired(): void - { - $this->required = false; - } - - public function adapt(mixed $value): void - { - $type = Schema::resolveType($value); - - $this->type = Schema::mergeTypes($this->type, $type); - - if (!!is_null($this->elements)) { - if (str_contains($this->type, "[]")) { - foreach ($value as $element) { - if (!in_array($element, $this->elements)) { - $this->elements[] = $element; - } - } - } - else if (!in_array($value, $this->elements)) { - $this->elements[] = $value; - } - - if (count($this->elements) > 20) { - $this->elements = null; - } - } - } - - public function strings(): array { - $name = $this->name; - - if (!$this->required) { - $name = "[{$name}]"; - } - - $rows = [ - sprintf("%s: %s", $name, $this->type), - ]; - - if (!empty($this->elements)) { - sort($this->elements); - $elements = []; - foreach ($this->elements as $element) { - $elements[] = is_string($element) ? "\"{$element}\"" : strval($element); - } - $rows[] = " Elements: [ " . implode(", ", $elements) . " ]"; - } - - return $rows; - } - -} \ No newline at end of file diff --git a/src/Command/Pf2eSchemaGenerator/SchemaType/AbstractSchemaType.php b/src/Command/Pf2eSchemaGenerator/SchemaType/AbstractSchemaType.php new file mode 100644 index 0000000..033b15f --- /dev/null +++ b/src/Command/Pf2eSchemaGenerator/SchemaType/AbstractSchemaType.php @@ -0,0 +1,101 @@ +is_nullable = true; + return $this; + } + + /** + * Check if type is compatible with current type. + * + * @param mixed $value Value to be checked + * @return bool True if value is compatible with current type + */ + abstract public function compatibleValue(mixed $value): bool; + + /** + * Adapt to new value. + * If value is compatible with current type, `$this` will be returned. + * If value is not compatible, new type will be created. + * + * @param mixed $value Value to be checked + * @return AbstractSchemaType Compatible type + */ + public function adapt(mixed $value): static|AbstractSchemaType + { + if ($this->compatibleValue($value)) { + return $this; + } + + return new MixedSchemaType($this); + } + + /** + * Add new possible value. + * + * @param mixed $value New value + */ + abstract public function addValue(mixed $value): void; + + /** + * String representation of type + * + * @return string + */ + abstract public function __toString(): string; + + /** + * Get short name of type + * @return string + */ + abstract public function getTypeName(): string; + + + public const string TAB = " "; + + public static int $elementLimit = 20; + + public static function createFromValue(mixed $value): AbstractSchemaType + { + $valueType = static::resolveType($value); + + return match ($valueType) { + "null" => new EmptySchemaType(), + "array" => new ArraySchemaType(), + "object" => new ObjectSchemaType(), + default => new StaticSchemaType($valueType), + }; + } + + public static function resolveType(mixed $value): string + { + $type = gettype($value); + + if ($type === "array") { + if (empty($value)) { + return "null"; + } + + if (array_is_list($value)) { + return "array"; + } + + return "object"; + } + + return $type; + } + + protected static function indent(string $string): string { + return str_replace(PHP_EOL, PHP_EOL . static::TAB, $string); + } +} \ No newline at end of file diff --git a/src/Command/Pf2eSchemaGenerator/SchemaType/ArraySchemaType.php b/src/Command/Pf2eSchemaGenerator/SchemaType/ArraySchemaType.php new file mode 100644 index 0000000..a196a36 --- /dev/null +++ b/src/Command/Pf2eSchemaGenerator/SchemaType/ArraySchemaType.php @@ -0,0 +1,121 @@ + + */ + private array $values = []; + + public function __construct() + { + $this->type = new EmptySchemaType(); + } + + /** + * @inheritDoc + */ + public function compatibleValue(mixed $value): bool + { + if ("none" === static::resolveType($value)) { + return true; + } + + if ("array" !== static::resolveType($value)) { + return false; + } + + foreach ($value as $v) { + if (!$this->type->compatibleValue($v)) { + return false; + } + } + + return true; + } + + /** + * @inheritDoc + */ + public function adapt(mixed $value): static|AbstractSchemaType + { + if (("null" !== static::resolveType($value)) && ($this->type instanceof EmptySchemaType)) { + $this->type = static::createFromValue($value[0]); + } + + if ($this->compatibleValue($value)) { + foreach ($value as $v) { + $this->type = $this->type->adapt($v); + } + + return $this; + } + + return new MixedSchemaType($this); + } + + /** + * @inheritDoc + */ + public function addValue(mixed $value): void + { + $this->min_count = min($this->min_count, count($value)); + $this->max_count = max($this->max_count, count($value)); + + foreach ($value as $element) { + $this->type->addValue($element); + } + } + + /** + * @inheritDoc + */ + public function __toString(): string + { + $rows = [$this->getTypeName()]; + + $rows[] = "Length: min={$this->min_count}, max={$this->max_count}"; + + $rows[] = "Value type: " . self::indent($this->type->__toString()); + + return implode(PHP_EOL.self::TAB, $rows); + } + + /** + * @inheritDoc + */ + public function getTypeName(): string + { + if ($this->type instanceof StaticSchemaType) { + return $this->type->getTypeName() . "[]"; + } + else { + return "array of " . $this->type->getTypeName(); + } + } +} \ No newline at end of file diff --git a/src/Command/Pf2eSchemaGenerator/SchemaType/EmptySchemaType.php b/src/Command/Pf2eSchemaGenerator/SchemaType/EmptySchemaType.php new file mode 100644 index 0000000..d438199 --- /dev/null +++ b/src/Command/Pf2eSchemaGenerator/SchemaType/EmptySchemaType.php @@ -0,0 +1,49 @@ +compatibleValue($value)) { + return $this; + } + + return AbstractSchemaType::createFromValue($value)->markNullable(); + } + + /** + * @inheritDoc + */ + public function addValue(mixed $value): void + { + } + + /** + * @inheritDoc + */ + public function __toString(): string + { + return "empty[] -> null"; + } + + /** + * @inheritDoc + */ + public function getTypeName(): string + { + return "null"; + } +} \ No newline at end of file diff --git a/src/Command/Pf2eSchemaGenerator/SchemaType/MixedSchemaType.php b/src/Command/Pf2eSchemaGenerator/SchemaType/MixedSchemaType.php new file mode 100644 index 0000000..f77e5ad --- /dev/null +++ b/src/Command/Pf2eSchemaGenerator/SchemaType/MixedSchemaType.php @@ -0,0 +1,105 @@ +types = [$baseType]; + } + + /** + * Add new type + * + * @param mixed $value + * @return \App\Command\Pf2eSchemaGenerator\SchemaType\AbstractSchemaType + */ + public function addType(mixed $value): AbstractSchemaType + { + $newType = AbstractSchemaType::createFromValue($value); + $this->types[] = $newType; + return $newType; + } + + /** + * Get compatible type + * @param mixed $value + * @return null|AbstractSchemaType + */ + public function getCompatibleType(mixed $value): ?AbstractSchemaType + { + dd($this->types, $value); + + foreach ($this->types as $type) { + if ($type->compatibleValue($value)) { + return $type; + } + } + + return null; + } + + /** + * @inheritDoc + */ + public function compatibleValue(mixed $value): bool + { + return true; + } + + /** + * @inheritDoc + */ + public function adapt(mixed $value): self|AbstractSchemaType + { + $type = $this->getCompatibleType($value); + + if ($type === null) { + $this->addType($value); + } + + return $this; + } + + /** + * @inheritDoc + */ + public function addValue(mixed $value): void + { + $this->getCompatibleType($value)?->addValue($value); + } + + /** + * @inheritDoc + */ + public function __toString(): string + { + $result = ["mixed"]; + + foreach ($this->types as $type) { + $result[] = static::indent($type->__toString()); + } + + return implode(PHP_EOL.static::TAB, $result); + } + + /** + * @inheritDoc + */ + public function getTypeName(): string + { + $parts = []; + foreach ($this->types as $type) { + $parts[] = $type->getTypeName(); + } + return implode("|", $parts); + } +} \ No newline at end of file diff --git a/src/Command/Pf2eSchemaGenerator/SchemaType/ObjectSchemaType.php b/src/Command/Pf2eSchemaGenerator/SchemaType/ObjectSchemaType.php new file mode 100644 index 0000000..b0ee5ec --- /dev/null +++ b/src/Command/Pf2eSchemaGenerator/SchemaType/ObjectSchemaType.php @@ -0,0 +1,101 @@ + + */ + public array $fields = []; + + /** + * @inheritDoc + */ + public function compatibleValue(mixed $value): bool + { + return (static::resolveType($value) === "object") || (static::resolveType($value) === "null"); + } + + /** + * @inheritDoc + */ + public function adapt(mixed $value): static|AbstractSchemaType + { + if (!$this->compatibleValue($value)) { + return new MixedSchemaType($this); + } + + foreach ($this->fields as $key => $field) { + if (!array_key_exists($key, $value)) { + $field->markNullable(); + } + } + + foreach ($value as $key => $v) { + if (!array_key_exists($key, $this->fields)) { + $this->fields[$key] = AbstractSchemaType::createFromValue($v); + + if (!$this->fresh) { + $this->fields[$key]->markNullable(); + } + } + + $this->fields[$key] = $this->fields[$key]->adapt($v); + } + + $this->fresh = false; + return $this; + } + + /** + * @inheritDoc + */ + public function addValue(mixed $value): void + { + if (empty($value)) { + $this->markNullable(); + return; + } + + foreach ($this->fields as $key => $field) { + if (!array_key_exists($key, $value)) { + $field->markNullable(); + } + } + + foreach ($value as $key => $v) { + $this->fields[$key]->addValue($v); + } + } + + /** + * @inheritDoc + */ + public function __toString(): string + { + $rows = [ "object" ]; + + foreach ($this->fields as $key => $field) { + $rows[] = sprintf("%s%s%s: %s", + $field->is_nullable ? "[" : "", + $key, + $field->is_nullable ? "]" : "", + static::indent($field->__toString()), + ); + } + + return implode(PHP_EOL.static::TAB, $rows); + } + + /** + * @inheritDoc + */ + public function getTypeName(): string + { + return "object"; + } + +} \ No newline at end of file diff --git a/src/Command/Pf2eSchemaGenerator/SchemaType/StaticSchemaType.php b/src/Command/Pf2eSchemaGenerator/SchemaType/StaticSchemaType.php new file mode 100644 index 0000000..68d3aa7 --- /dev/null +++ b/src/Command/Pf2eSchemaGenerator/SchemaType/StaticSchemaType.php @@ -0,0 +1,86 @@ + + */ + private array $values = []; + + public function __construct(string $type) + { + $this->type = $type; + } + + /** + * Check if type is compatible with current type. + * + * @param mixed $value Value to be checked + * @return bool True if value is compatible with current type + */ + public function compatibleValue(mixed $value): bool + { + $valueType = static::resolveType($value); + + return $valueType === $this->type; + } + + /** + * Add new possible value. + * + * @param mixed $value New value + */ + public function addValue(mixed $value): void + { + if (!array_key_exists($value, $this->values)) { + $this->values[$value] = 0; + } + + $this->values[$value]++; + } + + /** + * String representation of type + * + * @return string + */ + public function __toString(): string + { + $result = $this->type; + + $countValues = count($this->values); + + $result .= " (different values: {$countValues})"; + + if (($countValues > 0) && ($countValues < AbstractSchemaType::$elementLimit)) { + $items = []; + + foreach ($this->values as $value => $count) { + $items[] = sprintf('%s (x%d)', is_string($value) ? "'{$value}'" : $value, $count); + } + + $result .= PHP_EOL . static::TAB . " Possible values: [" . implode(', ', $items) . "]"; + } + + return $result; + } + + /** + * @inheritDoc + */ + public function getTypeName(): string + { + return $this->type; + } +} \ No newline at end of file diff --git a/src/Command/Pf2eTypesCommand.php b/src/Command/Pf2eTypesCommand.php index d3381fc..b60d8c5 100644 --- a/src/Command/Pf2eTypesCommand.php +++ b/src/Command/Pf2eTypesCommand.php @@ -67,6 +67,8 @@ class Pf2eTypesCommand extends Command } } + ksort($types); + if ($full) { $table = []; diff --git a/src/Entity/Item.php b/src/Entity/Item.php index 37bf23b..b28a693 100644 --- a/src/Entity/Item.php +++ b/src/Entity/Item.php @@ -17,10 +17,8 @@ class Item #[ORM\Column] private ?int $id = null; - #[ORM\Column(type: UuidType::NAME, unique: true)] - #[ORM\GeneratedValue(strategy: 'CUSTOM')] - #[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')] - private ?Uuid $uuid = null; + #[ORM\Column(length: 255, unique: true)] + private ?string $aonprdId = null; #[ORM\Column(length: 255)] private ?string $name = null; @@ -44,9 +42,16 @@ class Item return $this->id; } - public function getUuid(): ?Uuid + public function getAonprdId(): ?string { - return $this->uuid; + return $this->aonprdId; + } + + public function setAonprdId(?string $aonprdId): static + { + $this->aonprdId = $aonprdId; + + return $this; } public function getName(): ?string diff --git a/src/Entity/ItemLocalization.php b/src/Entity/ItemLocalization.php index 512bb95..e70020c 100644 --- a/src/Entity/ItemLocalization.php +++ b/src/Entity/ItemLocalization.php @@ -17,6 +17,12 @@ class ItemLocalization #[ORM\ManyToOne(inversedBy: 'itemLocalizations')] private ?Item $item = null; + #[ORM\Column(enumType: ItemTypeEnum::class)] + private ?ItemTypeEnum $type = null; + + #[ORM\Column(length: 10)] + private ?string $locale = null; + #[ORM\Column(length: 255)] private ?string $title = null; @@ -43,6 +49,30 @@ class ItemLocalization return $this; } + public function getType(): ?ItemTypeEnum + { + return $this->type; + } + + public function setType(ItemTypeEnum $type): static + { + $this->type = $type; + + return $this; + } + + public function getLocale(): ?string + { + return $this->locale; + } + + public function setLocale(?string $locale): static + { + $this->locale = $locale; + + return $this; + } + public function getTitle(): ?string { return $this->title; diff --git a/src/Entity/Pf2e/Source.php b/src/Entity/Pf2e/Source.php new file mode 100644 index 0000000..43a78cf --- /dev/null +++ b/src/Entity/Pf2e/Source.php @@ -0,0 +1,91 @@ +id; + } + + public function setId(string $value): static + { + $this->id = $value; + + return $this; + } + + public function getItem(): ?Item + { + return $this->item; + } + + public function setItem(Item $item): static + { + $this->item = $item; + + return $this; + } + + public function getRarity(): ?Rarity + { + return $this->rarity; + } + + public function setRarity(Rarity $rarity): static + { + $this->rarity = $rarity; + + return $this; + } + + public function getSourceCategory(): ?string + { + return $this->source_category; + } + + public function setSourceCategory(?string $source_category): static + { + $this->source_category = $source_category; + + return $this; + } + + public function getSourceGroup(): ?string + { + return $this->source_group; + } + + public function setSourceGroup(?string $source_group): static + { + $this->source_group = $source_group; + + return $this; + } + +} diff --git a/src/Entity/Pf2e/Tpait.php b/src/Entity/Pf2e/Tpait.php index 23ef30e..9262678 100644 --- a/src/Entity/Pf2e/Tpait.php +++ b/src/Entity/Pf2e/Tpait.php @@ -11,9 +11,8 @@ use Doctrine\ORM\Mapping as ORM; class Tpait { #[ORM\Id] - #[ORM\GeneratedValue] #[ORM\Column] - private ?int $id = null; + private ?string $id = null; #[ORM\Column(enumType: Rarity::class)] private ?Rarity $rarity = null; @@ -27,6 +26,13 @@ class Tpait return $this->id; } + public function setId(string $value): static + { + $this->id = $value; + + return $this; + } + public function getRarity(): ?Rarity { return $this->rarity; diff --git a/src/Repository/Pf2e/SourceRepository.php b/src/Repository/Pf2e/SourceRepository.php new file mode 100644 index 0000000..a7e7bee --- /dev/null +++ b/src/Repository/Pf2e/SourceRepository.php @@ -0,0 +1,43 @@ + + */ +class SourceRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Source::class); + } + +// /** +// * @return Source[] Returns an array of Source objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('s') +// ->andWhere('s.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('s.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?Source +// { +// return $this->createQueryBuilder('s') +// ->andWhere('s.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/src/Service/AonprdConnectionService.php b/src/Service/AonprdConnectionService.php index 7c117e8..066a9d5 100644 --- a/src/Service/AonprdConnectionService.php +++ b/src/Service/AonprdConnectionService.php @@ -7,7 +7,20 @@ use Symfony\Contracts\HttpClient\ResponseInterface; class AonprdConnectionService { - public const DATA_LINK = "https://elasticsearch.aonprd.com"; + public const string DATA_LINK = "https://elasticsearch.aonprd.com"; + + public static function testItemTypeFilter(string $type, array $types): bool { + return empty($types) || in_array($type, $types); + } + + public static function splitItemFullId(string $fullId): array { + preg_match("#^([a-z\\-]+?)-(\\d.*)$#", $fullId, $matches); + + return [ + "type" => $matches[1], + "id"=> $matches[2], + ]; + } private ?int $revision = null; @@ -36,14 +49,64 @@ class AonprdConnectionService return $this->revision; } + public function setRevision(?int $revision): void + { + $this->revision = $revision; + } + public function loadCurrentIndex(int $revision = null): array { if ($revision === null) { $revision = $this->getRevision(); } - $data = $this->load("/json-data/aon{$revision}-index.json"); - - return $data; + return $this->load("/json-data/aon{$revision}-index.json"); } + + public function loadIndexPage(string $page, array $types = []): array + { + $data = $this->load("/json-data/{$page}.json"); + + if (empty($types)) { + return $data; + } + + $result = []; + + foreach ($data as $element) { + if (AonprdConnectionService::testItemTypeFilter($element["category"], $types)) { + $result[] = $element; + } + } + + return $result; + } + + public function getIndexPageListByType(array $types = []): array + { + $typePages = []; + + $index = $this->loadCurrentIndex($this->revision); + + foreach ($index as $page => $elements) { + foreach ($elements as $element) { + $type = self::splitItemFullId($element)["type"]; + + if (!AonprdConnectionService::testItemTypeFilter($type, $types)) { + continue; + } + + if (!isset($typePages[$type])) { + $typePages[$type] = []; + } + + if (!in_array($page, $typePages[$type])) { + $typePages[$type][] = $page; + } + } + } + + return $typePages; + } + } \ No newline at end of file