diff --git a/.github/workflows/build-ci-atlas.yml b/.github/workflows/build-ci-atlas.yml index 7a4ebd03f..30b4b06b1 100644 --- a/.github/workflows/build-ci-atlas.yml +++ b/.github/workflows/build-ci-atlas.yml @@ -20,6 +20,7 @@ jobs: - "8.4" laravel: - "11.*" + - "12.*" steps: - uses: "actions/checkout@v4" diff --git a/.github/workflows/build-ci.yml b/.github/workflows/build-ci.yml index d16a5885f..659c316d3 100644 --- a/.github/workflows/build-ci.yml +++ b/.github/workflows/build-ci.yml @@ -28,19 +28,18 @@ jobs: laravel: - "10.*" - "11.*" + - "12.*" include: - php: "8.1" laravel: "10.*" mongodb: "5.0" mode: "low-deps" os: "ubuntu-latest" - - php: "8.4" - laravel: "11.*" - mongodb: "7.0" - os: "ubuntu-latest" exclude: - php: "8.1" laravel: "11.*" + - php: "8.1" + laravel: "12.*" steps: - uses: "actions/checkout@v4" diff --git a/composer.json b/composer.json index 2855a9546..64006a47b 100644 --- a/composer.json +++ b/composer.json @@ -25,11 +25,11 @@ "php": "^8.1", "ext-mongodb": "^1.15", "composer-runtime-api": "^2.0.0", - "illuminate/cache": "^10.36|^11", - "illuminate/container": "^10.0|^11", - "illuminate/database": "^10.30|^11", - "illuminate/events": "^10.0|^11", - "illuminate/support": "^10.0|^11", + "illuminate/cache": "^10.36|^11|^12", + "illuminate/container": "^10.0|^11|^12", + "illuminate/database": "^10.30|^11|^12", + "illuminate/events": "^10.0|^11|^12", + "illuminate/support": "^10.0|^11|^12", "mongodb/mongodb": "^1.21", "symfony/http-foundation": "^6.4|^7" }, @@ -38,8 +38,8 @@ "league/flysystem-gridfs": "^3.28", "league/flysystem-read-only": "^3.0", "phpunit/phpunit": "^10.3|^11.5.3", - "orchestra/testbench": "^8.0|^9.0", - "mockery/mockery": "^1.4.4@stable", + "orchestra/testbench": "^8.0|^9.0|^10.0", + "mockery/mockery": "^1.4.4", "doctrine/coding-standard": "12.0.x-dev", "spatie/laravel-query-builder": "^5.6|^6", "phpstan/phpstan": "^1.10", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 67fdd4154..ba1f3b7aa 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,5 +1,15 @@ parameters: ignoreErrors: + - + message: "#^Class MongoDB\\\\Laravel\\\\Query\\\\Grammar does not have a constructor and must be instantiated without any parameters\\.$#" + count: 1 + path: src/Connection.php + + - + message: "#^Class MongoDB\\\\Laravel\\\\Schema\\\\Grammar does not have a constructor and must be instantiated without any parameters\\.$#" + count: 1 + path: src/Connection.php + - message: "#^Access to an undefined property Illuminate\\\\Container\\\\Container\\:\\:\\$config\\.$#" count: 3 diff --git a/src/Connection.php b/src/Connection.php index 980750093..4dd04120d 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -355,13 +355,15 @@ protected function getDefaultPostProcessor() /** @inheritdoc */ protected function getDefaultQueryGrammar() { - return new Query\Grammar(); + // Argument added in Laravel 12 + return new Query\Grammar($this); } /** @inheritdoc */ protected function getDefaultSchemaGrammar() { - return new Schema\Grammar(); + // Argument added in Laravel 12 + return new Schema\Grammar($this); } /** diff --git a/src/Schema/Blueprint.php b/src/Schema/Blueprint.php index a525a9cee..1197bfde1 100644 --- a/src/Schema/Blueprint.php +++ b/src/Schema/Blueprint.php @@ -4,9 +4,9 @@ namespace MongoDB\Laravel\Schema; -use Illuminate\Database\Connection; -use Illuminate\Database\Schema\Blueprint as SchemaBlueprint; +use Illuminate\Database\Schema\Blueprint as BaseBlueprint; use MongoDB\Collection; +use MongoDB\Laravel\Connection; use function array_flip; use function implode; @@ -16,17 +16,14 @@ use function is_string; use function key; -class Blueprint extends SchemaBlueprint +/** @property Connection $connection */ +class Blueprint extends BaseBlueprint { - /** - * The MongoConnection object for this blueprint. - * - * @var Connection - */ - protected $connection; + // Import $connection property and constructor for Laravel 12 compatibility + use BlueprintLaravelCompatibility; /** - * The Collection object for this blueprint. + * The MongoDB collection object for this blueprint. * * @var Collection */ @@ -39,18 +36,6 @@ class Blueprint extends SchemaBlueprint */ protected $columns = []; - /** - * Create a new schema blueprint. - */ - public function __construct(Connection $connection, string $collection) - { - parent::__construct($collection); - - $this->connection = $connection; - - $this->collection = $this->connection->getCollection($collection); - } - /** @inheritdoc */ public function index($columns = null, $name = null, $algorithm = null, $options = []) { diff --git a/src/Schema/BlueprintLaravelCompatibility.php b/src/Schema/BlueprintLaravelCompatibility.php new file mode 100644 index 000000000..bf288eae8 --- /dev/null +++ b/src/Schema/BlueprintLaravelCompatibility.php @@ -0,0 +1,50 @@ +<?php + +namespace MongoDB\Laravel\Schema; + +use Closure; +use Illuminate\Database\Connection; +use Illuminate\Database\Schema\Blueprint as BaseBlueprint; + +use function property_exists; + +/** + * The $connection property and constructor param were added in Laravel 12 + * We keep the untyped $connection property for older version of Laravel to maintain compatibility + * and not break projects that would extend the MongoDB Blueprint class. + * + * @see https://github.com/laravel/framework/commit/f29df4740d724f1c36385c9989569e3feb9422df#diff-68f714a9f1b751481b993414d3f1300ad55bcef12084ec0eb8f47f350033c24bR107 + * + * phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses + */ +if (! property_exists(BaseBlueprint::class, 'connection')) { + /** @internal For compatibility with Laravel 10 and 11 */ + trait BlueprintLaravelCompatibility + { + /** + * The MongoDB connection object for this blueprint. + * + * @var Connection + */ + protected $connection; + + public function __construct(Connection $connection, string $collection, ?Closure $callback = null) + { + parent::__construct($collection, $callback); + + $this->connection = $connection; + $this->collection = $connection->getCollection($collection); + } + } +} else { + /** @internal For compatibility with Laravel 12+ */ + trait BlueprintLaravelCompatibility + { + public function __construct(Connection $connection, string $collection, ?Closure $callback = null) + { + parent::__construct($connection, $collection, $callback); + + $this->collection = $connection->getCollection($collection); + } + } +} diff --git a/src/Schema/Builder.php b/src/Schema/Builder.php index 4af15f1f9..ef450745a 100644 --- a/src/Schema/Builder.php +++ b/src/Schema/Builder.php @@ -7,6 +7,7 @@ use Closure; use MongoDB\Collection; use MongoDB\Driver\Exception\ServerException; +use MongoDB\Laravel\Connection; use MongoDB\Model\CollectionInfo; use MongoDB\Model\IndexInfo; @@ -16,11 +17,14 @@ use function array_keys; use function array_map; use function array_merge; +use function array_values; use function assert; use function count; use function current; use function implode; use function in_array; +use function is_array; +use function is_string; use function iterator_to_array; use function sort; use function sprintf; @@ -28,6 +32,7 @@ use function substr; use function usort; +/** @property Connection $connection */ class Builder extends \Illuminate\Database\Schema\Builder { /** @@ -137,9 +142,10 @@ public function dropAllTables() } } - public function getTables() + /** @param string|null $schema Database name */ + public function getTables($schema = null) { - $db = $this->connection->getDatabase(); + $db = $this->connection->getDatabase($schema); $collections = []; foreach ($db->listCollectionNames() as $collectionName) { @@ -150,7 +156,8 @@ public function getTables() $collections[] = [ 'name' => $collectionName, - 'schema' => null, + 'schema' => $db->getDatabaseName(), + 'schema_qualified_name' => $db->getDatabaseName() . '.' . $collectionName, 'size' => $stats[0]?->storageStats?->totalSize ?? null, 'comment' => null, 'collation' => null, @@ -165,9 +172,29 @@ public function getTables() return $collections; } - public function getTableListing() + /** + * @param string|null $schema + * @param bool $schemaQualified If a schema is provided, prefix the collection names with the schema name + * + * @return array + */ + public function getTableListing($schema = null, $schemaQualified = false) { - $collections = iterator_to_array($this->connection->getDatabase()->listCollectionNames()); + $collections = []; + + if ($schema === null || is_string($schema)) { + $collections[$schema ?? 0] = iterator_to_array($this->connection->getDatabase($schema)->listCollectionNames()); + } elseif (is_array($schema)) { + foreach ($schema as $db) { + $collections[$db] = iterator_to_array($this->connection->getDatabase($db)->listCollectionNames()); + } + } + + if ($schema && $schemaQualified) { + $collections = array_map(fn ($db, $collections) => array_map(static fn ($collection) => $db . '.' . $collection, $collections), array_keys($collections), $collections); + } + + $collections = array_merge(...array_values($collections)); sort($collections); diff --git a/tests/Query/BuilderTest.php b/tests/Query/BuilderTest.php index 2cc0c5764..20b5a12fb 100644 --- a/tests/Query/BuilderTest.php +++ b/tests/Query/BuilderTest.php @@ -1605,7 +1605,7 @@ private static function getBuilder(): Builder $connection = m::mock(Connection::class); $processor = m::mock(Processor::class); $connection->shouldReceive('getSession')->andReturn(null); - $connection->shouldReceive('getQueryGrammar')->andReturn(new Grammar()); + $connection->shouldReceive('getQueryGrammar')->andReturn(new Grammar($connection)); return new Builder($connection, null, $processor); } diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index a55c8c0e0..643e00e6a 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -35,6 +35,7 @@ public function tearDown(): void Photo::truncate(); Label::truncate(); Skill::truncate(); + Soft::truncate(); parent::tearDown(); } diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php index e2f4f7b7e..8e91a2f66 100644 --- a/tests/SchemaTest.php +++ b/tests/SchemaTest.php @@ -395,6 +395,7 @@ public function testGetTables() { DB::connection('mongodb')->table('newcollection')->insert(['test' => 'value']); DB::connection('mongodb')->table('newcollection_two')->insert(['test' => 'value']); + $dbName = DB::connection('mongodb')->getDatabaseName(); $tables = Schema::getTables(); $this->assertIsArray($tables); @@ -403,9 +404,13 @@ public function testGetTables() foreach ($tables as $table) { $this->assertArrayHasKey('name', $table); $this->assertArrayHasKey('size', $table); + $this->assertArrayHasKey('schema', $table); + $this->assertArrayHasKey('schema_qualified_name', $table); if ($table['name'] === 'newcollection') { $this->assertEquals(8192, $table['size']); + $this->assertEquals($dbName, $table['schema']); + $this->assertEquals($dbName . '.newcollection', $table['schema_qualified_name']); $found = true; } } @@ -428,6 +433,27 @@ public function testGetTableListing() $this->assertContains('newcollection_two', $tables); } + public function testGetTableListingBySchema() + { + DB::connection('mongodb')->table('newcollection')->insert(['test' => 'value']); + DB::connection('mongodb')->table('newcollection_two')->insert(['test' => 'value']); + $dbName = DB::connection('mongodb')->getDatabaseName(); + + $tables = Schema::getTableListing([$dbName, 'database__that_does_not_exists'], schemaQualified: true); + + $this->assertIsArray($tables); + $this->assertGreaterThanOrEqual(2, count($tables)); + $this->assertContains($dbName . '.newcollection', $tables); + $this->assertContains($dbName . '.newcollection_two', $tables); + + $tables = Schema::getTableListing([$dbName, 'database__that_does_not_exists'], schemaQualified: false); + + $this->assertIsArray($tables); + $this->assertGreaterThanOrEqual(2, count($tables)); + $this->assertContains('newcollection', $tables); + $this->assertContains('newcollection_two', $tables); + } + public function testGetColumns() { $collection = DB::connection('mongodb')->table('newcollection');