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

Fix #20268: Minor optimisation in \yii\helpers\BaseArrayHelper::map #20269

Merged
merged 6 commits into from
Oct 27, 2024

Conversation

chriscpty
Copy link
Contributor

@chriscpty chriscpty commented Oct 17, 2024

Q A
Is bugfix?
New feature? ✔️
Breaks BC?
Fixed issues #20268

Information re: what this does is in the issue.

In the process, I also improved the ArrayHelper::map() tests a bit to also test cases where callbacks are passed in for the three parameters.

+ use array_column for faster mapping if no callbacks or grouping are required
Copy link

codecov bot commented Oct 17, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 64.84%. Comparing base (3c75ff1) to head (4ed589f).
Report is 2 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master   #20269      +/-   ##
============================================
- Coverage     64.95%   64.84%   -0.11%     
- Complexity    11396    11401       +5     
============================================
  Files           430      430              
  Lines         36925    37138     +213     
============================================
+ Hits          23984    24083      +99     
- Misses        12941    13055     +114     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

+ also test with callbacks
@samdark samdark added this to the 2.0.52 milestone Oct 17, 2024
@samdark samdark requested review from a team October 17, 2024 13:55
@rob006
Copy link
Contributor

rob006 commented Oct 17, 2024

I'm afraid this will change behavior for keys with dots, for exampleArrayHelper::map($array, 'model1.id', 'model2.name'). Old implementation tried to fetch value from $arrayItem['model1']['id'], new one will always use $arrayItem['model1.id'].

@@ -595,6 +595,9 @@ public static function getColumn($array, $name, $keepKeys = true)
*/
public static function map($array, $from, $to, $group = null)
{
if (is_string($from) && is_string($to) && $group === null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add a check that both $from and $to don't contain a dot (.).
The ArrayHelper::getValue() function supports getting a value by a "path" string.
Without this check the following code would break:

$array = [
    [
        'prop' => [
            'a' => 'foo',
            'b' => 'bar',
        ],
    ],
];

ArrayHelper::map($array, 'prop.a', 'prop.b');

Copy link
Member

@samdark samdark Oct 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. It is necessary. @chriscpty would you please redo performance test with such a check?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

performance with the added check:

before: 1729233314.6667
after: 1729233315.4382
elapsed: 0.77146291732788
before: 1729233315.4382
after: 1729233316.245
elapsed: 0.80677795410156
before: 1729233316.245
after: 1729233317.0443
elapsed: 0.79932498931885
before: 1729233317.0443
after: 1729233317.8304
elapsed: 0.78613996505737
before: 1729233317.8305
after: 1729233318.6256
elapsed: 0.79515385627747
bench size: 18000

I'll add the fix (and related tests) in a second

! don't use array_column if ArrayHelper strings are paths
+ add according tests
+ add BaseStringHelper::contains() helper function and according tests
@chriscpty
Copy link
Contributor Author

I also added a BaseStringHelper::contains() function as php7 does not have str_contains. This will become redundant once the minimum supported php version is raised to php8.0 or higher.

@rob006
Copy link
Contributor

rob006 commented Oct 20, 2024

I also added a BaseStringHelper::contains()

I'm not sure if it is worth to polyfill function, that exists in PHP since 4 years. This method is already redundant for ~70% of framework users.

@chriscpty
Copy link
Contributor Author

This method is already redundant for ~70% of framework users

if that number is true, that'd mean not including the polyfill would be a breaking change for 30% of users. Yii2's minimum supported php version is still 7.3 as far as I'm aware & I don't want to introduce breaking changes for a minor optimisation.

Whether yii2 should bother supporting PHP versions that are EOL is a separate question that is up to the maintainers to answer.

@rob006
Copy link
Contributor

rob006 commented Oct 21, 2024

if that number is true, that'd mean not including the polyfill would be a breaking change for 30% of users.

In what scenario not adding new function would be a BC break? 70% of framework installations is for PHP8 - in that case developer could use str_contains() directly, the rest could stick to strpos() or use actual polyfill from symfony. There is no need to add BaseStringHelper::contains() in order to implement this minor optimization for ArrayHelper::map().

@chriscpty
Copy link
Contributor Author

chriscpty commented Oct 21, 2024

so in other words, you'd rather have it as a construct like

if (function_exists('str_contains') ? str_contains([...]) : (mb_strpos([...]) !== -1))

?

I originally considered doing that, but figured it'd make the condition for the optimisation ugly to read.

EDIT: or am I mistunderstanding and what you're saying is "this optimisation is not worth adding a new function, so you'd rather not add it"? I'd get that, but also don't think there's harm in adding it

@rob006
Copy link
Contributor

rob006 commented Oct 21, 2024

My suggestion is:

  1. Do not add BaseStringHelper::contains().
  2. in Arrayhelper::map() use strpos($to, '.') === false do detect if key contains dot.

If we really want some kind contains() helper in StringHelper, then IMO it should provide something that str_contains() do not provide. For example proper multibyte support - in that case it should consistently use mb_*functions and should not fallback to str_contains(). But this is out of scope of this PR.

! use strpos rather than polyfill
- remove BaseStringHelper::contains
@chriscpty
Copy link
Contributor Author

alright, done :)

@samdark samdark changed the title Minor optimisation in ArrayHelper::map Fix #20268: Minor optimisation in \yii\helpers\BaseArrayHelper::map Oct 27, 2024
@samdark samdark merged commit e4d5d73 into yiisoft:master Oct 27, 2024
84 of 87 checks passed
@samdark
Copy link
Member

samdark commented Oct 27, 2024

Thank you!

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.

5 participants