diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..b18fd29357 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'weekly' diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index ebc92647ae..ac585b1f16 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -13,7 +13,7 @@ jobs: steps: - name: "Checkout" - uses: "actions/checkout@v3" + uses: "actions/checkout@v4" - name: "Install PHP" uses: "shivammathur/setup-php@v2" @@ -27,7 +27,7 @@ jobs: dependency-versions: "highest" - name: "Run PHP-CS-Fixer" - run: "bin/php-cs-fixer fix --ansi --verbose --diff --dry-run" + run: "vendor/bin/php-cs-fixer fix --ansi --verbose --diff --dry-run" rector: name: "Rector" @@ -35,7 +35,7 @@ jobs: steps: - name: "Checkout" - uses: "actions/checkout@v3" + uses: "actions/checkout@v4" - name: "Install PHP" uses: shivammathur/setup-php@v2 @@ -51,7 +51,7 @@ jobs: composer-options: "--prefer-dist --prefer-stable" - name: Rector - run: "bin/rector --no-progress-bar --dry-run" + run: "vendor/bin/rector --no-progress-bar --dry-run" composer: name: Composer @@ -60,7 +60,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install PHP with extensions uses: shivammathur/setup-php@v2 @@ -81,7 +81,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install required dependencies run: sudo apt-get update && sudo apt-get install libxml2-utils @@ -96,7 +96,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Ruby 3.0 uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 794714aa26..200f7cb755 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -11,7 +11,7 @@ env: jobs: phpunit: - name: "PHPUnit ${{ matrix.php-version }} (${{ matrix.deps }})${{ matrix.dbal-version && format(' - DBAL {0}', matrix.dbal-version) || '' }}" + name: "PHPUnit ${{ matrix.php-version }} (${{ matrix.deps }})" runs-on: "ubuntu-20.04" services: @@ -29,23 +29,15 @@ jobs: - "8.2" deps: - "highest" - dbal-version: - - "" include: - deps: "lowest" php-version: "7.4" - - deps: "highest" - php-version: "8.2" - dbal-version: "^2.13.1" - - deps: "highest" - php-version: "8.2" - dbal-version: "^3.2" - deps: "highest" php-version: "8.1" steps: - name: "Checkout" - uses: "actions/checkout@v3" + uses: "actions/checkout@v4" with: fetch-depth: 2 @@ -59,10 +51,6 @@ jobs: - name: "Temp: test Symfony 7 RC" run: "composer config minimum-stability RC" - - name: "Restrict DBAL version" - if: "${{ matrix.dbal-version }}" - run: "composer require --dev --no-update doctrine/dbal:${{ matrix.dbal-version }}" - # Remove PHP-CS-Fixer to avoid conflicting dependency ranges (i.e. doctrine/annotations) - name: "Remove PHP-CS-Fixer" run: "composer remove --dev --no-update friendsofphp/php-cs-fixer" @@ -73,7 +61,7 @@ jobs: dependency-versions: "${{ matrix.deps }}" - name: "Run PHPUnit" - run: "bin/phpunit -c tests --coverage-clover coverage.xml" + run: "vendor/bin/phpunit -c tests --coverage-clover coverage.xml" - name: "Upload coverage file" uses: "actions/upload-artifact@v3" @@ -88,7 +76,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Install PHP" uses: "shivammathur/setup-php@v2" @@ -115,7 +103,7 @@ jobs: steps: - name: "Checkout" - uses: "actions/checkout@v3" + uses: "actions/checkout@v4" with: fetch-depth: 2 diff --git a/.github/workflows/qa-dockerfile.yml b/.github/workflows/qa-dockerfile.yml index d78f441473..a8c86903ea 100644 --- a/.github/workflows/qa-dockerfile.yml +++ b/.github/workflows/qa-dockerfile.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Lint Dockerfile uses: hadolint/hadolint-action@v3.1.0 @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-latest name: Build containers with Docker Compose steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build "php" container uses: isbang/compose-action@v1.5.1 diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index d3fee8d496..3bdd5c5f0a 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install PHP with extensions uses: shivammathur/setup-php@v2 @@ -31,4 +31,4 @@ jobs: composer-options: --prefer-dist --prefer-stable --no-interaction --no-progress - name: PHPStan - run: bin/phpstan --memory-limit=1G analyse --error-format=github + run: vendor/bin/phpstan --memory-limit=1G analyse --error-format=github diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c45197a3e..0d58794624 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,9 +18,13 @@ a release. --- ## [Unreleased] +### Added +- Tree: Added `@template` and `@template-extends` annotations to the Tree repositories + ### Changed - Dropped support for PHP < 7.4 - Dropped support for Symfony < 5.4 +- Dropped support for doctrine/dbal < 3.2 ### Deprecated - Calling `Gedmo\Mapping\Event\Adapter\ORM::getObjectManager()` and `getObject()` on EventArgs that do not implement `getObjectManager()` and `getObject()` (such as old EventArgs implementing `getEntityManager()` and `getEntity()`) diff --git a/README.md b/README.md index 27ff2bd9bb..14fdf65fdb 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ To set up and run the tests, follow these steps: - From the project root, run `docker compose up -d` to start containers in daemon mode - Enter the container via `docker compose exec php bash` (you are now in the root directory: `/var/www`) - Install Composer dependencies via `composer install` -- Run the tests: `bin/phpunit -c tests/` +- Run the tests: `vendor/bin/phpunit -c tests/` ### Running the Example diff --git a/composer.json b/composer.json index 5ea599cc8a..c2352369a2 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,7 @@ }, "require-dev": { "doctrine/cache": "^1.11 || ^2.0", - "doctrine/dbal": "^2.13.1 || ^3.2", + "doctrine/dbal": "^3.2", "doctrine/doctrine-bundle": "^2.3", "doctrine/mongodb-odm": "^2.3", "doctrine/orm": "^2.14.0", @@ -69,7 +69,7 @@ "symfony/yaml": "^5.4 || ^6.0 || ^7.0" }, "conflict": { - "doctrine/dbal": "<2.13.1 || ^3.0 <3.2", + "doctrine/dbal": "<3.2", "doctrine/mongodb-odm": "<2.3", "doctrine/orm": "<2.14.0 || 2.16.0 || 2.16.1", "sebastian/comparator": "<2.0" @@ -89,7 +89,6 @@ } }, "config": { - "bin-dir": "bin", "sort-packages": true }, "extra": { diff --git a/example/app/Entity/Category.php b/example/app/Entity/Category.php index 47ea051479..bd51a48b90 100644 --- a/example/app/Entity/Category.php +++ b/example/app/Entity/Category.php @@ -11,7 +11,9 @@ namespace App\Entity; +use App\Entity\Repository\CategoryRepository; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Gedmo\Mapping\Annotation as Gedmo; @@ -23,6 +25,10 @@ * * @Gedmo\TranslationEntity(class="App\Entity\CategoryTranslation") */ +#[Gedmo\Tree(type: 'nested')] +#[ORM\Table(name: 'ext_categories')] +#[ORM\Entity(repositoryClass: CategoryRepository::class)] +#[Gedmo\TranslationEntity(class: CategoryTranslation::class)] class Category { /** @@ -30,6 +36,9 @@ class Category * @ORM\Id * @ORM\GeneratedValue */ + #[ORM\Column(type: Types::INTEGER)] + #[ORM\Id] + #[ORM\GeneratedValue] private $id; /** @@ -39,6 +48,8 @@ class Category * * @ORM\Column(length=64) */ + #[Gedmo\Translatable] + #[ORM\Column(length: 64)] private $title; /** @@ -48,6 +59,8 @@ class Category * * @ORM\Column(type="text", nullable=true) */ + #[Gedmo\Translatable] + #[ORM\Column(type: Types::TEXT, nullable: true)] private $description; /** @@ -58,6 +71,9 @@ class Category * * @ORM\Column(length=64, unique=true) */ + #[Gedmo\Translatable] + #[Gedmo\Slug(fields: ['created', 'title'])] + #[ORM\Column(length: 64, unique: true)] private $slug; /** @@ -65,6 +81,8 @@ class Category * * @ORM\Column(type="integer") */ + #[Gedmo\TreeLeft] + #[ORM\Column(type: Types::INTEGER)] private $lft; /** @@ -72,6 +90,8 @@ class Category * * @ORM\Column(type="integer") */ + #[Gedmo\TreeRight] + #[ORM\Column(type: Types::INTEGER)] private $rgt; /** @@ -80,6 +100,9 @@ class Category * @ORM\ManyToOne(targetEntity="Category", inversedBy="children") * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE") */ + #[Gedmo\TreeParent] + #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] + #[ORM\JoinColumn(name: 'parent_id', referencedColumnName: 'id', onDelete: 'CASCADE')] private $parent; /** @@ -87,6 +110,8 @@ class Category * * @ORM\Column(type="integer", nullable=true) */ + #[Gedmo\TreeRoot] + #[ORM\Column(type: Types::INTEGER, nullable: true)] private $root; /** @@ -94,11 +119,14 @@ class Category * * @ORM\Column(name="lvl", type="integer") */ + #[Gedmo\TreeLevel] + #[ORM\Column(name: 'lvl', type: Types::INTEGER)] private $level; /** * @ORM\OneToMany(targetEntity="Category", mappedBy="parent") */ + #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] private Collection $children; /** @@ -106,6 +134,8 @@ class Category * * @ORM\Column(type="datetime") */ + #[Gedmo\Timestampable(on: 'create')] + #[ORM\Column(type: Types::DATETIME_MUTABLE)] private $created; /** @@ -113,6 +143,8 @@ class Category * * @ORM\Column(type="datetime") */ + #[Gedmo\Timestampable(on: 'update')] + #[ORM\Column(type: Types::DATETIME_MUTABLE)] private $updated; /** @@ -120,6 +152,8 @@ class Category * * @ORM\Column(type="string") */ + #[Gedmo\Blameable(on: 'create')] + #[ORM\Column(type: Types::STRING)] private $createdBy; /** @@ -127,6 +161,8 @@ class Category * * @ORM\Column(type="string") */ + #[Gedmo\Blameable(on: 'update')] + #[ORM\Column(type: Types::STRING)] private $updatedBy; /** @@ -136,6 +172,7 @@ class Category * cascade={"persist", "remove"} * ) */ + #[ORM\OneToMany(targetEntity: CategoryTranslation::class, mappedBy: 'object', cascade: ['persist', 'remove'])] private $translations; public function __construct() diff --git a/example/app/Entity/CategoryTranslation.php b/example/app/Entity/CategoryTranslation.php index b46128e3e1..2f28b50045 100644 --- a/example/app/Entity/CategoryTranslation.php +++ b/example/app/Entity/CategoryTranslation.php @@ -22,16 +22,21 @@ * })} * ) */ +#[ORM\Entity] +#[ORM\Table(name: 'category_translations')] +#[ORM\UniqueConstraint(name: 'lookup_unique_idx', columns: ['locale', 'object_id', 'field'])] class CategoryTranslation extends AbstractPersonalTranslation { /** * @ORM\ManyToOne(targetEntity="Category", inversedBy="translations") * @ORM\JoinColumn(name="object_id", referencedColumnName="id", onDelete="CASCADE") */ + #[ORM\ManyToOne(targetEntity: Category::class, inversedBy: 'translations')] + #[ORM\JoinColumn(name: 'object_id', referencedColumnName: 'id', onDelete: 'CASCADE')] protected $object; /** - * Convinient constructor + * Convenient constructor * * @param string $locale * @param string $field diff --git a/example/app/Entity/Repository/CategoryRepository.php b/example/app/Entity/Repository/CategoryRepository.php index e21b8bd5d8..543ea30ee5 100644 --- a/example/app/Entity/Repository/CategoryRepository.php +++ b/example/app/Entity/Repository/CategoryRepository.php @@ -11,8 +11,12 @@ namespace App\Entity\Repository; +use App\Entity\Category; use Gedmo\Tree\Entity\Repository\NestedTreeRepository; +/** + * @template-extends NestedTreeRepository + */ final class CategoryRepository extends NestedTreeRepository { } diff --git a/example/em.php b/example/em.php index d0cf1bafda..1ba7b8af55 100644 --- a/example/em.php +++ b/example/em.php @@ -17,9 +17,11 @@ use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\Driver\AnnotationDriver; +use Doctrine\ORM\Mapping\Driver\AttributeDriver; use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; use Gedmo\Blameable\BlameableListener; use Gedmo\DoctrineExtensions; +use Gedmo\Mapping\Driver\AttributeReader; use Gedmo\Sluggable\SluggableListener; use Gedmo\Timestampable\TimestampableListener; use Gedmo\Translatable\TranslatableListener; @@ -65,12 +67,23 @@ // For larger applications, you may use multiple cache pools to store cacheable data in different locations. $cache = new ArrayAdapter(); -// Build the annotation reader for the application, -// by default we will use a decorated reader supporting a backend cache. -$annotationReader = new PsrCachedReader( - new AnnotationReader(), - $cache -); +$annotationReader = null; +$extensionReader = null; + +// For PHP 8, we will provide the extensions an attribute reader, while PHP 7 will require the annotation reader +// (which will only be created when `doctrine/annotations` is installed) +if (PHP_VERSION_ID >= 80000) { + $extensionReader = new AttributeReader(); +} + +// Build the annotation reader for the application when the `doctrine/annotations` package is installed. +// By default, we will use a decorated reader supporting a backend cache. +if (class_exists(AnnotationReader::class)) { + $extensionReader = $annotationReader = new PsrCachedReader( + new AnnotationReader(), + $cache + ); +} // Create the mapping driver chain that will be used to read metadata from our various sources. $mappingDriver = new MappingDriverChain(); @@ -83,46 +96,55 @@ ); // Register the application entities to our driver chain. -// Our application uses Annotations for mapping, but you can also use XML. -$mappingDriver->addDriver( - new AnnotationDriver( - $annotationReader, - [__DIR__.'/app/Entity'] - ), - 'App\Entity' -); +// Our application uses Annotations or Attributes for mapping, but you can also use XML. +if (PHP_VERSION_ID >= 80000) { + $mappingDriver->addDriver( + new AttributeDriver( + [__DIR__.'/app/Entity'] + ), + 'App\Entity' + ); +} else { + $mappingDriver->addDriver( + new AnnotationDriver( + $annotationReader, + [__DIR__.'/app/Entity'] + ), + 'App\Entity' + ); +} // Next, we will create the event manager and register the listeners for the extensions we will be using. $eventManager = new EventManager(); // Sluggable extension $sluggableListener = new SluggableListener(); -$sluggableListener->setAnnotationReader($annotationReader); +$sluggableListener->setAnnotationReader($extensionReader); $sluggableListener->setCacheItemPool($cache); $eventManager->addEventSubscriber($sluggableListener); // Tree extension $treeListener = new TreeListener(); -$treeListener->setAnnotationReader($annotationReader); +$treeListener->setAnnotationReader($extensionReader); $treeListener->setCacheItemPool($cache); $eventManager->addEventSubscriber($treeListener); // Loggable extension, not used in example // $loggableListener = new Gedmo\Loggable\LoggableListener; -// $loggableListener->setAnnotationReader($annotationReader); +// $loggableListener->setAnnotationReader($extensionReader); // $loggableListener->setCacheItemPool($cache); // $loggableListener->setUsername('admin'); // $eventManager->addEventSubscriber($loggableListener); // Timestampable extension $timestampableListener = new TimestampableListener(); -$timestampableListener->setAnnotationReader($annotationReader); +$timestampableListener->setAnnotationReader($extensionReader); $timestampableListener->setCacheItemPool($cache); $eventManager->addEventSubscriber($timestampableListener); // Blameable extension $blameableListener = new BlameableListener(); -$blameableListener->setAnnotationReader($annotationReader); +$blameableListener->setAnnotationReader($extensionReader); $blameableListener->setCacheItemPool($cache); $blameableListener->setUserValue('MyUsername'); // determine from your environment $eventManager->addEventSubscriber($blameableListener); @@ -134,13 +156,13 @@ // but most importantly, it must be set before the entity manager is flushed. $translatableListener->setTranslatableLocale('en'); $translatableListener->setDefaultLocale('en'); -$translatableListener->setAnnotationReader($annotationReader); +$translatableListener->setAnnotationReader($extensionReader); $translatableListener->setCacheItemPool($cache); $eventManager->addEventSubscriber($translatableListener); // Sortable extension, not used in example // $sortableListener = new Gedmo\Sortable\SortableListener; -// $sortableListener->setAnnotationReader($annotationReader); +// $sortableListener->setAnnotationReader($extensionReader); // $sortableListener->setCacheItemPool($cache); // $eventManager->addEventSubscriber($sortableListener); diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index fa044419d4..77e18b9602 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -517,12 +517,12 @@ parameters: - message: "#^Method Gedmo\\\\Uploadable\\\\Event\\\\UploadableBaseEventArgs\\:\\:__construct\\(\\) has parameter \\$config with no value type specified in iterable type array\\.$#" - count: 2 + count: 1 path: src/Uploadable/Event/UploadableBaseEventArgs.php - message: "#^Method Gedmo\\\\Uploadable\\\\Event\\\\UploadableBaseEventArgs\\:\\:getExtensionConfiguration\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 2 + count: 1 path: src/Uploadable/Event/UploadableBaseEventArgs.php - @@ -532,7 +532,7 @@ parameters: - message: "#^Property Gedmo\\\\Uploadable\\\\Event\\\\UploadableBaseEventArgs\\:\\:\\$config type has no value type specified in iterable type array\\.$#" - count: 2 + count: 1 path: src/Uploadable/Event/UploadableBaseEventArgs.php - @@ -542,7 +542,7 @@ parameters: - message: "#^Property Gedmo\\\\Uploadable\\\\Event\\\\UploadableBaseEventArgs\\:\\:\\$extensionConfiguration type has no value type specified in iterable type array\\.$#" - count: 2 + count: 1 path: src/Uploadable/Event/UploadableBaseEventArgs.php - @@ -616,7 +616,7 @@ parameters: path: tests/Gedmo/Timestampable/Fixture/ArticleCarbon.php - - message: "#^Property Gedmo\\\\Tests\\\\Tree\\\\MaterializedPathODMMongoDBRepositoryTest\\:\\:\\$repo \\(Gedmo\\\\Tree\\\\Document\\\\MongoDB\\\\Repository\\\\MaterializedPathRepository\\) does not accept Doctrine\\\\ORM\\\\EntityRepository\\\\.$#" + message: "#^Property Gedmo\\\\Tests\\\\Tree\\\\MaterializedPathODMMongoDBRepositoryTest\\:\\:\\$repo \\(Gedmo\\\\Tree\\\\Document\\\\MongoDB\\\\Repository\\\\MaterializedPathRepository\\\\) does not accept Doctrine\\\\ORM\\\\EntityRepository\\\\.$#" count: 1 path: tests/Gedmo/Tree/MaterializedPathODMMongoDBRepositoryTest.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 4696ad4625..21d4cd1b3f 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -11,8 +11,6 @@ parameters: bootstrapFiles: - tests/bootstrap.php treatPhpDocTypesAsCertain: false - checkMissingVarTagTypehint: true - checkMissingTypehints: true ignoreErrors: - '#^Property Gedmo\\Tests\\.+\\Fixture\\[^:]+::\$\w+ is never written, only read\.$#' - '#^Property Gedmo\\Tests\\.+\\Fixture\\[^:]+::\$\w+ is never read, only written\.$#' @@ -21,11 +19,3 @@ parameters: - '#^Method Gedmo\\Uploadable\\Mapping\\Validator::validateConfiguration\(\) with return type void returns array but should not return anything\.$#' - '#^Result of static method Gedmo\\Uploadable\\Mapping\\Validator::validateConfiguration\(\) \(void\) is used\.$#' - '#^Result of method Gedmo\\Mapping\\Driver::readExtendedMetadata\(\) \(void\) is used\.$#' - -rules: - - PHPStan\Rules\Constants\MissingClassConstantTypehintRule - - PHPStan\Rules\Functions\MissingFunctionParameterTypehintRule - - PHPStan\Rules\Functions\MissingFunctionReturnTypehintRule - - PHPStan\Rules\Methods\MissingMethodParameterTypehintRule - - PHPStan\Rules\Methods\MissingMethodReturnTypehintRule - - PHPStan\Rules\Properties\MissingPropertyTypehintRule diff --git a/src/DoctrineExtensions.php b/src/DoctrineExtensions.php index 861108d100..aebf9bc8c9 100644 --- a/src/DoctrineExtensions.php +++ b/src/DoctrineExtensions.php @@ -15,6 +15,7 @@ use Doctrine\ODM\MongoDB\Mapping\Driver as DriverMongodbODM; use Doctrine\ORM\Mapping\Driver as DriverORM; use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; +use Gedmo\Exception\RuntimeException; use Symfony\Component\Cache\Adapter\ArrayAdapter; /** @@ -36,15 +37,19 @@ final class DoctrineExtensions */ public static function registerMappingIntoDriverChainORM(MappingDriverChain $driverChain, ?Reader $reader = null): void { - if (!$reader) { - $reader = self::createAnnotationReader(); - } - $annotationDriver = new DriverORM\AnnotationDriver($reader, [ + $paths = [ __DIR__.'/Translatable/Entity', __DIR__.'/Loggable/Entity', __DIR__.'/Tree/Entity', - ]); - $driverChain->addDriver($annotationDriver, 'Gedmo'); + ]; + + if (\PHP_VERSION_ID >= 80000) { + $driver = new DriverORM\AttributeDriver($paths); + } else { + $driver = new DriverORM\AnnotationDriver($reader ?? self::createAnnotationReader(), $paths); + } + + $driverChain->addDriver($driver, 'Gedmo'); } /** @@ -53,15 +58,19 @@ public static function registerMappingIntoDriverChainORM(MappingDriverChain $dri */ public static function registerAbstractMappingIntoDriverChainORM(MappingDriverChain $driverChain, ?Reader $reader = null): void { - if (!$reader) { - $reader = self::createAnnotationReader(); - } - $annotationDriver = new DriverORM\AnnotationDriver($reader, [ + $paths = [ __DIR__.'/Translatable/Entity/MappedSuperclass', __DIR__.'/Loggable/Entity/MappedSuperclass', __DIR__.'/Tree/Entity/MappedSuperclass', - ]); - $driverChain->addDriver($annotationDriver, 'Gedmo'); + ]; + + if (\PHP_VERSION_ID >= 80000) { + $driver = new DriverORM\AttributeDriver($paths); + } else { + $driver = new DriverORM\AnnotationDriver($reader ?? self::createAnnotationReader(), $paths); + } + + $driverChain->addDriver($driver, 'Gedmo'); } /** @@ -70,14 +79,18 @@ public static function registerAbstractMappingIntoDriverChainORM(MappingDriverCh */ public static function registerMappingIntoDriverChainMongodbODM(MappingDriverChain $driverChain, ?Reader $reader = null): void { - if (!$reader) { - $reader = self::createAnnotationReader(); - } - $annotationDriver = new DriverMongodbODM\AnnotationDriver($reader, [ + $paths = [ __DIR__.'/Translatable/Document', __DIR__.'/Loggable/Document', - ]); - $driverChain->addDriver($annotationDriver, 'Gedmo'); + ]; + + if (\PHP_VERSION_ID >= 80000) { + $driver = new DriverMongodbODM\AttributeDriver($paths); + } else { + $driver = new DriverMongodbODM\AnnotationDriver($reader ?? self::createAnnotationReader(), $paths); + } + + $driverChain->addDriver($driver, 'Gedmo'); } /** @@ -86,14 +99,18 @@ public static function registerMappingIntoDriverChainMongodbODM(MappingDriverCha */ public static function registerAbstractMappingIntoDriverChainMongodbODM(MappingDriverChain $driverChain, ?Reader $reader = null): void { - if (!$reader) { - $reader = self::createAnnotationReader(); - } - $annotationDriver = new DriverMongodbODM\AnnotationDriver($reader, [ + $paths = [ __DIR__.'/Translatable/Document/MappedSuperclass', __DIR__.'/Loggable/Document/MappedSuperclass', - ]); - $driverChain->addDriver($annotationDriver, 'Gedmo'); + ]; + + if (\PHP_VERSION_ID >= 80000) { + $driver = new DriverMongodbODM\AttributeDriver($paths); + } else { + $driver = new DriverMongodbODM\AnnotationDriver($reader ?? self::createAnnotationReader(), $paths); + } + + $driverChain->addDriver($driver, 'Gedmo'); } /** @@ -111,8 +128,15 @@ public static function registerAnnotations(): void // Purposefully no-op'd, all supported versions of `doctrine/annotations` support autoloading } + /** + * @throws RuntimeException if running PHP 7 and the `doctrine/annotations` package is not installed + */ private static function createAnnotationReader(): PsrCachedReader { + if (!class_exists(AnnotationReader::class)) { + throw new RuntimeException(sprintf('The "%1$s" class requires the "doctrine/annotations" package to use annotations but it is not available. Try running "composer require doctrine/annotations" or upgrade to PHP 8 to use attributes.', self::class)); + } + return new PsrCachedReader(new AnnotationReader(), new ArrayAdapter()); } } diff --git a/src/Mapping/ExtensionMetadataFactory.php b/src/Mapping/ExtensionMetadataFactory.php index 011f29164b..aa66605fed 100644 --- a/src/Mapping/ExtensionMetadataFactory.php +++ b/src/Mapping/ExtensionMetadataFactory.php @@ -12,7 +12,8 @@ use Doctrine\Bundle\DoctrineBundle\Mapping\MappingDriver as DoctrineBundleMappingDriver; use Doctrine\Common\Annotations\Reader; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata as DocumentClassMetadata; -use Doctrine\ORM\Mapping\ClassMetadataInfo as EntityClassMetadata; +use Doctrine\ORM\Mapping\ClassMetadata as EntityClassMetadata; +use Doctrine\ORM\Mapping\ClassMetadataInfo as LegacyEntityClassMetadata; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\Mapping\Driver\DefaultFileLocator; use Doctrine\Persistence\Mapping\Driver\MappingDriver; @@ -95,7 +96,7 @@ public function __construct(ObjectManager $objectManager, string $extensionNames /** * Reads extension metadata * - * @param ClassMetadata&(DocumentClassMetadata|EntityClassMetadata) $meta + * @param ClassMetadata&(DocumentClassMetadata|EntityClassMetadata|LegacyEntityClassMetadata) $meta * * @return array the metatada configuration */ @@ -116,7 +117,7 @@ public function getExtensionMetadata($meta) $class = $this->objectManager->getClassMetadata($parentClass); - assert($class instanceof DocumentClassMetadata || $class instanceof EntityClassMetadata); + assert($class instanceof DocumentClassMetadata || $class instanceof EntityClassMetadata || $class instanceof LegacyEntityClassMetadata); $extendedMetadata = $this->driver->readExtendedMetadata($class, $config); diff --git a/src/Mapping/MappedEventSubscriber.php b/src/Mapping/MappedEventSubscriber.php index d93815a39b..73800fb27c 100644 --- a/src/Mapping/MappedEventSubscriber.php +++ b/src/Mapping/MappedEventSubscriber.php @@ -17,7 +17,8 @@ use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata as DocumentClassMetadata; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Mapping\ClassMetadataInfo as EntityClassMetadata; +use Doctrine\ORM\Mapping\ClassMetadata as EntityClassMetadata; +use Doctrine\ORM\Mapping\ClassMetadataInfo as LegacyEntityClassMetadata; use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\ObjectManager; @@ -229,7 +230,7 @@ final public function setCacheItemPool(CacheItemPoolInterface $cacheItemPool): v */ public function loadMetadataForObjectClass(ObjectManager $objectManager, $metadata) { - assert($metadata instanceof DocumentClassMetadata || $metadata instanceof EntityClassMetadata); + assert($metadata instanceof DocumentClassMetadata || $metadata instanceof EntityClassMetadata || $metadata instanceof LegacyEntityClassMetadata); $factory = $this->getExtensionMetadataFactory($objectManager); diff --git a/src/References/Mapping/Event/Adapter/ODM.php b/src/References/Mapping/Event/Adapter/ODM.php index 7ca6f30df6..b302171467 100644 --- a/src/References/Mapping/Event/Adapter/ODM.php +++ b/src/References/Mapping/Event/Adapter/ODM.php @@ -11,7 +11,7 @@ use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Proxy\Proxy as ORMProxy; +use Doctrine\Persistence\Proxy as PersistenceProxy; use Gedmo\Mapping\Event\Adapter\ODM as BaseAdapterODM; use Gedmo\References\Mapping\Event\ReferencesAdapter; use ProxyManager\Proxy\GhostObjectInterface; @@ -32,7 +32,7 @@ public function getIdentifier($om, $object, $single = true) } if ($om instanceof EntityManagerInterface) { - if ($object instanceof ORMProxy) { + if ($object instanceof PersistenceProxy) { $id = $om->getUnitOfWork()->getEntityIdentifier($object); } else { $meta = $om->getClassMetadata(get_class($object)); diff --git a/src/References/Mapping/Event/Adapter/ORM.php b/src/References/Mapping/Event/Adapter/ORM.php index 407612f4a3..1f116c122a 100644 --- a/src/References/Mapping/Event/Adapter/ORM.php +++ b/src/References/Mapping/Event/Adapter/ORM.php @@ -12,7 +12,7 @@ use Doctrine\ODM\MongoDB\DocumentManager as MongoDocumentManager; use Doctrine\ODM\PHPCR\DocumentManager as PhpcrDocumentManager; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Proxy\Proxy as ORMProxy; +use Doctrine\Persistence\Proxy as PersistenceProxy; use Gedmo\Exception\InvalidArgumentException; use Gedmo\Mapping\Event\Adapter\ORM as BaseAdapterORM; use Gedmo\References\Mapping\Event\ReferencesAdapter; @@ -79,7 +79,7 @@ public function getSingleReference($om, $class, $identifier) public function extractIdentifier($om, $object, $single = true) { - if ($object instanceof ORMProxy) { + if ($object instanceof PersistenceProxy) { $id = $om->getUnitOfWork()->getEntityIdentifier($object); } else { $meta = $om->getClassMetadata(get_class($object)); diff --git a/src/Tool/Wrapper/EntityWrapper.php b/src/Tool/Wrapper/EntityWrapper.php index d1a4c8cd82..63bbe6187f 100644 --- a/src/Tool/Wrapper/EntityWrapper.php +++ b/src/Tool/Wrapper/EntityWrapper.php @@ -12,7 +12,6 @@ use Doctrine\Common\Util\ClassUtils; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Proxy\Proxy; use Doctrine\Persistence\Proxy as PersistenceProxy; /** diff --git a/src/Tree/Document/MongoDB/Repository/AbstractTreeRepository.php b/src/Tree/Document/MongoDB/Repository/AbstractTreeRepository.php index 10cd066590..f86552f840 100644 --- a/src/Tree/Document/MongoDB/Repository/AbstractTreeRepository.php +++ b/src/Tree/Document/MongoDB/Repository/AbstractTreeRepository.php @@ -22,7 +22,11 @@ use Gedmo\Tree\TreeListener; /** - * @phpstan-extends DocumentRepository + * @template T of object + * + * @phpstan-extends DocumentRepository + * + * @phpstan-implements RepositoryInterface */ abstract class AbstractTreeRepository extends DocumentRepository implements RepositoryInterface { @@ -40,6 +44,7 @@ abstract class AbstractTreeRepository extends DocumentRepository implements Repo */ protected $repoUtils; + /** @param ClassMetadata $class */ public function __construct(DocumentManager $em, UnitOfWork $uow, ClassMetadata $class) { parent::__construct($em, $uow, $class); diff --git a/src/Tree/Document/MongoDB/Repository/MaterializedPathRepository.php b/src/Tree/Document/MongoDB/Repository/MaterializedPathRepository.php index b3d87172f5..d135c914db 100644 --- a/src/Tree/Document/MongoDB/Repository/MaterializedPathRepository.php +++ b/src/Tree/Document/MongoDB/Repository/MaterializedPathRepository.php @@ -24,6 +24,10 @@ * * @author Gustavo Falco * @author Gediminas Morkevicius + * + * @template T of object + * + * @template-extends AbstractTreeRepository */ class MaterializedPathRepository extends AbstractTreeRepository { diff --git a/src/Tree/Entity/Repository/AbstractTreeRepository.php b/src/Tree/Entity/Repository/AbstractTreeRepository.php index d2ab20bb7a..d06aded5b5 100644 --- a/src/Tree/Entity/Repository/AbstractTreeRepository.php +++ b/src/Tree/Entity/Repository/AbstractTreeRepository.php @@ -23,7 +23,11 @@ use Gedmo\Tree\TreeListener; /** - * @phpstan-extends EntityRepository + * @template T of object + * + * @template-extends EntityRepository + * + * @template-implements RepositoryInterface */ abstract class AbstractTreeRepository extends EntityRepository implements RepositoryInterface { @@ -41,6 +45,7 @@ abstract class AbstractTreeRepository extends EntityRepository implements Reposi */ protected $repoUtils; + /** @param ClassMetadata $class */ public function __construct(EntityManagerInterface $em, ClassMetadata $class) { parent::__construct($em, $class); diff --git a/src/Tree/Entity/Repository/ClosureTreeRepository.php b/src/Tree/Entity/Repository/ClosureTreeRepository.php index 61677bf7a2..30e6cb081b 100644 --- a/src/Tree/Entity/Repository/ClosureTreeRepository.php +++ b/src/Tree/Entity/Repository/ClosureTreeRepository.php @@ -23,6 +23,10 @@ * * @author Gustavo Adrian * @author Gediminas Morkevicius + * + * @template T of object + * + * @template-extends AbstractTreeRepository */ class ClosureTreeRepository extends AbstractTreeRepository { diff --git a/src/Tree/Entity/Repository/MaterializedPathRepository.php b/src/Tree/Entity/Repository/MaterializedPathRepository.php index 5c8c280e86..88d416d9e6 100644 --- a/src/Tree/Entity/Repository/MaterializedPathRepository.php +++ b/src/Tree/Entity/Repository/MaterializedPathRepository.php @@ -21,6 +21,10 @@ * * @author Gustavo Falco * @author Gediminas Morkevicius + * + * @template T of object + * + * @template-extends AbstractTreeRepository */ class MaterializedPathRepository extends AbstractTreeRepository { diff --git a/src/Tree/Entity/Repository/NestedTreeRepository.php b/src/Tree/Entity/Repository/NestedTreeRepository.php index e7a01dac4d..d894dc682e 100644 --- a/src/Tree/Entity/Repository/NestedTreeRepository.php +++ b/src/Tree/Entity/Repository/NestedTreeRepository.php @@ -10,9 +10,9 @@ namespace Gedmo\Tree\Entity\Repository; use Doctrine\ORM\Exception\ORMException; -use Doctrine\ORM\Proxy\Proxy; use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; +use Doctrine\Persistence\Proxy; use Gedmo\Exception\InvalidArgumentException; use Gedmo\Exception\RuntimeException; use Gedmo\Exception\UnexpectedValueException; @@ -28,6 +28,10 @@ * * @author Gediminas Morkevicius * + * @template T of object + * + * @template-extends AbstractTreeRepository + * * @method persistAsFirstChild($node) * @method persistAsFirstChildOf($node, $parent) * @method persistAsLastChild($node) diff --git a/src/Tree/RepositoryInterface.php b/src/Tree/RepositoryInterface.php index 5c4009e834..d6b88f882e 100644 --- a/src/Tree/RepositoryInterface.php +++ b/src/Tree/RepositoryInterface.php @@ -16,6 +16,8 @@ * * @author Gustavo Falco * @author Gediminas Morkevicius + * + * @template T of object */ interface RepositoryInterface extends RepositoryUtilsInterface { @@ -26,6 +28,8 @@ interface RepositoryInterface extends RepositoryUtilsInterface * @param string $direction * * @return iterable + * + * @phpstan-return iterable */ public function getRootNodes($sortByField = null, $direction = 'asc'); @@ -38,6 +42,10 @@ public function getRootNodes($sortByField = null, $direction = 'asc'); * @param bool $includeNode Flag indicating whether the given node should be included in the results * * @return array + * + * @phpstan-param T $node + * + * @phpstan-return iterable */ public function getNodesHierarchy($node = null, $direct = false, array $options = [], $includeNode = false); @@ -53,6 +61,9 @@ public function getNodesHierarchy($node = null, $direct = false, array $options * @return iterable List of children * * @phpstan-param 'asc'|'desc'|'ASC'|'DESC'|array $direction + * @phpstan-param T|null $node + * + * @phpstan-return iterable */ public function getChildren($node = null, $direct = false, $sortByField = null, $direction = 'ASC', $includeNode = false); diff --git a/src/Tree/RepositoryUtils.php b/src/Tree/RepositoryUtils.php index d39e9d6d8e..cad3eafe91 100644 --- a/src/Tree/RepositoryUtils.php +++ b/src/Tree/RepositoryUtils.php @@ -19,10 +19,12 @@ /** * @final since gedmo/doctrine-extensions 3.11 + * + * @template T of object */ class RepositoryUtils implements RepositoryUtilsInterface { - /** @var ClassMetadata */ + /** @var ClassMetadata */ protected $meta; /** @var TreeListener */ @@ -31,7 +33,7 @@ class RepositoryUtils implements RepositoryUtilsInterface /** @var ObjectManager&(DocumentManager|EntityManagerInterface) */ protected $om; - /** @var RepositoryInterface */ + /** @var RepositoryInterface */ protected $repo; /** @@ -44,8 +46,9 @@ class RepositoryUtils implements RepositoryUtilsInterface /** * @param ObjectManager&(DocumentManager|EntityManagerInterface) $om + * @param ClassMetadata $meta * @param TreeListener $listener - * @param RepositoryInterface $repo + * @param RepositoryInterface $repo */ public function __construct(ObjectManager $om, ClassMetadata $meta, $listener, $repo) { @@ -56,7 +59,7 @@ public function __construct(ObjectManager $om, ClassMetadata $meta, $listener, $ } /** - * @return ClassMetadata + * @return ClassMetadata */ public function getClassMetadata() { diff --git a/src/Tree/Strategy/ORM/Nested.php b/src/Tree/Strategy/ORM/Nested.php index a60e2fd74f..fed285638b 100644 --- a/src/Tree/Strategy/ORM/Nested.php +++ b/src/Tree/Strategy/ORM/Nested.php @@ -12,7 +12,7 @@ use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Proxy\Proxy; +use Doctrine\Persistence\Proxy; use Gedmo\Exception\InvalidArgumentException; use Gedmo\Exception\UnexpectedValueException; use Gedmo\Mapping\Event\AdapterInterface; diff --git a/tests/Gedmo/Blameable/Fixture/Document/Article.php b/tests/Gedmo/Blameable/Fixture/Document/Article.php index 367696a7c8..a8973fd6c9 100644 --- a/tests/Gedmo/Blameable/Fixture/Document/Article.php +++ b/tests/Gedmo/Blameable/Fixture/Document/Article.php @@ -36,10 +36,10 @@ class Article private ?string $title = null; /** - * @ODM\ReferenceOne(targetDocument="Type") + * @ODM\ReferenceOne(targetDocument="Gedmo\Tests\Blameable\Fixture\Document\Type") */ #[Odm\ReferenceOne(targetDocument: Type::class)] - private ?\Gedmo\Tests\Blameable\Fixture\Document\Type $type = null; + private ?Type $type = null; /** * @ODM\Field(type="string") @@ -60,7 +60,7 @@ class Article private ?string $updated = null; /** - * @ODM\ReferenceOne(targetDocument="User") + * @ODM\ReferenceOne(targetDocument="Gedmo\Tests\Blameable\Fixture\Document\User") * * @Gedmo\Blameable(on="create") */ diff --git a/tests/Gedmo/DoctrineExtensionsTest.php b/tests/Gedmo/DoctrineExtensionsTest.php new file mode 100644 index 0000000000..dcb91e19ff --- /dev/null +++ b/tests/Gedmo/DoctrineExtensionsTest.php @@ -0,0 +1,157 @@ + http://www.gediminasm.org + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gedmo\Tests; + +use Doctrine\Common\Annotations\AnnotationReader; +use Doctrine\ODM\MongoDB\Mapping\Driver as DriverMongodbODM; +use Doctrine\ORM\Mapping\Driver as DriverORM; +use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; +use Gedmo\DoctrineExtensions; +use PHPUnit\Framework\TestCase; + +/** + * This test covers the driver registration helpers in the {@see DoctrineExtensions} class. + */ +final class DoctrineExtensionsTest extends TestCase +{ + /** + * @requires PHP >= 8.0 + */ + public function testRegistersAttributeDriverForConcreteOrmEntitiesToChain(): void + { + $chain = new MappingDriverChain(); + + DoctrineExtensions::registerMappingIntoDriverChainORM($chain); + + $drivers = $chain->getDrivers(); + + static::assertArrayHasKey('Gedmo', $drivers); + static::assertInstanceOf(DriverORM\AttributeDriver::class, $drivers['Gedmo'], 'The attribute driver should be registered to the chain on PHP 8'); + } + + public function testRegistersAnnotationDriverForConcreteOrmEntitiesToChain(): void + { + if (\PHP_VERSION_ID >= 80000 || !class_exists(AnnotationReader::class)) { + static::markTestSkipped('Test only applies to PHP 7 and requires the doctrine/annotations package'); + } + + $chain = new MappingDriverChain(); + + DoctrineExtensions::registerMappingIntoDriverChainORM($chain); + + $drivers = $chain->getDrivers(); + + static::assertArrayHasKey('Gedmo', $drivers); + static::assertInstanceOf(DriverORM\AnnotationDriver::class, $drivers['Gedmo'], 'The annotations driver should be registered to the chain on PHP 7'); + } + + /** + * @requires PHP >= 8.0 + */ + public function testRegistersAttributeDriverForAbstractOrmSuperclassesToChain(): void + { + $chain = new MappingDriverChain(); + + DoctrineExtensions::registerAbstractMappingIntoDriverChainORM($chain); + + $drivers = $chain->getDrivers(); + + static::assertArrayHasKey('Gedmo', $drivers); + static::assertInstanceOf(DriverORM\AttributeDriver::class, $drivers['Gedmo'], 'The attribute driver should be registered to the chain on PHP 8'); + } + + public function testRegistersAnnotationDriverForAbstractOrmSuperclassesToChain(): void + { + if (\PHP_VERSION_ID >= 80000 || !class_exists(AnnotationReader::class)) { + static::markTestSkipped('Test only applies to PHP 7 and requires the doctrine/annotations package'); + } + + $chain = new MappingDriverChain(); + + DoctrineExtensions::registerAbstractMappingIntoDriverChainORM($chain); + + $drivers = $chain->getDrivers(); + + static::assertArrayHasKey('Gedmo', $drivers); + static::assertInstanceOf(DriverORM\AnnotationDriver::class, $drivers['Gedmo'], 'The annotations driver should be registered to the chain on PHP 7'); + } + + /** + * @requires PHP >= 8.0 + */ + public function testRegistersAttributeDriverForConcreteOdmDocumentsToChain(): void + { + if (!class_exists(DriverMongodbODM\AttributeDriver::class)) { + static::markTestSkipped('Test requires the attribute mapping driver from the doctrine/mongodb-odm package'); + } + + $chain = new MappingDriverChain(); + + DoctrineExtensions::registerMappingIntoDriverChainMongodbODM($chain); + + $drivers = $chain->getDrivers(); + + static::assertArrayHasKey('Gedmo', $drivers); + static::assertInstanceOf(DriverMongodbODM\AttributeDriver::class, $drivers['Gedmo'], 'The attribute driver should be registered to the chain on PHP 8'); + } + + public function testRegistersAnnotationDriverForConcreteOdmDocumentsToChain(): void + { + if (\PHP_VERSION_ID >= 80000 || !class_exists(AnnotationReader::class)) { + static::markTestSkipped('Test only applies to PHP 7 and requires the doctrine/annotations package'); + } + + $chain = new MappingDriverChain(); + + DoctrineExtensions::registerMappingIntoDriverChainMongodbODM($chain); + + $drivers = $chain->getDrivers(); + + static::assertArrayHasKey('Gedmo', $drivers); + static::assertInstanceOf(DriverMongodbODM\AnnotationDriver::class, $drivers['Gedmo'], 'The annotations driver should be registered to the chain on PHP 7'); + } + + /** + * @requires PHP >= 8.0 + */ + public function testRegistersAttributeDriverForAbstractOdmSuperclassesToChain(): void + { + if (!class_exists(DriverMongodbODM\AttributeDriver::class)) { + static::markTestSkipped('Test requires the attribute mapping driver from the doctrine/mongodb-odm package'); + } + + $chain = new MappingDriverChain(); + + DoctrineExtensions::registerAbstractMappingIntoDriverChainMongodbODM($chain); + + $drivers = $chain->getDrivers(); + + static::assertArrayHasKey('Gedmo', $drivers); + static::assertInstanceOf(DriverMongodbODM\AttributeDriver::class, $drivers['Gedmo'], 'The attribute driver should be registered to the chain on PHP 8'); + } + + public function testRegistersAnnotationDriverForAbstractOdmSuperclassesToChain(): void + { + if (\PHP_VERSION_ID >= 80000 || !class_exists(AnnotationReader::class)) { + static::markTestSkipped('Test only applies to PHP 7 and requires the doctrine/annotations package'); + } + + $chain = new MappingDriverChain(); + + DoctrineExtensions::registerAbstractMappingIntoDriverChainMongodbODM($chain); + + $drivers = $chain->getDrivers(); + + static::assertArrayHasKey('Gedmo', $drivers); + static::assertInstanceOf(DriverMongodbODM\AnnotationDriver::class, $drivers['Gedmo'], 'The annotations driver should be registered to the chain on PHP 7'); + } +} diff --git a/tests/Gedmo/Mapping/Fixture/Document/User.php b/tests/Gedmo/Mapping/Fixture/Document/User.php index 29687f3c5d..420bbb9889 100644 --- a/tests/Gedmo/Mapping/Fixture/Document/User.php +++ b/tests/Gedmo/Mapping/Fixture/Document/User.php @@ -12,11 +12,13 @@ namespace Gedmo\Tests\Mapping\Fixture\Document; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; +use Doctrine\ODM\MongoDB\Types\Type; use Gedmo\Tests\Mapping\Mock\Extension\Encoder\Mapping as Ext; /** * @ODM\Document(collection="test_users") */ +#[ODM\Document(collection: 'test_users')] class User { /** @@ -24,6 +26,7 @@ class User * * @ODM\Id */ + #[ODM\Id] private $id; /** @@ -31,6 +34,8 @@ class User * * @ODM\Field(type="string") */ + #[Ext\Encode(type: 'sha1', secret: 'xxx')] + #[ODM\Field(type: Type::STRING)] private ?string $name = null; /** @@ -38,6 +43,8 @@ class User * * @ODM\Field(type="string") */ + #[Ext\Encode(type: 'md5')] + #[ODM\Field(type: Type::STRING)] private ?string $password = null; public function setName(?string $name): void diff --git a/tests/Gedmo/Mapping/Fixture/MappedSuperClass.php b/tests/Gedmo/Mapping/Fixture/MappedSuperClass.php index c9d59543e5..60592871d4 100644 --- a/tests/Gedmo/Mapping/Fixture/MappedSuperClass.php +++ b/tests/Gedmo/Mapping/Fixture/MappedSuperClass.php @@ -38,6 +38,7 @@ class MappedSuperClass * * @Ext\Encode(type="md5") */ + #[Ext\Encode(type: 'md5')] #[ORM\Column(length: 32)] private ?string $content = null; diff --git a/tests/Gedmo/Mapping/Fixture/User.php b/tests/Gedmo/Mapping/Fixture/User.php index b617783645..fe3949ab33 100644 --- a/tests/Gedmo/Mapping/Fixture/User.php +++ b/tests/Gedmo/Mapping/Fixture/User.php @@ -40,6 +40,7 @@ class User * * @ORM\Column(length=64) */ + #[Ext\Encode(type: 'sha1', secret: 'xxx')] #[ORM\Column(length: 64)] private ?string $name = null; @@ -48,6 +49,7 @@ class User * * @ORM\Column(length=32) */ + #[Ext\Encode(type: 'md5')] #[ORM\Column(length: 32)] private ?string $password = null; diff --git a/tests/Gedmo/Mapping/MappingEventAdapterTest.php b/tests/Gedmo/Mapping/MappingEventAdapterTest.php index 3ea084d924..d4240ba124 100644 --- a/tests/Gedmo/Mapping/MappingEventAdapterTest.php +++ b/tests/Gedmo/Mapping/MappingEventAdapterTest.php @@ -11,8 +11,8 @@ namespace Gedmo\Tests\Mapping; -use Doctrine\ORM\EntityManager; -use Doctrine\ORM\Event\LifecycleEventArgs; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Event\PrePersistEventArgs; use Gedmo\Mapping\Event\Adapter\ORM as EventAdapterORM; use Gedmo\Tests\Mapping\Mock\EventSubscriberCustomMock; use Gedmo\Tests\Mapping\Mock\EventSubscriberMock; @@ -23,11 +23,8 @@ final class MappingEventAdapterTest extends TestCase { public function testCustomizedAdapter(): void { - $emMock = $this->getMockBuilder(EntityManager::class) - ->disableOriginalConstructor() - ->getMock(); $subscriber = new EventSubscriberCustomMock(); - $args = new LifecycleEventArgs(new \stdClass(), $emMock); + $args = new PrePersistEventArgs(new \stdClass(), $this->createStub(EntityManagerInterface::class)); $adapter = $subscriber->getAdapter($args); static::assertInstanceOf(CustomizedORMAdapter::class, $adapter); @@ -35,11 +32,9 @@ public function testCustomizedAdapter(): void public function testCorrectAdapter(): void { - $emMock = $this->getMockBuilder(EntityManager::class) - ->disableOriginalConstructor() - ->getMock(); + $emMock = $this->createStub(EntityManagerInterface::class); $subscriber = new EventSubscriberMock(); - $args = new LifecycleEventArgs(new \stdClass(), $emMock); + $args = new PrePersistEventArgs(new \stdClass(), $emMock); $adapter = $subscriber->getAdapter($args); static::assertInstanceOf(EventAdapterORM::class, $adapter); @@ -49,19 +44,14 @@ public function testCorrectAdapter(): void public function testAdapterBehavior(): void { - $eventArgsMock = $this->getMockBuilder(LifecycleEventArgs::class) - ->disableOriginalConstructor() - ->getMock(); - $eventArgsMock->expects(static::once()) - ->method('getObjectManager'); + $emMock = $this->createStub(EntityManagerInterface::class); + $entity = new \stdClass(); - $eventArgsMock->expects(static::once()) - ->method('getObject') - ->willReturn(new \stdClass()); + $args = new PrePersistEventArgs($entity, $emMock); $eventAdapter = new EventAdapterORM(); - $eventAdapter->setEventArgs($eventArgsMock); - $eventAdapter->getObjectManager(); - $eventAdapter->getObject(); + $eventAdapter->setEventArgs($args); + static::assertSame($eventAdapter->getObjectManager(), $emMock); + static::assertInstanceOf(\stdClass::class, $eventAdapter->getObject()); } } diff --git a/tests/Gedmo/Mapping/Mock/Extension/Encoder/Mapping/Driver/Annotation.php b/tests/Gedmo/Mapping/Mock/Extension/Encoder/Mapping/Driver/Annotation.php index 5a95e1aaaa..1341862561 100644 --- a/tests/Gedmo/Mapping/Mock/Extension/Encoder/Mapping/Driver/Annotation.php +++ b/tests/Gedmo/Mapping/Mock/Extension/Encoder/Mapping/Driver/Annotation.php @@ -11,26 +11,13 @@ namespace Gedmo\Tests\Mapping\Mock\Extension\Encoder\Mapping\Driver; -use Doctrine\Common\Annotations\AnnotationReader; -use Doctrine\Persistence\Mapping\Driver\MappingDriver; -use Gedmo\Mapping\Driver; +use Gedmo\Mapping\Driver\AbstractAnnotationDriver; use Gedmo\Tests\Mapping\Mock\Extension\Encoder\Mapping\Encode; -class Annotation implements Driver +class Annotation extends AbstractAnnotationDriver { - /** - * original driver if it is available - * - * @var MappingDriver - */ - protected $_originalDriver; - public function readExtendedMetadata($meta, array &$config) { - $reader = new AnnotationReader(); - // set annotation namespace and alias - // $reader->setAnnotationNamespaceAlias('Gedmo\Tests\Mapping\Mock\Extension\Encoder\Mapping\\', 'ext'); - $class = $meta->getReflectionClass(); // check only property annotations foreach ($class->getProperties() as $property) { @@ -41,8 +28,9 @@ public function readExtendedMetadata($meta, array &$config) ) { continue; } + // now lets check if property has our annotation - if ($encode = $reader->getPropertyAnnotation($property, Encode::class)) { + if ($encode = $this->reader->getPropertyAnnotation($property, Encode::class)) { $field = $property->getName(); // check if field is mapped if (!$meta->hasField($field)) { @@ -67,12 +55,4 @@ public function readExtendedMetadata($meta, array &$config) return $config; } - - /** - * Passes in the mapping read by original driver - */ - public function setOriginalDriver($driver): void - { - $this->_originalDriver = $driver; - } } diff --git a/tests/Gedmo/Mapping/Mock/Extension/Encoder/Mapping/Driver/Attribute.php b/tests/Gedmo/Mapping/Mock/Extension/Encoder/Mapping/Driver/Attribute.php new file mode 100644 index 0000000000..fb52e3b5c2 --- /dev/null +++ b/tests/Gedmo/Mapping/Mock/Extension/Encoder/Mapping/Driver/Attribute.php @@ -0,0 +1,18 @@ + http://www.gediminasm.org + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gedmo\Tests\Mapping\Mock\Extension\Encoder\Mapping\Driver; + +use Gedmo\Mapping\Driver\AttributeDriverInterface; + +class Attribute extends Annotation implements AttributeDriverInterface +{ +} diff --git a/tests/Gedmo/Mapping/Mock/Extension/Encoder/Mapping/Encode.php b/tests/Gedmo/Mapping/Mock/Extension/Encoder/Mapping/Encode.php index 8b3d341c7a..eb6c5ebc1e 100644 --- a/tests/Gedmo/Mapping/Mock/Extension/Encoder/Mapping/Encode.php +++ b/tests/Gedmo/Mapping/Mock/Extension/Encoder/Mapping/Encode.php @@ -11,12 +11,15 @@ namespace Gedmo\Tests\Mapping\Mock\Extension\Encoder\Mapping; -use Doctrine\Common\Annotations\Annotation; +use Gedmo\Mapping\Annotation\Annotation as GedmoAnnotation; /** * @Annotation + * + * @NamedArgumentConstructor */ -final class Encode extends Annotation +#[\Attribute(\Attribute::TARGET_PROPERTY)] +final class Encode implements GedmoAnnotation { /** * @var string @@ -27,4 +30,10 @@ final class Encode extends Annotation * @var string|null */ public $secret; + + public function __construct(string $type = 'md5', ?string $secret = null) + { + $this->type = $type; + $this->secret = $secret; + } } diff --git a/tests/Gedmo/Sluggable/Fixture/Document/Handler/TreeSlug.php b/tests/Gedmo/Sluggable/Fixture/Document/Handler/TreeSlug.php index e116dda904..c9a4545138 100644 --- a/tests/Gedmo/Sluggable/Fixture/Document/Handler/TreeSlug.php +++ b/tests/Gedmo/Sluggable/Fixture/Document/Handler/TreeSlug.php @@ -54,10 +54,10 @@ class TreeSlug private $alias; /** - * @ODM\ReferenceOne(targetDocument="TreeSlug") + * @ODM\ReferenceOne(targetDocument="Gedmo\Tests\Sluggable\Fixture\Document\Handler\TreeSlug") */ #[ODM\ReferenceOne(targetDocument: self::class)] - private ?\Gedmo\Tests\Sluggable\Fixture\Document\Handler\TreeSlug $parent = null; + private ?TreeSlug $parent = null; public function setParent(?self $parent = null): void { diff --git a/tests/Gedmo/Sortable/Fixture/CustomerType.php b/tests/Gedmo/Sortable/Fixture/CustomerType.php index 7fb77bbfa0..9492aa443a 100644 --- a/tests/Gedmo/Sortable/Fixture/CustomerType.php +++ b/tests/Gedmo/Sortable/Fixture/CustomerType.php @@ -14,7 +14,6 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\DBAL\Driver\PDO\Exception as PDODriverException; -use Doctrine\DBAL\Driver\PDOException as LegacyPDOException; use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; @@ -124,12 +123,6 @@ public function postRemove(): void $pdoException = new \PDOException('SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails', 23000); - // @todo: This check can be removed when dropping support for doctrine/dbal 2.x. - if (class_exists(LegacyPDOException::class)) { - // @phpstan-ignore-next-line - throw new ForeignKeyConstraintViolationException(sprintf('An exception occurred while deleting the customer type with id %s.', $this->getId()), new LegacyPDOException($pdoException)); - } - throw new ForeignKeyConstraintViolationException(PDODriverException::new($pdoException), null); } } diff --git a/tests/Gedmo/Timestampable/TimestampableTest.php b/tests/Gedmo/Timestampable/TimestampableTest.php index 46bc8f49b0..47fe59e62e 100644 --- a/tests/Gedmo/Timestampable/TimestampableTest.php +++ b/tests/Gedmo/Timestampable/TimestampableTest.php @@ -12,8 +12,7 @@ namespace Gedmo\Tests\Timestampable; use Doctrine\Common\EventManager; -use Doctrine\ORM\Proxy\Proxy; -use Gedmo\Tests\Mapping\Fixture\Xml\Timestampable; +use Doctrine\Persistence\Proxy; use Gedmo\Tests\Timestampable\Fixture\Article; use Gedmo\Tests\Timestampable\Fixture\Author; use Gedmo\Tests\Timestampable\Fixture\Comment; diff --git a/tests/Gedmo/Tool/BaseTestCaseMongoODM.php b/tests/Gedmo/Tool/BaseTestCaseMongoODM.php index cd9dda45cf..0d31c5d59f 100644 --- a/tests/Gedmo/Tool/BaseTestCaseMongoODM.php +++ b/tests/Gedmo/Tool/BaseTestCaseMongoODM.php @@ -99,6 +99,10 @@ protected function getMockMappedDocumentManager(?EventManager $evm = null, ?Conf */ protected function getMetadataDriverImplementation(): MappingDriver { + if (PHP_VERSION_ID >= 80000) { + return new AttributeDriver(); + } + return new AnnotationDriver($_ENV['annotation_reader']); } diff --git a/tests/Gedmo/Tool/BaseTestCaseORM.php b/tests/Gedmo/Tool/BaseTestCaseORM.php index 1bb864253f..3e77c9d360 100644 --- a/tests/Gedmo/Tool/BaseTestCaseORM.php +++ b/tests/Gedmo/Tool/BaseTestCaseORM.php @@ -12,7 +12,6 @@ namespace Gedmo\Tests\Tool; use Doctrine\Common\EventManager; -use Doctrine\DBAL\Driver; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Logging\Middleware; use Doctrine\ORM\Configuration; @@ -28,9 +27,7 @@ use Gedmo\Timestampable\TimestampableListener; use Gedmo\Translatable\TranslatableListener; use Gedmo\Tree\TreeListener; -use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Psr\Log\LoggerInterface; /** * Base test case contains common mock objects @@ -41,24 +38,13 @@ */ abstract class BaseTestCaseORM extends TestCase { - /** - * @var EntityManager|null - */ - protected $em; + protected ?EntityManager $em = null; - /** - * @var QueryAnalyzer - */ - protected $queryAnalyzer; - - /** - * @var MockObject&LoggerInterface - */ - protected $queryLogger; + protected QueryLogger $queryLogger; protected function setUp(): void { - $this->queryLogger = $this->createMock(LoggerInterface::class); + $this->queryLogger = new QueryLogger(); } /** @@ -86,22 +72,6 @@ protected function getDefaultMockSqliteEntityManager(?EventManager $evm = null, return $this->em = $em; } - /** - * TODO: Remove this method when dropping support of doctrine/dbal 2. - * - * Starts query statistic log - * - * @throws \RuntimeException - */ - protected function startQueryLog(): void - { - if (null === $this->em) { - throw new \RuntimeException('EntityManager must be initialized.'); - } - $this->queryAnalyzer = new QueryAnalyzer($this->em->getConnection()->getDatabasePlatform()); - $this->em->getConfiguration()->setSQLLogger($this->queryAnalyzer); - } - /** * Creates default mapping driver */ @@ -129,13 +99,9 @@ protected function getDefaultConfiguration(): Configuration $config->setProxyDir(TESTS_TEMP_DIR); $config->setProxyNamespace('Proxy'); $config->setMetadataDriverImpl($this->getMetadataDriverImplementation()); - - // TODO: Remove the "if" check when dropping support of doctrine/dbal 2. - if (class_exists(Middleware::class)) { - $config->setMiddlewares([ - new Middleware($this->queryLogger), - ]); - } + $config->setMiddlewares([ + new Middleware($this->queryLogger), + ]); return $config; } diff --git a/tests/Gedmo/Tool/QueryAnalyzer.php b/tests/Gedmo/Tool/QueryAnalyzer.php deleted file mode 100644 index 30f23ed90d..0000000000 --- a/tests/Gedmo/Tool/QueryAnalyzer.php +++ /dev/null @@ -1,133 +0,0 @@ - http://www.gediminasm.org - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Gedmo\Tests\Tool; - -use Doctrine\DBAL\Logging\SQLLogger; -use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Types\Type; - -/** - * TODO: Remove it when dropping support of doctrine/dbal 2 - * - * @author Gediminas Morkevicius - */ -final class QueryAnalyzer implements SQLLogger -{ - /** - * Used database platform - */ - private AbstractPlatform $platform; - - /** - * List of queries executed - * - * @var string[] - */ - private $queries = []; - - public function __construct(AbstractPlatform $platform) - { - $this->platform = $platform; - } - - public function startQuery($sql, ?array $params = null, ?array $types = null): void - { - $this->queries[] = $this->generateSql($sql, $params, $types); - } - - public function stopQuery(): void - { - } - - public function cleanUp(): self - { - $this->queries = []; - - return $this; - } - - /** - * @return string[] - */ - public function getExecutedQueries(): array - { - return $this->queries; - } - - public function getNumExecutedQueries(): int - { - return count($this->queries); - } - - /** - * Create the SQL with mapped parameters - * - * @param array|null $params - * @param array|null $types - */ - private function generateSql(string $sql, ?array $params, ?array $types): string - { - if (null === $params || [] === $params) { - return $sql; - } - $converted = $this->getConvertedParams($params, $types); - if (is_int(key($params))) { - $index = key($converted); - $sql = preg_replace_callback('@\?@sm', static function ($match) use (&$index, $converted) { - return $converted[$index++]; - }, $sql); - } else { - foreach ($converted as $key => $value) { - $sql = str_replace(':'.$key, $value, $sql); - } - } - - return $sql; - } - - /** - * Get the converted parameter list - * - * @param array $params - * @param array $types - * - * @return array - */ - private function getConvertedParams(array $params, array $types): array - { - $result = []; - foreach ($params as $position => $value) { - if (isset($types[$position])) { - $type = $types[$position]; - if (is_string($type)) { - $type = Type::getType($type); - } - if ($type instanceof Type) { - $value = $type->convertToDatabaseValue($value, $this->platform); - } - } else { - if ($value instanceof \DateTimeInterface) { - $value = $value->format($this->platform->getDateTimeFormatString()); - } elseif (null !== $value) { - $type = Type::getType(gettype($value)); - $value = $type->convertToDatabaseValue($value, $this->platform); - } - } - if (is_string($value)) { - $value = "'{$value}'"; - } elseif (null === $value) { - $value = 'NULL'; - } - $result[$position] = $value; - } - - return $result; - } -} diff --git a/tests/Gedmo/Tool/QueryLogger.php b/tests/Gedmo/Tool/QueryLogger.php new file mode 100644 index 0000000000..760a2b14db --- /dev/null +++ b/tests/Gedmo/Tool/QueryLogger.php @@ -0,0 +1,38 @@ + http://www.gediminasm.org + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gedmo\Tests\Tool; + +use Psr\Log\AbstractLogger; + +final class QueryLogger extends AbstractLogger +{ + /** @var array */ + public array $queries = []; + + /** + * @param mixed $level + * @param string $message + * @param mixed[] $context + */ + public function log($level, $message, array $context = []): void + { + $this->queries[] = [ + 'message' => $message, + 'context' => $context, + ]; + } + + public function reset(): void + { + $this->queries = []; + } +} diff --git a/tests/Gedmo/Translatable/Issue/Issue84Test.php b/tests/Gedmo/Translatable/Issue/Issue84Test.php index a7e465764e..ae2628ff16 100644 --- a/tests/Gedmo/Translatable/Issue/Issue84Test.php +++ b/tests/Gedmo/Translatable/Issue/Issue84Test.php @@ -12,7 +12,7 @@ namespace Gedmo\Tests\Translatable\Issue; use Doctrine\Common\EventManager; -use Doctrine\ORM\Proxy\Proxy; +use Doctrine\Persistence\Proxy; use Gedmo\Tests\Tool\BaseTestCaseORM; use Gedmo\Tests\Translatable\Fixture\Article; use Gedmo\Translatable\Entity\Translation; diff --git a/tests/Gedmo/Translatable/PersonalTranslationTest.php b/tests/Gedmo/Translatable/PersonalTranslationTest.php index 9f316510c5..c218a1b14e 100644 --- a/tests/Gedmo/Translatable/PersonalTranslationTest.php +++ b/tests/Gedmo/Translatable/PersonalTranslationTest.php @@ -12,7 +12,6 @@ namespace Gedmo\Tests\Translatable; use Doctrine\Common\EventManager; -use Doctrine\DBAL\Logging\Middleware; use Doctrine\DBAL\ParameterType; use Doctrine\ORM\Query; use Gedmo\Tests\Tool\BaseTestCaseORM; @@ -68,35 +67,29 @@ public function testShouldTranslateTheRecord(): void $this->populate(); $this->translatableListener->setTranslatableLocale('lt'); - // TODO: Remove the "if" check and "else" body when dropping support of doctrine/dbal 2. - if (class_exists(Middleware::class)) { - $this->queryLogger - ->expects(static::exactly(2)) - ->method('debug') - ->withConsecutive( - ['Executing statement: {sql} (parameters: {params}, types: {types})', [ - 'sql' => 'SELECT t0.id AS id_1, t0.title AS title_2 FROM Article t0 WHERE t0.id = ?', - 'params' => [1 => 1], - 'types' => [1 => ParameterType::INTEGER], - ]], - ['Executing statement: {sql} (parameters: {params}, types: {types})', [ - 'sql' => 'SELECT t0.id AS id_1, t0.locale AS locale_2, t0.field AS field_3, t0.content AS content_4, t0.object_id AS object_id_5 FROM article_translations t0 WHERE t0.object_id = ?', - 'params' => [1 => 1], - 'types' => [1 => ParameterType::INTEGER], - ]] - ); - } else { - $this->startQueryLog(); - } + $this->queryLogger->reset(); $article = $this->em->find(self::ARTICLE, ['id' => 1]); - // TODO: Remove the "if" block when dropping support of doctrine/dbal 2. - if (!class_exists(Middleware::class)) { - $sqlQueriesExecuted = $this->queryAnalyzer->getExecutedQueries(); - static::assertCount(2, $sqlQueriesExecuted); - static::assertSame('SELECT t0.id AS id_1, t0.locale AS locale_2, t0.field AS field_3, t0.content AS content_4, t0.object_id AS object_id_5 FROM article_translations t0 WHERE t0.object_id = 1', $sqlQueriesExecuted[1]); - } + static::assertCount(2, $this->queryLogger->queries); + + static::assertSame([ + 'message' => 'Executing statement: {sql} (parameters: {params}, types: {types})', + 'context' => [ + 'sql' => 'SELECT t0.id AS id_1, t0.title AS title_2 FROM Article t0 WHERE t0.id = ?', + 'params' => [1 => 1], + 'types' => [1 => ParameterType::INTEGER], + ], + ], $this->queryLogger->queries[0]); + + static::assertSame([ + 'message' => 'Executing statement: {sql} (parameters: {params}, types: {types})', + 'context' => [ + 'sql' => 'SELECT t0.id AS id_1, t0.locale AS locale_2, t0.field AS field_3, t0.content AS content_4, t0.object_id AS object_id_5 FROM article_translations t0 WHERE t0.object_id = ?', + 'params' => [1 => 1], + 'types' => [1 => ParameterType::INTEGER], + ], + ], $this->queryLogger->queries[1]); static::assertSame('lt', $article->getTitle()); } @@ -191,29 +184,7 @@ public function testShouldFindFromIdentityMap(): void $this->em->persist($article); $this->em->flush(); - // TODO: Remove the "if" check and "else" body when dropping support of doctrine/dbal 2. - if (class_exists(Middleware::class)) { - $this->queryLogger - ->expects(static::exactly(3)) - ->method('debug') - ->withConsecutive( - ['Beginning transaction'], - ['Executing statement: {sql} (parameters: {params}, types: {types})', [ - 'sql' => 'UPDATE article_translations SET content = ? WHERE id = ?', - 'params' => [ - 1 => 'change lt', - 2 => 1, - ], - 'types' => [ - 1 => ParameterType::STRING, - 2 => ParameterType::INTEGER, - ], - ]], - ['Committing transaction'] - ); - } else { - $this->startQueryLog(); - } + $this->queryLogger->reset(); $this->translatableListener->setTranslatableLocale('lt'); $article->setTitle('change lt'); @@ -221,12 +192,32 @@ public function testShouldFindFromIdentityMap(): void $this->em->persist($article); $this->em->flush(); - // TODO: Remove the "if" block when dropping support of doctrine/dbal 2. - if (!class_exists(Middleware::class)) { - $sqlQueriesExecuted = $this->queryAnalyzer->getExecutedQueries(); - static::assertCount(3, $sqlQueriesExecuted); // one update, transaction start - commit - static::assertSame("UPDATE article_translations SET content = 'change lt' WHERE id = 1", $sqlQueriesExecuted[1]); - } + static::assertCount(3, $this->queryLogger->queries); + + static::assertSame([ + 'message' => 'Beginning transaction', + 'context' => [], + ], $this->queryLogger->queries[0]); + + static::assertSame([ + 'message' => 'Executing statement: {sql} (parameters: {params}, types: {types})', + 'context' => [ + 'sql' => 'UPDATE article_translations SET content = ? WHERE id = ?', + 'params' => [ + 1 => 'change lt', + 2 => 1, + ], + 'types' => [ + 1 => ParameterType::STRING, + 2 => ParameterType::INTEGER, + ], + ], + ], $this->queryLogger->queries[1]); + + static::assertSame([ + 'message' => 'Committing transaction', + 'context' => [], + ], $this->queryLogger->queries[2]); } public function testShouldBeAbleToUseTranslationQueryHint(): void @@ -239,31 +230,21 @@ public function testShouldBeAbleToUseTranslationQueryHint(): void ->setHint(TranslatableListener::HINT_TRANSLATABLE_LOCALE, 'lt') ; - // TODO: Remove the "if" check and "else" body when dropping support of doctrine/dbal 2. - if (class_exists(Middleware::class)) { - $this->queryLogger - ->expects(static::exactly(1)) - ->method('debug') - ->withConsecutive( - ['Executing query: {sql}', [ - 'sql' => "SELECT CAST(t1_.content AS VARCHAR(128)) AS title_0 FROM Article a0_ LEFT JOIN article_translations t1_ ON t1_.locale = 'lt' AND t1_.field = 'title' AND t1_.object_id = a0_.id", - ]] - ); - } else { - $this->startQueryLog(); - } + $this->queryLogger->reset(); $result = $query->getArrayResult(); static::assertCount(1, $result); static::assertSame('lt', $result[0]['title']); - // TODO: Remove the "if" block when dropping support of doctrine/dbal 2. - if (!class_exists(Middleware::class)) { - $sqlQueriesExecuted = $this->queryAnalyzer->getExecutedQueries(); - static::assertCount(1, $sqlQueriesExecuted); - static::assertSame("SELECT CAST(t1_.content AS VARCHAR(128)) AS title_0 FROM Article a0_ LEFT JOIN article_translations t1_ ON t1_.locale = 'lt' AND t1_.field = 'title' AND t1_.object_id = a0_.id", $sqlQueriesExecuted[0]); - } + static::assertCount(1, $this->queryLogger->queries); + + static::assertSame([ + 'message' => 'Executing query: {sql}', + 'context' => [ + 'sql' => "SELECT CAST(t1_.content AS VARCHAR(128)) AS title_0 FROM Article a0_ LEFT JOIN article_translations t1_ ON t1_.locale = 'lt' AND t1_.field = 'title' AND t1_.object_id = a0_.id", + ], + ], $this->queryLogger->queries[0]); } protected function getUsedEntityFixtures(): array diff --git a/tests/Gedmo/Translatable/TranslationQueryWalkerTest.php b/tests/Gedmo/Translatable/TranslationQueryWalkerTest.php index f1e533f3e7..554899e4f6 100644 --- a/tests/Gedmo/Translatable/TranslationQueryWalkerTest.php +++ b/tests/Gedmo/Translatable/TranslationQueryWalkerTest.php @@ -12,7 +12,6 @@ namespace Gedmo\Tests\Translatable; use Doctrine\Common\EventManager; -use Doctrine\DBAL\Logging\Middleware; use Doctrine\ORM\Query; use Gedmo\Tests\Tool\BaseTestCaseORM; use Gedmo\Tests\Translatable\Fixture\Article; @@ -146,27 +145,13 @@ public function testShouldSelectWithTranslationFallbackOnSimpleObjectHydration() $this->translatableListener->setTranslatableLocale('ru_ru'); $this->translatableListener->setTranslationFallback(false); - // TODO: Remove the "if" check and "else" body when dropping support of doctrine/dbal 2. - if (class_exists(Middleware::class)) { - $this->queryLogger - ->expects(static::exactly(2)) - ->method('debug') - ->withConsecutive( - ['Executing query: {sql}'], - ['Executing query: {sql}'] - ); - } else { - $this->startQueryLog(); - } + $this->queryLogger->reset(); // simple object hydration $result = $q->getResult(Query::HYDRATE_SIMPLEOBJECT); - // TODO: Remove the "if" block when dropping support of doctrine/dbal 2. - if (!class_exists(Middleware::class)) { - static::assertSame(1, $this->queryAnalyzer->getNumExecutedQueries()); - $this->queryAnalyzer->cleanUp(); - } + static::assertCount(1, $this->queryLogger->queries); + $this->queryLogger->reset(); static::assertNull($result[0]->getTitle()); static::assertNull($result[0]->getContent()); @@ -175,10 +160,7 @@ public function testShouldSelectWithTranslationFallbackOnSimpleObjectHydration() $result = $q->getResult(Query::HYDRATE_SIMPLEOBJECT); - // TODO: Remove the "if" block when dropping support of doctrine/dbal 2. - if (!class_exists(Middleware::class)) { - static::assertSame(1, $this->queryAnalyzer->getNumExecutedQueries()); - } + static::assertCount(1, $this->queryLogger->queries); // Default translation is en_us, so we expect the results in that locale static::assertSame('Food', $result[0]->getTitle()); @@ -195,27 +177,13 @@ public function testSelectWithTranslationFallbackOnArrayHydration(): void $this->translatableListener->setTranslatableLocale('ru_ru'); $this->translatableListener->setTranslationFallback(false); - // TODO: Remove the "if" check and "else" body when dropping support of doctrine/dbal 2. - if (class_exists(Middleware::class)) { - $this->queryLogger - ->expects(static::exactly(2)) - ->method('debug') - ->withConsecutive( - ['Executing query: {sql}'], - ['Executing query: {sql}'] - ); - } else { - $this->startQueryLog(); - } + $this->queryLogger->reset(); // array hydration $result = $q->getArrayResult(); - // TODO: Remove the "if" block when dropping support of doctrine/dbal 2. - if (!class_exists(Middleware::class)) { - static::assertSame(1, $this->queryAnalyzer->getNumExecutedQueries()); - $this->queryAnalyzer->cleanUp(); - } + static::assertCount(1, $this->queryLogger->queries); + $this->queryLogger->reset(); static::assertNull($result[0]['title']); static::assertNull($result[0]['content']); @@ -224,10 +192,7 @@ public function testSelectWithTranslationFallbackOnArrayHydration(): void $result = $q->getArrayResult(); - // TODO: Remove the "if" block when dropping support of doctrine/dbal 2. - if (!class_exists(Middleware::class)) { - static::assertSame(1, $this->queryAnalyzer->getNumExecutedQueries()); - } + static::assertCount(1, $this->queryLogger->queries); // Default translation is en_us, so we expect the results in that locale static::assertSame('Food', $result[0]['title']); @@ -248,27 +213,13 @@ public function testSelectWithOptionalFallbackOnSimpleObjectHydration(): void $this->translatableListener->setTranslatableLocale('ru_ru'); $this->translatableListener->setTranslationFallback(false); - // TODO: Remove the "if" check and "else" body when dropping support of doctrine/dbal 2. - if (class_exists(Middleware::class)) { - $this->queryLogger - ->expects(static::exactly(2)) - ->method('debug') - ->withConsecutive( - ['Executing query: {sql}'], - ['Executing query: {sql}'] - ); - } else { - $this->startQueryLog(); - } + $this->queryLogger->reset(); // simple object hydration $result = $q->getResult(Query::HYDRATE_SIMPLEOBJECT); - // TODO: Remove the "if" block when dropping support of doctrine/dbal 2. - if (!class_exists(Middleware::class)) { - static::assertSame(1, $this->queryAnalyzer->getNumExecutedQueries()); - $this->queryAnalyzer->cleanUp(); - } + static::assertCount(1, $this->queryLogger->queries); + $this->queryLogger->reset(); static::assertNull($result[0]->getTitle()); static::assertSame('John Doe', $result[0]->getAuthor()); // optional fallback is true, force fallback @@ -277,10 +228,7 @@ public function testSelectWithOptionalFallbackOnSimpleObjectHydration(): void $this->translatableListener->setTranslationFallback(true); $result = $q->getResult(Query::HYDRATE_SIMPLEOBJECT); - // TODO: Remove the "if" block when dropping support of doctrine/dbal 2. - if (!class_exists(Middleware::class)) { - static::assertSame(1, $this->queryAnalyzer->getNumExecutedQueries()); - } + static::assertCount(1, $this->queryLogger->queries); // Default translation is en_us, so we expect the results in that locale static::assertSame('Food', $result[0]->getTitle()); @@ -361,29 +309,13 @@ public function testShouldSelectWithTranslationFallbackOnObjectHydration(): void $this->translatableListener->setTranslatableLocale('ru_ru'); $this->translatableListener->setTranslationFallback(false); - // TODO: Remove the "if" check and "else" body when dropping support of doctrine/dbal 2. - if (class_exists(Middleware::class)) { - $this->queryLogger - ->expects(static::exactly(4)) - ->method('debug') - ->withConsecutive( - ['Executing query: {sql}'], - ['Executing query: {sql}'], - ['Executing query: {sql}'], - ['Executing query: {sql}'] - ); - } else { - $this->startQueryLog(); - } + $this->queryLogger->reset(); // object hydration $result = $q->getResult(); - // TODO: Remove the "if" block when dropping support of doctrine/dbal 2. - if (!class_exists(Middleware::class)) { - static::assertSame(1, $this->queryAnalyzer->getNumExecutedQueries()); - $this->queryAnalyzer->cleanUp(); - } + static::assertCount(1, $this->queryLogger->queries); + $this->queryLogger->reset(); static::assertNull($result[0]->getTitle()); static::assertNull($result[0]->getContent()); @@ -391,10 +323,7 @@ public function testShouldSelectWithTranslationFallbackOnObjectHydration(): void $this->translatableListener->setTranslationFallback(true); $result = $q->getResult(); - // TODO: Remove the "if" block when dropping support of doctrine/dbal 2. - if (!class_exists(Middleware::class)) { - static::assertSame(1, $this->queryAnalyzer->getNumExecutedQueries()); - } + static::assertCount(1, $this->queryLogger->queries); // Default translation is en_us, so we expect the results in that locale static::assertSame('Food', $result[0]->getTitle()); diff --git a/tests/Gedmo/Tree/Fixture/Document/Article.php b/tests/Gedmo/Tree/Fixture/Document/Article.php index 465457b20f..2f47ab76c6 100644 --- a/tests/Gedmo/Tree/Fixture/Document/Article.php +++ b/tests/Gedmo/Tree/Fixture/Document/Article.php @@ -56,11 +56,11 @@ class Article /** * @Gedmo\TreeParent * - * @Mongo\ReferenceOne(targetDocument="Article") + * @Mongo\ReferenceOne(targetDocument="Gedmo\Tests\Tree\Fixture\Document\Article") */ #[Mongo\ReferenceOne(targetDocument: self::class)] #[Gedmo\TreeParent] - private ?\Gedmo\Tests\Tree\Fixture\Document\Article $parent = null; + private ?Article $parent = null; /** * @var int|null diff --git a/tests/Gedmo/Tree/Fixture/Repository/BehavioralCategoryRepository.php b/tests/Gedmo/Tree/Fixture/Repository/BehavioralCategoryRepository.php index f1e1d2e65f..a3ce3ff266 100644 --- a/tests/Gedmo/Tree/Fixture/Repository/BehavioralCategoryRepository.php +++ b/tests/Gedmo/Tree/Fixture/Repository/BehavioralCategoryRepository.php @@ -11,8 +11,12 @@ namespace Gedmo\Tests\Tree\Fixture\Repository; +use Gedmo\Tests\Tree\Fixture\BehavioralCategory; use Gedmo\Tree\Entity\Repository\NestedTreeRepository; +/** + * @template-extends NestedTreeRepository + */ final class BehavioralCategoryRepository extends NestedTreeRepository { } diff --git a/tests/Gedmo/Tree/MaterializedPathODMMongoDBRepositoryTest.php b/tests/Gedmo/Tree/MaterializedPathODMMongoDBRepositoryTest.php index 71f0fe3a6c..4476405fd0 100644 --- a/tests/Gedmo/Tree/MaterializedPathODMMongoDBRepositoryTest.php +++ b/tests/Gedmo/Tree/MaterializedPathODMMongoDBRepositoryTest.php @@ -30,9 +30,9 @@ final class MaterializedPathODMMongoDBRepositoryTest extends BaseTestCaseMongoOD private const CATEGORY = Category::class; /** - * @var MaterializedPathRepository + * @var MaterializedPathRepository */ - protected $repo; + private MaterializedPathRepository $repo; protected function setUp(): void { diff --git a/tests/Gedmo/Tree/MaterializedPathORMRepositoryTest.php b/tests/Gedmo/Tree/MaterializedPathORMRepositoryTest.php index 1abc6aff04..1f244d9981 100644 --- a/tests/Gedmo/Tree/MaterializedPathORMRepositoryTest.php +++ b/tests/Gedmo/Tree/MaterializedPathORMRepositoryTest.php @@ -12,6 +12,7 @@ namespace Gedmo\Tests\Tree; use Doctrine\Common\EventManager; +use Doctrine\Persistence\Proxy; use Gedmo\Exception\InvalidArgumentException; use Gedmo\Tests\Tool\BaseTestCaseORM; use Gedmo\Tests\Tree\Fixture\MPCategory; @@ -30,8 +31,8 @@ final class MaterializedPathORMRepositoryTest extends BaseTestCaseORM private const CATEGORY = MPCategory::class; private const CATEGORY_WITH_TRIMMED_SEPARATOR = MPCategoryWithTrimmedSeparator::class; - /** @var MaterializedPathRepository */ - protected $repo; + /** @var MaterializedPathRepository */ + private MaterializedPathRepository $repo; private TreeListener $listener; @@ -147,11 +148,11 @@ public function testGetChildrenForEntityWithTrimmedSeparators(): void { $this->populate(self::CATEGORY_WITH_TRIMMED_SEPARATOR); - $this->repo = $this->em->getRepository(self::CATEGORY_WITH_TRIMMED_SEPARATOR); - $root = $this->repo->findOneBy(['title' => 'Food']); + $repo = $this->em->getRepository(self::CATEGORY_WITH_TRIMMED_SEPARATOR); + $root = $repo->findOneBy(['title' => 'Food']); // Get all children from the root, NOT including it - $result = $this->repo->getChildren($root, false, 'title'); + $result = $repo->getChildren($root, false, 'title'); static::assertCount(4, $result); static::assertSame('Carrots', $result[0]->getTitle()); @@ -160,7 +161,7 @@ public function testGetChildrenForEntityWithTrimmedSeparators(): void static::assertSame('Vegitables', $result[3]->getTitle()); // Get all children from the root, including it - $result = $this->repo->getChildren($root, false, 'title', 'asc', true); + $result = $repo->getChildren($root, false, 'title', 'asc', true); static::assertCount(5, $result); static::assertSame('Carrots', $result[0]->getTitle()); @@ -170,13 +171,13 @@ public function testGetChildrenForEntityWithTrimmedSeparators(): void static::assertSame('Vegitables', $result[4]->getTitle()); // Get direct children from the root, NOT including it - $result = $this->repo->getChildren($root, true, 'title', 'asc'); + $result = $repo->getChildren($root, true, 'title', 'asc'); static::assertCount(2, $result); static::assertSame('Fruits', $result[0]->getTitle()); static::assertSame('Vegitables', $result[1]->getTitle()); // Get direct children from the root, including it - $result = $this->repo->getChildren($root, true, 'title', 'asc', true); + $result = $repo->getChildren($root, true, 'title', 'asc', true); static::assertCount(3, $result); static::assertSame('Food', $result[0]->getTitle()); @@ -184,7 +185,7 @@ public function testGetChildrenForEntityWithTrimmedSeparators(): void static::assertSame('Vegitables', $result[2]->getTitle()); // Get ALL nodes - $result = $this->repo->getChildren(null, false, 'title'); + $result = $repo->getChildren(null, false, 'title'); static::assertCount(9, $result); static::assertSame('Best Whisky', $result[0]->getTitle()); @@ -198,7 +199,7 @@ public function testGetChildrenForEntityWithTrimmedSeparators(): void static::assertSame('Whisky', $result[8]->getTitle()); // Get ALL root nodes - $result = $this->repo->getChildren(null, true, 'title'); + $result = $repo->getChildren(null, true, 'title'); static::assertCount(3, $result); static::assertSame('Drinks', $result[0]->getTitle()); @@ -334,6 +335,7 @@ public function testIssue458(): void $newNode = $this->createCategory(); $parent = $node->getParent(); + static::assertInstanceOf(Proxy::class, $parent); static::assertFalse($parent->__isInitialized()); $newNode->setTitle('New Node'); diff --git a/tests/Gedmo/Tree/NestedTreeRootRepositoryTest.php b/tests/Gedmo/Tree/NestedTreeRootRepositoryTest.php index 41dc4a68eb..9b8c853a90 100644 --- a/tests/Gedmo/Tree/NestedTreeRootRepositoryTest.php +++ b/tests/Gedmo/Tree/NestedTreeRootRepositoryTest.php @@ -255,7 +255,6 @@ public function testShouldRemoveRootNodeFromTree(): void */ public function testGetPathAsStringWithInvalidStringMethod($stringMethod): void { - /** @var NestedTreeRepository $repo */ $repo = $this->em->getRepository(self::CATEGORY); $carrots = $repo->findOneBy(['title' => 'Carrots']); @@ -278,7 +277,7 @@ public static function invalidStringMethods(): iterable public function testShouldHandleBasicRepositoryMethods(): void { - /** @var NestedTreeRepository $repo */ + /** @var NestedTreeRepository $repo */ $repo = $this->em->getRepository(self::CATEGORY); $carrots = $repo->findOneBy(['title' => 'Carrots']); @@ -330,7 +329,7 @@ public function testShouldHandleBasicRepositoryMethods(): void public function testShouldHandleAdvancedRepositoryFunctions(): void { $this->populateMore(); - /** @var NestedTreeRepository $repo */ + /** @var NestedTreeRepository $repo */ $repo = $this->em->getRepository(self::CATEGORY); // verification diff --git a/tests/Gedmo/Tree/NestedTreeRootTest.php b/tests/Gedmo/Tree/NestedTreeRootTest.php index 24b97b1c0b..247b2de40b 100644 --- a/tests/Gedmo/Tree/NestedTreeRootTest.php +++ b/tests/Gedmo/Tree/NestedTreeRootTest.php @@ -16,7 +16,6 @@ use Gedmo\Tests\Tool\BaseTestCaseORM; use Gedmo\Tests\Tree\Fixture\ForeignRootCategory; use Gedmo\Tests\Tree\Fixture\RootCategory; -use Gedmo\Tree\Entity\Repository\NestedTreeRepository; use Gedmo\Tree\TreeListener; /** @@ -332,7 +331,6 @@ public function testRemoval(): void public function testTreeWithRootPointingAtAnotherTable(): void { // depopulate, i don't want the other stuff in db - /** @var NestedTreeRepository $repo */ $repo = $this->em->getRepository(ForeignRootCategory::class); $all = $repo->findAll(); foreach ($all as $one) { diff --git a/tests/Gedmo/Tree/TreeObjectHydratorTest.php b/tests/Gedmo/Tree/TreeObjectHydratorTest.php index a10069f8cc..4a34172972 100644 --- a/tests/Gedmo/Tree/TreeObjectHydratorTest.php +++ b/tests/Gedmo/Tree/TreeObjectHydratorTest.php @@ -12,7 +12,6 @@ namespace Gedmo\Tests\Tree; use Doctrine\Common\EventManager; -use Doctrine\DBAL\Logging\DebugStack; use Doctrine\ORM\Query; use Gedmo\Tests\Tool\BaseTestCaseORM; use Gedmo\Tests\Tree\Fixture\Category; @@ -48,8 +47,7 @@ public function testFullTreeHydration(): void $this->populate(); $this->em->clear(); - $stack = new DebugStack(); - $this->em->getConfiguration()->setSQLLogger($stack); + $this->queryLogger->reset(); $repo = $this->em->getRepository(self::ROOT_CATEGORY); @@ -90,7 +88,7 @@ public function testFullTreeHydration(): void static::assertCount(0, $citrons->getChildren()); // Make sure only one query was executed - static::assertCount(1, $stack->queries); + static::assertCount(1, $this->queryLogger->queries); } public function testPartialTreeHydration(): void @@ -98,10 +96,9 @@ public function testPartialTreeHydration(): void $this->populate(); $this->em->clear(); - $stack = new DebugStack(); - $this->em->getConfiguration()->setSQLLogger($stack); + $this->queryLogger->reset(); - /** @var NestedTreeRepository $repo */ + /** @var NestedTreeRepository $repo */ $repo = $this->em->getRepository(self::ROOT_CATEGORY); $fruits = $repo->findOneBy(['title' => 'Fruits']); @@ -124,7 +121,7 @@ public function testPartialTreeHydration(): void static::assertSame('Citrons', $citrons->getTitle()); static::assertCount(0, $citrons->getChildren()); - static::assertCount(2, $stack->queries); + static::assertCount(2, $this->queryLogger->queries); } public function testMultipleRootNodesTreeHydration(): void @@ -132,10 +129,9 @@ public function testMultipleRootNodesTreeHydration(): void $this->populate(); $this->em->clear(); - $stack = new DebugStack(); - $this->em->getConfiguration()->setSQLLogger($stack); + $this->queryLogger->reset(); - /** @var NestedTreeRepository $repo */ + /** @var NestedTreeRepository $repo */ $repo = $this->em->getRepository(self::ROOT_CATEGORY); $food = $repo->findOneBy(['title' => 'Food']); @@ -170,7 +166,7 @@ public function testMultipleRootNodesTreeHydration(): void static::assertSame('Citrons', $citrons->getTitle()); static::assertCount(0, $citrons->getChildren()); - static::assertCount(2, $stack->queries); + static::assertCount(2, $this->queryLogger->queries); } protected function getUsedEntityFixtures(): array diff --git a/tests/Gedmo/Wrapper/EntityWrapperTest.php b/tests/Gedmo/Wrapper/EntityWrapperTest.php index 1ee6d359db..8c7cce50d9 100644 --- a/tests/Gedmo/Wrapper/EntityWrapperTest.php +++ b/tests/Gedmo/Wrapper/EntityWrapperTest.php @@ -12,7 +12,7 @@ namespace Gedmo\Tests\Wrapper; use Doctrine\Common\EventManager; -use Doctrine\ORM\Proxy\Proxy; +use Doctrine\Persistence\Proxy; use Gedmo\Tests\Tool\BaseTestCaseORM; use Gedmo\Tests\Wrapper\Fixture\Entity\Article; use Gedmo\Tests\Wrapper\Fixture\Entity\Composite;