2

What I want to do is create a Comparable class, similar to IComparable in .NET, such that you can instantiate it like so:

$cmp = new MultiArrayCompare(2);

And then you can sort an array via:

usort($myArray, $cmp);

And it will sort an array of arrays on the 2nd index. To do that, I imagine usort would try to call $cmp like a function, so I'd have to override that behaviour somehow. It doesn't look like __call does what I want (thought for a minute it was like Python's __call__).

If this isn't possible... is there another nice way to create a general solution to this problem? Where you can create a user-defined sorting function but give pass it some value ("2" in this case)?


Using __invoke I was able to create these classes:

abstract class Comparable {
    abstract function Compare($a, $b);

    function __invoke($a, $b) {
        return $this->Compare($a, $b);
    }
}

class ArrayCompare extends Comparable {
    private $key;

    function __construct($key) {
        $this->key = $key;  
    }

    function Compare($a, $b) {
        if($a[$this->key] == $b[$this->key]) return 0;
        return $a[$this->key] < $b[$this->key] ? -1 : 1;
    }
}

class ArrayCaseCompare extends Comparable {
    private $key;

    function __construct($key) {
        $this->key = $key;  
    }

    function Compare($a, $b) {
        return strcasecmp($a[$this->key], $b[$this->key]);
    }
}

Which I can use to sort an array of arrays:

$arr = array(
    array(1,2,3),
    array(2,3,4),
    array(3,2,4),
)

usort($arr,new ArrayCompare(1));
mpen
  • 272,448
  • 266
  • 850
  • 1,236

3 Answers3

5

You are looking for __invoke:

The __invoke() method is called when a script tries to call an object as a function.

Example:

class SortAscending
{
    public function __invoke($a, $b)
    {
       return $a - $b;
    }
}

$numbers = array(4,7,2,3,9,1);
usort($numbers, new SortAscending);
print_r( $numbers );

Output (demo):

Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 7 [5] => 9 ) 

The slimmer alternative is using a Closure. Your code would simply be:

$arr = array(
    array(1,2,3),
    array(2,3,4),
    array(3,2,4),
);

$key = 1;
usort($arr, function($a, $b) use ($key) {
    return $a[$key] - $b[$key];
});

or - reusable and configurable (demo):

$ArrayCompare = function ($index) {
    return function($a, $b) use ($index) {
        return $a[$index] - $b[$index];
    };
};
usort($arr, $ArrayCompare(1));

Apart from that, you can use basically any method you want and specify the method in the callback:

usort($arr, array(new ArrayCompare(1), 'Compare'));

The above would achieve the same without magic. See the chapter on callbacks for additional options.

Gordon
  • 312,688
  • 75
  • 539
  • 559
  • Beautiful! Didn't know/forgot that existed. – mpen Mar 06 '12 at 20:51
  • Closures are nice, but they're not re-usable (and they're 5.3+ only). I foresee my `ArrayCompare` classes being used more than once :) – mpen Mar 06 '12 at 21:10
  • @Mark `__invoke` was only added with 5.3 as well. And you can assign a closure to a variable, e.g. `$emptyFn = function() {}` and then you pass that around, so it's reusable :) – Gordon Mar 06 '12 at 21:13
  • Touche. Still not *as* re-usable though. That function would be permanently bound to that variable (like the class instance), so you can't create new ones using the same method body as easily. – mpen Mar 06 '12 at 21:18
  • Oh god.. well,... then you're still using a global variable if you want to use that...monstrosity...everywhere ;) – mpen Mar 06 '12 at 21:34
  • @Mark i find it quite elegant. You just need create the closure once in your bootstrap and then pass it to the required codes. its not much different from what you'd do when you [use dependency injection in order to avoid tight coupling to the `new` keyword.](http://stackoverflow.com/questions/6094744/dependecy-hell-how-does-one-pass-dependencies-to-deeply-nested-objects/6095002#6095002) – Gordon Mar 06 '12 at 21:41
  • You find it more elegant than using a class? My classes are essentially utility classes, you can include them where you need them, and use them without worrying about scope. There's no global variables, there's no passing of variables, and no scope issues. They're more or less the same way you'd do it in .NET. I find classes quite appropriate for this. The function-in-a-function approach might be useful for other problems, but I don't feel that it's warranted here. – mpen Mar 06 '12 at 22:42
  • @Mark if "include where needed" implies `new`ing them then inside a collaboraor graph then it's not my cup of tea. I try to separate creator and collaborators graphs. – Gordon Mar 06 '12 at 22:46
  • I'm not sure what a "collaborator graph" is, but you would only `new` it where you need. *Usually* that would be in the call to `usort` itself, but maybe a little further up if you want to use the same class multiple times. You certainly wouldn't `new` it inside the include itself. – mpen Mar 06 '12 at 23:22
4

Maybe you are looking for __invoke()?

Example from the manual:

class CallableClass
{
    public function __invoke($x)
    {
        var_dump($x);
    }
}
$obj = new CallableClass;
$obj(5);
var_dump(is_callable($obj));
bfavaretto
  • 71,580
  • 16
  • 111
  • 150
1

Why you don't create a Comparer class that provides a set of static compareXXX() methods?

To me this looks much cleaner with an object oriented approach in mind...

CodeZombie
  • 5,367
  • 3
  • 30
  • 37
  • What I'm asking *is* object oriented... and static methods won't work because I need access to an instance variable (2 in my example). – mpen Mar 06 '12 at 20:51