diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..db6e8d8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI +on: [push, pull_request] + +jobs: + tests: + name: PHPUnit PHP ${{ matrix.php }} ${{ matrix.dependency }} (Symfony ${{ matrix.symfony }}) + runs-on: ubuntu-22.04 + strategy: + matrix: + php: + - '8.1' + - '8.2' + - '8.3' + symfony: + - '5.4.*' + - '6.4.*' + fail-fast: true + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + + - name: Configure Symfony + run: composer config extra.symfony.require "${{ matrix.symfony }}" + + - name: Update project dependencies + run: composer update --no-progress --ansi --prefer-stable + + - name: Validate composer + run: composer validate --strict --no-check-lock + + - name: PHP-CS-Fixer + run: vendor/bin/php-cs-fixer check -vv + + - name: PHPStan + run: vendor/bin/phpstan analyse diff --git a/.gitignore b/.gitignore index ed1d790..c2de9e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ composer.lock composer.phar +.php_cs.cache /vendor # Other diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..18c3340 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,14 @@ +in(__DIR__) +; + +return Retailcrm\PhpCsFixer\Defaults::rules([ + 'no_trailing_whitespace_in_string' => false, + ]) + ->setFinder($finder) + ->setCacheFile(__DIR__ . '/.php_cs.cache/results') +; diff --git a/Annotations/CustomIndex.php b/Annotations/CustomIndex.php index cb829dd..7cfbfe9 100644 --- a/Annotations/CustomIndex.php +++ b/Annotations/CustomIndex.php @@ -6,6 +6,7 @@ /** * @deprecated left only for automatic conversion of annotations to attributes + * * @Annotation * @Target("ANNOTATION") * @@ -47,5 +48,4 @@ class CustomIndex extends Annotation * @var array */ public $columns; - } diff --git a/Annotations/CustomIndexes.php b/Annotations/CustomIndexes.php index 4b9cdd3..878a536 100644 --- a/Annotations/CustomIndexes.php +++ b/Annotations/CustomIndexes.php @@ -6,6 +6,7 @@ /** * @deprecated left only for automatic conversion of annotations to attributes + * * @Annotation * @Target("CLASS") */ @@ -13,11 +14,11 @@ class CustomIndexes extends Annotation { /** * @CustomIndexes(indexes=[ + * * @CustomIndex(...), * @CustomIndex(...), * ... * ]) - **/ + **/ public $indexes = []; - } diff --git a/Command/IndexUpdateCommand.php b/Command/IndexUpdateCommand.php index f21e28e..3a30cf7 100644 --- a/Command/IndexUpdateCommand.php +++ b/Command/IndexUpdateCommand.php @@ -1,13 +1,14 @@ $indexesNames + * @param array $indexesNames * @param array $customIndexes */ private function createIndexes(array $indexesNames, array $customIndexes, ExtendedPlatform $platform): void @@ -69,12 +70,12 @@ private function createIndexes(array $indexesNames, array $customIndexes, Extend } } if (!$createFlag) { - $this->output->writeln("No index was created"); + $this->output->writeln('No index was created'); } } /** - * @param array $indexesNames + * @param array $indexesNames * @param array $customIndexes */ private function dropIndexes(array $indexesNames, array $customIndexes, ExtendedPlatform $platform): void @@ -88,7 +89,7 @@ private function dropIndexes(array $indexesNames, array $customIndexes, Extended } if (!$dropFlag) { - $this->output->writeln("No index was dropped."); + $this->output->writeln('No index was dropped.'); } } @@ -101,7 +102,7 @@ private function dropIndex(ExtendedPlatform $platform, string $indexName): void } $this->queryExecutor->dropIndex($platform, $indexName); - $this->output->writeln("Index ". $indexName ." was dropped."); + $this->output->writeln('Index ' . $indexName . ' was dropped.'); } private function createIndex(ExtendedPlatform $platform, CustomIndex $index): void @@ -110,26 +111,28 @@ private function createIndex(ExtendedPlatform $platform, CustomIndex $index): vo if (!count($errors)) { if ($this->input->getOption(self::DUMP_SQL_OPTION)) { $this->output->writeln($platform->createIndexSQL($index) . ';'); + return; } $this->queryExecutor->createIndex($platform, $index); - $this->output->writeln("Index ". $index->getName() ." was created."); + $this->output->writeln('Index ' . $index->getName() . ' was created.'); return; } - $this->output->writeln("Index ". $index->getName() ." was not created."); + $this->output->writeln('Index ' . $index->getName() . ' was not created.'); foreach ($errors as $error) { - $this->output->writeln("". $error->getMessage() .""); + $this->output->writeln('' . $error->getMessage() . ''); } } private function quoteSchema(string $name): string { $parts = explode('.', $name); - $parts[0] = '"'.$parts[0].'"'; + $parts[0] = '"' . $parts[0] . '"'; + return implode('.', $parts); } @@ -137,7 +140,7 @@ private function createExtendedPlatform(AbstractPlatform $platform): ExtendedPla { return match (true) { $platform instanceof PostgreSQLPlatform => new ExtendedPlatform(), - default => throw new \LogicException(sprintf("Platform %s does not support", $platform::class)), + default => throw new \LogicException(sprintf('Platform %s does not support', $platform::class)), }; } } diff --git a/DBAL/ExtendedPlatform.php b/DBAL/ExtendedPlatform.php index 84d5afd..cd3d45e 100644 --- a/DBAL/ExtendedPlatform.php +++ b/DBAL/ExtendedPlatform.php @@ -9,19 +9,19 @@ final class ExtendedPlatform extends PostgreSQLPlatform { public function createIndexSQL(CustomIndex $index): string { - $sql = 'CREATE '; + $sql = 'CREATE'; if ($index->getUnique()) { - $sql .= 'UNIQUE '; + $sql .= ' UNIQUE'; } - $sql .= 'INDEX ' . $index->getName() . ' '; - $sql .= 'ON ' . $index->getTableName() . ' '; + $sql .= ' INDEX ' . $index->getName(); + $sql .= ' ON ' . $index->getTableName(); if ($index->getUsing()) { - $sql .= 'USING ' . $index->getUsing() . ' '; + $sql .= ' USING ' . $index->getUsing(); } - $sql .= '(' . implode(', ', $index->getColumns()) . ')'; + $sql .= ' (' . implode(', ', $index->getColumns()) . ')'; if ($index->getWhere()) { $sql .= ' WHERE ' . $index->getWhere(); @@ -44,7 +44,7 @@ public function indexesNamesSelectSQL(bool $searchInAllSchemas): string { $sql = "SELECT schemaname || '.' || indexname as relname FROM pg_indexes WHERE indexname LIKE :indexName"; if (!$searchInAllSchemas) { - $sql .= " AND schemaname = current_schema()"; + $sql .= ' AND schemaname = current_schema()'; } return $sql; diff --git a/DTO/CustomIndex.php b/DTO/CustomIndex.php index 3ab3b20..891e5f6 100644 --- a/DTO/CustomIndex.php +++ b/DTO/CustomIndex.php @@ -12,14 +12,17 @@ final class CustomIndex #[Assert\Length(min: 1, max: 63, minMessage: 'Name must be set', maxMessage: 'Name is too long')] private ?string $name; + + /** @var string[] */ #[Assert\Count(min: 1, minMessage: 'You must specify at least one column')] #[Assert\All([ new Assert\Type([ - 'type' =>'string', + 'type' => 'string', 'message' => 'Column should be type of string', ]), ])] private array $columns = []; + private bool $unique; #[AllowedIndexType] private ?string $using; @@ -29,6 +32,9 @@ final class CustomIndex private string $schema; private string $currentSchema; + /** + * @param string[]|string $columns + */ public function __construct( string $tableName, string $schema, @@ -75,6 +81,9 @@ public function getName(): ?string return $this->name; } + /** + * @return string[] + */ public function getColumns(): array { return $this->columns; @@ -104,12 +113,14 @@ private function generateName(): void } $strToMd5 .= $this->getUsing() . ($this->getWhere() ? '_' . $this->getWhere() : ''); - $name = self::PREFIX . ( $this->getUnique() ? self::UNIQUE . '_' : '' ) . md5($strToMd5); + $name = self::PREFIX . ($this->getUnique() ? self::UNIQUE . '_' : '') . md5($strToMd5); $this->setName($name); - } - private function setColumns($columns): void + /** + * @param string[]|string $columns + */ + private function setColumns(array|string $columns): void { if (is_string($columns)) { $columns = [$columns]; @@ -123,7 +134,7 @@ private function setColumns($columns): void } } - private function setName($name): void + private function setName(string $name): void { if (!str_starts_with($name, self::PREFIX)) { $name = self::PREFIX . $name; diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 2ef4883..4628ee8 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -9,9 +9,6 @@ class Configuration implements ConfigurationInterface { private const AVAILABLE_INDEX_TYPES = ['btree', 'hash', 'gin', 'gist']; - /** - * {@inheritDoc} - */ public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('intaro_custom_index'); @@ -27,13 +24,14 @@ public function getConfigTreeBuilder(): TreeBuilder ->prototype('scalar') ->validate() ->ifNotInArray(self::AVAILABLE_INDEX_TYPES) - ->thenInvalid("Unknown index type. Allowed types: ".implode(', ', self::AVAILABLE_INDEX_TYPES).".") + ->thenInvalid('Unknown index type. Allowed types: ' . implode(', ', self::AVAILABLE_INDEX_TYPES) . '.') ->end() ->end() ->cannotBeEmpty() ->defaultValue(self::AVAILABLE_INDEX_TYPES) ->end() - ->end(); + ->end() + ; return $treeBuilder; } diff --git a/DependencyInjection/IntaroCustomIndexExtension.php b/DependencyInjection/IntaroCustomIndexExtension.php index 6966e1d..5d91e3f 100644 --- a/DependencyInjection/IntaroCustomIndexExtension.php +++ b/DependencyInjection/IntaroCustomIndexExtension.php @@ -2,19 +2,16 @@ namespace Intaro\CustomIndexBundle\DependencyInjection; -use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\HttpKernel\DependencyInjection\Extension; class IntaroCustomIndexExtension extends Extension { - /** - * {@inheritDoc} - */ public function load(array $configs, ContainerBuilder $container): void { - $loader = new PhpFileLoader($container, new FileLocator(dirname(__DIR__).'/config')); + $loader = new PhpFileLoader($container, new FileLocator(dirname(__DIR__) . '/config')); $loader->load('di.php'); $configuration = new Configuration(); diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fe601e8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +ARG PHP_IMAGE_TAG +FROM php:${PHP_IMAGE_TAG}-cli-alpine + +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer + +WORKDIR /opt/test diff --git a/IntaroCustomIndexBundle.php b/IntaroCustomIndexBundle.php index 30b4bd2..745e421 100644 --- a/IntaroCustomIndexBundle.php +++ b/IntaroCustomIndexBundle.php @@ -6,4 +6,4 @@ class IntaroCustomIndexBundle extends Bundle { -} \ No newline at end of file +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dd41bab --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +PHP=docker-compose run --rm --no-deps php + +vendor: composer.json + @$(PHP) composer install -o -n --no-ansi + @touch vendor || true + +php-cs: vendor + @$(PHP) vendor/bin/php-cs-fixer fix --using-cache=no -vv + +phpstan: vendor + @$(PHP) vendor/bin/phpstan analyse + +check: php-cs phpstan diff --git a/Metadata/Reader.php b/Metadata/Reader.php index b2f7609..3fd2212 100644 --- a/Metadata/Reader.php +++ b/Metadata/Reader.php @@ -7,6 +7,9 @@ use Doctrine\ORM\Mapping\ClassMetadataInfo; use Intaro\CustomIndexBundle\DTO\CustomIndex; +/** + * @template T of object + */ final class Reader implements ReaderInterface { public function __construct(private readonly EntityManagerInterface $em) @@ -34,13 +37,17 @@ public function getIndexes(string $currentSchema, bool $searchInAllSchemas): arr return $indexNamesToCustomIndexes; } + /** + * @param array $indexNamesToCustomIndexes + * @param ClassMetadata $metadata + */ private function collect( array &$indexNamesToCustomIndexes, ClassMetadata $metadata, string $tableName, string $currentSchema, bool $searchInAllSchemas, - bool $tablePostfix = false + bool $tablePostfix = false, ): void { $reflectionAttributes = $this->getCustomIndexesAttributes($metadata); if (empty($reflectionAttributes)) { @@ -72,29 +79,40 @@ private function collect( } } + /** + * @param ClassMetadata $metadata + * @param ClassMetadata $parentMetadata + */ private function getTableNameFromMetadata(ClassMetadata $metadata, ClassMetadata $parentMetadata): string { - if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_JOINED) { + if (ClassMetadataInfo::INHERITANCE_TYPE_JOINED === $metadata->inheritanceType) { return $parentMetadata->getTableName(); } return $metadata->getTableName(); } - /** @return array<\ReflectionAttribute> */ + /** + * @param ClassMetadata $meta + * + * @return array<\ReflectionAttribute> + */ private function getCustomIndexesAttributes(ClassMetadata $meta): array { - return $meta->getReflectionClass()->getAttributes(\Intaro\CustomIndexBundle\Metadata\Attribute\CustomIndex::class); + return $meta->getReflectionClass()->getAttributes(Attribute\CustomIndex::class); } + /** + * @param ClassMetadata $meta + */ private function isAbstract(ClassMetadata $meta): bool { return $meta->getReflectionClass()->isAbstract(); } - /** - * @param array $metadata + * @param array> $metadata + * * @return array */ private function getAbstractClassesInfo(array $metadata): array @@ -110,7 +128,9 @@ private function getAbstractClassesInfo(array $metadata): array } /** + * @param ClassMetadata $meta * @param array $abstractClasses + * * @return array */ private function searchParentsWithIndex(ClassMetadata $meta, array $abstractClasses): array diff --git a/Validator/Constraints/AllowedIndexType.php b/Validator/Constraints/AllowedIndexType.php index ca265ca..73f2f3a 100644 --- a/Validator/Constraints/AllowedIndexType.php +++ b/Validator/Constraints/AllowedIndexType.php @@ -2,10 +2,9 @@ namespace Intaro\CustomIndexBundle\Validator\Constraints; -use Attribute; use Symfony\Component\Validator\Constraint; -#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_METHOD)] +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD)] class AllowedIndexType extends Constraint { public string $message = 'Index type {{ type }} is not allowed. List of allowed types: {{ allowed_types }}.'; diff --git a/Validator/Constraints/AllowedIndexTypeValidator.php b/Validator/Constraints/AllowedIndexTypeValidator.php index 45cfea4..c4b8a88 100644 --- a/Validator/Constraints/AllowedIndexTypeValidator.php +++ b/Validator/Constraints/AllowedIndexTypeValidator.php @@ -15,9 +15,6 @@ public function __construct(private readonly array $allowedIndexTypes) { } - /** - * {@inheritdoc} - */ public function validate(mixed $value, Constraint $constraint): void { if (!$constraint instanceof AllowedIndexType) { diff --git a/composer.json b/composer.json index cc492c6..cb79799 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,11 @@ "symfony/http-kernel": "^5.0 || ^6.0", "symfony/validator": "^5.0 || ^6.0" }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.59", + "phpstan/phpstan": "^1.11", + "retailcrm/php-code-style": "^1.0" + }, "autoload": { "psr-4": { "Intaro\\CustomIndexBundle\\": "" diff --git a/config/di.php b/config/di.php index 6ea0deb..0daa9fb 100644 --- a/config/di.php +++ b/config/di.php @@ -5,6 +5,7 @@ use Intaro\CustomIndexBundle\Metadata\Reader; use Intaro\CustomIndexBundle\Validator\Constraints\AllowedIndexTypeValidator; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + use function Symfony\Component\DependencyInjection\Loader\Configurator\service; return static function (ContainerConfigurator $container): void { diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1e1f9f0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +services: + php: + build: + context: . + args: + PHP_IMAGE_TAG: ${PHP_IMAGE_TAG:-8.1} + volumes: + - "./:/opt/test" diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..bc4477e --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,10 @@ +parameters: + level: 8 + paths: + - Command/ + - config/ + - DBAL/ + - DTO/ + - Metadata/ + - Validator/ + - IntaroCustomIndexBundle.php