-
Notifications
You must be signed in to change notification settings - Fork 264
PHPLIB-955: Require PHP 8.1 #1374
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
Conversation
Marking this ready for review. I'll revisit the failing tests shortly, as I suspect those will be fixed by #1373 |
@@ -1,5 +1,7 @@ | |||
<?php | |||
|
|||
declare(strict_types=1); |
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.
This directive is necessary to disable type coercion in testConstructorOutArgumentTypeCheck
; all of the invalid values provided there can be coerced to strings.
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.
This means that the validation is weaker than before, since the values were not cast. Do we consider that it's the user's responsibility to send the right type and use strict mode if they wish to avoid this kind of error? In any case, it won't work with an int casted to 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.
Before this change, users could pass an int which we'd have to guard against. With the string
type enforced by PHP, there are two scenarios: if the calling file has strict_types
disabled, an int would be coerced to string; with strict_types
enabled a TypeError
is thrown. The end result is the same: the method will not be called with a non-string parameter.
* @psalm-param BSONType $value | ||
* @return mixed | ||
* @psalm-return NativeType | ||
* @throws UnsupportedValueException if the decoder does not support the value | ||
*/ | ||
abstract public function decode($value); | ||
abstract public function decode(mixed $value); |
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.
Confirmed this is not a breaking change for classes that override this abstract method. https://3v4l.org/donQG
I don't get why Symfony BC promise prohibit adding typehint to arguments in classes. https://symfony.com/doc/current/contributing/code/bc.html#changing-classes
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.
Correct. Without a type specified for an argument, people can't add their own, narrower types. When we add a type, people are still allowed to widen types or omit the type entirely, which is why we can add parameter types in classes and interfaces in minor versions.
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.
/me looks over at the Covariance and Contravariance poster hanging on my wall.
@@ -54,8 +54,7 @@ class StreamWrapper | |||
/** @var resource|null Stream context (set by PHP) */ | |||
public $context; | |||
|
|||
/** @var ReadableStream|WritableStream|null */ | |||
private $stream; | |||
private ReadableStream|WritableStream|null $stream = null; |
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.
Adding the type on properties produces an error in case of access to uninitialized property. That's why you added the default null value here.
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.
That's correct 👍
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.
When does PHP raise an error for the uninitialized property? I'm curious if the error is only triggered by explicitly assigning null
in the property declaration. If so, would you rather do without that and omit |null
from the 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.
If the type was only ReadableStream|WritableStream
without a default value, accessing $this->stream
would cause a fatal error. I believe we could theoretically use that instead of assert
in a number of places, as even with disabled assertion handling any access to the stream would result in an object access on null
, which again yields a fatal error.
I'll note that isset
does not trigger the uninitialised property error, so we would still be able to check if a stream was initialised. I can change this if y'all prefer to leave null
out of the 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.
To add on to this, I'll note that psalm does not like properties without a default value that aren't initialised in the constructor:
ERROR: MissingConstructor - src/GridFS/StreamWrapper.php:56:43 - MongoDB\GridFS\StreamWrapper has an uninitialized property MongoDB\GridFS\StreamWrapper::$stream, but no constructor (see https://psalm.dev/073)
private ReadableStream|WritableStream $stream;
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.
Feel free to leave as-is. Seems like the path of least resistance.
tests/TestCase.php
Outdated
@@ -250,7 +243,8 @@ protected function getInvalidBooleanValues(bool $includeNull = false): array | |||
*/ | |||
protected function getInvalidDocumentValues(bool $includeNull = false): array | |||
{ | |||
return array_merge([123, 3.14, 'foo', true, PackedArray::fromPHP([])], $includeNull ? [null] : []); | |||
return array_merge([123, 3.14, 'foo', true], $includeNull ? [null] : []); | |||
// return array_merge([123, 3.14, 'foo', true, PackedArray::fromPHP([])], $includeNull ? [null] : []); |
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.
You left this commented.
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.
Whoops, removed.
For context, with the object|array
type now enforced for documents, PackedArray
instances no longer cause an error. I could bring back the PackedArray
check if we want to be more specific about what objects are allowed, but I defer to @jmikola on this.
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.
Isn't the PackedArray check still in place? I saw you removed various conditionals when adding array|object
type hints, but is_document()
still rejects PackedArray instances. I would expect that that InvalidArgumentException::expectedDocumentType()
still gets thrown for PackedArray instances.
I assume changing InvalidArgumentException
expectations to TypeError
is what conflicts here. I don't feel strongly about this, but I'd argue that parameter and option type checks shouldn't necessarily be expected to raise TypeError
. There are still various other contexts where we raise InvalidArgumentException
for validation errors (e.g. option value is out of range, or a string value doesn't satisfy our homemade enum).
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 is indeed. I've updated the type assertions based on the value given to expect a TypeError
or InvalidArgumentException
and restored the PackedArray
instance as an invalid document, as it adds confidence that we have the right checks in place.
With TypeError
being a thing, I wonder if we should consider throwing TypeError
throwables instead of InvalidArgumentException
on type mismatches.
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.
With TypeError being a thing, I wonder if we should consider throwing TypeError throwables instead of InvalidArgumentException on type mismatches.
That sounds like a great idea, now that we can reliably expect TypeErrors from PHP itself. Might be something to consider for both PHPC and PHPLIB, and then we can also remove the vapid documentation for throwing InvalidArgumentException from most things.
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
</MoreSpecificReturnType> | ||
</file> | ||
<file src="src/Model/BSONIterator.php"> | ||
<MixedArgument> | ||
<code><![CDATA[$this->options['typeMap']]]></code> |
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.
Should we define array shape for the options?
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.
We had that. However, in the param
declaration, it would have to be array{typeMap?: array, ...}
, whereas for the property it's array{typeMap: array, ...}
. With promoted properties, the same type is used for the parameter and property, leading to this error. Instead of adding custom logic to work around this, I decided to add the error to the baseline.
@@ -397,7 +397,7 @@ private static function getVersion(): string | |||
if (self::$version === null) { | |||
try { | |||
self::$version = InstalledVersions::getPrettyVersion('mongodb/mongodb') ?? 'unknown'; | |||
} catch (Throwable $t) { | |||
} catch (Throwable) { |
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.
Noted that the variable is no longer required as of PHP 8.0 (see: Exceptions).
tests/TestCase.php
Outdated
@@ -250,7 +243,8 @@ protected function getInvalidBooleanValues(bool $includeNull = false): array | |||
*/ | |||
protected function getInvalidDocumentValues(bool $includeNull = false): array | |||
{ | |||
return array_merge([123, 3.14, 'foo', true, PackedArray::fromPHP([])], $includeNull ? [null] : []); | |||
return array_merge([123, 3.14, 'foo', true], $includeNull ? [null] : []); | |||
// return array_merge([123, 3.14, 'foo', true, PackedArray::fromPHP([])], $includeNull ? [null] : []); |
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.
Isn't the PackedArray check still in place? I saw you removed various conditionals when adding array|object
type hints, but is_document()
still rejects PackedArray instances. I would expect that that InvalidArgumentException::expectedDocumentType()
still gets thrown for PackedArray instances.
I assume changing InvalidArgumentException
expectations to TypeError
is what conflicts here. I don't feel strongly about this, but I'd argue that parameter and option type checks shouldn't necessarily be expected to raise TypeError
. There are still various other contexts where we raise InvalidArgumentException
for validation errors (e.g. option value is out of range, or a string value doesn't satisfy our homemade enum).
bf2e611
to
48c14de
Compare
48c14de
to
a44c71f
Compare
PHPLIB-955
The first commit updates the requirement in composer.json and updates CI configs. In the second commit, I applied all automated fixes from phpcbf. This mainly affects the use of union types as well as the
mixed
type.A couple of notes on phpcs rules:
?Type
types toType|null
. I prefer the short syntax over the consistency with other union types (e.g.Type|Other|null
)