<?php
namespace Functional;
use ArrayObject;
use Transversable;
/**
Extra Higher Order Functions
*/
class HOF
{
/**
A typical flatmap implementation
- Callback transformation,
- Flatten 1 level (but only if it's traversable i.e. jagged array)
- Removed any nulls
*/
static function flatMap($collection, callable $callback)
{
InvalidArgumentException::assertCollection($collection, __FUNCTION__, 1);
$flattened = [];
foreach ($collection as $index => $element)
{
$result = $callback($element, $index, $collection);
if (is_array($result) || $result instanceof Traversable)
{
foreach ($result as $item)
{
$flattened[] = $item;
}
}
elseif ($result !== null)
{
$flattened[] = $result;
}
}
return $flattened;
}
/**
Partial Application
PHP can substitute an array for parameters
http://www.php.net/manual/en/function.call-user-func-array.php
*/
static public function partialApplication(/* $function, $arguments... */)
{
// Retrieve passed in arguments
$arguments = func_get_args();
// Retrieve function (1st element)
$function = array_shift($arguments);
// Return a new function
return function() use ($function, $arguments)
{
return call_user_func_array($function, array_merge($arguments, func_get_args()));
};
}
}
/**
Enumerator Sort Types
*/
abstract class SortType
{
const Ascending = 0;
const Descending = 1;
const Custom = 2;
}
/**
Array Helper for Functional Method Chaining
*/
class FArray extends ArrayObject
{
private function _arrayFunc($action, $argv)
{
return call_user_func_array($action,
array_merge(array($this->getArrayCopy()), $argv));
}
public static function init($array)
{
return new FArray($array);
}
public static function initSequence(...$array)
{
return new FArray($array);
}
/**
Sort altered to return a sorted copy; to enable method chaining
*/
private function _sort($type, $argv)
{
$copyOfArray = $this->getArrayCopy();
switch ($type)
{
case SortType::Ascending:
sort($copyOfArray);
break;
case SortType::Descending:
rsort($copyOfArray);
break;
default:
sort($copyOfArray, $argv[0]);
}
return new FArray($copyOfArray);
}
/**
Functional Method Chaining Implementation
Style Choice:
Method names have been shortened,
specifically `array_` prefix has been removed
*/
public function __call($func, $argv)
{
switch ($func)
{
case 'map':
return new FArray(call_user_func_array('array_map',
array_merge($argv, array($this->getArrayCopy()))));
case 'flatMap':
return new FArray(HOF::flatMap($this->getArrayCopy(), $argv[0]));
case 'sort':
return $this->_sort(SortType::Ascending, $argv);
case 'rsort':
return $this->_sort(SortType::Descending, $argv);
case 'reduce':
return $this->_arrayFunc('array_reduce', $argv);
case 'product':
return $this->_arrayFunc('array_product', $argv);
case 'sum':
return $this->_arrayFunc('array_sum', $argv);
case 'each':
case 'walk':
return array_walk($this, $argv[0]);
case 'print':
if ($argv[0] == true)
{
return print join("\n", $this->getArrayCopy()) . "\n";
}
return print "[" . join(", ", $this->getArrayCopy()) . "]\n";
case 'walkRecursive':
return array_walk_recursive($this, $argv[0]);
// return $this->_arrayFunc('array_walk_recursive', $argv);
case 'replace':
return $this->_arrayFunc('array_replace', $argv);
case 'reverse':
return new FArray($this->_arrayFunc('array_reverse', $argv));
case 'filter':
return new FArray($this->_arrayFunc('array_filter', $argv));
case 'keys':
return new FArray($this->_arrayFunc('array_keys', $argv));
default:
throw new BadMethodCallException(__CLASS__.'->'.$func);
}
}
public function __toString()
{
return '[' . join(", ", $this->getArrayCopy()) . ']';
}
}
/**
Custom Asserts
Used to validate that the passed in $collection is transversable
*/
class InvalidArgumentException extends \InvalidArgumentException
{
public static function assertCollection($collection, $callee, $paramPosition)
{
self::assertCollectionAlike($collection, 'Traversable', $callee, $paramPosition);
}
private static function customMessage($callee, $parameterPosition, $className)
{
return sprintf('%s() expects parameter %d to be array or instance of %s',
$callee,
$paramPosition,
$className);
}
private static function assertCollectionAlike($collection, $className, $callee, $paramPosition)
{
if (!is_array($collection) && !$collection instanceof $className) {
throw new static(self::customMessage($callee, $paramPosition, $className));
}
}
}
?>