mercredi 29 avril 2015

Sort Array of Objects

I want to share with you a code that i have written to sort an Array of objects with multi-criteria
Example is a good teacher :

<?php
class User {
    private $name;     private $age;     public function __construct($name, $age) {         $this->name = $name;         $this->age   = $age;     }         public function getName() {         return $this->name;     }          public function getAge() {         return $this->age;     } } ?>


<?php
    $users = array(new User('Denis' , 25),
new User('Anis'  , 29),
new User('Anis'  , 26),
new User('Nikita', 15),
new User('Zelda' , 34),
new User('Zelda' , 90));

// to sort your array based on name with ASC Order
// 1st : Create the search criteria
// the first parameter must be the getter, second : order by ASC or DSC, third the type of your data
$criteria_1 = new Criteria ('getName', Criteria::_SORT_ASC, Criteria::_SORT_STRING);
// create the sorter object. Note that the second parameter must be an array
$sortObject = new SortObjectArray($users, array($criteria_1));
var_dump ($sortObject->getSortedObjectArray());

// Using Multi-Criteria (like ORDER BY NAME, AGE in SQL)
$criteria_2 = new Criteria ('getAge', Criteria::_SORT_DSC, Criteria::_SORT_INTEGER); $sortObject = new SortObjectArray($users, array($criteria_1, $criteria_2)); var_dump ($sortObject->getSortedObjectArray());
Here the source code
class Criteria {
 const _SORT_INTEGER = 1;
 const _SORT_FLOAT   = 2;
 const _SORT_STRING  = 3;
 const _SORT_ASC = 1;
 const _SORT_DSC = 2;
 
 private $sortType;
 private $sortCriteria;
 private $sortOrder;
 private $strict;
 
 public function __construct($sortCriteria, $sortOrder, $sortType, $strict = FALSE) {
  $this->setSortCriteria($sortCriteria);
  $this->setSortOrder($sortOrder);
  $this->setSortType($sortType);
  $this->setStrict($strict);
 }
 public function setStrict($strict) {
  if(!is_bool($strict)) {
   throw new Exception("parameter must be a boolean");
  }
  $this->strict = $strict;
 }
 public function isStrict() {
  return $this->strict;
 }
 
 public function getSortType() {
  return $this->sortType;
 }
 /**
 * the primitive type variables that will be used for sorting
 * @param1 sortType integer, must be on of (_SORT_INTEGER|_SORT_FLOAT|_SORT_STRING)
 * @throws Exception
 */
 public function setSortType($sortType) {
  if(is_null($sortType)) {
   throw new Exception("MANDATORY_ATTRIBUTE");
  }
  $sortType = (int)$sortType;
  if(!is_integer($sortType) ||
    ($sortType != self::_SORT_INTEGER && $sortType != self::_SORT_FLOAT && $sortType != self::_SORT_STRING)) {
   throw new Exception("sort must be in integer. use theses defines : (_SORT_INTEGER|_SORT_FLOAT|_SORT_STRING)");
  }             
  $this->sortType = $sortType;
 }
 /**
 * @param1 string, must be the accessor of the object, example : getProject::getCmsserver::getId, or getProject::getFileserver::getName. Note : use the defined separator CRITERIA_SEPERATOR 
  */
 public function setSortCriteria($sortCriteria) {
  if(is_null($sortCriteria) || strlen($sortCriteria) == 0) {
   throw new Exception("MANDATORY_ATTRIBUTE");
  }
  $this->sortCriteria = $sortCriteria;
 }
 
 public function getSortCriteria() {
  return $this->sortCriteria;
 }
 
 /**
 * @param1 the order of sort, must be one of (_SORT_ASC|_SORT_DSC)
  */
 public function setSortOrder($sortOrder) {
  if(is_null($sortOrder)) {
   throw new Exception("MANDATORY_ATTRIBUTE");
  }
  $sortOrder = (int)$sortOrder;
  if(!is_integer($sortOrder) ||
      ($sortOrder != self::_SORT_ASC && $sortOrder != self::_SORT_DSC)) {
   throw new Exception("sort must be in integer. use theses defines : (_SORT_ASC|_SORT_DSC)");
  }
  $this->sortOrder = $sortOrder;
 }
 public function getSortOrder() {
  return $this->sortOrder;
 }
}

class SortObjectArray {
 const CRITERIA_SEPERATOR = "::";
 
 private $criteria;
 private $objectArray;
 private $sortedObjectArray;
 
 private function setObjectArray($objectArray) {
  if(is_null($objectArray) || !is_array($objectArray)) {
   throw new Exception("MANDATORY_ATTRIBUTE");
  }
  if(count($objectArray) < 1) {
   throw new Exception('table to be sort must at least has 2 elements, current size = ' . count($objectArray));
  }
  $this->objectArray = $objectArray;
 }
 private function getObjectArray() {
  return $this->objectArray;
 }
 
