Skip to content
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

Merged
merged 1 commit into from
Apr 22, 2022

Conversation

emteknetnz
Copy link
Member

@emteknetnz emteknetnz commented Mar 7, 2022

@emteknetnz emteknetnz force-pushed the pulls/1/php81 branch 3 times, most recently from 2fa6161 to b536605 Compare April 4, 2022 06:47
@emteknetnz emteknetnz changed the title NEW PHP 8.1 compatibility ENH PHP 8.1 compatibility Apr 4, 2022
@emteknetnz emteknetnz force-pushed the pulls/1/php81 branch 2 times, most recently from d652048 to 0c18046 Compare April 13, 2022 05:36
@emteknetnz emteknetnz marked this pull request as ready for review April 20, 2022 05:08
Copy link
Member

@GuySartorelli GuySartorelli left a 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 ?? []);
Copy link
Member

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);
Copy link
Member

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);
Copy link
Member

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.

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 ?? []);
Copy link
Member

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 ?? []);
Copy link
Member

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().

Suggested change
return array_filter($ids ?? []);
return array_filter($ids ?: []);

Copy link
Member Author

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)) {
Copy link
Member

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 ?? []))
Copy link
Member

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'] ?? []));
Copy link
Member

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.

https://github.com/silverstripe/silverstripe-framework/blob/1261926911aa5fc59af7287f9895c212db101158/src/Dev/BulkLoader.php#L230

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)) {
Copy link
Member

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'] ?? []));
Copy link
Member

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.

@emteknetnz
Copy link
Member Author

emteknetnz commented Apr 21, 2022

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.

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

@GuySartorelli
Copy link
Member

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.

@maxime-rainville
Copy link
Contributor

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.

Copy link
Member

@GuySartorelli GuySartorelli left a 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.

Copy link
Member

@GuySartorelli GuySartorelli left a 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.

code/ModelAdmin.php Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants