3

I'm trying to sort any array with array_multisort() and everything is working great. However, based on conditions in my script, I need to change the options.

What I have so far is this:

array_multisort(
    $sort1,
    SORT_ASC,
    $sort2,
    SORT_ASC,
    $sort3,
    SORT_ASC, 
    $arraytosort
);

I would like to write something that will allow a more flexible/dynamic payload of sorting data/rules. Something like this:

$dynamicSort = "$sort1,SORT_ASC,$sort2,SORT_ASC,$sort3,SORT_ASC,";

array_multisort(
    $dynamicSort, 
    $arraytosort
);

How can I feed an unknown number of parameters to array_multisort() and have it modify the $arraytosort array?

mickmackusa
  • 43,625
  • 12
  • 83
  • 136
websiteguru
  • 493
  • 1
  • 11
  • 23
  • `eval` it would be, but really, don't do it. I don't see why you can't do this with a loop and without `array_multisort`. – zneak Mar 19 '10 at 03:10
  • this was just an example... I have a multidimensional array with about 30 points of sort... as far as I know thats what array_multisort is for. – websiteguru Mar 19 '10 at 03:23

5 Answers5

5

You could try to use call_user_func_array. But I've never tried it on a built-in function before. Here is an example:

$dynamicSort = "$sort1,SORT_ASC,$sort2,SORT_ASC,$sort3,SORT_ASC";
$param = array_merge(explode(",", $dynamicSort), array($arrayToSort))
call_user_func_array('array_multisort', $param)
St. John Johnson
  • 6,590
  • 7
  • 35
  • 56
  • 1
    I had the same thought and went to try, and it does seem to work on built-in functions (despite the name!) – Chris Mar 19 '10 at 03:24
  • hmmm seems like a perfect solutions but I keep getting "Argument #1 is expected to be an array or a sort flag" even though it works normally. – websiteguru Mar 19 '10 at 04:31
  • 1
    Oh, try single quotes instead of double quotes. It may be trying to pass the value of $sort1 instead the name "sort1" – St. John Johnson Mar 19 '10 at 11:43
  • I'm very curious about how the array data contained in `$sort1`, `$sort2`, and `$sort3` can possibly be stored as the `$dynamicSort` string. Please prove that this accepted answer works by providing a runnable online demo link. – mickmackusa Jan 19 '23 at 22:36
3

I had the same problem with this answer: "Argument #1 is expected to be an array or a sort flag"

For anyone having the same problem try this instead:

$dynamicSort = array(&$sort1, SORT_ASC, &$sort2, SORT_ASC, &$sort3, SORT_ASC); 
$param = array_merge($dynamicSort, array(&$arrayToSort));
call_user_func_array('array_multisort', $param);

Note that i have used the reference to my variables "&$" instead of $. This works great in php 5.3 but may cause error in 5.2 due to a bug.

Preda Bogdan
  • 106
  • 2
  • 7
2

It is important to understand that the array sent to call_user_func_array() must consist only of references; it is not important whether the array itself is passed by reference. I spent the better part of a day troubleshooting this; the fact that the examples on the function page at php.net all used literal arrays led me to this page: php Bug #49353. Problem solved.

This doesn't seem to be very well (or consistently) documented, so here goes....

These DO NOT WORK (PHP 5.3.3):

$multisort_array = array($arr1, SORT_DESC, SORT_STRING, $arr2);      // array of values
call_user_func_array('array_multisort', $multisort_array);           // array passed by value

$multisort_array = array($arr1, SORT_DESC, SORT_STRING, $arr2);      // array of values
call_user_func_array('array_multisort', &$multisort_array);          // array passed by reference

$multisort_array = array(&$arr1, SORT_DESC, SORT_STRING, &$arr2);    // non-constants by reference
call_user_func_array('array_multisort', $multisort_array);           // array passed by value

$multisort_array = array(&$arr1, SORT_DESC, SORT_STRING, &$arr2);    // non-constants by reference
call_user_func_array('array_multisort', &$multisort_array);          // array passed by reference

These DO WORK:

$sort = array('desc' => SORT_DESC, 'string' => SORT_STRING);
$multisort_array = array(&$arr1, &$sort['desc'], &$sort['string'], &$arr2);      // all by reference
call_user_func_array('array_multisort', $multisort_array);                       // array passed by value

$sort = array('desc' => SORT_DESC, 'string' => SORT_STRING);
$multisort_array = array(&$arr1, &$sort['desc'], &$sort['string'], &$arr2);      // all by reference
call_user_func_array('array_multisort', &$multisort_array);                      // array passed by reference
vanwinter
  • 153
  • 1
  • 8
  • What is the benefit of making a PHP constant a reference variable? Or do you just like the _look_ of variables with `&` in front of the dollar sign? – mickmackusa Jan 19 '23 at 22:38
1

To add onto the existing answers, just thought I would add a little something. For anyone passing the desired "sort by" as a comma-separated $_POST variable (or any comma-separated variable for that matter):

//$_POST["sort_by"] = "column_A DESC, column_B ASC, columns_C DESC";
$sort_bys = explode(",", $_POST["sort_by"]);
$dynamicSort = array();
foreach($sort_bys as $sort_by){
    $sort_by2 = trim(str_replace('DESC','',$sort_by));
    $direction = (strpos($sort_by, 'DESC') !== false)?SORT_DESC:SORT_ASC;
    $$sort_by2  = array_column($array_to_sort, $sort_by2);
    $dynamicSort[] = &$$sort_by2;
    $dynamicSort[] = $direction;
    $dynamicSort[] = SORT_NUMERIC; //or SORT_STRING or SORT_REGULAR ...
}    
$param = array_merge($dynamicSort, array(&$array_to_sort));
call_user_func_array('array_multisort', $param);
Qooch
  • 11
  • 1
1

From PHP5.6, you can use a variadic technique. Simply push all of your sorting data and sorting logic into an indexed array, then use the splat operator to unpack the parameters into array_multisort(). Be sure to make the array that you wish to modify -- modifiable by reference before pushing it into the parameters array.

The pushing of parameters into $sortingParams below can be written more succinctly as a single declaration, but I think it will be easier to conceptualize this way. These individual pushes would be suitable inside of an iterating process (e.g. foreach()).

For every column of data used to sort the parent array, you may elect to push zero, one, or two additional elements to best signify the sorting logic.

Code: (Demo)

$array = [
    ['number' => 2, 'letter' => 'a', 'price' => 9.99], 
    ['number' => 3, 'letter' => 'b', 'price' => 9.99], 
    ['number' => 1, 'letter' => 'c', 'price' => 9.50],
    ['number' => 1, 'letter' => 'd', 'price' => 10],
    ['number' => 1, 'letter' => 'e', 'price' => 9.99],
];

$sortingParams[] = array_column($array, 'number');  // 1-dimensional
$sortingParams[] = SORT_ASC;                        // this is omittable as well because it is assumed (just for demo)
$sortingParams[] = array_column($array, 'price');   // 1-dimensional
$sortingParams[] = SORT_DESC;
$sortingParams[] = SORT_NUMERIC;                    // this is omittable as well because it is assumed (just for demo)
$sortingParams[] = &$array;                         // this is the actual master array which should be modified

array_multisort(...$sortingParams);                 // unpack with splat operator
var_export($array);

Output:

array (
  0 => 
  array (
    'number' => 1,
    'letter' => 'd',
    'price' => 10,
  ),
  1 => 
  array (
    'number' => 1,
    'letter' => 'e',
    'price' => 9.99,
  ),
  2 => 
  array (
    'number' => 1,
    'letter' => 'c',
    'price' => 9.5,
  ),
  3 => 
  array (
    'number' => 2,
    'letter' => 'a',
    'price' => 9.99,
  ),
  4 => 
  array (
    'number' => 3,
    'letter' => 'b',
    'price' => 9.99,
  ),
)

This technique is super powerful if you have dynamic rules being passed to your process. In my case, I needed to collect filters from my DataTables UI and regenerate the data as a .csv. I merely needed to iterate through DataTable's order data and derive my set of rules - done.

I find this syntax much kinder on the eyes versus call_user_func_array().

Here is a more complex implementation: Sort array of associative arrays on multiple columns using specified sorting rules

mickmackusa
  • 43,625
  • 12
  • 83
  • 136