diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index d6322e59..ff96e40f 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -13,8 +13,8 @@ on: jobs: coding-standards: name: "Coding Standards" - uses: "doctrine/.github/.github/workflows/coding-standards.yml@1.3.0" + uses: "doctrine/.github/.github/workflows/coding-standards.yml@5.2.0" with: - php-version: '8.1' + php-version: '8.2' composer-options: '--prefer-dist --ignore-platform-req=php' diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 14d6da58..f2cdaae4 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -19,7 +19,6 @@ jobs: fail-fast: false matrix: php-version: - - "8.1" - "8.2" - "8.3" dependencies: diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 74a86494..9be77f61 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -12,7 +12,6 @@ jobs: strategy: matrix: php-version: - - "8.1" - "8.2" - "8.3" diff --git a/.gitignore b/.gitignore index ff5dc42b..ba8e64dc 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ composer.phar composer.lock .DS_Store .phpcs-cache -.phpunit.result.cache +.phpunit.cache /tests/Stubs/storage/framework/views/* !/tests/Stubs/storage/framework/views/.gitkeep diff --git a/README.md b/README.md index ad8c732e..3846d8a4 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,9 @@ or in the docs directory. Versions -------- -* Version 3 supports DBAL ^4.0 and ORM ^3.0. See the [upgrade guide](https://laravel-doctrine-orm-official.readthedocs.io/en/latest/upgrade.html) for more information. -* Version 2 supports Laravel 9 - 11, DBAL ^3.0 and ORM ^2.0. -* Version 1 supports Laravel 6 - 9, DBAL ^2.0 and ORM ^2.0. +* Version 3 supports DBAL ^4.0, ORM ^3.0, and PHP 8.2. See the [upgrade guide](https://laravel-doctrine-orm-official.readthedocs.io/en/latest/upgrade.html) for more information. +* Version 2 supports Laravel 9 - 11, DBAL ^3.0, ORM ^2.0, and PHP ^8.0. +* Version 1 supports Laravel 6 - 9, DBAL ^2.0, ORM ^2.0, and PHP ^5.5 - ^8.0. See [documentation in version 2](https://github.com/laravel-doctrine/orm/tree/2.0?tab=readme-ov-file#versions) License diff --git a/composer.json b/composer.json index e8df54b5..55b61197 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "doctrine/dbal": "^3.0 || ^4.0", "doctrine/orm": "^3.1", "doctrine/persistence": "^3.3", @@ -44,9 +44,10 @@ "php-parallel-lint/php-parallel-lint": "^1.4", "phpstan/phpstan": "^1.9", "phpstan/phpstan-deprecation-rules": "^1.1", - "phpunit/phpunit": "^9.3", + "phpunit/phpunit": "^11.4", "fakerphp/faker": "^1.23", - "laravel/framework": "^10.0 || ^11.0" + "laravel/framework": "^10.0 || ^11.0", + "orchestra/testbench": "^9.5" }, "conflict": { "laravel/lumen": "*" @@ -61,7 +62,10 @@ }, "autoload-dev": { "psr-4": { - "LaravelDoctrineTest\\ORM\\": "tests/" + "LaravelDoctrineTest\\ORM\\": "tests/", + "Workbench\\App\\": "workbench/app/", + "Workbench\\Database\\Factories\\": "workbench/database/factories/", + "Workbench\\Database\\Seeders\\": "workbench/database/seeders/" } }, "suggest": { @@ -95,6 +99,21 @@ "vendor/bin/phpunit", "vendor/bin/phpstan analyze src --level 1" ], - "coverage": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=coverage" + "coverage": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=coverage", + "post-autoload-dump": [ + "@clear", + "@prepare" + ], + "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi", + "prepare": "@php vendor/bin/testbench package:discover --ansi", + "build": "@php vendor/bin/testbench workbench:build --ansi", + "serve": [ + "Composer\\Config::disableProcessTimeout", + "@build", + "@php vendor/bin/testbench serve --ansi" + ], + "lint": [ + "@php vendor/bin/phpstan analyse --verbose --ansi" + ] } } diff --git a/config/doctrine.php b/config/doctrine.php index e99d9e39..ea89777f 100644 --- a/config/doctrine.php +++ b/config/doctrine.php @@ -104,7 +104,6 @@ | */ 'extensions' => [ - //LaravelDoctrine\ORM\Extensions\TablePrefix\TablePrefixExtension::class, //LaravelDoctrine\Extensions\Timestamps\TimestampableExtension::class, //LaravelDoctrine\Extensions\SoftDeletes\SoftDeleteableExtension::class, //LaravelDoctrine\Extensions\Sluggable\SluggableExtension::class, diff --git a/phpunit.xml b/phpunit.xml index 493fd4a7..165b475a 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,24 +1,26 @@ - - - - src/ - - + cacheDirectory=".phpunit.cache" + executionOrder="depends,defects" + shortenArraysForExportThreshold="10" + requireCoverageMetadata="false" + beStrictAboutCoverageMetadata="false" + beStrictAboutOutputDuringTests="true" + displayDetailsOnPhpunitDeprecations="true" + failOnPhpunitDeprecation="true" + failOnRisky="true" + failOnWarning="true"> - - ./tests/ + + tests + + + + src + + diff --git a/src/DoctrineServiceProvider.php b/src/DoctrineServiceProvider.php index 69dfbcad..e048bdfd 100644 --- a/src/DoctrineServiceProvider.php +++ b/src/DoctrineServiceProvider.php @@ -10,10 +10,8 @@ use Doctrine\ORM\Proxy\Autoloader; use Doctrine\Persistence\ManagerRegistry; use Illuminate\Contracts\Container\Container; -use Illuminate\Contracts\Validation\Factory as ValidationFactory; use Illuminate\Notifications\ChannelManager; use Illuminate\Support\ServiceProvider; -use Illuminate\Support\Str; use InvalidArgumentException; use LaravelDoctrine\ORM\Auth\DoctrineUserProvider; use LaravelDoctrine\ORM\Configuration\Cache\CacheManager; @@ -37,8 +35,8 @@ use function assert; use function class_exists; +use function config; use function config_path; -use function property_exists; class DoctrineServiceProvider extends ServiceProvider { @@ -50,13 +48,9 @@ public function boot(): void $this->extendAuthManager(); $this->extendNotificationChannel(); - if (! $this->isLumen()) { - $this->publishes([ - $this->getConfigPath() => config_path('doctrine.php'), - ], 'config'); - } - - $this->ensureValidatorIsUsable(); + $this->publishes([ + $this->getConfigPath() => config_path('doctrine.php'), + ], 'config'); } /** @@ -83,28 +77,6 @@ public function register(): void $this->registerPresenceVerifierProvider(); } - protected function ensureValidatorIsUsable(): void - { - if (! $this->isLumen()) { - return; - } - - assert(property_exists($this->app, 'availableBindings')); - - if ($this->shouldRegisterDoctrinePresenceValidator()) { - // due to weirdness the default presence verifier overrides one set by a service provider - // so remove them so we can re add our implementation later - unset($this->app->availableBindings['validator']); - unset($this->app->availableBindings[ValidationFactory::class]); - } else { - // resolve the db, - // this makes `isset($this->app['db']) == true` - // which is required to set the presence verifier - // in the default ValidationServiceProvider implementation - $this->app['db']; - } - } - /** * Merge config */ @@ -114,14 +86,6 @@ protected function mergeConfig(): void $this->getConfigPath(), 'doctrine', ); - - if (! $this->isLumen()) { - return; - } - - $this->app->configure('cache'); - $this->app->configure('database'); - $this->app->configure('doctrine'); } /** @@ -232,15 +196,7 @@ protected function registerExtensions(): void */ protected function registerPresenceVerifierProvider(): void { - if ($this->isLumen()) { - $this->app->singleton('validator', function () { - $this->app->register(PresenceVerifierProvider::class); - - return $this->app->make('validator'); - }); - } else { - $this->app->register(PresenceVerifierProvider::class); - } + $this->app->register(PresenceVerifierProvider::class); } /** @@ -279,7 +235,7 @@ protected function extendAuthManager(): void /** * Boots the extension manager at the appropriate time depending on if the app - * is running as Laravel HTTP, Lumen HTTP or in a console environment + * is running as Laravel HTTP or in a console environment */ protected function bootExtensionManager(): void { @@ -354,13 +310,8 @@ protected function registerConsoleCommands(): void ]); } - protected function isLumen(): bool - { - return Str::contains($this->app->version(), 'Lumen'); - } - protected function shouldRegisterDoctrinePresenceValidator(): bool { - return $this->app['config']->get('doctrine.doctrine_presence_verifier', true); + return config('doctrine.doctrine_presence_verifier', true); } } diff --git a/src/IlluminateRegistry.php b/src/IlluminateRegistry.php index a4eae915..45663967 100644 --- a/src/IlluminateRegistry.php +++ b/src/IlluminateRegistry.php @@ -301,8 +301,10 @@ public function getRepository(string $persistentObject, string|null $persistentM * Gets the object manager associated with a given class. * * @param class-string $className A persistent object class name. + * + * @return ($throwExceptionIfNotFound is true ? ObjectManager : ObjectManager|null) */ - public function getManagerForClass(string $className): ObjectManager|null + public function getManagerForClass(string $className, bool $throwExceptionIfNotFound = false): ObjectManager|null { // Check for namespace alias if (strpos($className, ':') !== false) { @@ -335,7 +337,11 @@ public function getManagerForClass(string $className): ObjectManager|null } } - throw new RuntimeException('No manager found for class ' . $className); + if ($throwExceptionIfNotFound) { + throw new RuntimeException('No manager found for class ' . $className); + } + + return null; } /** diff --git a/src/Testing/Concerns/InteractsWithEntities.php b/src/Testing/Concerns/InteractsWithEntities.php deleted file mode 100644 index ae980130..00000000 --- a/src/Testing/Concerns/InteractsWithEntities.php +++ /dev/null @@ -1,102 +0,0 @@ -entityManager()->find($class, $id); - - Assert::assertNotNull($entity, 'A [' . $class . '] entity was not found by id: ' . print_r($id, true)); - - return $entity; - } - - public function entityDoesNotExist(string $class, mixed $id): void - { - Assert::assertNull( - $this->entityManager()->find($class, $id), - 'A [' . $class . '] entity was found by id: ' . print_r($id, true), - ); - } - - /** - * @param mixed[] $criteria - * - * @return mixed[] - */ - public function entitiesMatch(string $class, array $criteria, int|null $count = null): mixed - { - $entities = $this->entityManager()->getRepository($class)->findBy($criteria); - - Assert::assertNotEmpty($entities, 'No [' . $class . '] entities were found with the given criteria: ' . $this->outputCriteria($criteria)); - - if ($count !== null) { - Assert::assertCount( - $count, - $entities, - 'Expected to find ' . $count . ' [' . $class . '] entities, but found ' . count($entities) . - ' with the given criteria: ' . $this->outputCriteria($criteria), - ); - } - - return $entities; - } - - /** @param mixed[] $criteria */ - public function noEntitiesMatch(string $class, array $criteria): void - { - Assert::assertEmpty( - $this->entityManager()->getRepository($class)->findBy($criteria), - 'Some [' . $class . '] entities were found with the given criteria: ' . $this->outputCriteria($criteria), - ); - } - - /** - * Replaces entities with their ids in the criteria array and print_r them - * - * @param mixed[] $criteria - */ - private function outputCriteria(array $criteria): string - { - $criteria = collect($criteria)->map(function ($value) { - if (! is_object($value)) { - return $value; - } - - $unityOfWork = $this->entityManager()->getUnitOfWork(); - if ($unityOfWork->isInIdentityMap($value)) { - return $unityOfWork->getEntityIdentifier($value); - } - - return $value; - })->all(); - - return print_r($criteria, true); - } - - protected function entityManager(): mixed - { - if (! isset($this->app)) { - Assert::markTestSkipped( - 'Tests that interact with entities through Doctrine need to have Laravel\'s Application object.' . PHP_EOL . - 'Please extend Laravel\'s TestCase to use this trait.', - ); - } - - return $this->app->make('em'); - } -} diff --git a/testbench.yaml b/testbench.yaml new file mode 100644 index 00000000..d5e9cf31 --- /dev/null +++ b/testbench.yaml @@ -0,0 +1,20 @@ +providers: + - LaravelDoctrine\ORM\DoctrineServiceProvider + +workbench: + start: '/' + install: true + health: false + discovers: + web: fale + api: false + commands: false + components: false + views: false + build: + - asset-publish + - create-sqlite-db + - db-wipe + assets: + - laravel-assets + sync: [] diff --git a/tests/Assets/Extensions/ExtensionMock.php b/tests/Assets/Extensions/ExtensionMock.php index c84350de..358fdd85 100644 --- a/tests/Assets/Extensions/ExtensionMock.php +++ b/tests/Assets/Extensions/ExtensionMock.php @@ -14,7 +14,7 @@ class ExtensionMock implements Extension public function addSubscribers(EventManager $manager, EntityManagerInterface $em): void { // Confirm it gets called - (new ExtensionManagerTest())->assertTrue(true); + (new ExtensionManagerTest('test'))->assertTrue(true); } /** @return mixed[] */ diff --git a/tests/Assets/MyDoctrineExtender.php b/tests/Assets/MyDoctrineExtender.php index 5da39a47..1833d9ba 100644 --- a/tests/Assets/MyDoctrineExtender.php +++ b/tests/Assets/MyDoctrineExtender.php @@ -14,6 +14,6 @@ class MyDoctrineExtender implements DoctrineExtender { public function extend(Configuration $configuration, Connection $connection, EventManager $eventManager): void { - (new DoctrineManagerTest())->assertExtendedCorrectly($configuration, $connection, $eventManager); + (new DoctrineManagerTest('test'))->assertExtendedCorrectly($configuration, $connection, $eventManager); } } diff --git a/tests/Feature/Configuration/Cache/ApcCacheProviderTest.php b/tests/Feature/Configuration/Cache/ApcCacheProviderTest.php index f093f740..58273b72 100644 --- a/tests/Feature/Configuration/Cache/ApcCacheProviderTest.php +++ b/tests/Feature/Configuration/Cache/ApcCacheProviderTest.php @@ -10,7 +10,7 @@ use Mockery as m; use Psr\Cache\CacheItemPoolInterface; -class ApcCacheProviderTest extends CacheProviderTest +class ApcCacheProviderTest extends CacheProvider { public function getProvider(): mixed { diff --git a/tests/Feature/Configuration/Cache/ArrayCacheProviderTest.php b/tests/Feature/Configuration/Cache/ArrayCacheProviderTest.php index 2b15cfcd..51a8f364 100644 --- a/tests/Feature/Configuration/Cache/ArrayCacheProviderTest.php +++ b/tests/Feature/Configuration/Cache/ArrayCacheProviderTest.php @@ -7,7 +7,7 @@ use LaravelDoctrine\ORM\Configuration\Cache\ArrayCacheProvider; use Symfony\Component\Cache\Adapter\ArrayAdapter; -class ArrayCacheProviderTest extends CacheProviderTest +class ArrayCacheProviderTest extends CacheProvider { public function getProvider(): mixed { diff --git a/tests/Feature/Configuration/Cache/CacheManagerTest.php b/tests/Feature/Configuration/Cache/CacheManagerTest.php index 0cc64c33..7405dbbf 100644 --- a/tests/Feature/Configuration/Cache/CacheManagerTest.php +++ b/tests/Feature/Configuration/Cache/CacheManagerTest.php @@ -19,18 +19,18 @@ class CacheManagerTest extends TestCase { protected CacheManager $manager; - protected Container $app; + protected Container $testApp; protected Repository $config; protected function setUp(): void { - $this->app = m::mock(Container::class); - $this->app->shouldReceive('make')->andReturn(m::self()); - $this->app->shouldReceive('get')->with('doctrine.cache.default', 'array')->andReturn('array'); + $this->testApp = m::mock(Container::class); + $this->testApp->shouldReceive('make')->andReturn(m::self()); + $this->testApp->shouldReceive('get')->with('doctrine.cache.default', 'array')->andReturn('array'); $this->manager = new CacheManager( - $this->app, + $this->testApp, ); parent::setUp(); @@ -38,7 +38,7 @@ protected function setUp(): void public function testDriverReturnsTheDefaultDriver(): void { - $this->app->shouldReceive('resolve')->andReturn(new ArrayCacheProvider()); + $this->testApp->shouldReceive('resolve')->andReturn(new ArrayCacheProvider()); $this->assertInstanceOf(ArrayCacheProvider::class, $this->manager->driver()); $this->assertInstanceOf(ArrayAdapter::class, $this->manager->driver()->resolve()); @@ -49,7 +49,7 @@ public function testDriverCanReturnAGivenDriver(): void $config = m::mock(Repository::class); $app = m::mock(Application::class); - $this->app->shouldReceive('resolve')->andReturn(new FileCacheProvider( + $this->testApp->shouldReceive('resolve')->andReturn(new FileCacheProvider( $config, $app, )); diff --git a/tests/Feature/Configuration/Cache/CacheProviderTest.php b/tests/Feature/Configuration/Cache/CacheProvider.php similarity index 91% rename from tests/Feature/Configuration/Cache/CacheProviderTest.php rename to tests/Feature/Configuration/Cache/CacheProvider.php index d3763c14..98d7e841 100644 --- a/tests/Feature/Configuration/Cache/CacheProviderTest.php +++ b/tests/Feature/Configuration/Cache/CacheProvider.php @@ -7,7 +7,7 @@ use LaravelDoctrineTest\ORM\TestCase; use Mockery; -abstract class CacheProviderTest extends TestCase +abstract class CacheProvider extends TestCase { abstract public function getProvider(): mixed; diff --git a/tests/Feature/Configuration/Cache/FileCacheProviderTest.php b/tests/Feature/Configuration/Cache/FileCacheProviderTest.php index 9bc16e20..de844bf1 100644 --- a/tests/Feature/Configuration/Cache/FileCacheProviderTest.php +++ b/tests/Feature/Configuration/Cache/FileCacheProviderTest.php @@ -9,13 +9,16 @@ use Mockery as m; use Symfony\Component\Cache\Adapter\FilesystemAdapter; -class FileCacheProviderTest extends CacheProviderTest +class FileCacheProviderTest extends CacheProvider { public function getProvider(): mixed { $config = m::mock(Repository::class); $config->shouldReceive('get') - ->with('cache.stores.file.path', '/storage/framework/cache') + ->with( + 'cache.stores.file.path', + $this->applicationBasePath() . '/storage/framework/cache', + ) ->once() ->andReturn('/tmp'); diff --git a/tests/Feature/Configuration/Cache/MemcachedCacheProviderTest.php b/tests/Feature/Configuration/Cache/MemcachedCacheProviderTest.php index c130b09e..7ef640a0 100644 --- a/tests/Feature/Configuration/Cache/MemcachedCacheProviderTest.php +++ b/tests/Feature/Configuration/Cache/MemcachedCacheProviderTest.php @@ -10,7 +10,7 @@ use Mockery as m; use Psr\Cache\CacheItemPoolInterface; -class MemcachedCacheProviderTest extends CacheProviderTest +class MemcachedCacheProviderTest extends CacheProvider { public function getProvider(): mixed { diff --git a/tests/Feature/Configuration/Cache/PhpFileCacheProviderTest.php b/tests/Feature/Configuration/Cache/PhpFileCacheProviderTest.php index 7fbce6c3..bc305183 100644 --- a/tests/Feature/Configuration/Cache/PhpFileCacheProviderTest.php +++ b/tests/Feature/Configuration/Cache/PhpFileCacheProviderTest.php @@ -9,13 +9,16 @@ use Mockery as m; use Symfony\Component\Cache\Adapter\PhpFilesAdapter; -class PhpFileCacheProviderTest extends CacheProviderTest +class PhpFileCacheProviderTest extends CacheProvider { public function getProvider(): mixed { $config = m::mock(Repository::class); $config->shouldReceive('get') - ->with('cache.stores.file.path', '/storage/framework/cache') + ->with( + 'cache.stores.file.path', + $this->applicationBasePath() . '/storage/framework/cache', + ) ->once() ->andReturn('/tmp'); diff --git a/tests/Feature/Configuration/Cache/RedisCacheProviderTest.php b/tests/Feature/Configuration/Cache/RedisCacheProviderTest.php index a2a8fa03..fed819f0 100644 --- a/tests/Feature/Configuration/Cache/RedisCacheProviderTest.php +++ b/tests/Feature/Configuration/Cache/RedisCacheProviderTest.php @@ -10,7 +10,7 @@ use Mockery as m; use Psr\Cache\CacheItemPoolInterface; -class RedisCacheProviderTest extends CacheProviderTest +class RedisCacheProviderTest extends CacheProvider { public function getProvider(): mixed { diff --git a/tests/Feature/Configuration/Connections/ConnectionManagerTest.php b/tests/Feature/Configuration/Connections/ConnectionManagerTest.php index 896c7dd3..332ee3c4 100644 --- a/tests/Feature/Configuration/Connections/ConnectionManagerTest.php +++ b/tests/Feature/Configuration/Connections/ConnectionManagerTest.php @@ -19,20 +19,20 @@ class ConnectionManagerTest extends TestCase { protected ConnectionManager $manager; - protected Container $app; + protected Container $testApp; protected Repository $config; protected function setUp(): void { - $this->app = m::mock(Container::class); - $this->app->shouldReceive('make')->andReturn(m::self()); + $this->testApp = m::mock(Container::class); + $this->testApp->shouldReceive('make')->andReturn(m::self()); $this->config = m::mock(Repository::class); $this->config->shouldReceive('get'); $this->manager = new ConnectionManager( - $this->app, + $this->testApp, ); parent::setUp(); @@ -40,7 +40,7 @@ protected function setUp(): void public function testDriverReturnsTheDefaultDriver(): void { - $this->app->shouldReceive('resolve')->andReturn( + $this->testApp->shouldReceive('resolve')->andReturn( (new MysqlConnection($this->config))->resolve(), ); @@ -50,7 +50,7 @@ public function testDriverReturnsTheDefaultDriver(): void public function testDriverCanReturnAGivenDriver(): void { - $this->app->shouldReceive('resolve')->andReturn( + $this->testApp->shouldReceive('resolve')->andReturn( (new SqliteConnection($this->config))->resolve(), ); diff --git a/tests/Feature/Configuration/Connections/PrimaryReadReplicaConnectionTest.php b/tests/Feature/Configuration/Connections/PrimaryReadReplicaConnectionTest.php index 074f42c8..4b964228 100644 --- a/tests/Feature/Configuration/Connections/PrimaryReadReplicaConnectionTest.php +++ b/tests/Feature/Configuration/Connections/PrimaryReadReplicaConnectionTest.php @@ -9,6 +9,7 @@ use LaravelDoctrine\ORM\Configuration\Connections\PrimaryReadReplicaConnection; use LaravelDoctrineTest\ORM\TestCase; use Mockery as m; +use PHPUnit\Framework\Attributes\DataProvider; use function class_exists; @@ -31,57 +32,57 @@ protected function setUp(): void * * @return mixed[] */ - public function getPrimaryReplicaConnectionData(): array + public static function getPrimaryReplicaConnectionData(): array { $out = []; // Case #0. Simple valid configuration with mysql base settings. $out[] = [ - $this->getResolvedMysqlConfig(), - $this->getInputConfigwithArrayOfReplicasInReadKey(), - $this->getExpectedConfig(), + self::getResolvedMysqlConfig(), + self::getInputConfigwithArrayOfReplicasInReadKey(), + self::getExpectedConfig(), ]; // Case #1. Configuration is only set in the read/write nodes. $out[] = [ ['driver' => 'pdo_mysql'], - $this->getNodesInputConfig(), - $this->getNodesExpectedConfig(), + self::getNodesInputConfig(), + self::getNodesExpectedConfig(), ]; // Case #2. Simple valid configuration with oracle base settings. $out[] = [ - $this->getResolvedOracleConfig(), - $this->getInputConfigwithArrayOfReplicasInReadKey(), - $this->getOracleExpectedConfig(), + self::getResolvedOracleConfig(), + self::getInputConfigwithArrayOfReplicasInReadKey(), + self::getOracleExpectedConfig(), ]; // Case #3. Simple valid configuration with pgqsql base settings. $out[] = [ - $this->getResolvedPgqsqlConfig(), - $this->getInputConfigwithArrayOfReplicasInReadKey(), - $this->getPgsqlExpectedConfig(), + self::getResolvedPgqsqlConfig(), + self::getInputConfigwithArrayOfReplicasInReadKey(), + self::getPgsqlExpectedConfig(), ]; // Case #4. Simple valid configuration with sqlite base settings. $out[] = [ - $this->getResolvedSqliteConfig(), - $this->getSqliteInputConfig(), - $this->getSqliteExpectedConfig(), + self::getResolvedSqliteConfig(), + self::getSqliteInputConfig(), + self::getSqliteExpectedConfig(), ]; // Case #5. Valid configuration as with 1 replica 'read' entry and plain-text host $out[] = [ - $this->getResolvedMysqlConfig(), - $this->getInputConfigWithPlainTextHostValue(), - $this->getExpectedConfigForCase5(), + self::getResolvedMysqlConfig(), + self::getInputConfigWithPlainTextHostValue(), + self::getExpectedConfigForCase5(), ]; // Case #6. Valid configuration as with 1 replica config 'read' entry and array of hosts in 'host' key $out[] = [ - $this->getResolvedMysqlConfig(), - $this->getInputConfigWithArrayAsHostValue(), - $this->getExpectedConfigForCase6(), + self::getResolvedMysqlConfig(), + self::getInputConfigWithArrayAsHostValue(), + self::getExpectedConfigForCase6(), ]; return $out; @@ -93,9 +94,8 @@ public function getPrimaryReplicaConnectionData(): array * @param mixed[] $resolvedBaseSettings * @param mixed[] $settings * @param mixed[] $expectedOutput - * - * @dataProvider getPrimaryReplicaConnectionData */ + #[DataProvider('getPrimaryReplicaConnectionData')] public function testPrimaryReplicaConnection(array $resolvedBaseSettings, array $settings, array $expectedOutput): void { $this->assertEquals( @@ -109,7 +109,7 @@ public function testPrimaryReplicaConnection(array $resolvedBaseSettings, array * * @return mixed[] */ - private function getInputConfigwithArrayOfReplicasInReadKey(): array + private static function getInputConfigwithArrayOfReplicasInReadKey(): array { return [ 'driver' => 'mysql', @@ -147,7 +147,7 @@ private function getInputConfigwithArrayOfReplicasInReadKey(): array } /** @return mixed[] */ - private function getInputConfigWithPlainTextHostValue(): array + private static function getInputConfigWithPlainTextHostValue(): array { return [ 'driver' => 'mysql', @@ -180,7 +180,7 @@ private function getInputConfigWithPlainTextHostValue(): array } /** @return mixed[] */ - private function getInputConfigWithArrayAsHostValue(): array + private static function getInputConfigWithArrayAsHostValue(): array { return [ 'driver' => 'mysql', @@ -217,7 +217,7 @@ private function getInputConfigWithArrayAsHostValue(): array * * @return mixed[] */ - private function getExpectedConfig(): array + private static function getExpectedConfig(): array { return [ 'wrapperClass' => PrimaryReadReplicaDoctrineWrapper::class, @@ -267,7 +267,7 @@ private function getExpectedConfig(): array * * @return mixed[] */ - private function getExpectedConfigForCase5(): array + private static function getExpectedConfigForCase5(): array { return [ 'wrapperClass' => PrimaryReadReplicaDoctrineWrapper::class, @@ -307,7 +307,7 @@ private function getExpectedConfigForCase5(): array * * @return mixed[] */ - private function getExpectedConfigForCase6(): array + private static function getExpectedConfigForCase6(): array { return [ 'wrapperClass' => PrimaryReadReplicaDoctrineWrapper::class, @@ -357,7 +357,7 @@ private function getExpectedConfigForCase6(): array * * @return mixed[] */ - private function getNodesInputConfig(): array + private static function getNodesInputConfig(): array { return [ 'write' => [ @@ -391,7 +391,7 @@ private function getNodesInputConfig(): array * * @return mixed[] */ - private function getNodesExpectedConfig(): array + private static function getNodesExpectedConfig(): array { return [ 'wrapperClass' => PrimaryReadReplicaDoctrineWrapper::class, @@ -427,9 +427,9 @@ private function getNodesExpectedConfig(): array * * @return mixed[] */ - private function getOracleExpectedConfig(): array + private static function getOracleExpectedConfig(): array { - $expectedConfigOracle = $this->getNodesExpectedConfig(); + $expectedConfigOracle = self::getNodesExpectedConfig(); $expectedConfigOracle['driver'] = 'oci8'; $expectedConfigOracle['primary']['user'] = 'homestead1'; $expectedConfigOracle['serverVersion'] = '5.8'; @@ -447,9 +447,9 @@ private function getOracleExpectedConfig(): array * * @return mixed[] */ - private function getPgsqlExpectedConfig(): array + private static function getPgsqlExpectedConfig(): array { - $expectedConfigPgsql = $this->getNodesExpectedConfig(); + $expectedConfigPgsql = self::getNodesExpectedConfig(); $expectedConfigPgsql['driver'] = 'pgsql'; $expectedConfigPgsql['primary']['user'] = 'homestead1'; $expectedConfigPgsql['primary']['sslmode'] = 'sslmode'; @@ -470,7 +470,7 @@ private function getPgsqlExpectedConfig(): array * * @return mixed[] */ - private function getSqliteExpectedConfig(): array + private static function getSqliteExpectedConfig(): array { return [ 'wrapperClass' => PrimaryReadReplicaDoctrineWrapper::class, @@ -512,9 +512,9 @@ private function getSqliteExpectedConfig(): array * * @return mixed[] */ - private function getSqliteInputConfig(): array + private static function getSqliteInputConfig(): array { - $inputConfigSqlite = $this->getInputConfigwithArrayOfReplicasInReadKey(); + $inputConfigSqlite = self::getInputConfigwithArrayOfReplicasInReadKey(); unset($inputConfigSqlite['read'][0]['database']); unset($inputConfigSqlite['read'][1]['database']); unset($inputConfigSqlite['write']['database']); @@ -527,7 +527,7 @@ private function getSqliteInputConfig(): array * * @return mixed[] */ - private function getResolvedMysqlConfig(): array + private static function getResolvedMysqlConfig(): array { return [ 'driver' => 'pdo_mysql', @@ -547,7 +547,7 @@ private function getResolvedMysqlConfig(): array * * @return mixed[] */ - private function getResolvedOracleConfig(): array + private static function getResolvedOracleConfig(): array { return [ 'driver' => 'oci8', @@ -564,7 +564,7 @@ private function getResolvedOracleConfig(): array * * @return mixed[] */ - private function getResolvedSqliteConfig(): array + private static function getResolvedSqliteConfig(): array { return [ 'driver' => 'pdo_sqlite', @@ -580,7 +580,7 @@ private function getResolvedSqliteConfig(): array * * @return mixed[] */ - private function getResolvedPgqsqlConfig(): array + private static function getResolvedPgqsqlConfig(): array { return [ 'driver' => 'pgsql', diff --git a/tests/Feature/Configuration/MetaData/MetaDataManagerTest.php b/tests/Feature/Configuration/MetaData/MetaDataManagerTest.php index 11490e47..67fb2db0 100644 --- a/tests/Feature/Configuration/MetaData/MetaDataManagerTest.php +++ b/tests/Feature/Configuration/MetaData/MetaDataManagerTest.php @@ -15,15 +15,15 @@ class MetaDataManagerTest extends TestCase { protected MetaDataManager $manager; - protected Container $app; + protected Container $testApp; protected function setUp(): void { - $this->app = m::mock(Container::class); - $this->app->shouldReceive('make')->andReturn(m::self()); + $this->testApp = m::mock(Container::class); + $this->testApp->shouldReceive('make')->andReturn(m::self()); $this->manager = new MetaDataManager( - $this->app, + $this->testApp, ); parent::setUp(); @@ -31,7 +31,7 @@ protected function setUp(): void public function testDriverReturnsTheDefaultDriver(): void { - $this->app->shouldReceive('resolve')->andReturn(new XmlDriver('locator', '.xml')); + $this->testApp->shouldReceive('resolve')->andReturn(new XmlDriver('locator', '.xml')); $this->assertInstanceOf(XmlDriver::class, $this->manager->driver()); } diff --git a/tests/Feature/DoctrineServiceProviderCustomFunctionsTest.php b/tests/Feature/DoctrineServiceProviderCustomFunctionsTest.php new file mode 100644 index 00000000..87f7bfc2 --- /dev/null +++ b/tests/Feature/DoctrineServiceProviderCustomFunctionsTest.php @@ -0,0 +1,37 @@ +app->get('registry'); + + $this->assertInstanceOf( + ManagerRegistry::class, + $registry, + ); + } +} diff --git a/tests/Feature/DoctrineServiceProviderExtensionTest.php b/tests/Feature/DoctrineServiceProviderExtensionTest.php new file mode 100644 index 00000000..48b6cb5a --- /dev/null +++ b/tests/Feature/DoctrineServiceProviderExtensionTest.php @@ -0,0 +1,32 @@ +app->get('registry'); + + $this->assertTrue(true); + } +} diff --git a/tests/Feature/DoctrineServiceProviderInvalidExtensionTest.php b/tests/Feature/DoctrineServiceProviderInvalidExtensionTest.php new file mode 100644 index 00000000..b7832330 --- /dev/null +++ b/tests/Feature/DoctrineServiceProviderInvalidExtensionTest.php @@ -0,0 +1,34 @@ +set('doctrine.extensions', ['invalid' => 'InvalalidExtension']); + }); + } + + public function testInvalidException(): void + { + $this->expectException(ExtensionNotFound::class); + + $registry = $this->app->get('registry'); + } +} diff --git a/tests/Feature/DoctrineServiceProviderTest.php b/tests/Feature/DoctrineServiceProviderTest.php new file mode 100644 index 00000000..0c013cfd --- /dev/null +++ b/tests/Feature/DoctrineServiceProviderTest.php @@ -0,0 +1,40 @@ +app->get('registry'); + + $this->assertInstanceOf( + ManagerRegistry::class, + $registry, + ); + } + + public function testEntityManagerSingleton(): void + { + $em1 = $this->app->get('em'); + $em2 = $this->app->get('em'); + + $this->assertSame($em1, $em2); + } + + public function testMetaDataFactory(): void + { + $metaDataFactory = $this->app->get(ClassMetadataFactory::class); + + $this->assertInstanceOf( + ClassMetadataFactory::class, + $metaDataFactory, + ); + } +} diff --git a/tests/Feature/EntityManagerFactoryTest.php b/tests/Feature/EntityManagerFactoryTest.php index b26ec66d..af9434a2 100644 --- a/tests/Feature/EntityManagerFactoryTest.php +++ b/tests/Feature/EntityManagerFactoryTest.php @@ -38,6 +38,7 @@ use LaravelDoctrineTest\ORM\TestCase; use Mockery as m; use Mockery\Mock; +use PHPUnit\Framework\Attributes\DataProvider; use Psr\Cache\CacheItemPoolInterface; use ReflectionException; use ReflectionObject; @@ -1008,15 +1009,15 @@ protected function enableLaravelNamingStrategy(): void * * @return mixed[] */ - public function getTestPrimaryReadReplicaConnectionData(): array + public static function getTestPrimaryReadReplicaConnectionData(): array { $out = []; // Case #0. Simple valid configuration, everything should go well. - $out[] = [$this->getDummyBaseInputConfig()]; + $out[] = [self::getDummyBaseInputConfig()]; //Case #1. No read DBs set. - $inputConfig = $this->getDummyBaseInputConfig(); + $inputConfig = self::getDummyBaseInputConfig(); unset($inputConfig['read']); $out[] = [ @@ -1026,7 +1027,7 @@ public function getTestPrimaryReadReplicaConnectionData(): array ]; //Case #2. 'read' isn't an array - $inputConfig = $this->getDummyBaseInputConfig(); + $inputConfig = self::getDummyBaseInputConfig(); $inputConfig['read'] = 'test'; $out[] = [ @@ -1036,7 +1037,7 @@ public function getTestPrimaryReadReplicaConnectionData(): array ]; //Case #3. 'read' has non array entries. - $inputConfig = $this->getDummyBaseInputConfig(); + $inputConfig = self::getDummyBaseInputConfig(); $inputConfig['read'][] = 'test'; $out[] = [ @@ -1046,7 +1047,7 @@ public function getTestPrimaryReadReplicaConnectionData(): array ]; //Case #4. 'read' has empty entries. - $inputConfig = $this->getDummyBaseInputConfig(); + $inputConfig = self::getDummyBaseInputConfig(); $inputConfig['read'][] = []; $out[] = [ @@ -1056,7 +1057,7 @@ public function getTestPrimaryReadReplicaConnectionData(): array ]; //Case #5. 'read' has empty first entry. (reported by maxbrokman.) - $inputConfig = $this->getDummyBaseInputConfig(); + $inputConfig = self::getDummyBaseInputConfig(); $inputConfig['read'][0] = []; $out[] = [ @@ -1072,9 +1073,8 @@ public function getTestPrimaryReadReplicaConnectionData(): array * Check if config is handled correctly. * * @param mixed[] $inputConfig - * - * @dataProvider getTestPrimaryReadReplicaConnectionData */ + #[DataProvider('getTestPrimaryReadReplicaConnectionData')] public function testPrimaryReadReplicaConnection( array $inputConfig, string $expectedException = '', @@ -1134,7 +1134,7 @@ protected function tearDown(): void * * @return mixed[] */ - private function getDummyBaseInputConfig(): array + private static function getDummyBaseInputConfig(): array { return [ 'driver' => 'mysql', diff --git a/tests/Feature/IlluminateRegistryTest.php b/tests/Feature/IlluminateRegistryTest.php index 79ef0fc9..5385a106 100644 --- a/tests/Feature/IlluminateRegistryTest.php +++ b/tests/Feature/IlluminateRegistryTest.php @@ -485,6 +485,37 @@ public function testGetManagerForClassWithNamespace(): void $this->assertEquals($entityManager, $this->registry->getManagerForClass('Alias:Scientist')); } + public function testGetManagerForClassReturnsNullWhenNotFound(): void + { + $this->container->shouldReceive('singleton'); + $this->registry->addManager('default'); + + $entityManager = m::mock(EntityManagerInterface::class); + $this->container->shouldReceive('make') + ->with('doctrine.managers.default') + ->andReturn($entityManager); + + $metadataFactory = m::mock(ClassMetadataFactory::class); + $metadataFactory->shouldReceive('isTransient') + ->with('LaravelDoctrineTest\ORM\Assets\Entity\Scientist') + ->once() + ->andReturnFalse(); + + $metadata = m::mock(ClassMetadata::class); + $metadata->shouldReceive('getName') + ->once() + ->andReturn('LaravelDoctrineTest\ORM\Assets\Entity\Theory'); + + $metadataFactory->shouldReceive('getAllMetadata') + ->once() + ->andReturn([$metadata]); + + $entityManager->shouldReceive('getMetadataFactory') + ->andReturn($metadataFactory); + + $this->assertNull($this->registry->getManagerForClass('LaravelDoctrineTest\ORM\Assets\Entity\Scientist')); + } + public function testGetManagerForClassThrowsExceptionWhenNotFound(): void { $this->expectException(RuntimeException::class); @@ -515,7 +546,7 @@ public function testGetManagerForClassThrowsExceptionWhenNotFound(): void $entityManager->shouldReceive('getMetadataFactory') ->andReturn($metadataFactory); - $this->assertEquals($entityManager, $this->registry->getManagerForClass('LaravelDoctrineTest\ORM\Assets\Entity\Scientist')); + $this->assertEquals($entityManager, $this->registry->getManagerForClass('LaravelDoctrineTest\ORM\Assets\Entity\Scientist', true)); } public function testGetManagerForClassInvalidClass(): void diff --git a/tests/Feature/Testing/Concerns/InteractsWithEntitiesTest.php b/tests/Feature/Testing/Concerns/InteractsWithEntitiesTest.php deleted file mode 100644 index bb9371dd..00000000 --- a/tests/Feature/Testing/Concerns/InteractsWithEntitiesTest.php +++ /dev/null @@ -1,100 +0,0 @@ -em = Mockery::mock(EntityManagerInterface::class); - - $this->app = Mockery::mock(Container::class); - $this->app - ->allows('make') - ->with('em') - ->andReturn($this->em); - - parent::setUp(); - } - - public function testEntitiesMatchWithMatch(): void - { - $repository = Mockery::mock(EntityRepository::class); - $repository->expects('findBy') - ->with(['someField' => 'someValue']) - ->once() - ->andReturn(['entity']); - - $this->em->expects('getRepository') - ->with('SomeClass') - ->once() - ->andReturn($repository); - - $this->entitiesMatch('SomeClass', ['someField' => 'someValue']); - } - - public function testEntitiesMatchWithoutMatch(): void - { - $repository = Mockery::mock(EntityRepository::class); - $repository->expects('findBy') - ->with(['someField' => 'someValue']) - ->once() - ->andReturn([]); - - $this->em->expects('getRepository') - ->with('SomeClass') - ->once() - ->andReturn($repository); - - $this->expectException(ExpectationFailedException::class); - $this->entitiesMatch('SomeClass', ['someField' => 'someValue']); - } - - public function testNoEntitiesMatchWithMatch(): void - { - $repository = Mockery::mock(EntityRepository::class); - $repository->expects('findBy') - ->with(['someField' => 'someValue']) - ->once() - ->andReturn(['entity']); - - $this->em->expects('getRepository') - ->with('SomeClass') - ->once() - ->andReturn($repository); - - $this->expectException(ExpectationFailedException::class); - $this->noEntitiesMatch('SomeClass', ['someField' => 'someValue']); - } - - public function testNoEntitiesMatchWithoutMatch(): void - { - $repository = Mockery::mock(EntityRepository::class); - $repository->expects('findBy') - ->with(['someField' => 'someValue']) - ->once() - ->andReturn([]); - - $this->em->expects('getRepository') - ->with('SomeClass') - ->once() - ->andReturn($repository); - - $this->noEntitiesMatch('SomeClass', ['someField' => 'someValue']); - } -} diff --git a/tests/TestCase.php b/tests/TestCase.php index 315aaa79..c20aabf4 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -4,29 +4,20 @@ namespace LaravelDoctrineTest\ORM; -use Illuminate\Foundation\Application; -use PHPUnit\Framework\TestCase as PHPUnitTestCase; +use Orchestra\Testbench\Concerns\WithWorkbench; +use Orchestra\Testbench\TestCase as OrchestraTestCase; -class TestCase extends PHPUnitTestCase +class TestCase extends OrchestraTestCase { - private Application $application; + use WithWorkbench; protected function setUp(): void { - $this->application = new Application(); - parent::setUp(); } protected function tearDown(): void { - unset($this->application); - parent::tearDown(); } - - public function getApplication(): Application - { - return $this->application; - } } diff --git a/workbench/app/Models/.gitkeep b/workbench/app/Models/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/workbench/app/Models/User.php b/workbench/app/Models/User.php new file mode 100644 index 00000000..1405251f --- /dev/null +++ b/workbench/app/Models/User.php @@ -0,0 +1,45 @@ + + */ + protected $fillable = [ + 'name', + 'email', + 'password', + ]; + + /** + * The attributes that should be hidden for serialization. + * + * @var array + */ + protected $hidden = [ + 'password', + 'remember_token', + ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'email_verified_at' => 'datetime', + 'password' => 'hashed', + ]; +} diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php new file mode 100644 index 00000000..e8cec9c2 --- /dev/null +++ b/workbench/app/Providers/WorkbenchServiceProvider.php @@ -0,0 +1,24 @@ +withRouting( + web: __DIR__.'/../routes/web.php', + commands: __DIR__.'/../routes/console.php', + ) + ->withMiddleware(function (Middleware $middleware) { + // + }) + ->withExceptions(function (Exceptions $exceptions) { + // + })->create(); diff --git a/workbench/bootstrap/providers.php b/workbench/bootstrap/providers.php new file mode 100644 index 00000000..3ac44ad1 --- /dev/null +++ b/workbench/bootstrap/providers.php @@ -0,0 +1,5 @@ + + */ +class UserFactory extends Factory +{ + /** + * The current password being used by the factory. + */ + protected static ?string $password; + + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = User::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => fake()->name(), + 'email' => fake()->unique()->safeEmail(), + 'email_verified_at' => now(), + 'password' => static::$password ??= Hash::make('password'), + 'remember_token' => Str::random(10), + ]; + } + + /** + * Indicate that the model's email address should be unverified. + */ + public function unverified(): static + { + return $this->state(fn (array $attributes) => [ + 'email_verified_at' => null, + ]); + } +} diff --git a/workbench/database/migrations/.gitkeep b/workbench/database/migrations/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/workbench/database/seeders/DatabaseSeeder.php b/workbench/database/seeders/DatabaseSeeder.php new file mode 100644 index 00000000..ce9bd159 --- /dev/null +++ b/workbench/database/seeders/DatabaseSeeder.php @@ -0,0 +1,23 @@ +create(); + + UserFactory::new()->create([ + 'name' => 'Test User', + 'email' => 'test@example.com', + ]); + } +} diff --git a/workbench/resources/views/.gitkeep b/workbench/resources/views/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/workbench/routes/console.php b/workbench/routes/console.php new file mode 100644 index 00000000..eff2ed24 --- /dev/null +++ b/workbench/routes/console.php @@ -0,0 +1,8 @@ +comment(Inspiring::quote()); +})->purpose('Display an inspiring quote')->hourly(); diff --git a/workbench/routes/web.php b/workbench/routes/web.php new file mode 100644 index 00000000..86a06c53 --- /dev/null +++ b/workbench/routes/web.php @@ -0,0 +1,7 @@ +