Say I have a group of people, each person having a name, sex, and age property, coded like this:
public class Person
{
private $name, $sex, $age;
public function Person ($name, $sex, $age)
{
$this->name = $name;
$this->sex = $sex;
$this->age = $age;
}
public function getName();
public function getSex();
public function getAge();
}
public class People
{
private $people = array();
public function addPerson ($name, $sex, $age)
{
$this->people[] = new Person($name, $sex, $age);
}
}
How could I implement a method sortPeople()
which sorted the $people
array in ascending order of the peoples' names to the People
class?
Here is a working code with a static method. It also uses the fact that the static method can access private ivars :) It also uses PHP awesome reflexivity <3.
The good point about that code is that Person
is the class to provide the sort method, which is better on an OOP point of view. Only the class Person
should be the one to know how to sort other Person
. Neither People
or another indepent function should.
Note: not using is_callable()
, as it only verifies if the parameter can be called as a function, but does not check if it's actually callable with the current visibility (public, private, protected)
class Person
{
private $name, $sex, $age;
public function Person($name, $sex, $age)
{
$this->name = $name;
$this->sex = $sex;
$this->age = $age;
}
public static function sortByName(Person $p1, Person $p2)
{
return strcmp($p1->name, $p2->name);
}
public static function sortByAge(Person $p1, Person $p2)
{
return ($p1->age - $p2->age);
}
}
class People
{
private $people = array();
public function addPerson($name, $sex, $age)
{
$this->people[] = new Person($name, $sex, $age);
}
public function display()
{
print_r($this->people);
}
public function sort($attribute = 'name')
{
$sortFct = 'sortBy' . ucfirst(strtolower($attribute));
if (!in_array($sortFct, get_class_methods('Person')))
{
throw new Exception('People->sort(): Can\'t sort by ' . $attribute);
}
usort($this->people, 'Person::' . $sortFct);
}
}
$people = new People;
$people->addPerson('Steve', 'M', 31);
$people->addPerson('John', 'M', 24);
$people->addPerson('Jane', 'F', 26);
$people->addPerson('Sally', 'F', 21);
$people->display();
$people->sort();
$people->display();
$people->sort('age');
$people->display();
Take a look at usort. It allows you to specify your own comparison function. Every time two objects need to be compared, it will call that comparison function you specify to see which one is greater than the other (or if they are equal). In your comparison function you can do whatever you need to with the fields in the two Person
objects to compare them.
For doing callbacks with class methods (as in your example), look at passing callbacks. For example, you could do something like this:
class People {
// your previously defined stuff here...
public function sort() {
usort($this->people, array($this, 'comparePeople'));
}
public function comparePeople(Person $p1, Person $p2) {
return strcmp($p1->getName(), $p2->getName());
}
}
You would also of course need to add getName()
to your Person
class.
For a static approach, it might look something like this:
function sortPeople($people) {
usort($people, array('People', 'comparePeople'));
}
class People {
// your previously defined stuff here...
public static function comparePeople(Person $p1, Person $p2) {
return strcmp($p1->getName(), $p2->getName());
}
}
As you can see, it looks very similar. I would not recommend you use the static approach. It's messier and violates the single responsibility principle.
Especially if getName() is time consuming operation you may be better off with using decorate-sort-undecorate pattern.
<?php
class Person {
private $name;
function getName() {
return $this->name;
}
function __construct($name) {
$this->name = $name;
}
}
$people = array(
new Person('Jim'),
new Person('Tom'),
new Person('Tim'),
new Person('Adam')
);
// actual sorting below
$people = array_map(create_function('$a', 'return array($a->getName(), $a);'), $people); // transform array of objects into array of arrays consisted of sort key and object
sort($people); // sort array of arrays
$people = array_map('end', $people); // take only last element from each array
print_r($people);
Instead of sorting array of objects you sort array of arrays whose last element is object and first is the key by which you want to sort. After sorting you keep only the object.
You can use just sort
for sorting array of arrays because PHP compares two same length arrays by comparing its elements one by one.
You may use more then one sort key, for example sort by surname and if surnames are identical take first name into account. You can do this by decorating with multiple keys with order of importance like so:
$people = array_map(create_function('$a', 'return array($a->getSurname(), $a->getName(), $a);'), $people);
This way might be faster that using usort
because it calls getName() only n-times for sorting array of length n. Comparisons during sort are done using built in comparator so should be fast. In usort
method custom comparator is called multiple times (more than n times) during sort and it may slow down things.