I’ve got a website running on a pretty old PHP 5.6.38 installation… and I’ve now taken the plunge to move it onto the latest XAMPP version (with PHP 8.0.3).
Unsurprisingly, there’s a few changes necessary, but the one I can’t seem to get sorted is the one relating to the deprecated "create_function" function. I used this to allow me to dynamically sort associative arrays by one or more key names… for example :
usort($myarray, create_function('$a,$b', get_usort_function('field2 ASC, field5 ASC')));
Now, I’ve read that I should be using an anonymous function, so have changed the code to be as follows…
usort($myarray, function($a,$b) { get_usort_function('field2 ASC, field5 ASC'); } );
The get_usort_function is used to create the text required for the comparisons – so for the example above it would return something like…
$field2=compare_ints($a['field2'], $b['field2']); if($field2==0){return compare_ints($a['field5'], $b['field5']);}else{return $field2;}
Now, in the PHP8 version then the anonymous function isn’t working – BUT if I hardcode the string that get_usort_function returns then it DOES work. Am I missing something?
A simplified example of this in action is as follows…
<?php
function compare_ints($val1, $val2)
{
return $val1 <=> $val2;
}
function dynamic_create_usort_function()
{
$str='return compare_ints($a[' . "'" . 'id' . "'" . '], $b[' . "'" . 'id' . "'" . ']);';
return $str;
}
$a1 = array( 'id' => 9, 'name' => 'Andy');
$a2 = array( 'id' => 5, 'name' => 'Bob');
$a = array($a1, $a2);
$s = dynamic_create_usort_function();
print "nn***$s***nn";
print_r($a);
usort($a, function($a,$b) { dynamic_create_usort_function(); } );
print_r($a);
usort($a, function($a,$b) { return compare_ints($a['id'], $b['id']); } );
print_r($a);
?>
The above example gives output of…
***return compare_ints($a['id'], $b['id']);***
Array
(
[0] => Array
(
[id] => 9
[name] => Andy
)
[1] => Array
(
[id] => 5
[name] => Bob
)
)
Array
(
[0] => Array
(
[id] => 9
[name] => Andy
)
[1] => Array
(
[id] => 5
[name] => Bob
)
)
Array
(
[0] => Array
(
[id] => 5
[name] => Bob
)
[1] => Array
(
[id] => 9
[name] => Andy
)
)
I’d really like to solve this as my website makes a lot of use of this usort function! So, obviously the least amount of rework that’ll be needed is the dream…
Thanks in advance,
Darren
2
Answers
Thanks!
I've taken what you've said and reworked things to get a working solution that I need...
This works pretty much exactly as I need it now, with the spaceship operator allowing me to get rid of the extra "helper" functions (i.e. compare_dates, compare_ints) and the final eval (of the string that was produced) meaning that the function is returned rather than a string of the function.
Whether I should really be using eval() I'm not sure - prob not, but given that the content of the function is dynamic (i.e. due to the fields being sorted by being dynamic) then I think it's maybe justified.
Now I just need to rework the code slightly, so that all of these calls...
...become calls like this...
Better get my regex skills sorted for a global search/replace rather than manually editing each file!
Thanks for your help :-)
Issue
create_function
takes two strings, the arguments and body of the function to create. Internally, it useseval
to create something callable, which is generally frowned upon, as it increases the attack surface area.From your description, the
get_usort_function
function returns a string; as you noted, if called like:it would return something like:
You’d noted that hardcoding the string in the callable passed to
usort
works, which I’m imagining is something like:but a more accurate hardcoding, given the above description of how
get_usort_function
works, would be:When written out like this, it’s clear that changing the
usort
invocation fromcreate_function
to using a callable as you’ve indicated above won’t return anything (sousort
will leave all the elements in their existing order). This may be the part that wasn’t clear in your understanding of how it was working.Solution
You could do something like the following (which may be similar to a simplified version of the internal logic of the existing
get_usort_function
):which gives the following output:
The main takeaway here is using the
use
keyword on the anonymous function returned byget_usort_callable
to make$fields
available for use. If you wanted to match the functionality of the existingget_usort_function
you could rewrite it to take astring
, and split that up.Minimal work
Given that you are going to have to move away from
create_function
to use PHP 8, the least amount of work you can do will be to rewriteget_usort_function
to return a callable (similar to above) and replace invocations like:with:
Given that you have access to the internal logic of
get_usort_function
it should not be too difficult, and thankfully that is only in one spot. The refactoring of the call-sites is pretty mechanical too, almost a find and replace with any IDE.Going forward (and depending on your preference), you may want to replace the SQL
ORDER BY
style string with a structured array, eg:becomes:
to avoid possible issues with whitespacing etc that require additional handling in
get_usort_function
.create_function
vs anonymous functionsWhen moving from uses of
create_function
to using anonymous functions, you no longer need to have the arguments and body of the function passed as strings. For example, the following two callables are equivalent:and could be used interchangeably.
Adding in one level of indirection (as you have in your question), through use of the following functions:
these two are also equivalent:
The main takeaway from this is that the
sort_by_callable
function itself returns a specialised anonymous function, which will sort on the field passed in, as opposed to a string containing the code to perform the same logic.