-
Notifications
You must be signed in to change notification settings - Fork 261
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PHPLIB-1137: Reject PackedArrays when expecting documents #1117
PHPLIB-1137: Reject PackedArrays when expecting documents #1117
Conversation
*/ | ||
function is_document($document): bool | ||
{ | ||
return is_array($document) || (is_object($document) && ! $document instanceof PackedArray); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noting that we don't bother checking if array is a list, since APIs that take arrays as documents generally use an object cast to ensure proper BSON encoding as a document type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, but can a list be a valid value for the commands? Can 0
be used as a field name?
If not, it may help developers if they're notified as soon as possible that the value they've passed is a list of documents, when a single document is expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, but can a list be a valid value for the commands?
Strictly speaking, a list is not a valid document, even though it has been treated as such in PHPLIB/PHPC.
Can
0
be used as a field name?
Yes. Arrays are serialised like objects in BSON, but denoted through a special type. Likewise, inserting a structure like [0 => 'foo', 2 => 'bar']
would always yield an object with "0"
and "2"
as keys (since the value isn't a list).
If not, it may help developers if they're notified as soon as possible that the value they've passed is a list of documents, when a single document is expected.
I agree, which is why we're prohibiting PackedArray
, as we're always sure that it's an array, not an object. For PHP arrays however this isn't as simple and sometimes performance sensitive, which is why it wasn't added before (I'll leave it to @jmikola to confirm this). There is one more difference: with a structure like [0 => 'foo', 1 => 'bar']
, the trained eye sees an array, but it can easily be used as an object. A PackedArray
is by definition always an array, which is why we're prohibiting it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PHP arrays however this isn't as simple and sometimes performance sensitive
I'm not sure what this is referring to. Regarding performance, I know we definitely didn't want to call bsonSerialize()
too many times for differentiating things that might encode as BSON documents or arrays.
I agree with the change here, though. PackedArray is the only object that we know for certain will not be BSON document.
tests/TestCase.php
Outdated
@@ -139,6 +140,11 @@ public function provideInvalidIntegerValues() | |||
return $this->wrapValuesForDataProvider($this->getInvalidIntegerValues()); | |||
} | |||
|
|||
public function provideInvalidUpdateValues() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this only used for Operation classes? If so, you can consider moving it to MongoDB\Tests\Operation\TestCase
, which is where I put other providers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good point. I've moved this and getInvalidUpdateValues
for consistency.
This also provides a nice refactoring opportunity down the line once we make all our data providers static (which is required in later PHPUnit versions): we can offload these data providers to separate classes or traits and import them as necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving our data providers and utility functions into traits has been on my mind for a few months now. Static methods are great, but I suppose there's nothing holding up an immediate trait migration today.
I created PHPLIB-1170 to track this.
Do all versions of PHPUnit allow static data providers? I saw that sebastianbergmann/phpunit@9caafe2 now requires static
as of PHPUnit 10+, but I'm curious if anything is holding us up from making the change today. Our PHP 7.2+ requirement means we'll only be using PHPUnit 8.5+.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, static data providers have been allowed for quite some time but have only become required as of PHPUnit 10. We could make that change already.
65883a2
to
73af4cb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's great to have factored this recurring code pattern.
src/Command/ListCollections.php
Outdated
if (isset($options['filter']) && ! is_array($options['filter']) && ! is_object($options['filter'])) { | ||
throw InvalidArgumentException::invalidType('"filter" option', $options['filter'], 'array or object'); | ||
if (isset($options['filter']) && ! is_document($options['filter'])) { | ||
throw InvalidArgumentException::invalidType('"filter" option', $options['filter'], 'document'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"document" is not a native php type. The term should be defined somewhere. Maybe update the message in MongoDB\Exception\InvalidArgumentException
.
- Expected %s to have type "document" but found "string"
+ Expected %s to have type "document" (array or object) but found "string"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yes. I was trying to avoid "array or object" since that would include all objects including PackedArray
. I can add it if we want to glance over the packed array case. What do you think @jmikola?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought I left a comment about my concern with using "document" as a type since we use actual PHP types everywhere else. I can't find that so perhaps I deleted the draft before submitting my last review.
In any event, I agree that something like "document" (array or object)
would be preferable. We don't need to call out PackedArray specifically, as anyone using that should understand that it's not a document -- and if it leads to a bug report it'd be a nice opportunity to educate someone :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you pass document (array or object)
to the invalidType()
factory method I suppose it's going to be wrapped in double quotes. Perhaps you just want to pass document
and let the factory method deal with formatting -- but if that's a bit hairy (especially with the existing logic to handle arrays) then maybe just let it get quoted as-is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps you just want to pass document and let the factory method deal with formatting
That's what I was thinking. To factorise the message in the exception class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've created a new expectedDocumentType
factory for this case - it seemed a more sensible choice than messing around with the formatting.
*/ | ||
function is_document($document): bool | ||
{ | ||
return is_array($document) || (is_object($document) && ! $document instanceof PackedArray); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, but can a list be a valid value for the commands? Can 0
be used as a field name?
If not, it may help developers if they're notified as soon as possible that the value they've passed is a list of documents, when a single document is expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM once you add "array or object" to the "document" type exception message.
With this refactoring, data providers become more concise; each data entry also gets a distinct name making for easier identification of failing tests
028bd7e
to
e9e5636
Compare
e9e5636
to
3af1d1f
Compare
* v1.16: PHPLIB-1137: Reject PackedArrays when expecting documents (#1117)
PHPLIB-1137
This PR adds a new
is_document
function that is used to validate input options. While we acceptarray|object
for documents, aPackedArray
instance is one of those objects where we are absolutely sure that it is not a document.This PR also includes a commit to refactor most data providers that provide options to operation classes. This was done to give the individual data sets a name, making it easier to spot failing tests. Due to the large amount of unrelated changes in that first commit, I suggest reviewing the commits separately.