How can I make my own custom class be sortable using sort() for example?
I've been scanning the web to find any method of making a class Comparable like in Java but without much luck. I tried implementing __equals() but without luck. I've also tried with __toString(). My class looks like this:
class Genre {
private $genre;
private $count;
...
}
I want to sort them by count which is an Integer, in descending order... ($genre is a string)
You can create a custom sort method and use the http://www.php.net/manual/en/function.usort.php function to call it.
Example:
$Collection = array(..); // An array of Genre objects
// Either you must make count a public variable, or create
// an accessor function to access it
function CollectionSort($a, $b)
{
if ($a->count == $b->count)
{
return 0;
}
return ($a->count < $b->count) ? -1 : 1;
}
usort($Collection, "CollectionSort");
If you'd like to make a more generic collection system you could try something like this
interface Sortable
{
public function GetSortField();
}
class Genre implements Sortable
{
private $genre;
private $count;
public function GetSortField()
{
return $count;
}
}
class Collection
{
private $Collection = array();
public function AddItem($Item)
{
$this->Collection[] = $Item;
}
public function GetItems()
{
return $this->Collection;
}
public function Sort()
{
usort($this->Collection, 'GenericCollectionSort');
}
}
function GenericCollectionSort($a, $b)
{
if ($a->GetSortField() == $b->GetSortField())
{
return 0;
}
return ($a->GetSortField() < $b->GetSortField()) ? -1 : 1;
}
$Collection = new Collection();
$Collection->AddItem(...); // Add as many Genre objects as you want
$Collection->Sort();
$SortedGenreArray = $Collection->GetItems();
maybe you can use the function "usort":
class Genre {
private $genre;
private $count;
...
public function __construct($g, $c)
{
$this->genre=g;
$this->count=c;
}
public static function compare($a, $b)
{
if ($a->count < $b->count) return -1;
else if($a->count == $b->count) return 0;
else return 1;
}
...
}
$genres= array(
new Genre (1, 5),
new Genre (2, 2),
new Genre (3, 7)
);
usort($genres, array("Genre", "compare"));
Regards Thomas
The simplest way to do this is to simply use usort on a method within the class that accepts as input an array of matching objects. This is example 3 in the documentation linked to above. However, this is somewhat clunky and ugly.
A better way is to create a new type of array class specific to the desired objects using ArrayAccess. This will enable you to use the custom array like you'd expect but then also be able to run arbitrary sorting methods on the array.
Under the hood, you'd likely still want to use usort, but you'd be hiding that fact behind a much nicer interface that might look like this:
$array = new GenreCollection();
$array[] = new Genre(1); // Genre's constructor is a factory that can load genres via an ID
$array[] = new Genre(2);
$array[] = new Genre(3);
$array[] = new Genre(4);
$array[] = new Genre(5);
// Example Sorts
$array->SortByName();
$array->SortByDateInvented();
$array->SortByID();
$array->SortBySubGenres(); // Arranges Genres into a hierarchy where 'Death Metal' comes with other metal after 'Heavy Metal' - Add in a nest-level value for making dropdowns and other nested lists.
__toString() works for me:
class Genre
{
private $genre;
private $count;
public function __construct( $genre, $count = 0 )
{
$this->genre = $genre;
$this->count = $count;
}
public function __toString()
{
return $this->count . ' ' . $this->genre;
}
}
$collection = array(
new Genre( 'alternative', 3 ),
new Genre( 'jazz', 2 ),
new Genre( 'hiphop', 1 ),
new Genre( 'heavy metal', 1 )
);
natsort( $collection );
foreach( $collection as $genre )
{
echo $genre . "\n";
}
Produces:
1 heavy metal
1 hiphop
2 jazz
3 alternative
Related
So I have a function
namespace MyApp\MyBundle\Classes;
class FieldSorter{
public function sortFieldsByIndex($fields){
$fields->uasort(function ($fa, $fb) {
if ($fa->getIndex() == $fb->getIndex()) {
return 0;
}
return ($fa->getIndex() < $fb->getIndex()) ? -1 : 1;
});
return $fields
}
}
which works and orders the fields when using an array object iterator. When I try to phpunit test it,
namespace MyApp\MyBundle\Classes;
use MyApp\MyBundle\Classes\Field;
class FieldSorterTest extends TestCase {
protected $object;
protected $field1;
protected $field2;
protected $fields = array();
public function testSortFieldsByIndex(){
$this->field1 = new Field();
$this->field1->setIndex(4);
$this->field2 = new Field();
$this->field2->setIndex(3);
$fields = new \ArrayObject(array($this->field1, $this->field2));
$this->object = new FieldSorter();
$fieldSorted = $this->object->sortFieldsByIndex($fields);
// pre test
echo $fieldSorted[0]->getIndex();
}
}
The echo produces a 4, not 3....
Is there something I am missing here?
echo $fieldSorted[0]->getIndex();
was the problem (thanks for the help in the comments). Instead, if I do
$count = 3;
foreach($fieldsSorted as $fieldSorted){
$this->assertEquals($count, $fieldSorted->getIndex());
$count++;
}
Then I get the tests passing, because the later index really does come later (e.g. 3 then 4)
I would like to use the list() statement in combination with an object.
$tuple = new Tuple();
// ..
list ($guestbook, $entry, $author) = $tuple;
This would work if $tuple was an array with three elements. But its an object.
Is there any way without using a method of Tuple (returning that kind of array) like implementing a fancy native interface I yet don't know?
You can implement the interface ArrayAccess to do so:
class Tuple implements ArrayAccess {
private $arr;
public function __construct($arr) {
$this->arr = $arr;
}
public function offsetExists($offset) {
return array_key_exists($offset, $this->arr);
}
public function offsetGet($offset) {
return $this->arr[$offset];
}
public function offsetSet($offset, $value) {
return $this->arr[$offset] = $value;
}
public function offsetUnset($offset) {
unset($this->arr[$offset]);
}
}
$tuple = new Tuple([1, 2, 3]);
list($am, $stram, $gram) = $tuple;
echo $am;
echo $stram;
echo $gram;
// outputs: 123
See this previous post:
Convert PHP object to associative array
I am assuming (I haven't tested it) you could then do:
$tuple = new Tuple();
list ($guestbook, $entry, $author) = (array) $tuple;
You can do this:
$tumple = new Tumple();
$properties = get_object_vars($tumple);// ["guestbook" => "Feel", "entry" => "Good", "author" => "Inc"];
I am building a PHP function to enqueue JavaScript files into a PHP array and then have another PHP function that will load all the JS files into a page and load them in the order based on a sort number that can be passed into the enqueue function. Similar to how WordPress loads JS and CSS files.
So my PHP function enqueue_js_script() might look like this below which takes in a key name for the JS file, a file path to the JS file, and a sort order number which is optional. It then would add the JS file to a PHP class property $this->_js_files[$script_key]...
public function enqueue_js_script($script_key, $file_source, $load_order = 0){
$this->_js_scripts[$script_key] = $file_source;
}
Then I will also have a PHP function load_js_scripts() which will print each script file path into the header of a webpages HTML.
This is where I want to take into consideration the $load_order passed into enqueue_js_script() to print the scripts into the HTML in the order based on these numbers.
How can I use this sort order number to sort my array of JS scripts?
UPDATE
It looks like I should store the sort number in an array like this instead...
$this->_js_files[$script_key] = array(
'file_source' => $file_source,
'sort' => $load_order
);
Using usort and a custom sorting function:
<?php
public function enqueue_js_script($script_key, $file_source, $load_order = 0){
$jsScript = new \stdClass;
$jsScript->load_order = $load_order;
$jsScript->script_key = $script_key;
$this->_js_scripts[$script_key] = $jsScript;
}
function sortJSFiles($a, $b)
{
if ($a->load_order == $b->load_order) {
return 0;
}
return ($a->load_order < $b->load_order) ? -1 : 1;
}
usort($this->_js_scripts, "sortJSFiles");
Having to pass your array key is not really good practice. The $array[] = $foo construction adds $foo as the new last item of $array.
Using usort.
<?php
class OrderStack {
private $contents = array();
public function add($order, $load) {
if (!is_int($order) || $order < 0) {
throw new InvalidArgumentException("$order must be a non-negative integer");
}
$this->contents[] = array($order, $load);
}
public function get_array() {
usort(
$this->contents,
'OrderStack::compare'
);
return array_map(
'OrderStack::get_load',
$this->contents
);
}
private static function get_load($stack_item) {
return $stack_item[1];
}
private static function compare($a, $b) {
return $a[0] - $b[0];
}
}
class YourClass {
private $_js_scripts;
public function __construct() {
$this->_js_scripts = new OrderStack();
}
public function enqueue_js_script($file_source, $load_order = 0) {
$this->_js_scripts->add($load_order, $file_source);
}
public function get_js_scripts() {
return $this->_js_scripts->get_array();
}
}
?>
The OrderStack class is reusable.
So I have this controller that passes an associative array called $pagedata to the view. Inside this array are 3 more associative arrays, and the view renders 3 select elements with the array data as options. I want to sort the 3 arrays but I don't want to write sort 3 times here or add order_by into the query methods, because there are dozens of similar pages and I don't want to write hundreds of sort method calls. I was told I could solve this in the constructor. I was wondering if there's an OOP solution that lets me automatically sort all child arrays inside $pagedata.
class Sku extends CI_Controller {
protected $pagedata = array();
public function __construct()
{
parent::__construct();
$this->load->model('mc');
}
public function inventory()
{
$this->pagedata['class_ids'] = $this->mc->get_class_ids();
$this->pagedata['retail_readys'] = $this->mc->get_all_retail_ready();
$this->pagedata['statuses'] = $this->mc->get_all_status();
}
}
Edit:
I'm exploring using an ArrayObject or wrapping $pagedata in an object and watch for changes.
ok this will be painfull for codeigniter but yes a kind of solution
public function __construct()
{
parent::__construct();
$this->load->model('mc');
$class_ids = $this->mc->get_class_ids();
$class_ids = $this->sortAscending($class_ids, $key);
$this->pagedata['class_ids'] = $class_ids;
$retail_readys = $this->mc->get_all_retail_ready();
$class_ids = $this->sortAscending($class_ids, $key);
$this->pagedata['class_ids'] = $class_ids;
$statuses = $this->mc->get_all_status();
$statuses = $this->sortAscending($class_ids, $key);
$this->pagedata['statuses'] = $statuses;
}
function sortAscending($accounts, $key)
{
$ascending = function($accountA, $accountB) use ($key) {
if ($accountA[$key] == $accountB[$key]) {
return 0;
}
return ($accountA[$key] < $accountB[$key]) ? -1 : 1;
};
usort($accounts, $ascending);
return $accounts;
}
public function inventory()
{
// already get values
//$this->pagedata['class_ids'] = $this->mc->get_class_ids();
//$this->pagedata['retail_readys'] = $this->mc->get_all_retail_ready();
//$this->pagedata['statuses'] = $this->mc->get_all_status();
$this->load->view('index',$this->pagedata);
}
public function another_function()
{
// already get values
//$this->pagedata['class_ids'] = $this->mc->get_class_ids();
//$this->pagedata['retail_readys'] = $this->mc->get_all_retail_ready();
//$this->pagedata['statuses'] = $this->mc->get_all_status();
$this->load->view('another page',$this->pagedata);
}
Already I extended and implemented from SPL iterator.
But if I want to use it, I should use it on a foreach.
I tried to use it in a while like this:
$news = new testClass();
while( $row = $news )
echo $row["name"];
It will create an infinite loop !
But with foreach, it works fine!
Here is top of my class:
class testClass implements \Iterator
Where is the mistake ?
Fist, bravo on using the SPL classes for this type of 'standard' problem. Too often have I seen inexperienced/sloppy developers (or even good ones that simply don't think ahead) reinvent the wheel in these types of situations.
You're missing some very important details about the implementation of the iterator interface.
see PHP:Iterator - Manual for more information, and the reference implementation from below.
First, you need to implement the, rewind, current, key, next, and valid functions. the reference implementation looks like this:
class myIterator implements Iterator {
private $position = 0;
private $array = array(
"firstelement",
"secondelement",
"lastelement",
);
public function __construct() {
$this->position = 0;
}
function rewind() {
var_dump(__METHOD__);
$this->position = 0;
}
function current() {
var_dump(__METHOD__);
return $this->array[$this->position];
}
function key() {
var_dump(__METHOD__);
return $this->position;
}
function next() {
var_dump(__METHOD__);
++$this->position;
}
function valid() {
var_dump(__METHOD__);
return isset($this->array[$this->position]);
}
}
)
And the code for traversing that implementation looks like this:
$it = new myIterator;
foreach($it as $key => $value) {
var_dump($key, $value);
echo "\n";
}
foreach is language construct that iterates through all elements. while executes block of code until given condition is true. To make it work you have to use your own function that checks for valid key and returns current element.
Finally I created a simple example of this:
<?php
/**
* #author Soroush Khosravi
* #copyright 2013
*/
class _Iterator
{
private $array;
public function setArray(array $data)
{
$this->array = $data;
}
public function reader()
{
if (is_null($this->array))
return false;
$elem = array_shift($this->array);
if (count ($this->array) > 0)
return $elem;
return false;
}
}
Class child extends _Iterator
{
function execute()
{
$this->setArray(array(1,2,3,4,5,6));
return $this;
}
}
$obj = new child;
$obj = $obj->execute();
while($row = $obj->reader())
echo $row;
?>