mongodb/mongo-php-driver

PHP 8.1 serialize magic methods not working

eman1986 opened this issue ยท 8 comments

I'm assuming this is an issue with the driver, from what I saw on the library repo they're blaming you guys.

I have the latest version of the mongodb extension (1.12.0) and if I try to use the new __serialize() & __unserialize() methods it's not working, but what is considered depreciated by 8.1 is working perfectly.

from everything I read, the new magic methods should be supported but I'm not seeing that in my own testing.

also oddily enough PHPStorm is claiming it's deprecated but the php docs is not showing the same thing, I know the Serializable interface in general is deprecated but no where on the php docs or even mongodb docs is it stating that this applies to the mongodb serialization. I guess I just have some confusion on if this is actually an issue or if I'm just fine to use bsonSerialize & bsonUnserialize without worry.

from what I saw on the library repo they're blaming you guys.

What does "library repo" refer to? The extension and library are developed by the same team, and I don't think @alcaeus would throw me under the bus like that :)

I have the latest version of the mongodb extension (1.12.0) and if I try to use the new __serialize() & __unserialize() methods it's not working, but what is considered depreciated by 8.1 is working perfectly.

from everything I read, the new magic methods should be supported but I'm not seeing that in my own testing.

Can you clarify or demonstrate what is "not working"? The __serialize() and __unserialize() methods were introduced in 1.11.0 (PHPC-1849), and that included tests for PHP 8.1 and earlier versions. For example:

also oddily enough PHPStorm is claiming it's deprecated but the php docs is not showing the same thing, I know the Serializable interface in general is deprecated...

Per the Serializable interface docs, PHP 8.1+ only raises a deprecation notice if a class implements Serializable without also implementing __serialize() and __unserialize(). That behavior is discussed in more detail in the Phase Out Serializable RFC. I don't use PHPStorm, so I can't comment on what it is reporting -- but I would consider the PHP runtime to be authoritative.

...but no where on the php docs or even mongodb docs is it stating that this applies to the mongodb serialization. I guess I just have some confusion on if this is actually an issue or if I'm just fine to use bsonSerialize & bsonUnserialize without worry.

Note that the driver's MongoDB\BSON\Serializable and MongoDB\BSON\Unserializable interfaces have no relation to PHP's own object serialization (neither the Serializable interface nor the magic methods). Our BSON interfaces are only utilized internally by the driver and our BSON functions, when converting between PHP values and BSON.

the library repo is the php package you install through composer, I've seen other tickets make sure you don't mix up the two.

what's not working is when I add a record to the document it's just making an empty collection and I also can't read a record that is properly in the document. If I use the mongodb serializable interface it works perfectly.

It could be I'm not doing something right, there's not a whole lot of tutorials on the subject so I just followed what's in the mongodb php docs which is working and sounds like a fine solution that's not depreciated.

the library repo is the php package you install through composer, I've seen other tickets make sure you don't mix up the two.

I understand now that you're referring to mongodb/mongo-php-library, but I'm still confused about what you meant by:

from what I saw on the library repo they're blaming you guys

Who is is "they" referring to? If you're referring to users commenting in specific GitHub issues, it'd be helpful to link to those for context.


what's not working is when I add a record to the document it's just making an empty collection and I also can't read a record that is properly in the document. If I use the mongodb serializable interface it works perfectly.

If you're willing to share an executable script (e.g. something that expects to run against a local mongod) and a detailed description of expected vs. actual behavior, I'd be happy to look into this further. But there presently isn't enough information in this issue for me to provide any further input.

There's a ticket that mentioned that anything related to serialization is done on the driver side of things, I don't have the ticket on hand but it's not critically important, I was only mentioning it in case it came up that the library was at fault instead of the driver.

I can get you a basic test case, I have a demo project I made to play around with the concept, I just need to either make a gist or I can just zip up the demo project.

I can get you a basic test case, I have a demo project I made to play around with the concept, I just need to either make a gist or I can just zip up the demo project.

Gist would be preferred. Ideally, if you could get it down to a single file (apart from the autoload include and composer.json), that'd make this easiest to debug.

sorry for the delay, I was sick so I was offline for a bit there, here is the gist with my demo code

https://gist.github.com/eman1986/86cb6386fa13dd1d12fe0c603c21e3a2

I cloned your gist into a new directory and installed the following dependencies with Composer using PHP 8.1 (with ext-mongodb 1.12 installed):

{
    "require": {
        "symfony/uid": "^6.0",
        "mongodb/mongodb": "^1.11"
    }
}

I also noted that your script expects a local MongoDB server on port 27017, so I launched a 4.4 standalone.

Running Create.php produced the following output:

$ php Create.php 
Inserted with Object ID: 61df13ebd69f422f0808aab4

The following document was inserted into the demo.users collection:

> db.users.findOne()
{
	"_id" : ObjectId("61df13ebd69f422f0808aab4"),
	"id" : ObjectId("61df13ebd69f422f0808aab2"),
	"userUuid" : "691ff14d-2467-45ff-9b97-fd0988ffda59",
	"emailAddress" : "ac1Xo2NmH@example.com",
	"firstName" : "Mouse",
	"lastName" : "Provide",
	"language" : "en",
	"roles" : [
		"USER"
	],
	"isUserLocked" : false,
	"isUserEnabled" : true,
	"createdOn" : {
		
	},
	"updatedOn" : {
		
	}
}

Obviously, a few things stand out here:

  • The document has both id and _id fields. The id value corresponds to the ObjectId created in the User constructor, and _id is an ObjectId generated a very short time after when the driver inserts the document and realizes there is no existing _id field.
  • The createdOn and updatedOn fields are empty documents.
  • The case of the field names differs from what you later use when querying in GetOne.php.

Running GetOne.php found no document, but that was due to a typo in the query field (inconsistent character case) and a hard-coded UUID (presumably from a document you inserted locally). Both were easily fixed:

$query = [
-    'UserUuid' => '47f4b337-cd9d-446a-b030-c311e534e94a'
+    'userUuid' => '691ff14d-2467-45ff-9b97-fd0988ffda59'
];

Running the modified version of GetOne.php then produced the following output:

$ php GetOne.php 
object(MongoDB\Model\BSONDocument)#21 (1) {
  ["storage":"ArrayObject":private]=>
  array(12) {
    ["_id"]=>
    object(MongoDB\BSON\ObjectId)#17 (1) {
      ["oid"]=>
      string(24) "61df13ebd69f422f0808aab4"
    }
    ["id"]=>
    object(MongoDB\BSON\ObjectId)#18 (1) {
      ["oid"]=>
      string(24) "61df13ebd69f422f0808aab2"
    }
    ["userUuid"]=>
    string(36) "691ff14d-2467-45ff-9b97-fd0988ffda59"
    ["emailAddress"]=>
    string(21) "ac1Xo2NmH@example.com"
    ["firstName"]=>
    string(5) "Mouse"
    ["lastName"]=>
    string(7) "Provide"
    ["language"]=>
    string(2) "en"
    ["roles"]=>
    array(1) {
      [0]=>
      string(4) "USER"
    }
    ["isUserLocked"]=>
    bool(false)
    ["isUserEnabled"]=>
    bool(true)
    ["createdOn"]=>
    object(MongoDB\Model\BSONDocument)#19 (1) {
      ["storage":"ArrayObject":private]=>
      array(0) {
      }
    }
    ["updatedOn"]=>
    object(MongoDB\Model\BSONDocument)#20 (1) {
      ["storage":"ArrayObject":private]=>
      array(0) {
      }
    }
  }
}

This is what I would have expected to see given the code (i.e. there's no driver or library bug at play), but it's certainly not what you're expecting to happen.

I'll note that the __serialize() and __unserialize() methods in your User model are never called, since neither script invokes PHP serialization (i.e. serialize() and unserialize()). Since those methods were converting between DateTime and UTCDateTime objects, I assume you intended them to handle BSON serialization instead of PHP serialization. I changed your User model to implement MongoDB\BSON\Serializable and MongoDB\BSON\Unserializable:

+use MongoDB\BSON\Serializable;
+use MongoDB\BSON\Unserializable;
 
-class Users
+class Users implements Serializable, Unserializable
 {
     public ?ObjectId $id = null;

...
 
-    public function __serialize(): array
+    public function bsonSerialize(): array

...
 
-    public function __unserialize(array $data): void
+    public function bsonUnserialize(array $data): void

Running Create.php with the modified User model then resulted in the following document being inserted:

> db.users.findOne()
{
	"_id" : ObjectId("61df172dbafa05771149d7f2"),
	"CreatedOn" : ISODate("2022-01-12T18:00:13.449Z"),
	"UpdatedOn" : ISODate("2022-01-12T18:00:13.449Z"),
	"UserUuid" : "685d0372-719d-4fc9-8b6d-48536d9f23d3",
	"EmailAddress" : "ac1Xo2NmH@example.com",
	"FirstName" : "Mouse",
	"LastName" : "Provide",
	"Roles" : [
		"USER"
	],
	"IsUserLocked" : false,
	"IsUserEnabled" : true
}

I then modified GetOne.php to refer to the most recently inserted UUID and reverted to your original field name:

```diff
$query = [
-    'userUuid' => '691ff14d-2467-45ff-9b97-fd0988ffda59'
+    'UserUuid' => '685d0372-719d-4fc9-8b6d-48536d9f23d3'
];

Running GetOne.php once more then produced the following output:

$ php GetOne.php 
object(MongoDB\Model\BSONDocument)#20 (1) {
  ["storage":"ArrayObject":private]=>
  array(10) {
    ["_id"]=>
    object(MongoDB\BSON\ObjectId)#17 (1) {
      ["oid"]=>
      string(24) "61df172dbafa05771149d7f2"
    }
    ["CreatedOn"]=>
    object(MongoDB\BSON\UTCDateTime)#18 (1) {
      ["milliseconds"]=>
      string(13) "1642010413449"
    }
    ["UpdatedOn"]=>
    object(MongoDB\BSON\UTCDateTime)#19 (1) {
      ["milliseconds"]=>
      string(13) "1642010413449"
    }
    ["UserUuid"]=>
    string(36) "685d0372-719d-4fc9-8b6d-48536d9f23d3"
    ["EmailAddress"]=>
    string(21) "ac1Xo2NmH@example.com"
    ["FirstName"]=>
    string(5) "Mouse"
    ["LastName"]=>
    string(7) "Provide"
    ["Roles"]=>
    array(1) {
      [0]=>
      string(4) "USER"
    }
    ["IsUserLocked"]=>
    bool(false)
    ["IsUserEnabled"]=>
    bool(true)
  }
}

This looks more like what you'd have expected to see.

In summary, I think the root issue was confusing PHP's serialization methods with the BSON serialization interface used by the driver (as originally mentioned in #1288 (comment)). Hope that helps, but let me know if you have any other questions.

The bson serializer works great but I guess I thought with the magic method I didn't need to include the older method as that seemed redundant, I'll just stick with the bson serializer.