Work, work, work~~~

This commit is contained in:
2024-12-11 09:53:41 +04:00
parent c7c6ee27a9
commit 8ddb44f6b1
26 changed files with 1416 additions and 254 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
"minimum-stability": "stable", "minimum-stability": "stable",
"prefer-stable": true, "prefer-stable": true,
"require": { "require": {
"php": ">=8.2", "php": ">=8.3",
"ext-ctype": "*", "ext-ctype": "*",
"ext-iconv": "*", "ext-iconv": "*",
"doctrine/dbal": "^3", "doctrine/dbal": "^3",
+42
View File
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241210083555 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->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\')');
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241210084313 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->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)');
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241210085754 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->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');
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241210102535 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->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');
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241210103436 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->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\')');
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241210103547 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->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');
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241210103818 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->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');
}
}
+70 -63
View File
@@ -2,11 +2,12 @@
namespace App\Command; namespace App\Command;
use App\Command\Pf2eSchemaGenerator\Schema; use App\Command\Pf2eSchemaGenerator\SchemaType\AbstractSchemaType;
use App\Command\Pf2eSchemaGenerator\SchemaItem; use App\Command\Pf2eSchemaGenerator\SchemaType\ObjectSchemaType;
use App\Service\AonprdConnectionService; use App\Service\AonprdConnectionService;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
@@ -14,7 +15,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand( #[AsCommand(
name: 'pf2e:generate', name: 'pf2e:generate',
description: 'Add a short description for your command', description: 'Get schemas of data',
)] )]
class Pf2eGenerateCommand extends Command 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("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('type', null, InputOption::VALUE_REQUIRED, 'Specify types to import schema')
->addOption('all', null, InputOption::VALUE_NONE, 'Generate schema for all types') ->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[] * @var string[]
*/ */
private array $types; private array $types;
private array $typePages;
/** /**
* @var array<string, Schema> * @var array<string, ObjectSchemaType>
*/ */
private array $schemas = []; private array $schemas = [];
private OutputInterface $output;
protected function execute(InputInterface $input, OutputInterface $output): int protected function execute(InputInterface $input, OutputInterface $output): int
{ {
$this->output = $output;
$io = new SymfonyStyle($input, $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')) { if ($input->getOption('all')) {
$this->types = []; $this->types = array_keys($this->typePages);
}
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'));
} }
$typePages = $this->loadTypePages(); $this->progressBar = new ProgressBar($output);
$this->processPages();
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);
}
foreach ($this->schemas as $type => $schema) { foreach ($this->schemas as $type => $schema) {
$io->write("<options=bold,underscore>{$type}</>"); $io->write("<options=bold,underscore>{$type}</>");
@@ -90,45 +82,60 @@ class Pf2eGenerateCommand extends Command
return Command::SUCCESS; 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) { foreach ($elements as $element) {
preg_match("#^([a-z\\-]+?)-(\\d.*)$#", $element, $matches); if (array_key_exists("remaster_id", $element)) {
$type = $matches[1]; $progressBar->advance();
if (!empty($targetType) && !in_array($type, $this->types)) {
continue; continue;
} }
if (!isset($typePages[$type])) { $type = AonprdConnectionService::splitItemFullId($element["id"])["type"];
$typePages[$type] = []; $this->schemas[$type] = $this->schemas[$type]->adapt($element);
$this->schemas[$type]->addValue($element);
$progressBar->advance();
} }
if (!in_array($page, $typePages[$type])) { $progressBar->finish();
$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;
}
$this->schemas[$type]->process($element);
}
} }
} }
+192 -10
View File
@@ -2,6 +2,13 @@
namespace App\Command; 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\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument; 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\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\PropertyAccess\PropertyAccess;
#[AsCommand( #[AsCommand(
name: 'pf2e:import', name: 'pf2e:import',
description: 'Add a short description for your command', description: 'Import data',
)] )]
class Pf2eImportCommand extends Command class Pf2eImportCommand extends Command
{ {
public function __construct() public function __construct(
private AonprdConnectionService $connectionService,
private EntityManagerInterface $em
)
{ {
parent::__construct(); parent::__construct();
} }
@@ -30,28 +41,199 @@ class Pf2eImportCommand extends Command
; ;
} }
private int $revision;
/** /**
* @var string[] * @var string[]
*/ */
private array $types; private array $types;
private array $typePages;
private array $convertSchemas;
protected function execute(InputInterface $input, OutputInterface $output): int protected function execute(InputInterface $input, OutputInterface $output): int
{ {
$io = new SymfonyStyle($input, $output); $io = new SymfonyStyle($input, $output);
$arg1 = $input->getArgument('arg1');
if ($arg1) { if (!is_null($input->getOption('revision'))) {
$io->note(sprintf('You passed an argument: %s', $arg1)); $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; 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));
}
} }
@@ -0,0 +1,74 @@
<?php
// "entity_field" => "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"
]
],
*/
@@ -1,96 +0,0 @@
<?php
namespace App\Command\Pf2eSchemaGenerator;
class Schema
{
/**
* @var SchemaItem[]
*/
private array $items = [];
public function __construct()
{
}
public function process(mixed $element): void
{
$keys = array_keys($element);
foreach ($this->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);
}
}
@@ -1,72 +0,0 @@
<?php
namespace App\Command\Pf2eSchemaGenerator;
class SchemaItem
{
public string $name;
public string $type = "";
public bool $required = true;
public ?array $elements = [];
public function __construct($name)
{
$this->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;
}
}
@@ -0,0 +1,101 @@
<?php
namespace App\Command\Pf2eSchemaGenerator\SchemaType;
use SebastianBergmann\CodeCoverage\Report\PHP;
abstract class AbstractSchemaType
{
public bool $is_nullable = false;
public function markNullable(): static
{
$this->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);
}
}
@@ -0,0 +1,121 @@
<?php
namespace App\Command\Pf2eSchemaGenerator\SchemaType;
class ArraySchemaType extends AbstractSchemaType
{
/**
* Minimal array length
*
* @var int
*/
public int $min_count = PHP_INT_MAX;
/**
* Maximum array length
*
* @var int
*/
public int $max_count = PHP_INT_MIN;
/**
* Type of value
*
* @var AbstractSchemaType
*/
private AbstractSchemaType $type;
/**
* Calculation of values
*
* @var array<mixed, int>
*/
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();
}
}
}
@@ -0,0 +1,49 @@
<?php
namespace App\Command\Pf2eSchemaGenerator\SchemaType;
class EmptySchemaType extends AbstractSchemaType
{
/**
* @inheritDoc
*/
public function compatibleValue(mixed $value): bool
{
return static::resolveType($value) === "null";
}
/**
* @inheritDoc
*/
public function adapt(mixed $value): self|AbstractSchemaType
{
if ($this->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";
}
}
@@ -0,0 +1,105 @@
<?php
namespace App\Command\Pf2eSchemaGenerator\SchemaType;
class MixedSchemaType extends AbstractSchemaType
{
/**
* List of encountered values
*
* @var AbstractSchemaType[]
*/
public array $types;
public function __construct(AbstractSchemaType $baseType)
{
$this->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);
}
}
@@ -0,0 +1,101 @@
<?php
namespace App\Command\Pf2eSchemaGenerator\SchemaType;
class ObjectSchemaType extends AbstractSchemaType
{
private bool $fresh = true;
/**
* @var array<string, AbstractSchemaType>
*/
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<options=bold>%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";
}
}
@@ -0,0 +1,86 @@
<?php
namespace App\Command\Pf2eSchemaGenerator\SchemaType;
class StaticSchemaType extends AbstractSchemaType
{
/**
* Type of value
*
* @var string
*/
private string $type;
/**
* Calculation of values
*
* @var array<mixed, int>
*/
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;
}
}
+2
View File
@@ -67,6 +67,8 @@ class Pf2eTypesCommand extends Command
} }
} }
ksort($types);
if ($full) { if ($full) {
$table = []; $table = [];
+11 -6
View File
@@ -17,10 +17,8 @@ class Item
#[ORM\Column] #[ORM\Column]
private ?int $id = null; private ?int $id = null;
#[ORM\Column(type: UuidType::NAME, unique: true)] #[ORM\Column(length: 255, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')] private ?string $aonprdId = null;
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
private ?Uuid $uuid = null;
#[ORM\Column(length: 255)] #[ORM\Column(length: 255)]
private ?string $name = null; private ?string $name = null;
@@ -44,9 +42,16 @@ class Item
return $this->id; 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 public function getName(): ?string
+30
View File
@@ -17,6 +17,12 @@ class ItemLocalization
#[ORM\ManyToOne(inversedBy: 'itemLocalizations')] #[ORM\ManyToOne(inversedBy: 'itemLocalizations')]
private ?Item $item = null; 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)] #[ORM\Column(length: 255)]
private ?string $title = null; private ?string $title = null;
@@ -43,6 +49,30 @@ class ItemLocalization
return $this; 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 public function getTitle(): ?string
{ {
return $this->title; return $this->title;
+91
View File
@@ -0,0 +1,91 @@
<?php
namespace App\Entity\Pf2e;
use App\Entity\Item;
use App\Entity\Rarity;
use App\Repository\Pf2e\SourceRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: SourceRepository::class)]
class Source
{
#[ORM\Id]
#[ORM\Column]
private ?string $id = null;
#[ORM\OneToOne(cascade: ['persist', 'remove'])]
#[ORM\JoinColumn(nullable: false)]
private ?Item $item = null;
#[ORM\Column(enumType: Rarity::class)]
private ?Rarity $rarity = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $source_category = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $source_group = null;
public function getId(): ?string
{
return $this->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;
}
}
+8 -2
View File
@@ -11,9 +11,8 @@ use Doctrine\ORM\Mapping as ORM;
class Tpait class Tpait
{ {
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column] #[ORM\Column]
private ?int $id = null; private ?string $id = null;
#[ORM\Column(enumType: Rarity::class)] #[ORM\Column(enumType: Rarity::class)]
private ?Rarity $rarity = null; private ?Rarity $rarity = null;
@@ -27,6 +26,13 @@ class Tpait
return $this->id; return $this->id;
} }
public function setId(string $value): static
{
$this->id = $value;
return $this;
}
public function getRarity(): ?Rarity public function getRarity(): ?Rarity
{ {
return $this->rarity; return $this->rarity;
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace App\Repository\Pf2e;
use App\Entity\Pf2e\Source;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Source>
*/
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()
// ;
// }
}
+65 -2
View File
@@ -7,7 +7,20 @@ use Symfony\Contracts\HttpClient\ResponseInterface;
class AonprdConnectionService 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; private ?int $revision = null;
@@ -36,14 +49,64 @@ class AonprdConnectionService
return $this->revision; return $this->revision;
} }
public function setRevision(?int $revision): void
{
$this->revision = $revision;
}
public function loadCurrentIndex(int $revision = null): array public function loadCurrentIndex(int $revision = null): array
{ {
if ($revision === null) { if ($revision === null) {
$revision = $this->getRevision(); $revision = $this->getRevision();
} }
$data = $this->load("/json-data/aon{$revision}-index.json"); 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; 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;
}
} }