 private function setSortedObjectArray($sortedObjectArray) {
  $this->sortedObjectArray = $sortedObjectArray;
 }
 public function getSortedObjectArray() {
  return $this->sortedObjectArray;
 }
 private function setCriteria($criteria) {
  $this->criteria = $criteria;
 }
 private function getCriteria() {
  return $this->criteria;
 }
 /**
 * @param1 array of Objects to be sorted
 * @param2 array of Criteria's Object
 */
 public function __construct($objectArray, $criteria) {
  $this->setObjectArray($objectArray);
  $this->setCriteria($criteria);
  $this->doSorting();
 }
 
 /**
 * this function will create an interval that will be used by function to cut out the original array, that will be sorted independently
 * @param1 array of Objects
 * @param2 Criteria Object
 * @return array of integers, will be the computed interval
 */
 public function computeInterval($objectArray, $criteria) {
  if(!is_array($objectArray) || count($objectArray) == 0) {
   throw new Exception("can not compute anything");
  }
  if(count($objectArray) == 1) {
   return array(0, 0);
  }
  $interval    = array();
  $predecessor = NULL;
  $position    = 0;
  foreach($objectArray as $object) {
   if(is_null($predecessor)) {
    $predecessor = $this->getValue($object, $criteria->getSortCriteria());
    continue;
   }
   if($this->compare($predecessor,
      $this->getValue($object, $criteria->getSortCriteria()),
      $criteria) != 0) {
    $interval[] = $position;
    $predecessor = $this->getValue($object, $criteria->getSortCriteria());
   }
   $position++;
  }
  //var_dump(array_merge(array(0), $interval, array(count($objectArray)-1)));
  return array_merge(array(-1), $interval, array(count($objectArray)-1));
 }
 
 /**
 * bubble sort
 * @param1 array of Objects
 * @param2 Criteria Object
 * @return array of Objects, sorted
  */
 public function sortArray($objectArray, $criteria) {
  for($i=0 ; $i<count($objectArray)-1 ; $i++) {
   for($j=($i+1) ; $j<count($objectArray) ; $j++) {
    $r = $this->compare($this->getValue($objectArray[$i], $criteria->getSortCriteria()),
    $this->getValue($objectArray[$j], $criteria->getSortCriteria()),
    $criteria);        
    if($r < 0 && $criteria->getSortOrder() == Criteria::_SORT_DSC) {
     $temp = $objectArray[$i];
     $objectArray[$i] = $objectArray[$j];
     $objectArray[$j] = $temp;
     continue;
    }
    if($r > 0 && $criteria->getSortOrder()  == Criteria::_SORT_ASC) {
     $temp = $objectArray[$i];
     $objectArray[$i] = $objectArray[$j];
     $objectArray[$j] = $temp;
     continue;
    }
   }
  }
  return $objectArray ;
 }
 
 public function doSorting() {
  $tempObjectArray = array();     
  $objectArray     = $this->getObjectArray();
  $criteriaArray   = $this->getCriteria();

  for($i=0 ; $i<count($criteriaArray) ; $i++) {
   if($i==0) {           
    $objectArray = $this->sortArray($objectArray, $criteriaArray[$i]);
    continue;
   }
   $tempObjectArray = array();     
   $interval = $this->computeInterval($objectArray, $criteriaArray[$i-1]);
   $subArrays = $this->extractArraysByInterval($objectArray, $interval);
   foreach($subArrays as $arrayToSort) {
    $arrayToSort = $this->sortArray($arrayToSort, $criteriaArray[$i]);
    $tempObjectArray = array_merge($tempObjectArray, $arrayToSort);
   }
   $objectArray = $tempObjectArray;
  }
  $this->setSortedObjectArray($objectArray);
 }
 
 public function extractArraysByInterval($objectArray, $interval) {
  $extractedArrays = array();
  for($i=0 ; $i<count($interval)-1 ; $i++) {
   $temp = array();
   for($j=$interval[$i]+1 ; $j<=$interval[$i+1] ; $j++) {
    $temp[] = $objectArray[$j];
   }
   $extractedArrays[] = $temp;
  }
  return $extractedArrays;
 }
 
 /**
 * @return integer == 0 if varOne & varTwo are equals, < 0 if varOne is less than varTwo, > 0 if varOne is greater than varTwo
 */
 public function compare($varOne, $varTwo, $criteria) {
  switch($criteria->getSortType()) {           
   case Criteria::_SORT_INTEGER : {
    $varOne = (int)$varOne;
    $varTwo = (int)$varTwo;
    return $varOne - $varTwo;
   }
   case Criteria::_SORT_FLOAT : {
    $varOne = (float)$varOne;
    $varTwo = (float)$varTwo;
    return $varOne - $varTwo;
   }
   default : {
    $varOne = (string)$varOne;
    $varTwo = (string)$varTwo;
    if($criteria->isStrict() === FALSE) {
        return strcmp($varOne, $varTwo);
    }
    return strcasecmp($varOne, $varTwo);
   }
  }
 }
 public function getValue($object, $sortCriteria) {
  $value = $object;            
  $sortCriteria = explode(self::CRITERIA_SEPERATOR, $sortCriteria);
  foreach($sortCriteria as $criteria) {
   $value = $value->{$criteria}();
  }
  return $value;
 }
}