diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 384d5c6..c87aec8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - php: [ '7.4', '8.0' ] + php: [ '7.4', '8.0', '8.1' ] os: [ ubuntu-latest ] stability: [ prefer-lowest, prefer-stable ] @@ -43,25 +43,19 @@ jobs: id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Restore Composer Cache - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-${{ matrix.php }}-composer- - - name: Install Dependencies - uses: nick-invision/retry@v1 - with: - timeout_minutes: 5 - max_attempts: 5 - command: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress - # Upgrade mockery on PHP 8 - - name: Install Dependencies - if: ${{ matrix.php == '8.0' }} - uses: nick-invision/retry@v1 - with: - timeout_minutes: 5 - max_attempts: 5 - command: composer update mockery/mockery --prefer-dist --no-interaction --no-progress + + - name: Install dependencies with composer + if: matrix.php-version != '8.1' + run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi + + - name: Install dependencies with composer php 8.1 + if: matrix.php-version == '8.1' + run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi --ignore-platform-reqs # Execution - name: Execute Tests @@ -71,4 +65,4 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} file: ./coverage.xml - fail_ci_if_error: false \ No newline at end of file + fail_ci_if_error: false diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 0000000..9e32920 --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,52 @@ +name: static analysis + +on: + pull_request: + push: + schedule: + - cron: '0 0 * * *' + +jobs: + mutation: + name: PHP ${{ matrix.php }}-${{ matrix.os }} + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + + php: + - "8.0" + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php }}" + tools: composer:v2, cs2pr + coverage: none + + - name: Determine composer cache directory + run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV + + - name: Cache dependencies installed with composer + uses: actions/cache@v2 + with: + path: ${{ env.COMPOSER_CACHE_DIR }} + key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: | + php${{ matrix.php }}-composer- + + - name: Update composer + run: composer self-update + + - name: Install dependencies with composer + run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi + + - name: Static analysis + run: vendor/bin/psalm --shepherd --stats --output-format=checkstyle | cs2pr --graceful-warnings --colorize diff --git a/.styleci.yml b/.styleci.yml index a6cd886..4fe219f 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -1,7 +1,11 @@ preset: psr12 risky: true -version: 8 +version: 7 + +finder: + not-name: + - "*.stub.txt" enabled: - alpha_ordered_traits @@ -12,7 +16,6 @@ enabled: - combine_nested_dirname - declare_strict_types - dir_constant - - final_static_access - fully_qualified_strict_types - function_to_constant - hash_to_slash_comment diff --git a/LICENSE b/LICENSE index 068b0e7..586cdb0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2019 Spiral Scout +Copyright (c) 2021 Spiral Scout Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 7a50236..045213d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,16 @@ -# CycleORM Schema renderer +# Cycle ORM Schema renderer -This package may be used to render schema roles in a terminal or generate php representation for CycleORM schema. +This package may be used to render Cycle ORM Schema in a terminal or generate php representation. + +## Installation + +The preferred way to install this package is through [Composer](https://getcomposer.org/download/): + +```bash +composer require cycle/schema-renderer +``` + +## Example ### Convert schema to array @@ -11,15 +21,17 @@ $converter = new \Cycle\Schema\Renderer\SchemaToArrayConverter(); $schemaArray = $converter->convert($schema); ``` -By default, SchemaToArrayConverter converts only common properties from `Cycle\ORM\SchemaInterface`. +If passed `SchemaInterface` doesn't contain `toArray()` method then the `SchemaToArrayConverter` will convert +only common properties from `Cycle\ORM\SchemaInterface`. Null values will be skipped also. + +In this case Iif you want to use custom properties you can pass them to the constructor -But if you want to use custom properties you can pass them to the constructor ```php $converter = new \Cycle\Schema\Renderer\SchemaToArrayConverter(); $schemaArray = $converter->convert($schema, [ - 'my_custom_property', - SchemaInterface::SOURCE, + 42, + CustomClass::CUSTOM_PROPERTY, ... ]); ``` @@ -27,29 +39,23 @@ $schemaArray = $converter->convert($schema, [ ### Render schema to a terminal ```php -use Cycle\Schema\Renderer\ConsoleRenderer\DefaultSchemaOutputRenderer; -use Cycle\Schema\Renderer\ConsoleRenderer\Formatters\StyledFormatter; -use Cycle\Schema\Renderer\ConsoleRenderer\Formatters\PlainFormatter; +use Cycle\Schema\Renderer\OutputSchemaRenderer; $output = new \Symfony\Component\Console\Output\ConsoleOutput(); -$formatter = new StyledFormatter(); // Colorized output -// or -$formatter = new PlainFormatter(); // Plain output without colors +$renderer = new OutputSchemaRenderer(colorize: true); -$renderer = new DefaultSchemaOutputRenderer($schemaArray, $formatter); - -foreach ($renderer as $role => $rows) { - $output->writeln($rows); -} +$output->write($renderer->render($schemaArray)); ``` -By default, DefaultSchemaOutputRenderer renders only common properties. -If you want to extend default CycleORM schema you can create custom renderers and add them to the Output renderer. +By default, `DefaultSchemaOutputRenderer` renders in template only common properties and relations. +Custom properties will be rendered as is in separated block. +If you want to extend default rendering template you can create custom renderers and add them to the Output renderer. ```php use Cycle\Schema\Renderer\ConsoleRenderer\Renderer; use Cycle\Schema\Renderer\ConsoleRenderer\Formatter; +use Cycle\Schema\Renderer\OutputSchemaRenderer; class CustomPropertyRenderer implements Renderer { @@ -65,60 +71,32 @@ class CustomPropertyRenderer implements Renderer { } } -$renderer = new DefaultSchemaOutputRenderer($schemaArray, $formatter); +$renderer = new OutputSchemaRenderer(); $renderer->addRenderer( new CustomPropertyRenderer(), new PropertyRenderer('my_custom_property', 'My super property') ); -foreach ($renderer as $role => $rows) { - $output->writeln($rows); -} +$output->write($renderer->render($schemaArray)) ``` ### Store schema in a PHP file ```php -use Cycle\Schema\Renderer\SchemaToPhpFileRenderer; -use Cycle\Schema\Renderer\PhpFileRenderer\DefaultSchemaGenerator; +use Cycle\Schema\Renderer\PhpSchemaRenderer; $path = __DIR__. '/schema.php' -$generator = new DefaultSchemaGenerator(); - -$renderer = new SchemaToPhpFileRenderer( - $orm->getSchema(), $generator -); +$renderer = new PhpSchemaRenderer(); -file_put_contents($path, $renderer->render()); +file_put_contents($path, $renderer->render($schemaArray)); ``` -By default, DefaultSchemaGenerator generates only common properties. -If you want to extend default CycleORM schema you can create custom generators and add them to the Output php file renderer. +The Renderer generates valid PHP code, in which constants from Cycle ORM classes are substituted +for better readability. +## License: -```php -use Cycle\Schema\Renderer\SchemaToPhpFileRenderer; -use Cycle\Schema\Renderer\PhpFileRenderer\DefaultSchemaGenerator; -use Cycle\Schema\Renderer\PhpFileRenderer\Generator; -use Cycle\Schema\Renderer\PhpFileRenderer\VarExporter; - -class CustomPropertyGenerator implements Generator { - - public function generate(array $schema, string $role): VarExporter - { - $key = 'my_custom_property'; - - return new VarExporter($key, $schema[$key] ?? null); - } -} - -$generator = new DefaultSchemaGenerator([ - 'my_custom_property' => new CustomPropertyGenerator() -]); - -$renderer = new SchemaToPhpFileRenderer( - $orm->getSchema(), $generator -); -``` +The MIT License (MIT). Please see [`LICENSE`](./LICENSE) for more information. +Maintained by [Spiral Scout](https://spiralscout.com). diff --git a/composer.json b/composer.json index 40aaa07..cef7e63 100644 --- a/composer.json +++ b/composer.json @@ -1,25 +1,37 @@ { - "name": "cycle/schema-renderer", - "type": "library", - "license": "MIT", - "description": "Utils for Cycle Schema rendering", - "require": { - "php": ">=7.4", - "cycle/orm": "^1.5|2.0.x-dev" - }, - "require-dev": { - "phpunit/phpunit": "^9.5", - "spiral/code-style": "^1.0", - "vimeo/psalm": "^4.9" - }, - "autoload": { - "psr-4": { - "Cycle\\Schema\\Renderer\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "Cycle\\Schema\\Renderer\\Tests\\": "tests/Schema/Renderer/" - } - } + "name": "cycle/schema-renderer", + "type": "library", + "license": "MIT", + "description": "Utils for Cycle ORM Schema rendering", + "require": { + "php": ">=7.4", + "cycle/orm": "1.2 - 2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "spiral/code-style": "^1.0", + "vimeo/psalm": "^4.10" + }, + "autoload": { + "psr-4": { + "Cycle\\Schema\\Renderer\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Cycle\\Schema\\Renderer\\Tests\\": "tests/Schema/Renderer/" + } + }, + "scripts": { + "test": [ + "phpcs --standard=phpcs.xml", + "psalm --no-cache", + "phpunit" + ] + }, + "config": { + "sort-packages": true + }, + "minimum-stability": "dev", + "prefer-stable": true } diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..a81e173 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ./src + diff --git a/psalm.xml b/psalm.xml index 30258a7..4e9226b 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,6 +1,6 @@ 'Role', - SchemaInterface::ENTITY => 'Entity', - SchemaInterface::MAPPER => 'Mapper', - SchemaInterface::SCOPE => 'Constrain', - SchemaInterface::REPOSITORY => 'Repository', - ]; - - public function __construct(array $schema, Formatter $formatter) - { - parent::__construct($schema, $formatter); - - $this->addRenderer(...[ - new TitleRenderer(), - - // Default properties renderer (Without extra logic) - ...array_map(static function ($property, string $title) { - return new PropertyRenderer($property, $title); - }, array_keys(static::DEFAULT_PROPERTY_LIST), static::DEFAULT_PROPERTY_LIST), - - new PrimaryKeysRenderer(), - new ColumnsRenderer(), - new RelationsRenderer(), - new CustomPropertiesRenderer(), - ]); - } -} diff --git a/src/ConsoleRenderer/Formatter.php b/src/ConsoleRenderer/Formatter.php index a6934d5..0fdf6da 100644 --- a/src/ConsoleRenderer/Formatter.php +++ b/src/ConsoleRenderer/Formatter.php @@ -6,13 +6,21 @@ interface Formatter { - const TITLE_LENGTH = 13; + public const TITLE_LENGTH = 13; + public const LINE_SEPARATOR = "\n"; + public const ROLE_BLOCK_SEPARATOR = "\n\n"; public function title(string $title): string; + public function property(string $string): string; + public function column(string $string): string; + public function info(string $string): string; + public function typecast(string $string): string; + public function entity(string $string): string; + public function error(string $string): string; } diff --git a/src/ConsoleRenderer/Formatters/PlainFormatter.php b/src/ConsoleRenderer/Formatter/PlainFormatter.php similarity index 92% rename from src/ConsoleRenderer/Formatters/PlainFormatter.php rename to src/ConsoleRenderer/Formatter/PlainFormatter.php index e5d9828..bcd0eb0 100644 --- a/src/ConsoleRenderer/Formatters/PlainFormatter.php +++ b/src/ConsoleRenderer/Formatter/PlainFormatter.php @@ -2,13 +2,12 @@ declare(strict_types=1); -namespace Cycle\Schema\Renderer\ConsoleRenderer\Formatters; +namespace Cycle\Schema\Renderer\ConsoleRenderer\Formatter; use Cycle\Schema\Renderer\ConsoleRenderer\Formatter; final class PlainFormatter implements Formatter { - public function title(string $title): string { return str_pad($title, self::TITLE_LENGTH, ' ', STR_PAD_LEFT); diff --git a/src/ConsoleRenderer/Formatters/StyledFormatter.php b/src/ConsoleRenderer/Formatter/StyledFormatter.php similarity index 90% rename from src/ConsoleRenderer/Formatters/StyledFormatter.php rename to src/ConsoleRenderer/Formatter/StyledFormatter.php index 81ad9ae..6c4847b 100644 --- a/src/ConsoleRenderer/Formatters/StyledFormatter.php +++ b/src/ConsoleRenderer/Formatter/StyledFormatter.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Cycle\Schema\Renderer\ConsoleRenderer\Formatters; +namespace Cycle\Schema\Renderer\ConsoleRenderer\Formatter; use Cycle\Schema\Renderer\ConsoleRenderer\Formatter; @@ -59,7 +59,8 @@ public function error(string $string): string private function colorize(string $string): string { return str_replace( - array_keys(self::COLORS_MAP), array_values(self::COLORS_MAP), + array_keys(self::COLORS_MAP), + array_values(self::COLORS_MAP), $string ); } diff --git a/src/ConsoleRenderer/OutputRenderer.php b/src/ConsoleRenderer/OutputRenderer.php index 24dfc57..1eb906a 100644 --- a/src/ConsoleRenderer/OutputRenderer.php +++ b/src/ConsoleRenderer/OutputRenderer.php @@ -4,42 +4,37 @@ namespace Cycle\Schema\Renderer\ConsoleRenderer; -use IteratorAggregate; -use Traversable; +use Cycle\Schema\Renderer\SchemaRenderer; -class OutputRenderer implements IteratorAggregate +class OutputRenderer implements SchemaRenderer { /** @var Renderer[] */ private array $renderers = []; - private array $schema; private Formatter $formatter; - public function __construct(array $schema, Formatter $formatter, array $renderers = []) + public function __construct(Formatter $formatter, array $renderers = []) { - $this->addRenderer(...$renderers); - $this->formatter = $formatter; - $this->schema = $schema; + $this->addRenderer(...$renderers); } - public function addRenderer(Renderer ...$renderers): void + final public function addRenderer(Renderer ...$renderers): void { foreach ($renderers as $renderer) { $this->renderers[] = $renderer; } } - /** - * @return Traversable - */ - public function getIterator(): Traversable + final public function render(array $schema): string { - foreach ($this->schema as $role => $schema) { - yield $role => $this->renderSchema($schema, $role); + $result = ''; + foreach ($schema as $role => $roleSchema) { + $result .= $this->renderRole($roleSchema, $role) . $this->formatter::ROLE_BLOCK_SEPARATOR; } + return $result; } - private function renderSchema(array $schema, string $role): string + private function renderRole(array $schema, string $role): string { $rows = []; @@ -49,6 +44,6 @@ private function renderSchema(array $schema, string $role): string } } - return implode("\n", $rows); + return \implode($this->formatter::LINE_SEPARATOR, $rows); } } diff --git a/src/ConsoleRenderer/Renderers/ColumnsRenderer.php b/src/ConsoleRenderer/Renderer/ColumnsRenderer.php similarity index 88% rename from src/ConsoleRenderer/Renderers/ColumnsRenderer.php rename to src/ConsoleRenderer/Renderer/ColumnsRenderer.php index 558c8d9..4ef4064 100644 --- a/src/ConsoleRenderer/Renderers/ColumnsRenderer.php +++ b/src/ConsoleRenderer/Renderer/ColumnsRenderer.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Cycle\Schema\Renderer\ConsoleRenderer\Renderers; +namespace Cycle\Schema\Renderer\ConsoleRenderer\Renderer; use Cycle\ORM\SchemaInterface; use Cycle\Schema\Renderer\ConsoleRenderer\Formatter; @@ -43,13 +43,13 @@ public function render(Formatter $formatter, array $schema, string $role): ?stri if ($typecast !== null) { $row .= sprintf( ' -> %s', - $formatter->typecast(implode('::', (array)$typecast)) + $formatter->typecast(\implode('::', (array)$typecast)) ); } $rows[] = $row; } - return implode("\n", $rows); + return \implode("\n", $rows); } } diff --git a/src/ConsoleRenderer/Renderers/CustomPropertiesRenderer.php b/src/ConsoleRenderer/Renderer/CustomPropertiesRenderer.php similarity index 52% rename from src/ConsoleRenderer/Renderers/CustomPropertiesRenderer.php rename to src/ConsoleRenderer/Renderer/CustomPropertiesRenderer.php index 8223a1d..1f9dd81 100644 --- a/src/ConsoleRenderer/Renderers/CustomPropertiesRenderer.php +++ b/src/ConsoleRenderer/Renderer/CustomPropertiesRenderer.php @@ -1,26 +1,34 @@ $exclude Values list that should be excluded + */ + public function __construct(array $exclude) { - $refl = new ReflectionClass(SchemaInterface::class); + $this->exclude = $exclude; + } - $customProperties = array_diff(array_keys($schema), $refl->getConstants()); + public function render(Formatter $formatter, array $schema, string $role): ?string + { + $customProperties = array_diff(array_keys($schema), $this->exclude); if ($customProperties === []) { return null; } $rows = [ - sprintf('%s:', $formatter->title('Custom props')) + sprintf('%s:', $formatter->title('Custom props')), ]; foreach ($customProperties as $property) { @@ -29,10 +37,10 @@ public function render(Formatter $formatter, array $schema, string $role): ?stri $rows[] = sprintf( ' %s: %s', $property, - $formatter->typecast($data) + $formatter->typecast(print_r($data, true)) ); } - return implode("\n", $rows); + return \implode("\n", $rows); } } diff --git a/src/ConsoleRenderer/Renderer/KeysRenderer.php b/src/ConsoleRenderer/Renderer/KeysRenderer.php new file mode 100644 index 0000000..4fb59b5 --- /dev/null +++ b/src/ConsoleRenderer/Renderer/KeysRenderer.php @@ -0,0 +1,44 @@ +title = $title; + $this->key = $key; + $this->required = $required; + } + + public function render(Formatter $formatter, array $schema, string $role): ?string + { + $keys = $schema[$this->key] ?? null; + + $row = sprintf('%s: ', $formatter->title($this->title)); + + if ($keys === null) { + return $this->required ? $row . $formatter->error('not defined') : null; + } + + $keys = \array_map( + static fn (string $key) => $formatter->property($key), + (array)$keys + ); + + return $row . \implode(', ', $keys); + } +} diff --git a/src/ConsoleRenderer/Renderers/PropertyRenderer.php b/src/ConsoleRenderer/Renderer/PropertyRenderer.php similarity index 56% rename from src/ConsoleRenderer/Renderers/PropertyRenderer.php rename to src/ConsoleRenderer/Renderer/PropertyRenderer.php index d217c6e..9416c8f 100644 --- a/src/ConsoleRenderer/Renderers/PropertyRenderer.php +++ b/src/ConsoleRenderer/Renderer/PropertyRenderer.php @@ -2,32 +2,35 @@ declare(strict_types=1); -namespace Cycle\Schema\Renderer\ConsoleRenderer\Renderers; +namespace Cycle\Schema\Renderer\ConsoleRenderer\Renderer; use Cycle\Schema\Renderer\ConsoleRenderer\Formatter; use Cycle\Schema\Renderer\ConsoleRenderer\Renderer; class PropertyRenderer implements Renderer { - /** @var int|string */ - private $property; + private int $property; private string $title; + private bool $required; - public function __construct($property, string $title) + public function __construct(int $property, string $title, bool $required = false) { $this->property = $property; $this->title = $title; + $this->required = $required; } public function render(Formatter $formatter, array $schema, string $role): ?string { + $row = sprintf('%s: ', $formatter->title($this->title)); + if (!array_key_exists($this->property, $schema)) { - return null; + return $this->required ? $row . $formatter->error('not defined') : null; } return sprintf( - '%s: %s', - $formatter->title($this->title), + '%s%s', + $row, $formatter->typecast($schema[$this->property]) ); } diff --git a/src/ConsoleRenderer/Renderers/RelationsRenderer.php b/src/ConsoleRenderer/Renderer/RelationsRenderer.php similarity index 70% rename from src/ConsoleRenderer/Renderers/RelationsRenderer.php rename to src/ConsoleRenderer/Renderer/RelationsRenderer.php index a743a7b..d74f2a2 100644 --- a/src/ConsoleRenderer/Renderers/RelationsRenderer.php +++ b/src/ConsoleRenderer/Renderer/RelationsRenderer.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Cycle\Schema\Renderer\ConsoleRenderer\Renderers; +namespace Cycle\Schema\Renderer\ConsoleRenderer\Renderer; use Cycle\ORM\Relation; use Cycle\ORM\SchemaInterface; @@ -23,14 +23,13 @@ class RelationsRenderer implements Renderer ]; private const STR_PREFETCH_MODE = [ - Relation::LOAD_PROMISE => 'promise', + Relation::LOAD_PROMISE => 'lazy', Relation::LOAD_EAGER => 'eager', ]; public function render(Formatter $formatter, array $schema, string $role): ?string { $title = sprintf('%s:', $formatter->title('Relations')); - $table = $schema[SchemaInterface::TABLE] ?? null; $relations = $schema[SchemaInterface::RELATIONS] ?? []; if (count($relations) === 0) { @@ -42,7 +41,7 @@ public function render(Formatter $formatter, array $schema, string $role): ?stri foreach ($relations as $field => $relation) { $type = self::STR_RELATION[$relation[Relation::TYPE] ?? ''] ?? '?'; $target = $relation[Relation::TARGET] ?? '?'; - $loading = self::STR_PREFETCH_MODE[$relation[Relation::LOAD] ?? ''] ?? '?'; + $loading = self::STR_PREFETCH_MODE[$relation[Relation::LOAD] ?? ''] ?? 'default'; $relSchema = $relation[Relation::SCHEMA]; $innerKey = $relSchema[Relation::INNER_KEY] ?? '?'; $outerKey = $relSchema[Relation::OUTER_KEY] ?? '?'; @@ -61,42 +60,46 @@ public function render(Formatter $formatter, array $schema, string $role): ?stri // print $row = sprintf( - " %s->%s %s %s %s load", + ' %s->%s %s %s', $formatter->entity($role), $formatter->property($field), $type, - $formatter->entity($target), - $loading + $formatter->entity($target) ); if ($morphKey !== null) { $row .= sprintf( - '%s: %s', - $formatter->title('Morphed key'), - $formatter->column($morphKey) + ', %s: %s', + $formatter->title('morphed key'), + $this->renderKeys($formatter, $morphKey) ); } - $rows[] = $row . ' ' . $formatter->info($cascadeStr); + $rows[] = $row . sprintf(', %s loading, %s', $formatter->info($loading), $formatter->info($cascadeStr)); $row = sprintf( - " %s %s.%s <=", + ' %s %s.%s <=', $nullableStr, - $formatter->column($table), - $formatter->column($innerKey), + $formatter->entity($role), + $this->renderKeys($formatter, $innerKey) ); if ($mmEntity !== null) { $row .= sprintf( - " %s.%s | %s.%s ", + ' %s.%s | %s.%s ', $formatter->entity($mmEntity), - $formatter->column($mmInnerKey), + $this->renderKeys($formatter, $mmInnerKey), $formatter->entity($mmEntity), - $formatter->column($mmOuterKey), + $this->renderKeys($formatter, $mmOuterKey) ); } - $rows[] = $row . sprintf("=> %s.%s", $formatter->entity($target), $formatter->column($outerKey)); + // todo: composite $outerKey + $rows[] = $row . sprintf( + '=> %s.%s', + $formatter->entity($target), + $this->renderKeys($formatter, $outerKey) + ); if (count($where)) { $rows[] = sprintf( @@ -115,6 +118,25 @@ public function render(Formatter $formatter, array $schema, string $role): ?stri } } - return implode("\n", $rows); + return \implode("\n", $rows); + } + + /** + * @param array|string $keys + */ + private function renderKeys(Formatter $formatter, $keys): string + { + $keys = (array)$keys; + $braces = \count($keys) > 1; + $keys = \array_map( + static fn (string $key) => $formatter->property($key), + $keys + ); + return sprintf( + '%s%s%s', + $braces ? '[' : '', + \implode(', ', $keys), + $braces ? ']' : '' + ); } } diff --git a/src/ConsoleRenderer/Renderers/TitleRenderer.php b/src/ConsoleRenderer/Renderer/TitleRenderer.php similarity index 70% rename from src/ConsoleRenderer/Renderers/TitleRenderer.php rename to src/ConsoleRenderer/Renderer/TitleRenderer.php index d6a2893..8f302cb 100644 --- a/src/ConsoleRenderer/Renderers/TitleRenderer.php +++ b/src/ConsoleRenderer/Renderer/TitleRenderer.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Cycle\Schema\Renderer\ConsoleRenderer\Renderers; +namespace Cycle\Schema\Renderer\ConsoleRenderer\Renderer; use Cycle\ORM\SchemaInterface; use Cycle\Schema\Renderer\ConsoleRenderer\Formatter; @@ -12,14 +12,14 @@ class TitleRenderer implements Renderer { public function render(Formatter $formatter, array $schema, string $role): ?string { - $database = $schema[SchemaInterface::DATABASE] ?? null; - $table = $schema[SchemaInterface::TABLE] ?? null; + $database = $schema[SchemaInterface::DATABASE] ?? ''; + $table = $schema[SchemaInterface::TABLE] ?? ''; $row = $formatter->entity("[{$role}]"); if ($database !== null) { $row .= sprintf( - " :: %s.%s", + ' :: %s.%s', $formatter->column($database), $formatter->column($table) ); diff --git a/src/ConsoleRenderer/Renderers/PrimaryKeysRenderer.php b/src/ConsoleRenderer/Renderers/PrimaryKeysRenderer.php deleted file mode 100644 index 2577c90..0000000 --- a/src/ConsoleRenderer/Renderers/PrimaryKeysRenderer.php +++ /dev/null @@ -1,29 +0,0 @@ -title('Primary key')); - - if ($pk === null) { - return $row . $formatter->error('not defined'); - } - - $pk = array_map(static function (string $key) use ($formatter) { - return $formatter->column($key); - }, (array)$pk); - - return $row . implode(', ', $pk); - } -} diff --git a/src/OutputSchemaRenderer.php b/src/OutputSchemaRenderer.php new file mode 100644 index 0000000..780cd9a --- /dev/null +++ b/src/OutputSchemaRenderer.php @@ -0,0 +1,102 @@ + 'Role', + 'ENTITY' => 'Entity', + 'MAPPER' => 'Mapper', + 'SCOPE' => 'Constrain', + 'REPOSITORY' => 'Repository', + ]; + + public function __construct(int $format = self::FORMAT_CONSOLE_COLOR) + { + $formatter = $format === self::FORMAT_CONSOLE_COLOR + ? new StyledFormatter() + : new PlainFormatter(); + parent::__construct($formatter); + + $constants = $this->getOrmConstants(); + $properties = $this->getOrmProperties($constants); + + $this->addRenderer(...[ + new TitleRenderer(), + + // Default properties renderer (Without extra logic) + ...array_map(static function ($property, string $title) { + return new PropertyRenderer($property, $title); + }, array_keys($properties), $properties), + + new KeysRenderer(SchemaInterface::PRIMARY_KEY, 'Primary key', true), + ]); + + // JTI support + if (isset($constants['PARENT'], $constants['PARENT_KEY'])) { + $this->addRenderer(...[ + new PropertyRenderer($constants['PARENT'], 'CHILDREN'), + new KeysRenderer($constants['PARENT_KEY'], 'STI key', false), + ]); + } + + // STI support + if (isset($constants['CHILDREN'], $constants['DISCRIMINATOR'])) { + $this->addRenderer(...[ + new PropertyRenderer($constants['CHILDREN'], 'Parent'), + new KeysRenderer($constants['DISCRIMINATOR'], 'Parent key', false), + ]); + } + + $this->addRenderer(...[ + new ColumnsRenderer(), + new RelationsRenderer(), + new CustomPropertiesRenderer(array_values($constants)), + ]); + } + + /** + * @param array $constants + * + * @return array + */ + private function getOrmProperties(array $constants): array + { + $result = []; + foreach ($constants as $name => $value) { + if (!array_key_exists($name, self::DEFAULT_PROPERTY_LIST)) { + continue; + } + $result[$value] = self::DEFAULT_PROPERTY_LIST[$name]; + } + return $result; + } + + private function getOrmConstants(): array + { + return array_filter( + (new \ReflectionClass(SchemaInterface::class))->getConstants(), + 'is_int' + ); + } +} diff --git a/src/PhpFileRenderer/DefaultSchemaGenerator.php b/src/PhpFileRenderer/DefaultSchemaGenerator.php deleted file mode 100644 index 1258a75..0000000 --- a/src/PhpFileRenderer/DefaultSchemaGenerator.php +++ /dev/null @@ -1,34 +0,0 @@ -getConstants() as $key => $value) { - if (!array_key_exists($value, $generators)) { - if ($key === 'RELATIONS') { - $generators[$value] = new RelationsGenerator(); - continue; - } - - $generators[$value] = new PrimitiveGenerator('SchemaInterface::' . $key, $value); - } - } - - parent::__construct([ - ...$generators, - new CustomPropertiesGenerator(array_keys($generators)) - ]); - } -} diff --git a/src/PhpFileRenderer/Exporter/ArrayBlock.php b/src/PhpFileRenderer/Exporter/ArrayBlock.php new file mode 100644 index 0000000..2e02b74 --- /dev/null +++ b/src/PhpFileRenderer/Exporter/ArrayBlock.php @@ -0,0 +1,65 @@ +value = $value; + $this->replaceKeys = $replaceKeys; + $this->replaceValues = $replaceValues; + } + + final public function setIndentLevel(int $indentLevel = 0): self + { + $this->indentLevel = $indentLevel; + return $this; + } + + final public function toString(): string + { + $result = []; + foreach ($this->value as $key => $value) { + if ($value instanceof ArrayItem) { + $value->setIndentLevel($this->indentLevel + 1); + $result[] = $value->toString(); + continue; + } + $item = $this->wrapItem($key, $value); + $item->setIndentLevel($this->indentLevel + 1); + + $result[] = $item->toString(); + } + $indent = \str_repeat(self::INDENT, $this->indentLevel + 1); + $closedIndent = \str_repeat(self::INDENT, $this->indentLevel); + return $result === [] + ? '[]' + : "[\n$indent" . \implode(",\n$indent", $result) . ",\n$closedIndent]"; + } + + /** + * @param int|string $key + * @param mixed $value + */ + protected function wrapItem($key, $value): ArrayItem + { + $item = isset($this->replaceKeys[$key]) + ? new ArrayItem($value, (string)$this->replaceKeys[$key], false) + : new ArrayItem($value, (string)$key, true); + + if (is_scalar($value) && isset($this->replaceValues[$key][$value])) { + $item->setValue($this->replaceValues[$key][$value], false); + } + return $item; + } +} diff --git a/src/PhpFileRenderer/Exporter/ArrayItem.php b/src/PhpFileRenderer/Exporter/ArrayItem.php new file mode 100644 index 0000000..de157e1 --- /dev/null +++ b/src/PhpFileRenderer/Exporter/ArrayItem.php @@ -0,0 +1,58 @@ +key = $key; + $this->value = $value; + $this->wrapKey = $wrapKey; + } + + public function setIndentLevel(int $indentLevel = 0): self + { + $this->indentLevel = $indentLevel; + return $this; + } + + public function toString(): string + { + $result = \str_repeat(self::INDENT, $this->indentLevel); + if ($this->key !== null) { + $result = $this->wrapKey ? "'{$this->key}' => " : "{$this->key} => "; + } + if ($this->value instanceof Indentable) { + $this->value->setIndentLevel($this->indentLevel); + } + return $result . ValueRenderer::render($this->value, $this->wrapValue, $this->indentLevel); + } + + /** + * @param mixed $value + */ + public function setValue($value, bool $wrapValue = true): self + { + $this->value = $value; + $this->wrapValue = $wrapValue; + + return $this; + } +} diff --git a/src/PhpFileRenderer/Exporter/ExporterItem.php b/src/PhpFileRenderer/Exporter/ExporterItem.php new file mode 100644 index 0000000..062eae2 --- /dev/null +++ b/src/PhpFileRenderer/Exporter/ExporterItem.php @@ -0,0 +1,12 @@ + $item) { + $str = ''; + if (!$item instanceof self && $withKeys) { + $str .= is_int($key) ? "{$key} => " : "'{$key}' => "; + } + $elements[] = $str . self::renderValue($item); + } + return '[' . \implode(', ', $elements) . ']'; + } + + private static function renderArrayBlock(array $value, bool $withKeys = true, int $indentLevel = 0): string + { + $braceIdent = \str_repeat(Indentable::INDENT, $indentLevel); + $itemIndent = \str_repeat(Indentable::INDENT, $indentLevel + 1); + $result = '['; + foreach ($value as $key => $item) { + $result .= "\n" . $itemIndent; + if ($item instanceof Indentable) { + $item->setIndentLevel($indentLevel + 1); + } elseif ($withKeys) { + $result .= is_int($key) ? "{$key} => " : "'{$key}' => "; + } + $result .= self::renderValue($item, true, $indentLevel + 1) . ','; + } + return $result . "\n$braceIdent]"; + } + + private static function isAutoIncrementedKeys(array $array): bool + { + return count($array) === 0 || array_keys($array) === range(0, count($array) - 1); + } + + private static function isScalarArrayValues(array $array): bool + { + foreach ($array as $value) { + if (!is_scalar($value)) { + return false; + } + } + return true; + } +} diff --git a/src/PhpFileRenderer/Exporter/Rendering/Indentable.php b/src/PhpFileRenderer/Exporter/Rendering/Indentable.php new file mode 100644 index 0000000..90ee678 --- /dev/null +++ b/src/PhpFileRenderer/Exporter/Rendering/Indentable.php @@ -0,0 +1,12 @@ +toString(); + case !$wrapValue || is_int($value): + return (string)$value; + case is_string($value): + return "'" . addslashes($value) . "'"; + default: + return "unserialize('" . addslashes(serialize($value)) . "')"; + } + } +} diff --git a/src/PhpFileRenderer/Generator.php b/src/PhpFileRenderer/Generator.php deleted file mode 100644 index 4a6069a..0000000 --- a/src/PhpFileRenderer/Generator.php +++ /dev/null @@ -1,15 +0,0 @@ - - */ - public function generate(array $schema, string $role): array; -} diff --git a/src/PhpFileRenderer/Generators/CustomPropertiesGenerator.php b/src/PhpFileRenderer/Generators/CustomPropertiesGenerator.php deleted file mode 100644 index 3ccf6f9..0000000 --- a/src/PhpFileRenderer/Generators/CustomPropertiesGenerator.php +++ /dev/null @@ -1,30 +0,0 @@ -propertiesWithGenerator = $propertiesWithGenerator; - } - - public function generate(array $schema, string $role): array - { - $properties = []; - - foreach ($schema as $key => $data) { - if (in_array($key, $this->propertiesWithGenerator, true)) { - continue; - } - $properties[] = new VarExporter($key, $data, is_string($key)); - } - - return $properties; - } -} diff --git a/src/PhpFileRenderer/Generators/PrimitiveGenerator.php b/src/PhpFileRenderer/Generators/PrimitiveGenerator.php deleted file mode 100644 index 38677db..0000000 --- a/src/PhpFileRenderer/Generators/PrimitiveGenerator.php +++ /dev/null @@ -1,29 +0,0 @@ -key = $key; - $this->property = $property; - } - - public function generate(array $schema, string $role): array - { - return [ - new VarExporter($this->key, $schema[$this->property] ?? null) - ]; - } -} diff --git a/src/PhpFileRenderer/Generators/RelationsGenerator.php b/src/PhpFileRenderer/Generators/RelationsGenerator.php deleted file mode 100644 index 2dcd413..0000000 --- a/src/PhpFileRenderer/Generators/RelationsGenerator.php +++ /dev/null @@ -1,97 +0,0 @@ - 'Relation::HAS_ONE', - Relation::HAS_MANY => 'Relation::HAS_MANY', - Relation::BELONGS_TO => 'Relation::BELONGS_TO', - Relation::REFERS_TO => 'Relation::REFERS_TO', - Relation::MANY_TO_MANY => 'Relation::MANY_TO_MANY', - Relation::BELONGS_TO_MORPHED => 'Relation::BELONGS_TO_MORPHED', - Relation::MORPHED_HAS_ONE => 'Relation::MORPHED_HAS_ONE', - Relation::MORPHED_HAS_MANY => 'Relation::MORPHED_HAS_MANY', - ]; - - private const PREFETCH_MODE = [ - Relation::LOAD_PROMISE => 'Relation::LOAD_PROMISE', - Relation::LOAD_EAGER => 'Relation::LOAD_EAGER', - ]; - - private const RELATION_OPTION = [ - Relation::MORPH_KEY => 'Relation::MORPH_KEY', - Relation::CASCADE => 'Relation::CASCADE', - Relation::NULLABLE => 'Relation::NULLABLE', - Relation::OUTER_KEY => 'Relation::OUTER_KEY', - Relation::INNER_KEY => 'Relation::INNER_KEY', - Relation::WHERE => 'Relation::WHERE', - Relation::THROUGH_INNER_KEY => 'Relation::THROUGH_INNER_KEY', - Relation::THROUGH_OUTER_KEY => 'Relation::THROUGH_OUTER_KEY', - Relation::THROUGH_ENTITY => 'Relation::THROUGH_ENTITY', - Relation::THROUGH_WHERE => 'Relation::THROUGH_WHERE', - ]; - - private const GENERAL_OPTION = [ - Relation::TYPE => 'Relation::TYPE', - Relation::TARGET => 'Relation::TARGET', - Relation::SCHEMA => 'Relation::SCHEMA', - Relation::LOAD => 'Relation::LOAD', - ]; - - public function generate(array $schema, string $role): array - { - $relations = $schema[SchemaInterface::RELATIONS] ?? []; - - $results = []; - foreach ($relations as $field => $relation) { - $relationResult = []; - foreach ($relation as $option => $value) { - $relationResult[] = $this->renderRelationOption($option, $value); - } - - $results[] = new VarExporter($field, $relationResult, true); - } - - return [ - new VarExporter('SchemaInterface::RELATIONS', $results) - ]; - } - - private function renderRelationOption(int $option, $value): VarExporter - { - $item = new VarExporter(self::GENERAL_OPTION[$option] ?? (string)$option, $value); - - // replace numeric keys and values with constants - if ($option === Relation::LOAD && array_key_exists($value, self::PREFETCH_MODE)) { - $item->setValue(self::PREFETCH_MODE[$value], false); - } elseif ($option === Relation::TYPE && array_key_exists($value, self::RELATION)) { - $item->setValue(self::RELATION[$value], false); - } elseif ($option === Relation::SCHEMA && is_array($value)) { - $item->setValue($this->renderRelationSchemaKeys($value)); - } - - return $item; - } - - private function renderRelationSchemaKeys(array $value): array - { - $result = []; - foreach ($value as $listKey => $listValue) { - $result[] = new VarExporter( - array_key_exists($listKey, self::RELATION_OPTION) ? self::RELATION_OPTION[$listKey] : (string)$listKey, - $listValue - ); - } - - return $result; - } -} diff --git a/src/PhpFileRenderer/Item/RelationBlock.php b/src/PhpFileRenderer/Item/RelationBlock.php new file mode 100644 index 0000000..fbd6306 --- /dev/null +++ b/src/PhpFileRenderer/Item/RelationBlock.php @@ -0,0 +1,63 @@ +setValue(new self($value, $this->getReplaceKeys()), false); + } + return $item; + } + + /** + * @return array + */ + private function getReplaceKeys(): array + { + static $result = []; + if ($result !== []) { + return $result; + } + $constants = (new \ReflectionClass(Relation::class))->getConstants(); + foreach ($constants as $name => $value) { + if (!in_array($name, self::RELATION_SCHEMA_KEYS, true) || array_key_exists($value, $result)) { + continue; + } + $result[$value] = 'Relation::' . $name; + } + + return $result; + } +} diff --git a/src/PhpFileRenderer/Item/RelationsBlock.php b/src/PhpFileRenderer/Item/RelationsBlock.php new file mode 100644 index 0000000..fe8b8a6 --- /dev/null +++ b/src/PhpFileRenderer/Item/RelationsBlock.php @@ -0,0 +1,49 @@ + 'Relation::TYPE', + Relation::TARGET => 'Relation::TARGET', + Relation::SCHEMA => 'Relation::SCHEMA', + Relation::LOAD => 'Relation::LOAD', + ]; + + private const OPTION_VALUES = [ + Relation::LOAD => [ + Relation::LOAD_PROMISE => 'Relation::LOAD_PROMISE', + Relation::LOAD_EAGER => 'Relation::LOAD_EAGER', + ], + Relation::TYPE => [ + Relation::HAS_ONE => 'Relation::HAS_ONE', + Relation::HAS_MANY => 'Relation::HAS_MANY', + Relation::BELONGS_TO => 'Relation::BELONGS_TO', + Relation::REFERS_TO => 'Relation::REFERS_TO', + Relation::MANY_TO_MANY => 'Relation::MANY_TO_MANY', + Relation::BELONGS_TO_MORPHED => 'Relation::BELONGS_TO_MORPHED', + Relation::MORPHED_HAS_ONE => 'Relation::MORPHED_HAS_ONE', + Relation::MORPHED_HAS_MANY => 'Relation::MORPHED_HAS_MANY', + ], + ]; + + /** + * @param int|string $key + * @param mixed $value + */ + protected function wrapItem($key, $value): ArrayItem + { + $item = parent::wrapItem($key, $value); + if (is_array($value)) { + $item->setValue(new RelationBlock($value, self::OPTION_KEYS, self::OPTION_VALUES), false); + } + return $item; + } +} diff --git a/src/PhpFileRenderer/Item/RoleBlock.php b/src/PhpFileRenderer/Item/RoleBlock.php new file mode 100644 index 0000000..0afcb81 --- /dev/null +++ b/src/PhpFileRenderer/Item/RoleBlock.php @@ -0,0 +1,25 @@ +setValue(new RelationsBlock($value), false); + } + return $item; + } +} diff --git a/src/PhpFileRenderer/SchemaGenerator.php b/src/PhpFileRenderer/SchemaGenerator.php deleted file mode 100644 index 7458d19..0000000 --- a/src/PhpFileRenderer/SchemaGenerator.php +++ /dev/null @@ -1,30 +0,0 @@ -generators = $generators; - } - - public function generate(array $schema, string $role): array - { - /** @var array $array */ - $array = []; - - foreach ($this->generators as $generator) { - foreach ($generator->generate($schema, $role) as $property) { - $array[] = $property; - } - } - - return $array; - } -} diff --git a/src/PhpFileRenderer/VarExporter.php b/src/PhpFileRenderer/VarExporter.php deleted file mode 100644 index 456621f..0000000 --- a/src/PhpFileRenderer/VarExporter.php +++ /dev/null @@ -1,118 +0,0 @@ -key = $key; - $this->value = $value; - $this->wrapKey = $wrapKey; - } - - public function __toString() - { - $result = ''; - if ($this->key !== null) { - $result = $this->wrapKey ? "'{$this->key}' => " : "{$this->key} => "; - } - return $result . $this->renderValue($this->value); - } - - public function setValue($value, bool $wrapValue = true): self - { - $this->value = $value; - $this->wrapValue = $wrapValue; - - return $this; - } - - /** - * @param mixed $value - * - * @return string - */ - private function renderValue($value): string - { - switch (true) { - case $value === null: - return 'null'; - case is_bool($value): - return $value ? 'true' : 'false'; - case is_array($value): - return $this->renderArray($value); - case !$this->wrapValue || is_int($value) || $value instanceof self: - return (string)$value; - case is_string($value): - return "'" . addslashes($value) . "'"; - default: - return "unserialize('" . addslashes(serialize($value)) . "')"; - } - } - - private function renderArray(array $value): string - { - $aiKeys = $this->isAutoIncrementedKeys($value); - $inline = $aiKeys && $this->isScalarArrayValues($value); - if ($inline) { - $result = $this->renderArrayInline($value, !$aiKeys); - if (strlen($result) <= self::MIX_LINE_LENGTH) { - return $result; - } - } - return $this->renderArrayBlock($value, !$aiKeys); - } - - private function renderArrayInline(array $value, bool $withKeys = true): string - { - $elements = []; - foreach ($value as $key => $item) { - $str = ''; - if (!$item instanceof self && $withKeys) { - $str .= is_int($key) ? "{$key} => " : "'{$key}' => "; - } - $elements[] = $str . $this->renderValue($item); - } - return '[' . implode(', ', $elements) . ']'; - } - - private function renderArrayBlock(array $value, bool $withKeys = true): string - { - $result = '['; - foreach ($value as $key => $item) { - $result .= "\n"; - if (!$item instanceof self && $withKeys) { - $result .= is_int($key) ? "{$key} => " : "'{$key}' => "; - } - $result .= $this->renderValue($item) . ','; - } - return str_replace("\n", "\n ", $result) . "\n]"; - } - - private function isAutoIncrementedKeys(array $array): bool - { - return count($array) === 0 || array_keys($array) === range(0, count($array) - 1); - } - - private function isScalarArrayValues(array $array): bool - { - foreach ($array as $value) { - if (!is_scalar($value)) { - return false; - } - } - return true; - } -} diff --git a/src/PhpSchemaRenderer.php b/src/PhpSchemaRenderer.php new file mode 100644 index 0000000..8d685d5 --- /dev/null +++ b/src/PhpSchemaRenderer.php @@ -0,0 +1,71 @@ + $roleSchema) { + $item = new ArrayItem( + $this->wrapRoleSchema($roleSchema), + $role, + true + ); + $items[] = $item->setIndentLevel(); + } + + $rendered = (new ArrayBlock($items))->toString(); + + return $result . "\nreturn {$rendered};\n"; + } + + protected function wrapRoleSchema(array $roleSchema): ExporterItem + { + return new RoleBlock( + $roleSchema, + $this->getSchemaConstants() + ); + } + + /** + * @return array (Option value => constant name) array + */ + final protected function getSchemaConstants(): array + { + $result = array_filter( + (new \ReflectionClass(SchemaInterface::class))->getConstants(), + static fn ($value): bool => is_int($value) + ); + $result = array_flip($result); + array_walk($result, static function (string &$name): void { + $name = "Schema::$name"; + }); + return $result; + } +} diff --git a/src/SchemaRenderer.php b/src/SchemaRenderer.php new file mode 100644 index 0000000..52c1977 --- /dev/null +++ b/src/SchemaRenderer.php @@ -0,0 +1,16 @@ +> $schema Cycle ORM schema as array. You can convert {@see SchemaInterface} + * instance via {@see SchemaToArrayConverter} + */ + public function render(array $schema): string; +} diff --git a/src/SchemaToArrayConverter.php b/src/SchemaToArrayConverter.php index f68aa98..5a546f9 100644 --- a/src/SchemaToArrayConverter.php +++ b/src/SchemaToArrayConverter.php @@ -6,28 +6,15 @@ use Cycle\ORM\SchemaInterface; -class SchemaToArrayConverter +/** + * The class converts the {@see SchemaInterface} class implementation into a Cycle ORM specific array + */ +final class SchemaToArrayConverter { - private const PROPERTIES = [ - SchemaInterface::ENTITY, - SchemaInterface::MAPPER, - SchemaInterface::SOURCE, - SchemaInterface::REPOSITORY, - SchemaInterface::DATABASE, - SchemaInterface::TABLE, - SchemaInterface::PRIMARY_KEY, - SchemaInterface::FIND_BY_KEYS, - SchemaInterface::COLUMNS, - SchemaInterface::RELATIONS, - SchemaInterface::CHILDREN, - SchemaInterface::SCOPE, - SchemaInterface::TYPECAST, - SchemaInterface::SCHEMA, - ]; - /** * @param SchemaInterface $schema - * @param array $customProperties + * @param array $customProperties + * * @return array> */ public function convert(SchemaInterface $schema, array $customProperties = []): array @@ -39,21 +26,31 @@ public function convert(SchemaInterface $schema, array $customProperties = []): $result = []; - $properties = [...self::PROPERTIES, ...$customProperties]; + $properties = array_merge($this->getSchemaConstants(), $customProperties); foreach ($schema->getRoles() as $role) { - $aliasOf = $schema->resolveAlias($role); - - if ($aliasOf !== null && $aliasOf !== $role) { - // This role is an alias - continue; - } - foreach ($properties as $property) { - $result[$role][$property] = $schema->define($role, $property); + /** @psalm-suppress ReservedWord */ + $value = $schema->define($role, $property); + if ($value === null) { + continue; + } + $result[$role][$property] = $value; } } return $result; } + + /** + * @return array + */ + private function getSchemaConstants(): array + { + $result = array_filter( + (new \ReflectionClass(SchemaInterface::class))->getConstants(), + static fn ($value): bool => is_int($value) + ); + return array_values($result); + } } diff --git a/src/SchemaToPhpFileRenderer.php b/src/SchemaToPhpFileRenderer.php deleted file mode 100644 index c09e63c..0000000 --- a/src/SchemaToPhpFileRenderer.php +++ /dev/null @@ -1,49 +0,0 @@ -generator = $generator; - $this->schema = $schema; - } - - public function render(): string - { - $schema = []; - - $result = "schema as $role => $roleSchema) { - $schema[] = new VarExporter( - $role, - $this->generator->generate($roleSchema, $role), - true - ); - } - - $renderedArray = implode(",\n", $schema); - - return $result . "\nreturn [\n{$renderedArray}\n];"; - } -} diff --git a/tests/Schema/Renderer/ConsoleRenderer/OutputRendererTest.php b/tests/Schema/Renderer/ConsoleRenderer/OutputRendererTest.php index 5b9f8f4..a5a2453 100644 --- a/tests/Schema/Renderer/ConsoleRenderer/OutputRendererTest.php +++ b/tests/Schema/Renderer/ConsoleRenderer/OutputRendererTest.php @@ -1,22 +1,23 @@ ['id', 'name'], SchemaInterface::TYPECAST => ['id' => 'int'], SchemaInterface::SCHEMA => [], - SchemaInterface::RELATIONS => [] + SchemaInterface::RELATIONS => [], ], TagContext::class => [ SchemaInterface::ROLE => 'tag_context', @@ -43,8 +44,8 @@ protected function setUp(): void SchemaInterface::COLUMNS => [], SchemaInterface::TYPECAST => ['id' => 'int', 'user_id' => 'int', 'tag_id' => 'int'], SchemaInterface::SCHEMA => [], - SchemaInterface::RELATIONS => [] - ] + SchemaInterface::RELATIONS => [], + ], ]); $this->schemaArray = (new SchemaToArrayConverter())->convert($schema); @@ -52,35 +53,37 @@ protected function setUp(): void public function testSchemaShouldBeRenderedByGivenRenderers(): void { - $renderer = new OutputRenderer( - $this->schemaArray, new StyledFormatter(), [ - new TitleRenderer() - ] - ); + $renderer = new OutputRenderer(new StyledFormatter(), [ + new TitleRenderer(), + ]); $this->assertSame( - "[tag] :: default.tag + <<render($this->schemaArray) ); } public function testSchemaShouldBeRenderedByGivenFormatter(): void { - $renderer = new OutputRenderer( - $this->schemaArray, new PlainFormatter(), [ - new TitleRenderer() - ] - ); + $renderer = new OutputRenderer(new PlainFormatter(), [ + new TitleRenderer(), + ]); $this->assertSame( - "[tag] :: default.tag + <<render($this->schemaArray) ); } } diff --git a/tests/Schema/Renderer/Fixtures/Tag.php b/tests/Schema/Renderer/Fixture/Tag.php similarity index 50% rename from tests/Schema/Renderer/Fixtures/Tag.php rename to tests/Schema/Renderer/Fixture/Tag.php index b43f72a..9402ad4 100644 --- a/tests/Schema/Renderer/Fixtures/Tag.php +++ b/tests/Schema/Renderer/Fixture/Tag.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Cycle\Schema\Renderer\Tests\Fixtures; +namespace Cycle\Schema\Renderer\Tests\Fixture; class Tag { diff --git a/tests/Schema/Renderer/Fixtures/TagContext.php b/tests/Schema/Renderer/Fixture/TagContext.php similarity index 53% rename from tests/Schema/Renderer/Fixtures/TagContext.php rename to tests/Schema/Renderer/Fixture/TagContext.php index 121b782..559beac 100644 --- a/tests/Schema/Renderer/Fixtures/TagContext.php +++ b/tests/Schema/Renderer/Fixture/TagContext.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Cycle\Schema\Renderer\Tests\Fixtures; +namespace Cycle\Schema\Renderer\Tests\Fixture; class TagContext { diff --git a/tests/Schema/Renderer/Fixtures/User.php b/tests/Schema/Renderer/Fixture/User.php similarity index 71% rename from tests/Schema/Renderer/Fixtures/User.php rename to tests/Schema/Renderer/Fixture/User.php index f3647a8..80e7395 100644 --- a/tests/Schema/Renderer/Fixtures/User.php +++ b/tests/Schema/Renderer/Fixture/User.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Cycle\Schema\Renderer\Tests\Fixtures; +namespace Cycle\Schema\Renderer\Tests\Fixture; class User { diff --git a/tests/Schema/Renderer/Fixture/console_output.stub.txt b/tests/Schema/Renderer/Fixture/console_output.stub.txt new file mode 100644 index 0000000..376d08b --- /dev/null +++ b/tests/Schema/Renderer/Fixture/console_output.stub.txt @@ -0,0 +1,37 @@ +[Cycle\Schema\Renderer\Tests\Fixture\User] :: default.user + Role: user + Mapper: Cycle\ORM\Mapper\Mapper + Primary key: id + Fields: + (property -> db.field -> typecast) + 0 -> id -> int + 1 -> email + 2 -> balance -> float + Relations: + Cycle\Schema\Renderer\Tests\Fixture\User->tags many to many Cycle\Schema\Renderer\Tests\Fixture\Tag, default loading, cascaded + n/a Cycle\Schema\Renderer\Tests\Fixture\User.id <= tag_context.user_id | tag_context.tag_id => Cycle\Schema\Renderer\Tests\Fixture\Tag.id + Cycle\Schema\Renderer\Tests\Fixture\User->tag belongs to Cycle\Schema\Renderer\Tests\Fixture\Tag, default loading, cascaded + n/a Cycle\Schema\Renderer\Tests\Fixture\User.tag_id <==> Cycle\Schema\Renderer\Tests\Fixture\Tag.id + +[Cycle\Schema\Renderer\Tests\Fixture\Tag] :: default.tag + Role: tag + Mapper: Cycle\ORM\Mapper\Mapper + Primary key: id, name + Fields: + (property -> db.field -> typecast) + 0 -> id -> int + 1 -> name + Relations: + Cycle\Schema\Renderer\Tests\Fixture\Tag->user belongs to Cycle\Schema\Renderer\Tests\Fixture\User, default loading, cascaded + n/a Cycle\Schema\Renderer\Tests\Fixture\Tag.user_id <==> Cycle\Schema\Renderer\Tests\Fixture\User.id + +[Cycle\Schema\Renderer\Tests\Fixture\TagContext] :: default.tag_user_map + Role: tag_context + Mapper: Cycle\ORM\Mapper\Mapper + Primary key: not defined + Fields: not defined + Relations: not defined + Custom props: + my_custom_property: super_value + 25: super_value + diff --git a/tests/Schema/Renderer/Fixture/console_output_plain.stub.txt b/tests/Schema/Renderer/Fixture/console_output_plain.stub.txt new file mode 100644 index 0000000..93a9f2b --- /dev/null +++ b/tests/Schema/Renderer/Fixture/console_output_plain.stub.txt @@ -0,0 +1,34 @@ +[user] :: default.user + Entity: Cycle\Schema\Renderer\Tests\Fixture\User + Mapper: Cycle\ORM\Mapper\Mapper + Primary key: id + Fields: + (property -> db.field -> typecast) + 0 -> id -> int + 1 -> email + 2 -> balance -> float + Relations: + user->tags many to many tag, default loading, cascaded + n/a user.id <= tag_context.user_id | tag_context.tag_id => tag.id + user->tag belongs to tag, default loading, cascaded + n/a user.tag_id <==> tag.id + +[tag] :: default.tag + Entity: Cycle\Schema\Renderer\Tests\Fixture\Tag + Mapper: Cycle\ORM\Mapper\Mapper + Primary key: id, name + Fields: + (property -> db.field -> typecast) + 0 -> id -> int + 1 -> name + Relations: + tag->user belongs to user, default loading, cascaded + n/a tag.user_id <==> user.id + +[tag_context] :: default.tag_user_map + Entity: Cycle\Schema\Renderer\Tests\Fixture\TagContext + Mapper: Cycle\ORM\Mapper\Mapper + Primary key: not defined + Fields: not defined + Relations: not defined + diff --git a/tests/Schema/Renderer/Fixture/console_output_with_custom_properties.stub.txt b/tests/Schema/Renderer/Fixture/console_output_with_custom_properties.stub.txt new file mode 100644 index 0000000..ef96217 --- /dev/null +++ b/tests/Schema/Renderer/Fixture/console_output_with_custom_properties.stub.txt @@ -0,0 +1,38 @@ +[Cycle\Schema\Renderer\Tests\Fixture\User] :: default.user + Role: user + Mapper: Cycle\ORM\Mapper\Mapper + Primary key: id + Fields: + (property -> db.field -> typecast) + 0 -> id -> int + 1 -> email + 2 -> balance -> float + Relations: + Cycle\Schema\Renderer\Tests\Fixture\User->tags many to many Cycle\Schema\Renderer\Tests\Fixture\Tag, default loading, cascaded + n/a Cycle\Schema\Renderer\Tests\Fixture\User.id <= tag_context.user_id | tag_context.tag_id => Cycle\Schema\Renderer\Tests\Fixture\Tag.id + Cycle\Schema\Renderer\Tests\Fixture\User->tag belongs to Cycle\Schema\Renderer\Tests\Fixture\Tag, default loading, cascaded + n/a Cycle\Schema\Renderer\Tests\Fixture\User.tag_id <==> Cycle\Schema\Renderer\Tests\Fixture\Tag.id + Source: test + +[Cycle\Schema\Renderer\Tests\Fixture\Tag] :: default.tag + Role: tag + Mapper: Cycle\ORM\Mapper\Mapper + Primary key: id, name + Fields: + (property -> db.field -> typecast) + 0 -> id -> int + 1 -> name + Relations: + Cycle\Schema\Renderer\Tests\Fixture\Tag->user belongs to Cycle\Schema\Renderer\Tests\Fixture\User, default loading, cascaded + n/a Cycle\Schema\Renderer\Tests\Fixture\Tag.user_id <==> Cycle\Schema\Renderer\Tests\Fixture\User.id + +[Cycle\Schema\Renderer\Tests\Fixture\TagContext] :: default.tag_user_map + Role: tag_context + Mapper: Cycle\ORM\Mapper\Mapper + Primary key: not defined + Fields: not defined + Relations: not defined + Custom props: + my_custom_property: super_value + 25: super_value + diff --git a/tests/Schema/Renderer/Fixture/generated_schema.stub.php b/tests/Schema/Renderer/Fixture/generated_schema.stub.php new file mode 100644 index 0000000..208186f --- /dev/null +++ b/tests/Schema/Renderer/Fixture/generated_schema.stub.php @@ -0,0 +1,82 @@ + [ + Schema::ENTITY => 'Cycle\\Schema\\Renderer\\Tests\\Fixture\\User', + Schema::MAPPER => 'Cycle\\ORM\\Mapper\\Mapper', + Schema::DATABASE => 'default', + Schema::TABLE => 'user', + Schema::PRIMARY_KEY => 'id', + Schema::COLUMNS => ['id', 'email', 'balance'], + Schema::RELATIONS => [ + 'tags' => [ + Relation::TYPE => Relation::MANY_TO_MANY, + Relation::TARGET => 'tag', + Relation::SCHEMA => [ + Relation::CASCADE => true, + Relation::THROUGH_ENTITY => 'tag_context', + Relation::INNER_KEY => 'id', + Relation::OUTER_KEY => 'id', + Relation::THROUGH_INNER_KEY => 'user_id', + Relation::THROUGH_OUTER_KEY => 'tag_id', + ], + ], + 'tag' => [ + Relation::TYPE => Relation::BELONGS_TO, + Relation::TARGET => 'tag', + Relation::SCHEMA => [ + Relation::CASCADE => true, + Relation::INNER_KEY => 'tag_id', + Relation::OUTER_KEY => 'id', + ], + ], + ], + Schema::TYPECAST => [ + 'id' => 'int', + 'balance' => 'float', + ], + Schema::SCHEMA => [], + ], + 'tag' => [ + Schema::ENTITY => 'Cycle\\Schema\\Renderer\\Tests\\Fixture\\Tag', + Schema::MAPPER => 'Cycle\\ORM\\Mapper\\Mapper', + Schema::DATABASE => 'default', + Schema::TABLE => 'tag', + Schema::PRIMARY_KEY => ['id', 'name'], + Schema::COLUMNS => ['id', 'name'], + Schema::RELATIONS => [ + 'user' => [ + Relation::TYPE => Relation::BELONGS_TO, + Relation::TARGET => 'user', + Relation::SCHEMA => [ + Relation::CASCADE => true, + Relation::INNER_KEY => 'user_id', + Relation::OUTER_KEY => 'id', + ], + ], + ], + Schema::TYPECAST => [ + 'id' => 'int', + ], + Schema::SCHEMA => [], + ], + 'tag_context' => [ + Schema::ENTITY => 'Cycle\\Schema\\Renderer\\Tests\\Fixture\\TagContext', + Schema::MAPPER => 'Cycle\\ORM\\Mapper\\Mapper', + Schema::DATABASE => 'default', + Schema::TABLE => 'tag_user_map', + Schema::COLUMNS => [], + Schema::RELATIONS => [], + Schema::TYPECAST => [ + 'id' => 'int', + 'user_id' => 'int', + 'tag_id' => 'int', + ], + Schema::SCHEMA => [], + ], +]; diff --git a/tests/Schema/Renderer/Fixtures/console_output.txt b/tests/Schema/Renderer/Fixtures/console_output.txt deleted file mode 100644 index 09f2258..0000000 --- a/tests/Schema/Renderer/Fixtures/console_output.txt +++ /dev/null @@ -1,36 +0,0 @@ -[Cycle\Schema\Renderer\Tests\Fixtures\User] :: default.user - Role: user - Mapper: Cycle\ORM\Mapper\Mapper - Primary key: id - Fields: - (property -> db.field -> typecast) - 0 -> id -> int - 1 -> email - 2 -> balance -> float - Relations: - Cycle\Schema\Renderer\Tests\Fixtures\User->tags many to many Cycle\Schema\Renderer\Tests\Fixtures\Tag ? load cascaded - n/a user.id <= tag_context.user_id | tag_context.tag_id => Cycle\Schema\Renderer\Tests\Fixtures\Tag.id - Cycle\Schema\Renderer\Tests\Fixtures\User->tag belongs to Cycle\Schema\Renderer\Tests\Fixtures\Tag ? load cascaded - n/a user.tag_id <==> Cycle\Schema\Renderer\Tests\Fixtures\Tag.id - -[Cycle\Schema\Renderer\Tests\Fixtures\Tag] :: default.tag - Role: tag - Mapper: Cycle\ORM\Mapper\Mapper - Primary key: id, name - Fields: - (property -> db.field -> typecast) - 0 -> id -> int - 1 -> name - Relations: - Cycle\Schema\Renderer\Tests\Fixtures\Tag->user belongs to Cycle\Schema\Renderer\Tests\Fixtures\User ? load cascaded - n/a tag.user_id <==> Cycle\Schema\Renderer\Tests\Fixtures\User.id - -[Cycle\Schema\Renderer\Tests\Fixtures\TagContext] :: default.tag_user_map - Role: tag_context - Mapper: Cycle\ORM\Mapper\Mapper - Primary key: not defined - Fields: not defined - Relations: not defined - Custom props: - my_custom_property: super_value - 25: super_value \ No newline at end of file diff --git a/tests/Schema/Renderer/Fixtures/console_output_plain.txt b/tests/Schema/Renderer/Fixtures/console_output_plain.txt deleted file mode 100644 index c3ea56c..0000000 --- a/tests/Schema/Renderer/Fixtures/console_output_plain.txt +++ /dev/null @@ -1,42 +0,0 @@ -[user] :: default.user - Entity: Cycle\Schema\Renderer\Tests\Fixtures\User - Mapper: Cycle\ORM\Mapper\Mapper - Constrain: not defined - Repository: not defined - Primary key: id - Fields: - (property -> db.field -> typecast) - 0 -> id -> int - 1 -> email - 2 -> balance -> float - Relations: - user->tags many to many tag ? load cascaded - n/a user.id <= tag_context.user_id | tag_context.tag_id => tag.id - user->tag belongs to tag ? load cascaded - n/a user.tag_id <==> tag.id -Custom props: - 123 : sdasd - abc : [111, 11] - -[tag] :: default.tag - Entity: Cycle\Schema\Renderer\Tests\Fixtures\Tag - Mapper: Cycle\ORM\Mapper\Mapper - Constrain: not defined - Repository: not defined - Primary key: id, name - Fields: - (property -> db.field -> typecast) - 0 -> id -> int - 1 -> name - Relations: - tag->user belongs to user ? load cascaded - n/a tag.user_id <==> user.id - -[tag_context] :: default.tag_user_map - Entity: Cycle\Schema\Renderer\Tests\Fixtures\TagContext - Mapper: Cycle\ORM\Mapper\Mapper - Constrain: not defined - Repository: not defined - Primary key: not defined - Fields: not defined - Relations: not defined diff --git a/tests/Schema/Renderer/Fixtures/console_output_with_custom_properties.txt b/tests/Schema/Renderer/Fixtures/console_output_with_custom_properties.txt deleted file mode 100644 index 964d430..0000000 --- a/tests/Schema/Renderer/Fixtures/console_output_with_custom_properties.txt +++ /dev/null @@ -1,37 +0,0 @@ -[Cycle\Schema\Renderer\Tests\Fixtures\User] :: default.user - Role: user - Mapper: Cycle\ORM\Mapper\Mapper - Primary key: id - Fields: - (property -> db.field -> typecast) - 0 -> id -> int - 1 -> email - 2 -> balance -> float - Relations: - Cycle\Schema\Renderer\Tests\Fixtures\User->tags many to many Cycle\Schema\Renderer\Tests\Fixtures\Tag ? load cascaded - n/a user.id <= tag_context.user_id | tag_context.tag_id => Cycle\Schema\Renderer\Tests\Fixtures\Tag.id - Cycle\Schema\Renderer\Tests\Fixtures\User->tag belongs to Cycle\Schema\Renderer\Tests\Fixtures\Tag ? load cascaded - n/a user.tag_id <==> Cycle\Schema\Renderer\Tests\Fixtures\Tag.id - Source: test - -[Cycle\Schema\Renderer\Tests\Fixtures\Tag] :: default.tag - Role: tag - Mapper: Cycle\ORM\Mapper\Mapper - Primary key: id, name - Fields: - (property -> db.field -> typecast) - 0 -> id -> int - 1 -> name - Relations: - Cycle\Schema\Renderer\Tests\Fixtures\Tag->user belongs to Cycle\Schema\Renderer\Tests\Fixtures\User ? load cascaded - n/a tag.user_id <==> Cycle\Schema\Renderer\Tests\Fixtures\User.id - -[Cycle\Schema\Renderer\Tests\Fixtures\TagContext] :: default.tag_user_map - Role: tag_context - Mapper: Cycle\ORM\Mapper\Mapper - Primary key: not defined - Fields: not defined - Relations: not defined - Custom props: - my_custom_property: super_value - 25: super_value \ No newline at end of file diff --git a/tests/Schema/Renderer/Fixtures/generated_schema.php b/tests/Schema/Renderer/Fixtures/generated_schema.php deleted file mode 100644 index cfb1d02..0000000 --- a/tests/Schema/Renderer/Fixtures/generated_schema.php +++ /dev/null @@ -1,101 +0,0 @@ - [ - SchemaInterface::ROLE => null, - SchemaInterface::ENTITY => 'Cycle\\Schema\\Renderer\\Tests\\Fixtures\\User', - SchemaInterface::MAPPER => 'Cycle\\ORM\\Mapper\\Mapper', - SchemaInterface::SOURCE => null, - SchemaInterface::REPOSITORY => null, - SchemaInterface::DATABASE => 'default', - SchemaInterface::TABLE => 'user', - SchemaInterface::PRIMARY_KEY => 'id', - SchemaInterface::FIND_BY_KEYS => null, - SchemaInterface::COLUMNS => ['id', 'email', 'balance'], - SchemaInterface::RELATIONS => [ - 'tags' => [ - Relation::TYPE => Relation::MANY_TO_MANY, - Relation::TARGET => 'tag', - Relation::SCHEMA => [ - Relation::CASCADE => true, - Relation::THROUGH_ENTITY => 'tag_context', - Relation::INNER_KEY => 'id', - Relation::OUTER_KEY => 'id', - Relation::THROUGH_INNER_KEY => 'user_id', - Relation::THROUGH_OUTER_KEY => 'tag_id', - ], - ], - 'tag' => [ - Relation::TYPE => Relation::BELONGS_TO, - Relation::TARGET => 'tag', - Relation::SCHEMA => [ - Relation::CASCADE => true, - Relation::INNER_KEY => 'tag_id', - Relation::OUTER_KEY => 'id', - ], - ], - ], - SchemaInterface::CHILDREN => null, - SchemaInterface::SCOPE => null, - SchemaInterface::TYPECAST => [ - 'id' => 'int', - 'balance' => 'float', - ], - SchemaInterface::SCHEMA => [], -], -'tag' => [ - SchemaInterface::ROLE => null, - SchemaInterface::ENTITY => 'Cycle\\Schema\\Renderer\\Tests\\Fixtures\\Tag', - SchemaInterface::MAPPER => 'Cycle\\ORM\\Mapper\\Mapper', - SchemaInterface::SOURCE => null, - SchemaInterface::REPOSITORY => null, - SchemaInterface::DATABASE => 'default', - SchemaInterface::TABLE => 'tag', - SchemaInterface::PRIMARY_KEY => ['id', 'name'], - SchemaInterface::FIND_BY_KEYS => null, - SchemaInterface::COLUMNS => ['id', 'name'], - SchemaInterface::RELATIONS => [ - 'user' => [ - Relation::TYPE => Relation::BELONGS_TO, - Relation::TARGET => 'user', - Relation::SCHEMA => [ - Relation::CASCADE => true, - Relation::INNER_KEY => 'user_id', - Relation::OUTER_KEY => 'id', - ], - ], - ], - SchemaInterface::CHILDREN => null, - SchemaInterface::SCOPE => null, - SchemaInterface::TYPECAST => [ - 'id' => 'int', - ], - SchemaInterface::SCHEMA => [], -], -'tag_context' => [ - SchemaInterface::ROLE => null, - SchemaInterface::ENTITY => 'Cycle\\Schema\\Renderer\\Tests\\Fixtures\\TagContext', - SchemaInterface::MAPPER => 'Cycle\\ORM\\Mapper\\Mapper', - SchemaInterface::SOURCE => null, - SchemaInterface::REPOSITORY => null, - SchemaInterface::DATABASE => 'default', - SchemaInterface::TABLE => 'tag_user_map', - SchemaInterface::PRIMARY_KEY => null, - SchemaInterface::FIND_BY_KEYS => null, - SchemaInterface::COLUMNS => [], - SchemaInterface::RELATIONS => [], - SchemaInterface::CHILDREN => null, - SchemaInterface::SCOPE => null, - SchemaInterface::TYPECAST => [ - 'id' => 'int', - 'user_id' => 'int', - 'tag_id' => 'int', - ], - SchemaInterface::SCHEMA => [], -] -]; \ No newline at end of file diff --git a/tests/Schema/Renderer/ConsoleRenderer/DefaultSchemaOutputRendererTest.php b/tests/Schema/Renderer/OutputSchemaRendererTest.php similarity index 71% rename from tests/Schema/Renderer/ConsoleRenderer/DefaultSchemaOutputRendererTest.php rename to tests/Schema/Renderer/OutputSchemaRendererTest.php index 05dc5a6..8affa67 100644 --- a/tests/Schema/Renderer/ConsoleRenderer/DefaultSchemaOutputRendererTest.php +++ b/tests/Schema/Renderer/OutputSchemaRendererTest.php @@ -2,20 +2,21 @@ declare(strict_types=1); -namespace Cycle\Schema\Renderer\Tests\ConsoleRenderer; +namespace Cycle\Schema\Renderer\Tests; use Cycle\ORM\Mapper\Mapper; use Cycle\ORM\Relation; +use Cycle\ORM\Schema; use Cycle\ORM\SchemaInterface; -use Cycle\Schema\Renderer\ConsoleRenderer\DefaultSchemaOutputRenderer; -use Cycle\Schema\Renderer\ConsoleRenderer\Formatters\StyledFormatter; -use Cycle\Schema\Renderer\ConsoleRenderer\Renderers\PropertyRenderer; -use Cycle\Schema\Renderer\Tests\Fixtures\Tag; -use Cycle\Schema\Renderer\Tests\Fixtures\TagContext; -use Cycle\Schema\Renderer\Tests\Fixtures\User; +use Cycle\Schema\Renderer\ConsoleRenderer\Renderer\PropertyRenderer; +use Cycle\Schema\Renderer\OutputSchemaRenderer; +use Cycle\Schema\Renderer\SchemaToArrayConverter; +use Cycle\Schema\Renderer\Tests\Fixture\Tag; +use Cycle\Schema\Renderer\Tests\Fixture\TagContext; +use Cycle\Schema\Renderer\Tests\Fixture\User; use PHPUnit\Framework\TestCase; -class DefaultSchemaOutputRendererTest extends TestCase +final class OutputSchemaRendererTest extends TestCase { private array $schemaArray; @@ -56,7 +57,7 @@ protected function setUp(): void Relation::OUTER_KEY => 'id', ], ], - ] + ], ], Tag::class => [ SchemaInterface::ROLE => 'tag', @@ -76,8 +77,8 @@ protected function setUp(): void Relation::INNER_KEY => 'user_id', Relation::OUTER_KEY => 'id', ], - ] - ] + ], + ], ], TagContext::class => [ SchemaInterface::ROLE => 'tag_context', @@ -89,34 +90,47 @@ protected function setUp(): void SchemaInterface::SCHEMA => [], SchemaInterface::RELATIONS => [], 'my_custom_property' => 'super_value', - 25 => 'super_value' - ] + 25 => 'super_value', + ], ]; } public function testSchemaShouldBeRendered(): void { - $renderer = new DefaultSchemaOutputRenderer( - $this->schemaArray, new StyledFormatter() - ); + $renderer = new OutputSchemaRenderer(OutputSchemaRenderer::FORMAT_CONSOLE_COLOR); + + $expected = file_get_contents(__DIR__ . '/Fixture/console_output.stub.txt'); $this->assertSame( - file_get_contents(__DIR__ . '/../Fixtures/console_output.txt'), - implode("\n\n", iterator_to_array($renderer)) + $expected, + $renderer->render($this->schemaArray) ); } - public function testSchemaWithExtraPropertiesShouldBeRendered(): void + public function testPlainFormat(): void { - $renderer = new DefaultSchemaOutputRenderer( - $this->schemaArray, new StyledFormatter() + $schema = new Schema($this->schemaArray); + $schemaArray = (new SchemaToArrayConverter())->convert($schema); + $renderer = new OutputSchemaRenderer(OutputSchemaRenderer::FORMAT_PLAIN_TEXT); + + $expected = file_get_contents(__DIR__ . '/Fixture/console_output_plain.stub.txt'); + + $this->assertSame( + $expected, + $renderer->render($schemaArray) ); + } + public function testSchemaWithExtraPropertiesShouldBeRendered(): void + { + $renderer = new OutputSchemaRenderer(OutputSchemaRenderer::FORMAT_CONSOLE_COLOR); $renderer->addRenderer(new PropertyRenderer(SchemaInterface::SOURCE, 'Source')); + $expected = file_get_contents(__DIR__ . '/Fixture/console_output_with_custom_properties.stub.txt'); + $this->assertSame( - file_get_contents(__DIR__ . '/../Fixtures/console_output_with_custom_properties.txt'), - implode("\n\n", iterator_to_array($renderer)) + $expected, + $renderer->render($this->schemaArray) ); } } diff --git a/tests/Schema/Renderer/PhpSchemaRendererTest.php b/tests/Schema/Renderer/PhpSchemaRendererTest.php new file mode 100644 index 0000000..b25e9d6 --- /dev/null +++ b/tests/Schema/Renderer/PhpSchemaRendererTest.php @@ -0,0 +1,176 @@ + [ + SchemaInterface::ROLE => 'user', + SchemaInterface::MAPPER => Mapper::class, + SchemaInterface::DATABASE => 'default', + SchemaInterface::TABLE => 'user', + SchemaInterface::PRIMARY_KEY => 'id', + SchemaInterface::COLUMNS => ['id', 'email', 'balance'], + SchemaInterface::TYPECAST => ['id' => 'int', 'balance' => 'float'], + SchemaInterface::SCHEMA => [], + SchemaInterface::RELATIONS => [ + 'tags' => [ + Relation::TYPE => Relation::MANY_TO_MANY, + Relation::TARGET => Tag::class, + Relation::SCHEMA => [ + Relation::CASCADE => true, + Relation::THROUGH_ENTITY => 'tag_context', + Relation::INNER_KEY => 'id', + Relation::OUTER_KEY => 'id', + Relation::THROUGH_INNER_KEY => 'user_id', + Relation::THROUGH_OUTER_KEY => 'tag_id', + ], + ], + 'tag' => [ + Relation::TYPE => Relation::BELONGS_TO, + Relation::TARGET => Tag::class, + Relation::SCHEMA => [ + Relation::CASCADE => true, + Relation::INNER_KEY => 'tag_id', + Relation::OUTER_KEY => 'id', + ], + ], + ], + ], + Tag::class => [ + SchemaInterface::ROLE => 'tag', + SchemaInterface::MAPPER => Mapper::class, + SchemaInterface::DATABASE => 'default', + SchemaInterface::TABLE => 'tag', + SchemaInterface::PRIMARY_KEY => ['id', 'name'], + SchemaInterface::COLUMNS => ['id', 'name'], + SchemaInterface::TYPECAST => ['id' => 'int'], + SchemaInterface::SCHEMA => [], + SchemaInterface::RELATIONS => [ + 'user' => [ + Relation::TYPE => Relation::BELONGS_TO, + Relation::TARGET => User::class, + Relation::SCHEMA => [ + Relation::CASCADE => true, + Relation::INNER_KEY => 'user_id', + Relation::OUTER_KEY => 'id', + ], + ], + ], + ], + TagContext::class => [ + SchemaInterface::ROLE => 'tag_context', + SchemaInterface::MAPPER => Mapper::class, + SchemaInterface::DATABASE => 'default', + SchemaInterface::TABLE => 'tag_user_map', + SchemaInterface::COLUMNS => [], + SchemaInterface::TYPECAST => ['id' => 'int', 'user_id' => 'int', 'tag_id' => 'int'], + SchemaInterface::SCHEMA => [], + SchemaInterface::RELATIONS => [], + ], + ]); + + $this->schema = (new SchemaToArrayConverter())->convert($schema); + } + + public function testRenderSchemaToPhpCode(): void + { + $renderer = new PhpSchemaRenderer(); + $expected = file_get_contents(__DIR__ . '/Fixture/generated_schema.stub.php'); + + $this->assertSame( + $expected, + $renderer->render($this->schema) + ); + } + + public function testRenderEmptySchemaToPhpCode(): void + { + $renderer = new PhpSchemaRenderer(); + + $this->assertSame( + <<render([]) + ); + } + + public function testRenderSchemaWithCustomPropertyToPhpCode(): void + { + $renderer = new PhpSchemaRenderer(); + + $this->assertSame( + << [ + Schema::ROLE => 'tag_context', + Schema::MAPPER => 'Cycle\\\\ORM\\\\Mapper\\\\Mapper', + Schema::DATABASE => 'default', + Schema::TABLE => 'tag_user_map', + Schema::COLUMNS => [], + Schema::TYPECAST => [ + 'id' => 'int', + 'user_id' => 'int', + 'tag_id' => 'int', + ], + Schema::SCHEMA => [], + '100' => 'Hello world', + 'hello' => 'world', + ], + ]; + + PHP + , + $renderer->render([ + TagContext::class => [ + SchemaInterface::ROLE => 'tag_context', + SchemaInterface::MAPPER => Mapper::class, + SchemaInterface::DATABASE => 'default', + SchemaInterface::TABLE => 'tag_user_map', + SchemaInterface::COLUMNS => [], + SchemaInterface::TYPECAST => ['id' => 'int', 'user_id' => 'int', 'tag_id' => 'int'], + SchemaInterface::SCHEMA => [], + 100 => 'Hello world', + 'hello' => 'world', + ], + ]) + ); + } +} diff --git a/tests/Schema/Renderer/SchemaToArrayConverterTest.php b/tests/Schema/Renderer/SchemaToArrayConverterTest.php index affdde9..717122a 100644 --- a/tests/Schema/Renderer/SchemaToArrayConverterTest.php +++ b/tests/Schema/Renderer/SchemaToArrayConverterTest.php @@ -1,5 +1,7 @@ 'value' + 123 => 'value', ], - ]); } @@ -67,43 +68,39 @@ public function testObjectShouldBeConvertedToArray() SchemaInterface::ENTITY => User::class, SchemaInterface::MAPPER => Mapper::class, SchemaInterface::SOURCE => 'users', - SchemaInterface::REPOSITORY => null, SchemaInterface::DATABASE => 'default', SchemaInterface::TABLE => 'user', SchemaInterface::PRIMARY_KEY => 'id', - SchemaInterface::FIND_BY_KEYS => null, SchemaInterface::COLUMNS => ['id', 'email', 'balance'], SchemaInterface::RELATIONS => [ 'tags' => [ Relation::TYPE => Relation::MANY_TO_MANY, - Relation::TARGET => 'Cycle\Schema\Renderer\Tests\Fixtures\Tag', + Relation::TARGET => 'Cycle\Schema\Renderer\Tests\Fixture\Tag', Relation::SCHEMA => [ Relation::CASCADE => true, - Relation::THROUGH_ENTITY => 'Cycle\Schema\Renderer\Tests\Fixtures\TagContext', + Relation::THROUGH_ENTITY => 'Cycle\Schema\Renderer\Tests\Fixture\TagContext', Relation::INNER_KEY => 'id', Relation::OUTER_KEY => 'id', Relation::THROUGH_INNER_KEY => 'user_id', Relation::THROUGH_OUTER_KEY => 'tag_id', - ] + ], ], 'tag' => [ Relation::TYPE => 12, - Relation::TARGET => 'Cycle\Schema\Renderer\Tests\Fixtures\Tag', + Relation::TARGET => 'Cycle\Schema\Renderer\Tests\Fixture\Tag', Relation::SCHEMA => [ Relation::CASCADE => true, Relation::INNER_KEY => 'tag_id', Relation::OUTER_KEY => 'id', - ] - ] + ], + ], ], - SchemaInterface::CHILDREN => null, - SchemaInterface::SCOPE => null, SchemaInterface::TYPECAST => [ 'id' => 'int', 'balance' => 'float', ], SchemaInterface::SCHEMA => [], - ] + ], ], (new SchemaToArrayConverter())->convert($this->schema)); } @@ -114,44 +111,40 @@ public function testObjectWithCustomPropertiesShouldBeConvertedToArray() SchemaInterface::ENTITY => User::class, SchemaInterface::MAPPER => Mapper::class, SchemaInterface::SOURCE => 'users', - SchemaInterface::REPOSITORY => null, SchemaInterface::DATABASE => 'default', SchemaInterface::TABLE => 'user', SchemaInterface::PRIMARY_KEY => 'id', - SchemaInterface::FIND_BY_KEYS => null, SchemaInterface::COLUMNS => ['id', 'email', 'balance'], SchemaInterface::RELATIONS => [ 'tags' => [ Relation::TYPE => Relation::MANY_TO_MANY, - Relation::TARGET => 'Cycle\Schema\Renderer\Tests\Fixtures\Tag', + Relation::TARGET => 'Cycle\Schema\Renderer\Tests\Fixture\Tag', Relation::SCHEMA => [ Relation::CASCADE => true, - Relation::THROUGH_ENTITY => 'Cycle\Schema\Renderer\Tests\Fixtures\TagContext', + Relation::THROUGH_ENTITY => 'Cycle\Schema\Renderer\Tests\Fixture\TagContext', Relation::INNER_KEY => 'id', Relation::OUTER_KEY => 'id', Relation::THROUGH_INNER_KEY => 'user_id', Relation::THROUGH_OUTER_KEY => 'tag_id', - ] + ], ], 'tag' => [ Relation::TYPE => 12, - Relation::TARGET => 'Cycle\Schema\Renderer\Tests\Fixtures\Tag', + Relation::TARGET => 'Cycle\Schema\Renderer\Tests\Fixture\Tag', Relation::SCHEMA => [ Relation::CASCADE => true, Relation::INNER_KEY => 'tag_id', Relation::OUTER_KEY => 'id', - ] - ] + ], + ], ], - SchemaInterface::CHILDREN => null, - SchemaInterface::SCOPE => null, SchemaInterface::TYPECAST => [ 'id' => 'int', 'balance' => 'float', ], SchemaInterface::SCHEMA => [], - 123 => 'value' - ] + 123 => 'value', + ], ], (new SchemaToArrayConverter())->convert($this->schema, [123])); } } diff --git a/tests/Schema/Renderer/SchemaToPhpFileRendererTest.php b/tests/Schema/Renderer/SchemaToPhpFileRendererTest.php deleted file mode 100644 index a853583..0000000 --- a/tests/Schema/Renderer/SchemaToPhpFileRendererTest.php +++ /dev/null @@ -1,253 +0,0 @@ - [ - SchemaInterface::ROLE => 'user', - SchemaInterface::MAPPER => Mapper::class, - SchemaInterface::DATABASE => 'default', - SchemaInterface::TABLE => 'user', - SchemaInterface::PRIMARY_KEY => 'id', - SchemaInterface::COLUMNS => ['id', 'email', 'balance'], - SchemaInterface::TYPECAST => ['id' => 'int', 'balance' => 'float'], - SchemaInterface::SCHEMA => [], - SchemaInterface::RELATIONS => [ - 'tags' => [ - Relation::TYPE => Relation::MANY_TO_MANY, - Relation::TARGET => Tag::class, - Relation::SCHEMA => [ - Relation::CASCADE => true, - Relation::THROUGH_ENTITY => 'tag_context', - Relation::INNER_KEY => 'id', - Relation::OUTER_KEY => 'id', - Relation::THROUGH_INNER_KEY => 'user_id', - Relation::THROUGH_OUTER_KEY => 'tag_id', - ], - ], - 'tag' => [ - Relation::TYPE => Relation::BELONGS_TO, - Relation::TARGET => Tag::class, - Relation::SCHEMA => [ - Relation::CASCADE => true, - Relation::INNER_KEY => 'tag_id', - Relation::OUTER_KEY => 'id', - ], - ], - ] - ], - Tag::class => [ - SchemaInterface::ROLE => 'tag', - SchemaInterface::MAPPER => Mapper::class, - SchemaInterface::DATABASE => 'default', - SchemaInterface::TABLE => 'tag', - SchemaInterface::PRIMARY_KEY => ['id', 'name'], - SchemaInterface::COLUMNS => ['id', 'name'], - SchemaInterface::TYPECAST => ['id' => 'int'], - SchemaInterface::SCHEMA => [], - SchemaInterface::RELATIONS => [ - 'user' => [ - Relation::TYPE => Relation::BELONGS_TO, - Relation::TARGET => User::class, - Relation::SCHEMA => [ - Relation::CASCADE => true, - Relation::INNER_KEY => 'user_id', - Relation::OUTER_KEY => 'id', - ], - ] - ] - ], - TagContext::class => [ - SchemaInterface::ROLE => 'tag_context', - SchemaInterface::MAPPER => Mapper::class, - SchemaInterface::DATABASE => 'default', - SchemaInterface::TABLE => 'tag_user_map', - SchemaInterface::COLUMNS => [], - SchemaInterface::TYPECAST => ['id' => 'int', 'user_id' => 'int', 'tag_id' => 'int'], - SchemaInterface::SCHEMA => [], - SchemaInterface::RELATIONS => [] - ] - ]); - - $this->schema = (new SchemaToArrayConverter())->convert($schema); - } - - public function testRenderSchemaToPhpCode(): void - { - $renderer = new SchemaToPhpFileRenderer($this->schema, new DefaultSchemaGenerator()); - - $this->assertSame( - file_get_contents(__DIR__ . '/Fixtures/generated_schema.php'), - $renderer->render() - ); - } - - public function testRenderEmptySchemaToPhpCode(): void - { - $renderer = new SchemaToPhpFileRenderer([], new DefaultSchemaGenerator()); - - $this->assertSame( - <<render() - ); - } - - public function testRenderSchemaWithCustomPropertyToPhpCode(): void - { - $renderer = new SchemaToPhpFileRenderer([ - TagContext::class => [ - SchemaInterface::ROLE => 'tag_context', - SchemaInterface::MAPPER => Mapper::class, - SchemaInterface::DATABASE => 'default', - SchemaInterface::TABLE => 'tag_user_map', - SchemaInterface::COLUMNS => [], - SchemaInterface::TYPECAST => ['id' => 'int', 'user_id' => 'int', 'tag_id' => 'int'], - SchemaInterface::SCHEMA => [], - 100 => 'Hello world', - 'hello' => 'world' - ] - ], new DefaultSchemaGenerator()); - - $this->assertSame( - << [ - SchemaInterface::ROLE => 'tag_context', - SchemaInterface::ENTITY => null, - SchemaInterface::MAPPER => 'Cycle\\\\ORM\\\\Mapper\\\\Mapper', - SchemaInterface::SOURCE => null, - SchemaInterface::REPOSITORY => null, - SchemaInterface::DATABASE => 'default', - SchemaInterface::TABLE => 'tag_user_map', - SchemaInterface::PRIMARY_KEY => null, - SchemaInterface::FIND_BY_KEYS => null, - SchemaInterface::COLUMNS => [], - SchemaInterface::RELATIONS => [], - SchemaInterface::CHILDREN => null, - SchemaInterface::SCOPE => null, - SchemaInterface::TYPECAST => [ - 'id' => 'int', - 'user_id' => 'int', - 'tag_id' => 'int', - ], - SchemaInterface::SCHEMA => [], - 100 => 'Hello world', - 'hello' => 'world', -] -]; -EOL - , - $renderer->render() - ); - } - - public function testRenderSchemaWithCustomPropertyWithDefinedGeneratorToPhpCode(): void - { - $generator = new DefaultSchemaGenerator([ - 100 => new class implements Generator { - public function generate(array $schema, string $role): array - { - return [ - new VarExporter('Hello', 'World', true) - ]; - } - } - ]); - - $renderer = new SchemaToPhpFileRenderer([ - TagContext::class => [ - SchemaInterface::ROLE => 'tag_context', - SchemaInterface::MAPPER => Mapper::class, - SchemaInterface::DATABASE => 'default', - SchemaInterface::TABLE => 'tag_user_map', - SchemaInterface::COLUMNS => [], - SchemaInterface::TYPECAST => ['id' => 'int', 'user_id' => 'int', 'tag_id' => 'int'], - SchemaInterface::SCHEMA => [], - 100 => 'Hello world', - 'hello' => 'world', - ] - ], $generator); - - $this->assertSame( - << [ - 'Hello' => 'World', - SchemaInterface::ROLE => 'tag_context', - SchemaInterface::ENTITY => null, - SchemaInterface::MAPPER => 'Cycle\\\\ORM\\\\Mapper\\\\Mapper', - SchemaInterface::SOURCE => null, - SchemaInterface::REPOSITORY => null, - SchemaInterface::DATABASE => 'default', - SchemaInterface::TABLE => 'tag_user_map', - SchemaInterface::PRIMARY_KEY => null, - SchemaInterface::FIND_BY_KEYS => null, - SchemaInterface::COLUMNS => [], - SchemaInterface::RELATIONS => [], - SchemaInterface::CHILDREN => null, - SchemaInterface::SCOPE => null, - SchemaInterface::TYPECAST => [ - 'id' => 'int', - 'user_id' => 'int', - 'tag_id' => 'int', - ], - SchemaInterface::SCHEMA => [], - 'hello' => 'world', -] -]; -EOL - , - $renderer->render() - ); - } -}