-
Notifications
You must be signed in to change notification settings - Fork 96
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
ENH PHP 8.1 compatibility #1294
ENH PHP 8.1 compatibility #1294
Conversation
2fa6161
to
b536605
Compare
d652048
to
0c18046
Compare
0c18046
to
d3ae28b
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.
Found a lot of situations where null coalescing just isn't necessary - and I stopped because there were just too many of them. We've discussed this a bit in person but for the sake of having this recorded somewhere can you please respond with your thinking on this? It's definitely overkill the way this is being done so far and it'll be good to have it recorded that this is a conscious decision.
@@ -73,7 +73,7 @@ public static function rules() | |||
|
|||
// Map over the array calling add_rule_for_controller on each | |||
$classes = CMSMenu::get_cms_classes(null, true, CMSMenu::URL_PRIORITY); | |||
array_map(array(__CLASS__, 'add_rule_for_controller'), $classes); | |||
array_map(array(__CLASS__, 'add_rule_for_controller'), $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.
CMSMenu::get_cms_classes()
always outputs an array, so null coalescing isn't necessary here.
@@ -92,7 +92,7 @@ protected static function add_rule_for_controller($controllerClass) | |||
if ($urlSegment) { | |||
// Make director rule | |||
if ($urlRule[0] == '/') { | |||
$urlRule = substr($urlRule, 1); | |||
$urlRule = substr($urlRule ?? '', 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.
To get to this bit of code, we must already have tried to get index 0 of $urlRule
which would have thrown an error if that is null - so null coalescing does nothing here.
@@ -154,7 +154,7 @@ public function applicablePagesHelper($ids, $methodName, $checkStagePages = true | |||
$draftPages = DataObject::get($managedClass)->byIDs($ids); | |||
|
|||
// Filter out the live-only ids | |||
$onlyOnLive = array_fill_keys($ids, true); | |||
$onlyOnLive = array_fill_keys($ids ?? [], true); |
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're already checking that $ids
is an array, so no need for null coalescing here.
silverstripe-admin/code/CMSBatchAction.php
Lines 144 to 146 in d3ae28b
if (!is_array($ids)) { | |
user_error("Bad \$ids passed to applicablePagesHelper()", E_USER_WARNING); | |
} |
@@ -163,7 +163,7 @@ public function applicablePagesHelper($ids, $methodName, $checkStagePages = true | |||
} | |||
} | |||
} | |||
$onlyOnLive = array_keys($onlyOnLive); | |||
$onlyOnLive = array_keys($onlyOnLive ?? []); |
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 is definitely an array here - it's assigned using array_fill_keys()
.
foreach ($ids as $k => $id) { | ||
$ids[$k] = (int)$id; | ||
} | ||
return array_filter($ids); | ||
return array_filter($ids ?? []); |
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.
$ids
can only be array
or false
- never null
(it's assigned using preg_split()
.
return array_filter($ids ?? []); | |
return array_filter($ids ?: []); |
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 fix here would be some defensive programming like adding a new line about this such as $ids = is_array($ids) ? $ids : [];
However since this hasn't come up in any unit test failures, I think we should just leave it as is. (There will be thousands of other examples similar to this)
foreach ($subClasses as $key => $className) { | ||
// Remove abstract classes and LeftAndMain | ||
if (in_array($className, $abstractClasses) || ClassInfo::classImplements($className, TestOnly::class)) { | ||
if (in_array($className, $abstractClasses ?? []) || ClassInfo::classImplements($className, TestOnly::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.
$abstractClasses
is explicitly declared as an array. No need for null coalescing here.
@@ -96,7 +96,7 @@ public function getAttributesHTML($attrs = null) | |||
|
|||
// Remove empty or excluded values | |||
foreach ($attrs as $key => $value) { | |||
if (($excludeKeys && in_array($key, $excludeKeys)) | |||
if (($excludeKeys && in_array($key, $excludeKeys ?? [])) |
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.
The first part of this condition checks for truthiness of $excludeKeys
which will fail and short-circuit on null
. No need for null coalescing here.
@@ -36,7 +36,7 @@ public function __construct($controller, $name, $fields = null, $actions = null, | |||
$importer = new GroupCsvBulkLoader(); | |||
$importSpec = $importer->getImportSpec(); | |||
|
|||
$columns = implode(', ', array_keys($importSpec['fields'])); | |||
$columns = implode(', ', array_keys($importSpec['fields'] ?? [])); |
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.
$importSpec['fields']
here comes from SilverStripe\Dev\BulkLoader
which explicitly casted it to array
.
And since GroupCsvBulkLoader
is instantiated with the new
keyword there's no chance of someone injecting their own class that has null there instead. No need for null coalescing here.
@@ -1765,7 +1765,7 @@ public function CMSVersionNumber() | |||
return ''; | |||
} | |||
$version = $lockModules[$moduleName]; | |||
if (preg_match('#^([0-9]+)\.([0-9]+)#', $version, $m)) { | |||
if (preg_match('#^([0-9]+)\.([0-9]+)#', $version ?? '', $m)) { |
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.
By this point if $lockModules[$moduleName]
was not null (i.e. !isset()
) we've already returned out of the method. No need for null coalescing here.
@@ -37,7 +37,7 @@ public function __construct($controller, $name, $fields = null, $actions = null, | |||
$importer = new MemberCsvBulkLoader(); | |||
$importSpec = $importer->getImportSpec(); | |||
|
|||
$columns = implode(', ', array_keys($importSpec['fields'])); | |||
$columns = implode(', ', array_keys($importSpec['fields'] ?? [])); |
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 is ultimately the same as the GroupCsvBulkLoader
import spec as mentioned above.
No need for null coalescing here.
Yes agree it's overkill, though I'm not sure how else we can do this. There is just vastly too many places in all the supported modules that needed to be updated to go through all of this manually. The code writing simply looks to see if a variable is being passed in as a parameter, rather than something like a scalar or a nested native function call with a single return type. Ideally the code writer would have more smarts about it where it could judge if modification being made to a variable could lead it to being in a nullable state. However even if we did have a method that could trace back changes made to variables, we're still stuck with the fact that the vast majority of Silverstripe methods have dynamic return types, and the docblock return types are very often wrong, so you cannot rely on them. Since variables are largely a result of calls to Silverstripe methods, we won't very far with an automated cleanup. At a future date, once Silverstripe method params and return types are fully strongly typed, I would like to do an automated cleanup run that does trace what type variables could be, and then based on that remove the redundant null coalescing statements Note: this is the code writer |
In general I agree - doing this in an automated fashion we're either going to go overboard and miss a few cases, or be light-handed and miss heaps of cases. But on the otherhand doing this all by hand (like I was for the review) is a long laborious process with little payoff. Ultimately having some null coalescing operators where they aren't strictly needed isn't going to hurt anybody, and like you say it'll be much easier to go back and clean that up in a future major release when things are strongly typed. I'll give Max and other core committers a chance to review this discussion before moving on but I think this is ultimately aan acceptable approach. |
I think Steve and I had a similar discussion in a previous Zoom call. Generally, my mind is in the same place yours are. There's not really a way to support PHP8.1 without warning and without having a big automating process that will type cast everything. To a degree, this will bite us in the ass later once we strongly type everything ... and will potentially neglect some of the benefit of strongly typing because it will obfuscate logic errors. In the end, we have to begrudgingly accept the automated approach and as a short term necessary evil. |
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.
As per the above conversation, the purposes of this PR is to avoid passing null (specifically null) from built-in functions that as of php 8.1 will throw deprecation warnings if null is passed in. The approach is necessarily heavy-handed, and while there are many situations where it isn't needed, it would be prohibitively laborious to find all of those situations by hand.
With that context, this looks good to me.
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.
Just a question here before I merge.
Issue silverstripe/silverstripe-framework#10250