diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f53f4c22..e8da5220e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. * Remove `MongoFailedJobProvider`, replaced by Laravel `DatabaseFailedJobProvider` by @GromNaN in [#3122](https://github.com/mongodb/laravel-mongodb/pull/3122) * Remove custom `PasswordResetServiceProvider`, use the default `DatabaseTokenRepository` by @GromNaN in [#3124](https://github.com/mongodb/laravel-mongodb/pull/3124) * Remove `Blueprint::background()` method by @GromNaN in [#3132](https://github.com/mongodb/laravel-mongodb/pull/3132) +* Replace `Collection` proxy class with Driver monitoring by @GromNaN in [#3137]((https://github.com/mongodb/laravel-mongodb/pull/3137) ## [4.8.0] - 2024-08-27 diff --git a/docs/includes/query-builder/QueryBuilderTest.php b/docs/includes/query-builder/QueryBuilderTest.php index bf92b9a6b..8b93adb16 100644 --- a/docs/includes/query-builder/QueryBuilderTest.php +++ b/docs/includes/query-builder/QueryBuilderTest.php @@ -8,7 +8,7 @@ use Illuminate\Pagination\AbstractPaginator; use Illuminate\Support\Facades\DB; use MongoDB\BSON\Regex; -use MongoDB\Laravel\Collection; +use MongoDB\Collection; use MongoDB\Laravel\Tests\TestCase; use function file_get_contents; diff --git a/src/Bus/MongoBatchRepository.php b/src/Bus/MongoBatchRepository.php index dd0713f97..c6314ba69 100644 --- a/src/Bus/MongoBatchRepository.php +++ b/src/Bus/MongoBatchRepository.php @@ -14,8 +14,8 @@ use Illuminate\Support\Carbon; use MongoDB\BSON\ObjectId; use MongoDB\BSON\UTCDateTime; +use MongoDB\Collection; use MongoDB\Driver\ReadPreference; -use MongoDB\Laravel\Collection; use MongoDB\Laravel\Connection; use MongoDB\Operation\FindOneAndUpdate; use Override; diff --git a/src/Cache/MongoLock.php b/src/Cache/MongoLock.php index e9bd3d607..d273b4d99 100644 --- a/src/Cache/MongoLock.php +++ b/src/Cache/MongoLock.php @@ -6,7 +6,7 @@ use Illuminate\Support\Carbon; use InvalidArgumentException; use MongoDB\BSON\UTCDateTime; -use MongoDB\Laravel\Collection; +use MongoDB\Collection; use MongoDB\Operation\FindOneAndUpdate; use Override; diff --git a/src/Cache/MongoStore.php b/src/Cache/MongoStore.php index e35d0f70d..e37884a93 100644 --- a/src/Cache/MongoStore.php +++ b/src/Cache/MongoStore.php @@ -7,7 +7,7 @@ use Illuminate\Contracts\Cache\Store; use Illuminate\Support\Carbon; use MongoDB\BSON\UTCDateTime; -use MongoDB\Laravel\Collection; +use MongoDB\Collection; use MongoDB\Laravel\Connection; use MongoDB\Operation\FindOneAndUpdate; use Override; diff --git a/src/Collection.php b/src/Collection.php deleted file mode 100644 index 22c0dfa05..000000000 --- a/src/Collection.php +++ /dev/null @@ -1,80 +0,0 @@ -connection = $connection; - $this->collection = $collection; - } - - /** - * Handle dynamic method calls. - * - * @return mixed - */ - public function __call(string $method, array $parameters) - { - $start = microtime(true); - $result = $this->collection->$method(...$parameters); - - // Once we have run the query we will calculate the time that it took to run and - // then log the query, bindings, and execution time so we will report them on - // the event that the developer needs them. We'll log time in milliseconds. - $time = $this->connection->getElapsedTime($start); - - $query = []; - - // Convert the query parameters to a json string. - array_walk_recursive($parameters, function (&$item, $key) { - if ($item instanceof ObjectID) { - $item = (string) $item; - } - }); - - // Convert the query parameters to a json string. - foreach ($parameters as $parameter) { - try { - $query[] = json_encode($parameter, JSON_THROW_ON_ERROR); - } catch (Exception) { - $query[] = '{...}'; - } - } - - $queryString = $this->collection->getCollectionName() . '.' . $method . '(' . implode(',', $query) . ')'; - - $this->connection->logQuery($queryString, [], $time); - - return $result; - } -} diff --git a/src/CommandSubscriber.php b/src/CommandSubscriber.php new file mode 100644 index 000000000..d9acf1c60 --- /dev/null +++ b/src/CommandSubscriber.php @@ -0,0 +1,63 @@ + */ + private array $commands = []; + + public function __construct(private Connection $connection) + { + } + + public function commandStarted(CommandStartedEvent $event) + { + $this->commands[$event->getOperationId()] = $event; + } + + public function commandFailed(CommandFailedEvent $event) + { + $this->logQuery($event); + } + + public function commandSucceeded(CommandSucceededEvent $event) + { + $this->logQuery($event); + } + + private function logQuery(CommandSucceededEvent|CommandFailedEvent $event): void + { + $startedEvent = $this->commands[$event->getOperationId()]; + unset($this->commands[$event->getOperationId()]); + + $command = ''; + foreach (get_object_vars($startedEvent->getCommand()) as $key => $value) { + if ($key[0] !== '$' && ! in_array($key, ['lsid', 'txnNumber'])) { + $command .= ($command ? ', ' : '{') . json_encode($key) . ':'; + try { + $command .= json_encode($value, JSON_THROW_ON_ERROR); + } catch (Throwable $e) { + $command .= json_encode('Invalid JSON: ' . $e->getMessage()); + } + } + } + + $command .= $command ? '}' : '{}'; + + $this->connection->logQuery($command, [], $event->getDurationMicros()); + } +} diff --git a/src/Connection.php b/src/Connection.php index cb2bc78de..8f63ff87e 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -8,6 +8,7 @@ use Illuminate\Database\Connection as BaseConnection; use InvalidArgumentException; use MongoDB\Client; +use MongoDB\Collection; use MongoDB\Database; use MongoDB\Driver\Exception\AuthenticationException; use MongoDB\Driver\Exception\ConnectionException; @@ -47,6 +48,8 @@ class Connection extends BaseConnection */ protected $connection; + private ?CommandSubscriber $commandSubscriber; + /** * Create a new database connection instance. */ @@ -62,6 +65,8 @@ public function __construct(array $config) // Create the connection $this->connection = $this->createConnection($dsn, $config, $options); + $this->commandSubscriber = new CommandSubscriber($this); + $this->connection->addSubscriber($this->commandSubscriber); // Select database $this->db = $this->connection->selectDatabase($this->getDefaultDatabaseName($dsn, $config)); @@ -97,9 +102,9 @@ public function table($table, $as = null) * * @return Collection */ - public function getCollection($name) + public function getCollection($name): Collection { - return new Collection($this, $this->db->selectCollection($this->tablePrefix . $name)); + return $this->db->selectCollection($this->tablePrefix . $name); } /** @inheritdoc */ @@ -198,6 +203,8 @@ public function ping(): void /** @inheritdoc */ public function disconnect() { + $this->connection?->removeSubscriber($this->commandSubscriber); + $this->commandSubscriber = null; $this->connection = null; } diff --git a/src/Query/AggregationBuilder.php b/src/Query/AggregationBuilder.php index ad0c195d4..0d4638731 100644 --- a/src/Query/AggregationBuilder.php +++ b/src/Query/AggregationBuilder.php @@ -10,9 +10,8 @@ use Iterator; use MongoDB\Builder\BuilderEncoder; use MongoDB\Builder\Stage\FluentFactoryTrait; -use MongoDB\Collection as MongoDBCollection; +use MongoDB\Collection; use MongoDB\Driver\CursorInterface; -use MongoDB\Laravel\Collection as LaravelMongoDBCollection; use function array_replace; use function collect; @@ -24,7 +23,7 @@ class AggregationBuilder use FluentFactoryTrait; public function __construct( - private MongoDBCollection|LaravelMongoDBCollection $collection, + private Collection $collection, private readonly array $options = [], ) { } diff --git a/src/Query/Builder.php b/src/Query/Builder.php index f4f31b58f..07fa27e63 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -82,7 +82,7 @@ class Builder extends BaseBuilder /** * The database collection. * - * @var \MongoDB\Laravel\Collection + * @var \MongoDB\Collection */ protected $collection; diff --git a/src/Schema/Blueprint.php b/src/Schema/Blueprint.php index 0ad4535cf..b69605b42 100644 --- a/src/Schema/Blueprint.php +++ b/src/Schema/Blueprint.php @@ -6,7 +6,7 @@ use Illuminate\Database\Connection; use Illuminate\Database\Schema\Blueprint as SchemaBlueprint; -use MongoDB\Laravel\Collection; +use MongoDB\Collection; use function array_flip; use function implode; @@ -21,14 +21,14 @@ class Blueprint extends SchemaBlueprint /** * The MongoConnection object for this blueprint. * - * @var \MongoDB\Laravel\Connection + * @var Connection */ protected $connection; /** * The MongoCollection object for this blueprint. * - * @var Collection|\MongoDB\Collection + * @var Collection|Collection */ protected $collection; diff --git a/tests/Cache/MongoLockTest.php b/tests/Cache/MongoLockTest.php index e3d2568d5..f305061cf 100644 --- a/tests/Cache/MongoLockTest.php +++ b/tests/Cache/MongoLockTest.php @@ -8,8 +8,8 @@ use Illuminate\Support\Facades\DB; use InvalidArgumentException; use MongoDB\BSON\UTCDateTime; +use MongoDB\Collection; use MongoDB\Laravel\Cache\MongoLock; -use MongoDB\Laravel\Collection; use MongoDB\Laravel\Tests\TestCase; use PHPUnit\Framework\Attributes\TestWith; diff --git a/tests/Casts/DecimalTest.php b/tests/Casts/DecimalTest.php index f69d24d62..184abd026 100644 --- a/tests/Casts/DecimalTest.php +++ b/tests/Casts/DecimalTest.php @@ -10,7 +10,7 @@ use MongoDB\BSON\Int64; use MongoDB\BSON\Javascript; use MongoDB\BSON\UTCDateTime; -use MongoDB\Laravel\Collection; +use MongoDB\Collection; use MongoDB\Laravel\Tests\Models\Casting; use MongoDB\Laravel\Tests\TestCase; diff --git a/tests/CollectionTest.php b/tests/CollectionTest.php deleted file mode 100644 index fbdbf3daf..000000000 --- a/tests/CollectionTest.php +++ /dev/null @@ -1,36 +0,0 @@ - 'bar']; - $where = ['id' => new ObjectID('56f94800911dcc276b5723dd')]; - $time = 1.1; - $queryString = 'name-collection.findOne({"id":"56f94800911dcc276b5723dd"})'; - - $mongoCollection = $this->getMockBuilder(MongoCollection::class) - ->disableOriginalConstructor() - ->getMock(); - - $mongoCollection->expects($this->once())->method('findOne')->with($where)->willReturn($return); - $mongoCollection->expects($this->once())->method('getCollectionName')->willReturn('name-collection'); - - $connection = $this->getMockBuilder(Connection::class)->disableOriginalConstructor()->getMock(); - $connection->expects($this->once())->method('getElapsedTime')->willReturn($time); - $connection->expects($this->once())->method('logQuery')->with($queryString, [], $time); - - $collection = new Collection($connection, $mongoCollection); - - $this->assertEquals($return, $collection->findOne($where)); - } -} diff --git a/tests/ConnectionTest.php b/tests/ConnectionTest.php index ac4cc78fc..b4aba463d 100644 --- a/tests/ConnectionTest.php +++ b/tests/ConnectionTest.php @@ -8,9 +8,10 @@ use Illuminate\Support\Facades\DB; use InvalidArgumentException; use MongoDB\Client; +use MongoDB\Collection; use MongoDB\Database; +use MongoDB\Driver\Exception\BulkWriteException; use MongoDB\Driver\Exception\ConnectionTimeoutException; -use MongoDB\Laravel\Collection; use MongoDB\Laravel\Connection; use MongoDB\Laravel\Query\Builder; use MongoDB\Laravel\Schema\Builder as SchemaBuilder; @@ -225,9 +226,6 @@ public function testCollection() $collection = DB::connection('mongodb')->table('unittests'); $this->assertInstanceOf(Builder::class, $collection); - - $collection = DB::connection('mongodb')->table('unittests'); - $this->assertInstanceOf(Builder::class, $collection); } public function testPrefix() @@ -251,7 +249,8 @@ public function testQueryLog() $this->assertCount(0, DB::getQueryLog()); DB::table('items')->get(); - $this->assertCount(1, DB::getQueryLog()); + $this->assertCount(1, $logs = DB::getQueryLog()); + $this->assertJsonStringEqualsJsonString('{"find":"items","filter":{}}', $logs[0]['query']); DB::table('items')->insert(['name' => 'test']); $this->assertCount(2, DB::getQueryLog()); @@ -264,6 +263,16 @@ public function testQueryLog() DB::table('items')->where('name', 'test')->delete(); $this->assertCount(5, DB::getQueryLog()); + + // Error + try { + DB::table('items')->where('name', 'test')->update( + ['$set' => ['embed' => ['foo' => 'bar']], '$unset' => ['embed' => ['foo']]], + ); + self::fail('Expected BulkWriteException'); + } catch (BulkWriteException) { + $this->assertCount(6, $logs = DB::getQueryLog()); + } } public function testSchemaBuilder() diff --git a/tests/ModelTest.php b/tests/ModelTest.php index 36465ce53..075c0d3ad 100644 --- a/tests/ModelTest.php +++ b/tests/ModelTest.php @@ -15,7 +15,7 @@ use MongoDB\BSON\Binary; use MongoDB\BSON\ObjectID; use MongoDB\BSON\UTCDateTime; -use MongoDB\Laravel\Collection; +use MongoDB\Collection; use MongoDB\Laravel\Connection; use MongoDB\Laravel\Eloquent\Model; use MongoDB\Laravel\Tests\Models\Book; diff --git a/tests/QueryBuilderTest.php b/tests/QueryBuilderTest.php index 846f48514..cc4de80cf 100644 --- a/tests/QueryBuilderTest.php +++ b/tests/QueryBuilderTest.php @@ -17,12 +17,12 @@ use MongoDB\BSON\ObjectId; use MongoDB\BSON\Regex; use MongoDB\BSON\UTCDateTime; +use MongoDB\Collection; use MongoDB\Driver\Cursor; use MongoDB\Driver\Monitoring\CommandFailedEvent; use MongoDB\Driver\Monitoring\CommandStartedEvent; use MongoDB\Driver\Monitoring\CommandSubscriber; use MongoDB\Driver\Monitoring\CommandSucceededEvent; -use MongoDB\Laravel\Collection; use MongoDB\Laravel\Query\Builder; use MongoDB\Laravel\Tests\Models\Item; use MongoDB\Laravel\Tests\Models\User;