- Notifications
You must be signed in to change notification settings - Fork 209
Description
Bug Report
foreach over ObjectId changes behavior after circular reference collector.
Before GC foreach over ObjectId executes one loop iteration with $key="oid".
After GC foreach over ObjectId executes zero loop iterations.
Environment
PHP inside Docker on Windows 10.
Docker Image: php:8.2.27-apache
PHP: 8.2.27
MongoDB PHP Driver: 1.20.1
MongoDB PHP Library: 1.20.0
MongoDB: 8.0.0
mongodb/laravel-mongodb: 4.8.1
MongoDB: I run DB in separate Docker container locally and use in PHP only HOST and PORT of DB.
www-data@5c2174446320:~/html$ php -i | grep -E 'mongodb|libmongoc|libbson' Cannot load Xdebug - it was already loaded /usr/local/etc/php/conf.d/mongodb.ini, mongodb libbson bundled version => 1.28.1 libmongoc bundled version => 1.28.1 libmongoc SSL => enabled libmongoc SSL library => OpenSSL libmongoc crypto => enabled libmongoc crypto library => libcrypto libmongoc crypto system profile => disabled libmongoc SASL => enabled libmongoc SRV => enabled libmongoc compression => enabled libmongoc compression snappy => enabled libmongoc compression zlib => enabled libmongoc compression zstd => enabled libmongocrypt bundled version => 1.11.0 libmongocrypt crypto => enabled libmongocrypt crypto library => libcrypto mongodb.debug => no value => no value
Test Script
I've used mongodb/laravel-mongodb to reproduce the bug but there is not so much Laravel specific and main issue in \MongoDB\BSON\ObjectId.
ObjectIdTestModel.php
<?php namespace App\Models; class ObjectIdTestModel extends \MongoDB\Laravel\Eloquent\Model { }
Test.php
\dump(); // Uncommenting following line fixes the test //gc_disable(); $objectId1 = new ObjectId(); $objectId2 = new ObjectId(); // Here we see public property "oid" echo 'objectId1: '; xdebug_debug_zval('objectId1'); echo 'objectId2: '; xdebug_debug_zval('objectId2'); // Check possible assumption about iterating $is_traversable = ($objectId1 instanceof \Traversable); echo 'is_traversable: ' . var_export($is_traversable, true) . "\n"; // Check there are no properties for iterating in foreach hence there is not property "oid" $reflect = new \ReflectionClass($objectId1); $properties = $reflect->getProperties(); echo 'properties: ' . print_r($properties, true); // Allocate a lot of objects for ($i = 0; $i < 200; $i++) { $model = new ObjectIdTestModel(); $model->save(); } $a = ObjectIdTestModel::all(); // First foreach over \MongoDB\BSON\ObjectId $number1 = 0; foreach ($objectId1 as $key => $value) { $number1 += 1; } // Allocate a lot of objects again $a = ObjectIdTestModel::all(); // Second foreach over \MongoDB\BSON\ObjectId $number2 = 0; foreach ($objectId2 as $key => $value) { $number2 += 1; } echo 'objectId1: '; xdebug_debug_zval('objectId1'); echo 'objectId2: '; xdebug_debug_zval('objectId2'); $this->assertEquals(1, $number1, 'number1'); $this->assertEquals(1, $number2, 'number2');
Test output:
objectId1: objectId1: (refcount=1, is_ref=0)=class MongoDB\BSON\ObjectId { public $oid = (refcount=1, is_ref=0)='679a97b2d1cc47db6503f004' } objectId2: objectId2: (refcount=1, is_ref=0)=class MongoDB\BSON\ObjectId { public $oid = (refcount=1, is_ref=0)='679a97b2d1cc47db6503f005' } is_traversable: false properties: Array ( ) objectId1: objectId1: (refcount=1, is_ref=0)=class MongoDB\BSON\ObjectId { public $oid = (refcount=1, is_ref=0)='679a97b2d1cc47db6503f004' } objectId2: objectId2: (refcount=1, is_ref=0)=class MongoDB\BSON\ObjectId { public $oid = (refcount=1, is_ref=0)='679a97b2d1cc47db6503f005' } ... number2 Failed asserting that 0 matches expected 1.
If the bug is not repoduced you can increase number of created objects in line for ($i = 0; $i < 200; $i++) {
Expected and Actual Behavior
Expected
$number1 === 1
$number2 === 1
Circular reference collector do not change script behavior.
Actual
$number1 === 1
$number2 === 0
Calling gc_disable() "fixes" the bug.
Preffered solution
Prohibit to iterate over \MongoDB\BSON\ObjectId especially by hidden public property "oid".
I have iterated it by mistake and prefer empty loop over object without public properties instead of strange behavior.