diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 8aca7aa..f3810fa 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -41,6 +41,6 @@ jobs: run: make php/tests # To generate the coverage report - name: SonarQube Scan - uses: SonarSource/sonarqube-scan-action@v5.2.0 + uses: SonarSource/sonarqube-scan-action@v6.0.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 87d2ce9..1eb221a 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -4,7 +4,7 @@ //~ Rules ->setRules( [ - '@PER-CS2.0' => true, + '@PER-CS3x0' => true, ] ) diff --git a/composer.json b/composer.json index 0c3f840..eb7d3c4 100644 --- a/composer.json +++ b/composer.json @@ -42,13 +42,13 @@ }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.86.0", - "phpstan/phpstan": "^2.1.22", + "friendsofphp/php-cs-fixer": "^3.89.2", + "phpstan/phpstan": "^2.1.31", "phpstan/phpstan-phpunit": "^2.0.7", - "phpstan/phpstan-strict-rules": "^2.0.6", - "phpunit/phpcov": "^11.0.1", - "phpunit/phpunit": "^12.3.6", - "symfony/cache": "^7.3.2", + "phpstan/phpstan-strict-rules": "^2.0.7", + "phpunit/phpcov": "^11.0.2", + "phpunit/phpunit": "^12.4.2", + "symfony/cache": "^7.3.6", "shipmonk/composer-dependency-analyser": "^1.8.3" } } diff --git a/src/EntityAwareInterface.php b/src/EntityAwareInterface.php index 9e32945..2653a55 100644 --- a/src/EntityAwareInterface.php +++ b/src/EntityAwareInterface.php @@ -35,7 +35,7 @@ public function disableIgnoreNotMappedFields(): static; * @param bool $exists * @return TEntity */ - public function newEntity(\stdClass|null $row = null, bool $exists = false): object; + public function newEntity(?\stdClass $row = null, bool $exists = false): object; /** * Create new entity from array. diff --git a/src/EntityInterface.php b/src/EntityInterface.php index d26f960..826ed4f 100644 --- a/src/EntityInterface.php +++ b/src/EntityInterface.php @@ -78,7 +78,7 @@ public function setExists(bool $exists): static; * @param string|null $property * @return bool */ - public function isUpdated(string|null $property = null): bool; + public function isUpdated(?string $property = null): bool; /** * Reset updated list of properties diff --git a/src/Generator/Compiler/Field/FieldValidatorService.php b/src/Generator/Compiler/Field/FieldValidatorService.php index 5cc2714..99ac28a 100644 --- a/src/Generator/Compiler/Field/FieldValidatorService.php +++ b/src/Generator/Compiler/Field/FieldValidatorService.php @@ -112,21 +112,21 @@ private function getValidatorOptionsFromType(Field $field, Type\TypeInterface $t private function getIntegerOptions(string $typeClass, bool $isUnsigned): array { return match ($typeClass) { - Type\TypeBigint::class => $isUnsigned ? - IntegerValidator::BIGINT_UNSIGNED : - IntegerValidator::BIGINT_SIGNED, - Type\TypeInt::class => $isUnsigned ? - IntegerValidator::INT_UNSIGNED : - IntegerValidator::INT_SIGNED, - Type\TypeMediumint::class => $isUnsigned ? - IntegerValidator::MEDIUMINT_UNSIGNED : - IntegerValidator::MEDIUMINT_SIGNED, - Type\TypeSmallint::class => $isUnsigned ? - IntegerValidator::SMALLINT_UNSIGNED : - IntegerValidator::SMALLINT_SIGNED, - Type\TypeTinyint::class => $isUnsigned ? - IntegerValidator::TINYINT_UNSIGNED : - IntegerValidator::TINYINT_SIGNED, + Type\TypeBigint::class => $isUnsigned + ? IntegerValidator::BIGINT_UNSIGNED + : IntegerValidator::BIGINT_SIGNED, + Type\TypeInt::class => $isUnsigned + ? IntegerValidator::INT_UNSIGNED + : IntegerValidator::INT_SIGNED, + Type\TypeMediumint::class => $isUnsigned + ? IntegerValidator::MEDIUMINT_UNSIGNED + : IntegerValidator::MEDIUMINT_SIGNED, + Type\TypeSmallint::class => $isUnsigned + ? IntegerValidator::SMALLINT_UNSIGNED + : IntegerValidator::SMALLINT_SIGNED, + Type\TypeTinyint::class => $isUnsigned + ? IntegerValidator::TINYINT_UNSIGNED + : IntegerValidator::TINYINT_SIGNED, default => [], }; } diff --git a/src/Generator/Templates/AbstractMapper.template b/src/Generator/Templates/AbstractMapper.template index 149ae14..334f975 100644 --- a/src/Generator/Templates/AbstractMapper.template +++ b/src/Generator/Templates/AbstractMapper.template @@ -47,10 +47,10 @@ abstract class Abstract{{ class.mapper }} public function __construct( string $connectionName, ConnectionFactory $connectionFactory, - ValidatorFactoryInterface|null $validatorFactory = null, - ValidatorEntityFactory|null $validatorEntityFactory = null, + ?ValidatorFactoryInterface $validatorFactory = null, + ?ValidatorEntityFactory $validatorEntityFactory = null, array $mappers = [], - CacheItemPoolInterface|null $cache = null, + ?CacheItemPoolInterface $cache = null, bool $enableCacheOnRead = false, ) { $this->setConnectionName($connectionName); diff --git a/src/Query/InsertBuilder.php b/src/Query/InsertBuilder.php index 83a6b21..d6eeb77 100644 --- a/src/Query/InsertBuilder.php +++ b/src/Query/InsertBuilder.php @@ -56,10 +56,10 @@ public function getQuery(bool $onDuplicateUpdate = false, bool $onDuplicateIgnor $this->appendUpdateValues(); } - return 'INSERT ' . $onDuplicateIgnoreClause . - 'INTO ' . $this->repository->getTable() . - $this->getQuerySet() . - $this->getQueryDuplicateUpdate() + return 'INSERT ' . $onDuplicateIgnoreClause + . 'INTO ' . $this->repository->getTable() + . $this->getQuerySet() + . $this->getQueryDuplicateUpdate() ; } diff --git a/src/Query/QueryBuilder.php b/src/Query/QueryBuilder.php index 13c4466..725e23c 100644 --- a/src/Query/QueryBuilder.php +++ b/src/Query/QueryBuilder.php @@ -24,13 +24,13 @@ class QueryBuilder extends SelectBuilder */ public function getQuery(bool $usePrefix = false, string $prefix = '', bool $onlyPrimaryKey = false): string { - return 'SELECT ' . $this->getQueryFieldsPersonalized() . - $this->getQueryFromPersonalized() . - $this->getQueryWhere() . - $this->getQueryGroupBy() . - $this->getQueryHaving() . - $this->getQueryOrderBy() . - $this->getQueryLimit() + return 'SELECT ' . $this->getQueryFieldsPersonalized() + . $this->getQueryFromPersonalized() + . $this->getQueryWhere() + . $this->getQueryGroupBy() + . $this->getQueryHaving() + . $this->getQueryOrderBy() + . $this->getQueryLimit() ; } @@ -43,10 +43,10 @@ public function getQuery(bool $usePrefix = false, string $prefix = '', bool $onl */ public function getQueryCount(string $field = '*'): string { - return 'SELECT COUNT(' . $field . ') AS nb_results' . - $this->getQueryFrom($this->repository) . - $this->getQueryWhere() . - $this->getQueryGroupBy() + return 'SELECT COUNT(' . $field . ') AS nb_results' + . $this->getQueryFrom($this->repository) + . $this->getQueryWhere() + . $this->getQueryGroupBy() ; } } diff --git a/src/Query/SelectBuilder.php b/src/Query/SelectBuilder.php index 9e14885..ffd11be 100644 --- a/src/Query/SelectBuilder.php +++ b/src/Query/SelectBuilder.php @@ -59,13 +59,13 @@ public function clear(bool $forNotCached = false): static */ public function getQuery(bool $usePrefix = false, string $prefix = '', bool $onlyPrimaryKey = false): string { - return 'SELECT ' . $this->getQueryFields($this->repository, $usePrefix, $onlyPrimaryKey) . - $this->getQueryFrom($this->repository) . - $this->getQueryJoin() . - $this->getQueryWhere() . - $this->getQueryGroupBy() . - $this->getQueryHaving() . - $this->getQueryOrderBy() . - $this->getQueryLimit(); + return 'SELECT ' . $this->getQueryFields($this->repository, $usePrefix, $onlyPrimaryKey) + . $this->getQueryFrom($this->repository) + . $this->getQueryJoin() + . $this->getQueryWhere() + . $this->getQueryGroupBy() + . $this->getQueryHaving() + . $this->getQueryOrderBy() + . $this->getQueryLimit(); } } diff --git a/src/Query/UpdateBuilder.php b/src/Query/UpdateBuilder.php index 9a1db77..7bd7fd4 100644 --- a/src/Query/UpdateBuilder.php +++ b/src/Query/UpdateBuilder.php @@ -57,6 +57,11 @@ public function getQuery(): string continue; } + //~ Skip fields that are not updated + if (!$this->repository->isEntityUpdated($this->entity, $field)) { + continue; + } + $this->addSet($field, $value); } diff --git a/src/Traits/CacheAwareTrait.php b/src/Traits/CacheAwareTrait.php index d8fb7c3..ae57d7a 100644 --- a/src/Traits/CacheAwareTrait.php +++ b/src/Traits/CacheAwareTrait.php @@ -72,7 +72,7 @@ public function disableCacheOnRead(): static * @param CacheItemPoolInterface|null $cache * @return static */ - protected function setCache(CacheItemPoolInterface|null $cache = null): static + protected function setCache(?CacheItemPoolInterface $cache = null): static { $this->cache = $cache; diff --git a/src/Traits/EntityAwareTrait.php b/src/Traits/EntityAwareTrait.php index c3aafde..81bb285 100644 --- a/src/Traits/EntityAwareTrait.php +++ b/src/Traits/EntityAwareTrait.php @@ -11,10 +11,8 @@ namespace Eureka\Component\Orm\Traits; -use Eureka\Component\Orm\EntityAwareInterface; use Eureka\Component\Orm\EntityInterface; use Eureka\Component\Orm\Enumerator\JoinType; -use Eureka\Component\Orm\RepositoryInterface; use Eureka\Component\Validation\Entity\GenericEntity; /** @@ -61,7 +59,7 @@ public function setEntityClass(string $entityClass): static * * @phpstan-return TEntity */ - public function newEntity(\stdClass|null $row = null, bool $exists = false): object + public function newEntity(?\stdClass $row = null, bool $exists = false): object { $entity = new $this->entityClass($this, $this->getValidatorFactory(), $this->getValidatorEntityFactory()); diff --git a/src/Traits/MapperTrait.php b/src/Traits/MapperTrait.php index 53ce3f3..e696144 100644 --- a/src/Traits/MapperTrait.php +++ b/src/Traits/MapperTrait.php @@ -191,6 +191,7 @@ public function query(QueryBuilderInterface $queryBuilder): array ); } + /** @var string|int $index */ $index = !empty($indexedBy) ? $row->{$indexedBy} : $id++; $entity = $this->newEntity($row, true); $collection[$index] = $entity; @@ -501,7 +502,9 @@ private function setIndexFieldsOnCollection(string $listIndexedBy, array $rawCol $nameMap = $this->getNamesMap($listIndexedBy); $getter = $nameMap['get']; foreach ($rawCollection as $item) { - $collection[$item->$getter()] = $item; + /** @var string|int $index */ + $index = $item->$getter(); + $collection[$index] = $item; } return $collection; diff --git a/tests/unit/QueryBuilder/QueryBuilderTest.php b/tests/unit/QueryBuilder/QueryBuilderTest.php index 624dfc2..875d10c 100644 --- a/tests/unit/QueryBuilder/QueryBuilderTest.php +++ b/tests/unit/QueryBuilder/QueryBuilderTest.php @@ -368,6 +368,33 @@ public function testIHaveAnExceptionWhenITryToGetQueryOnUpdateBuilderWithoutEnti $queryBuilder->getQuery(); } + /** + * @return void + * @throws OrmException + */ + public function testIHavePartialQueryUpdateWhenOnlySomePropertyAreUpdated(): void + { + $repository = $this->getUserRepository($this->getMockEntityFindAll()); + $user = $repository->newEntity( + (object) [ + 'user_id' => 1, + 'user_is_enabled' => true, + 'user_email' => 'user@example.com', + 'user_password' => md5('password'), + 'user_date_create' => '2020-01-01 10:00:00', + 'user_date_update' => null, + ], + true, + ); + + $user->setDateUpdate('2020-01-01 10:00:00'); + $queryBuilder = new UpdateBuilder($repository, $user); + + $suffix = '[a-z0-9]{13}'; + $patternQuery = "#UPDATE user SET `user_date_update` = :user_date_update_$suffix WHERE user_id = :user_id_$suffix#"; + self::assertMatchesRegularExpression($patternQuery, $queryBuilder->getQuery()); + } + /** * @param array $entityMock * @return ConnectionFactory