/** * Sorts the elements in an array by comparing return values from a specified method being called on each element. * * For example, if you had an array of UserClass objects and UserClass had a public method named `numPosts` that * would return an integer value, you could sort the users by the number of posts they've made by calling this * method with "numPosts" as the value of `$onMethodName` parameter. * * If you want to pass any custom arguments to the method that the array needs to be sorted on, just add them after * the comparator parameter when calling this method and the custom arguments will be passed to that method in the * same order. * * The method on which the array needs to be sorted can be static and, in this case, every element for which that * method is called is passed to that method as the first argument, in front of any custom arguments if they are * used. * * You can use your own comparator for the comparison of return values for the elements in the array, but the * default comparators, such as `CComparator::ORDER_ASC` and `CComparator::ORDER_DESC`, have got you covered when * sorting by scalar values, such as `string`, `int`, `float`, and `bool` in the ascending or descending order * respectively. And the default comparators are smart enough to know how to compare objects of those classes that * conform to the IEqualityAndOrder interface (static or not), including CArray and CMap. See the * [CComparator](CComparator.html) class for more on this. * * @param array $array The array to be sorted. * @param string $onMethodName The name of the method on which the elements are to be sorted. * @param callable $comparator **OPTIONAL. Default is** `CComparator::ORDER_ASC`. The function or method to be * used for the comparison of any two return values. If this parameter is provided, the comparator should take two * parameters, which are the return values for the two elements being compared, and return `-1` if the first * element needs to go before the second element in the sorted array, `1` if the other way around, and `0` if the * two elements are considered equal based on the return values. * * @return void * * @link CComparator.html CComparator */ public static function sortOn($array, $onMethodName, $comparator = CComparator::ORDER_ASC) { assert('is_carray($array) && is_cstring($onMethodName) && is_callable($comparator)', vs(isset($this), get_defined_vars())); $array = splarray($array); static $s_methodArgsStartPos = 3; $methodArgs = func_num_args() == $s_methodArgsStartPos ? [] : array_slice(func_get_args(), $s_methodArgsStartPos); $useComparator = function ($element0, $element1) use($onMethodName, $comparator, $methodArgs) { $className; $typeIsNonScalar = phred_classify_duo($element0, $element1, $className); if (CDebug::isDebugModeOn()) { // Except for a few special cases, any two elements being compared must be objects of the same // class. assert('$typeIsNonScalar', vs(isset($this), get_defined_vars())); $className0; $className1; phred_classify($element0, $className0); phred_classify($element1, $className1); assert('CString::equals($className0, $className1)', vs(isset($this), get_defined_vars())); } $reflClass = new ReflectionClass($className); assert('$reflClass->hasMethod($onMethodName)', vs(isset($this), get_defined_vars())); $reflMethod = $reflClass->getMethod($onMethodName); assert('$reflMethod->isPublic()', vs(isset($this), get_defined_vars())); $methodInvokeRes0; $methodInvokeRes1; if (!$reflMethod->isStatic()) { if (empty($methodArgs)) { $methodInvokeRes0 = $reflMethod->invoke($element0); $methodInvokeRes1 = $reflMethod->invoke($element1); } else { $methodInvokeRes0 = $reflMethod->invokeArgs($element0, $methodArgs); $methodInvokeRes1 = $reflMethod->invokeArgs($element1, $methodArgs); } } else { if (empty($methodArgs)) { $methodInvokeRes0 = $reflMethod->invoke(null, $element0); $methodInvokeRes1 = $reflMethod->invoke(null, $element1); } else { $methodInvokeRes0 = $reflMethod->invokeArgs(null, $element0, $methodArgs); $methodInvokeRes1 = $reflMethod->invokeArgs(null, $element1, $methodArgs); } } return call_user_func($comparator, $methodInvokeRes0, $methodInvokeRes1); }; self::sort($array, $useComparator); }
/** * Determines the order in which two values should appear in a place where it matters, assuming the ascending * order. * * If the values are objects of your custom class, the class should conform to the * [IEqualityAndOrder](IEqualityAndOrder.html) interface. * * @param mixed $value0 The first value for comparison. * @param mixed $value1 The second value for comparison. * * @return int A negative value (typically `-1`) if the first value should go before the second value, a positive * value (typically `1`) if the other way around, and `0` if the two values are equal. * * @link IEqualityAndOrder.html IEqualityAndOrder */ public static function orderAsc($value0, $value1) { if (CDebug::isDebugModeOn()) { if (!(is_cstring($value0) && is_cstring($value1) || is_carray($value0) && is_carray($value1) || is_cmap($value0) && is_cmap($value1))) { // With the above exceptions, the two values should be both either scalars or objects of the same // class. assert('is_object($value0) == is_object($value1)', vs(isset($this), get_defined_vars())); assert('!is_object($value0) || CString::equals(get_class($value0), get_class($value1))', vs(isset($this), get_defined_vars())); } } $className; if (!phred_classify_duo($value0, $value1, $className)) { // Compare the values as scalars. assert('(is_scalar($value0) || is_null($value0)) && (is_scalar($value1) || is_null($value1))', vs(isset($this), get_defined_vars())); return $value0 === $value1 ? 0 : ($value0 < $value1 ? -1 : 1); } else { // Compare the values as objects that may conform to one of the comparison interfaces. $reflClass = new ReflectionClass($className); if ($reflClass->implementsInterface("IEqualityAndOrderStatic")) { $res = call_user_func([$className, "compare"], $value0, $value1); assert('is_int($res)', vs(isset($this), get_defined_vars())); return $res; } if ($reflClass->implementsInterface("IEqualityAndOrder")) { $res = call_user_func([$value0, "compare"], $value1); assert('is_int($res)', vs(isset($this), get_defined_vars())); return $res; } // The class of the objects being compared does not implement any applicable comparison interfaces. assert('false', vs(isset($this), get_defined_vars())); } }