Skip to content

Instantly share code, notes, and snippets.

@gmazzap
Last active August 29, 2015 14:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gmazzap/48c5f80ad63bbb8d373b to your computer and use it in GitHub Desktop.
Save gmazzap/48c5f80ad63bbb8d373b to your computer and use it in GitHub Desktop.
Optimum: a class to filter arrays with callbacks.
<?php namespace GM;
use SplObjectStorage;
class Optimum
{
private $data;
private $raw;
private $map;
private $storage;
/**
* @param array $data Raw array to be transformed
* @param array $keys_map A map where keys are raw data keys, values transformed data keys
*/
public function __construct(array $data = array(), array $keys_map = array())
{
$this->data = $data;
$this->raw = $data;
$this->map = $keys_map;
$this->storage = new SplObjectStorage();
}
/**
* Returns data how it was before transformation
*
* @return array
*/
public function raw()
{
return $this->raw;
}
/**
* Returns data after transformation (if it happen)
*
* @return array
*/
public function data()
{
return $this->data;
}
/**
* Return all or a subset of transformed data. Subset may be set with
* - an array of keys
* - a string with a key
* - passing a number of elements in $what param
* - using $what and $len as $offset and $length params of array_slice
*
* @param mixed $what
* @param int $len
* @return array
* @see http://php.net/manual/en/function.array-slice.php
*/
public function take($what = true, $len = null)
{
if (is_int($what)) {
$slice = is_int($len) ? [$what, $len] : [ 0, $what];
return array_slice($this->data, $slice[0], $slice[1]);
} elseif (is_array($what) || is_string($what)) {
return array_flip(array_intersect(array_flip($this->data), (array) $what));
}
return ! empty($what) ? $this->data : array();
}
/**
* Add a callback to the transformer.
* First argument callback will reveice is current array item value.
* Second is the current array item key.
* Additional arguments can be passed as well in a callback-specific manner using $args param.
* Is possible modify args usinbg $slice param: a 2 item array with args for array_slice.
*
* @param \GM\callable $callback callable to add
* @param array $args additional arguments for the callable
* @param array $slice array slice arguments to transform args
* @return \GM\Optimum
*/
public function with(callable $callback, array $args = [], array $slice = [])
{
$closure = function () use ($callback) {
return call_user_func_array($callback, func_get_args());
};
$this->storage[$closure] = ['args' => $args, 'slice' => $slice];
return $this;
}
/**
* Add a callback to the transformer that run only on items with specifick keys.
* First argument callback will reveice is current array item value.
* Second is the current array item key.
* Additional arguments can be passed as well in a callback-specific manner using $args param.
* Is possible modify args usinbg $slice param: a 2 item array with args for array_slice.
*
* @param array $keys a set of key to run callback on related values
* @param \GM\callable $callback callable to add
* @param array $args additional arguments for the callable
* @param array $slice array slice arguments to transform args
* @return \GM\Optimum
*/
public function withFor(array $keys, callable $callback, array $args = [], array $slice = [])
{
$closure = function () use ($callback) {
return call_user_func_array($callback, func_get_args());
};
$this->storage[$closure] = ['args' => $args, 'slice' => $slice, 'keys' => $keys];
return $this;
}
/**
* Call recursevely all the registered callbacks to all items of original array.
*
* @return \GM\Optimum
*/
public function transform()
{
array_walk($this->data, [$this, 'run']);
$this->data = array_combine(
array_map([$this, 'map'], array_keys($this->data)), array_values($this->data)
);
return $this;
}
private function run(&$item, $key)
{
$this->storage->rewind();
while ($this->storage->valid()) {
$i = $this->storage->getInfo();
if (! isset($i['keys']) || in_array($key, $i['keys'], true)) {
$all_args = array_merge([$item, $key], $i['args']);
$args = ! empty($i['slice']) ? $this->args($all_args, $i['slice']) : $all_args;
$item = call_user_func_array($this->storage->current(), $args);
}
$this->storage->next();
}
}
private function map($key)
{
return isset($this->map[$key]) && is_string($this->map[$key]) ? $this->map[$key] : $key;
}
private function args(array $args, array $slice)
{
$i = array_shift($slice);
return empty($slice) ? array_slice($args, $i) : array_slice($args, $i, array_shift($slice));
}
}
@gmazzap
Copy link
Author

gmazzap commented Dec 16, 2014

Assuming this code:

$raw_data = [
     'foo' => 'foo',
     'bar' => 'bar',
     'baz' => 'baz'
];

$key_map = [
     'foo' => 'my_foo'
];

$transformer = new Optimum($raw_data, $key_map);

// every callback can have specific additional arguments
$transformer->with( function($item, $key, $append) {
    return $key.': ' . $item. ' '.$append;
  }, [' - append me'])
  // PHP functions just work, is possible to set array slice for args if needed
  ->with('strtoupper', [], [0, 1])
  // add callbacks for specific keys is easy
  ->withFor(['foo'], function($item) {
    return $item.'!!!';
  })
  // run callbacks and return transformer
  ->transform();

print_r( $transformer->take(['my_foo', 'bar']) );

print_r( $transformer->take(1) );

print_r( $transformer->take(-2, 1) );

print_r( $transformer->take('baz') );

Will output:

Array
(
    [my_foo] => FOO: FOO  - APPEND ME!!!
    [bar] => BAR: BAR  - APPEND ME
)

Array
(
    [my_foo] => FOO: FOO  - APPEND ME!!!
)

Array
(
    [bar] => BAR: BAR  - APPEND ME
)

Array
(
    [baz] => BAZ: BAZ  - APPEND ME
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment