Pass extra parameters to usort callback - php

I have the following functions. WordPress functions, but this is really a PHP question. They sort my $term objects according to the artist_lastname property in each object's metadata.
I want to pass a string into $meta in the first function. This would let me reuse this code as I could apply it to various metadata properties.
But I don't understand how I can pass extra parameters to the usort callback. I tried to make a JS style anonymous function but the PHP version on the server is too old (v. 5.2.17) and threw a syntax error.
Any help - or a shove towards the right corner of the manual - gratefully appreciated. Thanks!
function sort_by_term_meta($terms, $meta)
{
usort($terms,"term_meta_cmp");
}
function term_meta_cmp( $a, $b )
{
$name_a = get_term_meta($a->term_id, 'artist_lastname', true);
$name_b = get_term_meta($b->term_id, 'artist_lastname', true);
return strcmp($name_a, $name_b);
}
PHP Version: 5.2.17

I think this question deserves an update. I know the original question was for PHP version 5.2, but I came here looking for a solution and found one for newer versions of PHP and thought this might be useful for other people as well.
For PHP 5.3 and up, you can use the 'use' keyword to introduce local variables into the local scope of an anonymous function. So the following should work:
function sort_by_term_meta(&$terms, $meta) {
usort($terms, function($a, $b) use ($meta) {
$name_a = get_term_meta($a->term_id, 'artist_lastname', true);
$name_b = get_term_meta($b->term_id, 'artist_lastname', true);
return strcmp($name_a, $name_b);
});
}
Some more general code
If you want to sort an array just once and need an extra argument you can use an anonymous function like this:
usort($arrayToSort, function($a, $b) use ($myExtraArgument) {
//$myExtraArgument is available in this scope
//perform sorting, return -1, 0, 1
return strcmp($a, $b);
});
If you need a reusable function to sort an array which needs an extra argument, you can always wrap the anonymous function, like for the original question:
function mySortFunction(&$arrayToSort, $myExtraArgument1, $myExtraArgument2) {
usort($arrayToSort, function($a, $b) use ($myExtraArgument1, $myExtraArgument2) {
//$myExtraArgument1 and 2 are available in this scope
//perform sorting, return -1, 0, 1
return strcmp($a, $b);
});
}

In PHP, one option for a callback is to pass a two-element array containing an object handle and a method name to call on the object. For example, if $obj was an instance of class MyCallable, and you want to call the method1 method of MyCallable on $obj, then you can pass array($obj, "method1") as a callback.
One solution using this supported callback type is to define a single-use class that essentially acts like a closure type:
function sort_by_term_meta( $terms, $meta )
{
usort($terms, array(new TermMetaCmpClosure($meta), "call"));
}
function term_meta_cmp( $a, $b, $meta )
{
$name_a = get_term_meta($a->term_id, $meta, true);
$name_b = get_term_meta($b->term_id, $meta, true);
return strcmp($name_a, $name_b);
}
class TermMetaCmpClosure
{
private $meta;
function __construct( $meta ) {
$this->meta = $meta;
}
function call( $a, $b ) {
return term_meta_cmp($a, $b, $this->meta);
}
}

Assuming you've access to objects and static (PHP 5 or greater), you can create an object and pass the arguments directly there, like so:
<?php
class SortWithMeta {
private static $meta;
static function sort(&$terms, $meta) {
self::$meta = $meta;
usort($terms, array("SortWithMeta", "cmp_method"));
}
static function cmp_method($a, $b) {
$meta = self::$meta; //access meta data
// do comparison here
}
}
// then call it
SortWithMeta::sort($terms, array('hello'));
Assuming you don't have access to objects/static; you could just do a global:
$meta = array('hello'); //define meta in global
function term_meta_cmp($a, $b) {
global $meta; //access meta data
// do comparison here
}
usort($terms, 'term_meta_cmp');

Warning
This function has been DEPRECATED as of PHP 7.2.0. Relying on this function is highly discouraged.
The docs say that create_function() should work on PHP >= 4.0.1. Does this work?
function term_meta_cmp( $a, $b, $meta ) {
echo "$a, $b, $meta<hr>"; // Debugging output
}
$terms = array("d","c","b","a");
usort($terms, create_function('$a, $b', 'return term_meta_cmp($a, $b, "some-meta");'));

This won't help you at all with usort() but might be helpful nevertheless. You could sort the array using one of the other sorting functions, array_multisort().
The idea is to build an array of the values that you would be sorting on (the return values from get_term_meta()) and multisort that against your main $terms array.
function sort_by_term_meta(&$terms, $meta)
{
$sort_on = array();
foreach ($terms as $term) {
$sort_on[] = get_term_meta($term->term_id, $meta, true);
}
array_multisort($sort_on, SORT_ASC, SORT_STRING, $terms);
}

What is the Simplest Solution to Passing Args to usort()?
I like many of the answers here, but I wanted to have a solution that could be done as simply as possible, but could also be demonstrated! When calling usort, supply extra arguments like this...
usort($sortable, [$arg1, $arg2, ... $argn, compareFunction]);
But make sure to define these arguments before, so, you'll end up with something like...
$arg1 = 'something';
$arg2 = 'something else';
$argn = 'yet another thing';
usort($sortable, [$arg1, $arg2, ... $argn, compareFunction]);
Then $arg1, $arg2, and $argn will be available to the compareFunction().
Demo It Up!
To demonstrate, here is a usort() that only considers the first three letters of elements being compared...
function cmp ($a, $b) {
return strcmp(substr($a, 0, $num), substr($a, 0, $num));
}
$terms = ['123a', '123z', '123b',];
$num = 3;
$thing = 4;
usort($terms, [$num, $thing, cmp]);
print_r($terms);
Full Working Demo Online

Related

How to write a function that could be called like func(a)(b)(c) in php?

I need to realize function "calc" that works like that:
$sum = function($a, $b) { return $a + $b; };
calc(5)(3)(2)($sum); // 10
calc(1)(2)($sum); // 3
calc(2)(3)('pow'); // 8
I can write something like this:
function calc(){;
print_r(func_get_args());
return __FUNCTION__;
}
calc(3)(5)(2)('sum');
and it print Array ( [0] => 3 ) Array ( [0] => 5 ) Array ( [0] => 2 ) Array ( [0] => sum ).
So, when I get 'sum' in my function, i should have an array with all previous arguments.
But i have no idea, how can i pass current argument in next function call to manipulate all of them on last iteration. Or is there some sort of recursive solution?
What you're talking about is called Currying. The following code will require PHP 7, since it involves invoking a function returned from another one, which wasn't possible until PHP's Abstract Syntax Tree was implemented in that version.
First things first, you'll need a new sum() function that can operate on an arbitrary number of variables:
$sum = function(...$args) { return array_sum($args); };
Secondly, the important part. A function that returns a new anonymous function, accumulating the arguments as it goes. When you finally pass it something callable (either your $sum function, or a built-in function name like pow), it'll execute it, unpacking the arguments that it's built up.
function calc($x)
{
return function($y = null) use ($x)
{
if (is_callable($y)) {
return $y(...$x);
} else {
$args = (array) $x;
$args[] = $y;
return calc($args);
}
};
}
echo calc(5)(3)(2)($sum); // 10
echo calc(1)(2)($sum); // 3
echo calc(2)(3)('pow'); // 8
See https://3v4l.org/r0emm
(Note that internal functions will be limited to operating on the number of arguments they are defined to take - calc(2)(3)(4)('pow') will raise an error.)
This isn't a particularly common pattern to use (which is probably why you've found it hard to track down), so please for everyone who reads it's sake, think carefully about where you use it.
Credit to the curryAdd answer in this question for the starting blocks.
Edit: I stand corrected, you don't require globals it seems! Definitely use the #iainn's answer over this one.
So to achieve this you're going to have to use globals if you're not doing it within a class to maintain current state. You can see a working example of the below code here (note that it only works for PHP version 7 and above)
<?php
$sum = function(...$args) {
return array_sum($args);
};
function calc(...$args) {
global $globalArguments;
if (is_callable($args[0])) {
$callback = $args[0];
$arguments = array_map(function ($arg) {
return $arg[0];
}, $globalArguments);
return $callback(...$arguments);
}
$globalArguments[] = $args;
return __FUNCTION__;
}
echo calc(3)(2)($sum); // 5
I don't know why you want to do this, but I don't suggest it in production, globals aren't something that should really be used if you can avoid it.
function calc(int $value, Callable $function = null)
{
return function ($v) use ($value, $function) {
$f = function ($call) use ($value, $function) {
return (is_callable($call) && is_callable($function)) ? $call($function($call), $value) : $value;
};
return is_callable($v) ? $f($v) : calc($v, $f);
};
}

Is it possible to make a PHP function that would take any number of arguments?

I am writing some PHP code that would generate HTML files from templates.
I would like, if possible, to make a function that would take any strings I feed the function with, and put that into the file. Like so:
function generator($a, $b, $c, $n...){
$filename = $a . ".html";
ob_start ();
echo $b;
echo $c;
echo $d;
echo $n...;
$buffer = ob_get_clean();
file_put_contents($a, $buffer);
}
I need this, because different pages would have different number of include files, and with this I would be able to skip making different functions for specific pages. Just an iterator, and that's it.
Thanks!
From PHP 5.6+ you can use ... to indicate a variable number of arguments:
function test (... $args)
{
foreach ($args as $arg) {
echo $arg;
}
}
test("testing", "variable"); // testing variable
Demo
Variable-length argument lists from the manual
So, your function would look something like this:
function generator($a, $b, $c, ... $n) {
$filename = $a . ".html";
ob_start();
echo $b;
echo $c;
foreach ($n as $var) {
echo $var;
}
$buffer = ob_get_clean();
file_put_contents($a, $buffer);
}
You can also use variadic functions (PHP 5.6+) :
function generator($a, ...$args) {
echo $a . "\n";
print_r($args);
}
generator("test", 1, 2, 3, 4);
Outputs :
"test"
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
)
You can make it using an array as following :
function generator($array){
// set the first item of the array as name of the .html file and take it out of the array.
$filename = array_shift($array) . ".html";
ob_start ();
// echo all the array fields
foreach($array as $a){
echo $a;
}
$buffer = ob_get_clean();
file_put_contents($a, $buffer);
}
You can pass the array directly to call the function like the following :
generator( ["val_1", "val_2", "val_3"] );
Just use func_get_args(); inside your function to return an array of all arguments passed in.
You can also use func_get_arg($arg_num) to return a specific argument, or func_num_args to return the number of arguments.
All PHP functions allow any number of parameters, they just won't be callable by name, the only way is with these 3 functions.
Note, you may use a variadic argument as the last in the parameter list like so:
function my_func($x,$y, ... $z){
//Now $z is an array of all arguments after the first two
}
In the process of good design, I would think carefully about when and where to use things such as this. For example I currently work on a project that probably has over 200K lines of code and for better of worse this is actually never used.
The most common way is to pass an array "struct" to the method:
$args = array();
$args['kitchen'] = 'sink';
$args['bath'] = 'room';
$args['cat'] = array('fur','tail');
$func->someFunction($args);
If you wanted to have more control over the data you could create a struct and access that within the class. Public functions act as handlers.
class SomeClass {
....
private $args
public function setArgs($arg1,$arg2,$arg3) {
$this->arg1 = $arg1;
...
}
public function getArgs() {
return $this->args;
}
More rarely you can have C++ like control where you use a class just as a struct:
class MyStruct {
public $foo;
public $bar;
private $secret;
private function getSecret() {
return $secret;
}
protect function setSecret($val) {
$secret = $val;
}
}
Already mentioned is '...' which I nearly never see but it's interesting, though how useful ? Does this help explain what is going on?
function someFunction(... $args)
Usually you will see a mix of things in methods which helps articulate the purpose of it.
private function someSmallFunc($list = array(), $val = '', $limit = 10)
This example is to illustrate the natural grouping of information, data is in a list, $val is used for something to control the method along with $limit say limits the number of query results. Hence, you should think in this way about your methods IMO.
Also if you notice default values are set ($limit = 10) to in case they aren't passed in. For example if you call someSmallFunc($data, $someVal) (opposed to say someSmallFunc($data, $someVal, 20) ) and not pass in $limit it will default to 10.

Find an Object in an array by comparing Object->value to all Object->values in array PHP

So my array contains objects like this:
$arr = array(
new Card('10', 'Spades'),
new Card('Jack', 'Diamonds'),
new Card('King', 'Spades')
);
Now I have a function:
function hasCard(Card $card) {
if (in_array($card, $arr)) return true;
return false;
}
Now above does not really work since I need to compare ($card->rank == $arr[$x]->rank) for each element in that $arr without looping. Is there a function on PHP that allows you to modify the compareTo method of array_search?
I'd suggest using array_filter here. (Note: make sure $arr is available inside the hasCard function)
function hasCard(Card $card) {
$inArray = array_filter($arr, function($x) use($card){
return $x->rank === $card->rank;
});
return count($inArray) > 0;
}
DEMO: https://eval.in/166460
The $arr variable is not going to be available within the function hasCard, unless you pass it as a parameter.
To answer your question, look at array_filter. This will get you a callable function in which you can pass the $arr and $card as parameters.

Emulate ruby's inject() behavior in PHP

From this question here, I was writing an enum wrapper to have some methods that can be used with lambdas to somewhat emulate ruby's usage of blocks in enums.
class enum {
public $arr;
function __construct($array) {
$this->arr = $array;
}
function each($lambda) {
array_walk($this->arr, $lambda);
}
function find_all($lambda) {
return array_filter($this->arr, $lambda);
}
function inject($lambda, $initial=null) {
if ($initial == null) {
$first = array_shift($this->arr);
$result = array_reduce($this->arr, $lambda, $first);
array_unshift($this->arr, $first);
return $result;
} else {
return array_reduce($this->arr, $lambda, $initial);
}
}
}
$list = new enum(array(-1, 3, 4, 5, -7));
$list->each(function($a) { print $a . "\n";});
// in PHP you can also assign a closure to a variable
$pos = function($a) { return ($a < 0) ? false : true;};
$positives = $list->find_all($pos);
Now, how could I implement inject() as elegantly as possible?
EDIT: method implemented as seen above. Usage examples:
// inject() examples
$list = new enum(range(5, 10));
$sum = $list->inject(function($sum, $n) { return $sum+$n; });
$product = $list->inject(function($acc, $n) { return $acc*$n; }, 1);
$list = new enum(array('cat', 'sheep', 'bear'));
$longest = $list->inject(function($memo, $word) {
return (strlen($memo) > strlen($word)) ? $memo : $word; }
);
I'm not familiar with Ruby, but from the description, it seems similar to array_reduce.
mixed array_reduce ( array $input , callback $function [, mixed $initial = NULL ] )
array_reduce() applies iteratively the function function to the elements of the array input, so as to reduce the array to a single value.
In addition to "reduce", this operation is also sometimes called "fold"; in Mathematica:
Fold[f, init, {a, b, c, d}] == f[f[f[f[init, a], b], c], d]
The second form uses the first element of the collection as a the initial value (and skips that element while iterating).
This second form can be implemented this way:
//$arr is the initial array
$first = array_shift($arr);
$result = array_reduce($arr, $callback, $first);
Response to Mladen
The array functions in PHP cannot be used that way because they can only work with arrays, not arbitrary objects.
There are a few options here:
You could convert the object into an array prior to passing it to array_reduce. In practice, this doesn't have much value because the conversion consists of creating an array with the object properties as elements. This behavior can only be changed internally (writing a native extension).
You could have all your objects implement an interface with a method toArray that would have to be called priorly to passing it to array_reduce. Not a great idea, either.
You could implement a version of array_reduce that works with any Traversable object. This would be easy to do, but you couldn't put a Traversable type hint in the function declaration since arrays are not objects. With such a hint, every array would have to be encapsulated in an ArrayIterator object prior to the function call.

Sort Object in PHP [duplicate]

This question already has answers here:
Sort array of objects by one property
(23 answers)
Closed 3 months ago.
What is an elegant way to sort objects in PHP? I would love to accomplish something similar to this.
$sortedObjectArary = sort($unsortedObjectArray, $Object->weight);
Basically specify the array I want to sort as well as the field I want to sort on. I looked into multidimensional array sorting and there might be something useful there, but I don't see anything elegant or obvious.
Almost verbatim from the manual:
function compare_weights($a, $b) {
if($a->weight == $b->weight) {
return 0;
}
return ($a->weight < $b->weight) ? -1 : 1;
}
usort($unsortedObjectArray, 'compare_weights');
If you want objects to be able to sort themselves, see example 3 here: http://php.net/usort
For php >= 5.3
function osort(&$array, $prop)
{
usort($array, function($a, $b) use ($prop) {
return $a->$prop > $b->$prop ? 1 : -1;
});
}
Note that this uses Anonymous functions / closures. Might find reviewing the php docs on that useful.
You can even build the sorting behavior into the class you're sorting, if you want that level of control
class thingy
{
public $prop1;
public $prop2;
static $sortKey;
public function __construct( $prop1, $prop2 )
{
$this->prop1 = $prop1;
$this->prop2 = $prop2;
}
public static function sorter( $a, $b )
{
return strcasecmp( $a->{self::$sortKey}, $b->{self::$sortKey} );
}
public static function sortByProp( &$collection, $prop )
{
self::$sortKey = $prop;
usort( $collection, array( __CLASS__, 'sorter' ) );
}
}
$thingies = array(
new thingy( 'red', 'blue' )
, new thingy( 'apple', 'orange' )
, new thingy( 'black', 'white' )
, new thingy( 'democrat', 'republican' )
);
print_r( $thingies );
thingy::sortByProp( $thingies, 'prop1' );
print_r( $thingies );
thingy::sortByProp( $thingies, 'prop2' );
print_r( $thingies );
For that compare function, you can just do:
function cmp( $a, $b )
{
return $b->weight - $a->weight;
}
The usort function (http://uk.php.net/manual/en/function.usort.php) is your friend. Something like...
function objectWeightSort($lhs, $rhs)
{
if ($lhs->weight == $rhs->weight)
return 0;
if ($lhs->weight > $rhs->weight)
return 1;
return -1;
}
usort($unsortedObjectArray, "objectWeightSort");
Note that any array keys will be lost.
You could use the usort() function and make your own comparison function.
$sortedObjectArray = usort($unsortedObjectArray, 'sort_by_weight');
function sort_by_weight($a, $b) {
if ($a->weight == $b->weight) {
return 0;
} else if ($a->weight < $b->weight) {
return -1;
} else {
return 1;
}
}
Depending on the problem you are trying to solve, you may also find the SPL interfaces useful. For example, implementing the ArrayAccess interface would allow you to access your class like an array. Also, implementing the SeekableIterator interface would let you loop through your object just like an array. This way you could sort your object just as if it were a simple array, having full control over the values it returns for a given key.
For more details:
Zend Article
PHPriot Article
PHP Manual
function PHPArrayObjectSorter($array,$sortBy,$direction='asc')
{
$sortedArray=array();
$tmpArray=array();
foreach($this->$array as $obj)
{
$tmpArray[]=$obj->$sortBy;
}
if($direction=='asc'){
asort($tmpArray);
}else{
arsort($tmpArray);
}
foreach($tmpArray as $k=>$tmp){
$sortedArray[]=$array[$k];
}
return $sortedArray;
}
e.g =>
$myAscSortedArrayObject=PHPArrayObjectSorter($unsortedarray,$totalMarks,'asc');
$myDescSortedArrayObject=PHPArrayObjectSorter($unsortedarray,$totalMarks,'desc');
You can have almost the same code as you posted with sorted function from Nspl:
use function \nspl\a\sorted;
use function \nspl\op\propertyGetter;
use function \nspl\op\methodCaller;
// Sort by property value
$sortedByWeight = sorted($objects, propertyGetter('weight'));
// Or sort by result of method call
$sortedByWeight = sorted($objects, methodCaller('getWeight'));
Update from 2022 - sort array of objects:
usort($array, fn(object $a, object $b): int => $a->weight <=> $b->weight);
Full example:
$array = [
(object) ['weight' => 5],
(object) ['weight' => 10],
(object) ['weight' => 1],
];
usort($array, fn(object $a, object $b): int => $a->weight <=> $b->weight);
// Now, $array is sorted by objects' weight.
// display example :
echo json_encode($array);
Output:
[{"weight":1},{"weight":5},{"weight":10}]
Documentation links:
usort
spaceship operator (PHP 7.0)
scalar type declaration (PHP 7.0)
return type declaration (PHP 7.0)
arrow function (PHP 7.4)
If you want to explore the full (terrifying) extent of lambda style functions in PHP, see:
http://docs.php.net/manual/en/function.create-function.php

Categories