A Functional Guide to Cat Herding with PHP Generators
A Functional Guide to
Cat Herding with PHP
Generators
The Filter/Map/Reduce Pattern for PHP
Generators
A Functional Guide to Cat Herding with PHP Generators
• Blog Post
http://markbakeruk.net/2016/01/19/a-functional-guide-to-cat-herding-with-
php-generators/
• Code Examples
https://github.com/MarkBaker/GeneratorFunctionExamples
A Functional Guide to Cat Herding with PHP Generators
namespace GpxReader;
class GpxHandler {
protected $gpxReader;
public function __construct($gpxFilename) {
$this->gpxReader = new XMLReader();
$this->gpxReader->open($gpxFilename);
}
public function getElements($elementType) {
while ($this->gpxReader->read()) {
if ($this->gpxReader->nodeType == XMLREADER::ELEMENT &&
$this->gpxReader->name == $elementType) {
$doc = new DOMDocument('1.0', 'UTF-8');
$xml = simplexml_import_dom($doc->importNode($this->gpxReader->expand(), true));
$gpxAttributes = $this->readAttributes($this->gpxReader);
$gpxElement = $this->readChildren($xml);
$gpxElement->position = $gpxAttributes;
yield $gpxElement->timestamp => $gpxElement;
}
}
}
}
A Functional Guide to Cat Herding with PHP Generators
// Create our initial Generator to read the gpx file
$gpxReader = new GpxReaderGpxHandler($gpxFilename);
// Iterate over the trackpoint set from the gpx file,
// displaying each point detail in turn
foreach ($gpxReader->getElements('trkpt') as $time => $element) {
printf(
'%s' . PHP_EOL . ' latitude: %7.4f longitude: %7.4f elevation: %d' . PHP_EOL,
$time->format('Y-m-d H:i:s'),
$element->position->latitude,
$element->position->longitude,
$element->elevation
);
}
Cat Herding with PHP Generators – Filter
• A filter selects only a subset of values from the Traversable.
• The rules for filtering are defined in a callback function.
• If no callback is provided, then only non-empty values are returned.
Cat Herding with PHP Generators – Filter
array_filter()
Filters elements of an array using a callback function.
array array_filter ( array $array [, callable $callback] )
Iterates over each value in the array passing them to the callback function. If the
callback function returns true, the current value from array is returned into the
result array, otherwise it is discarded. Array keys are preserved.
Cat Herding with PHP Generators – Filter
function notEmpty($value) {
return !empty($value);
}
/**
* Version of filter to use with versions of PHP prior to 5.6.0,
* without the `$flag` option
*
**/
function filter(Traversable $filter, Callable $callback = null) {
if ($callback === null) {
$callback = 'notEmpty';
}
foreach ($filter as $key => $value) {
if ($callback($value)) {
yield $key => $value;
}
}
}
Cat Herding with PHP Generators – Filter
array_filter()
Filters elements of an array using a callback function.
array array_filter ( array $array [, callable $callback] )
array array_filter ( array $array [, callable $callback [, int $flag = 0 ]] )
Iterates over each value in the array passing them to the callback function. If the
callback function returns true, the current value from array is returned into the
result array, otherwise it is discarded. Array keys are preserved.
[PHP < 5.6.0]
[PHP >= 5.6.0]
Cat Herding with PHP Generators – Filter
/**
* The `$flag` option (and the constants ARRAY_FILTER_USE_KEY and ARRAY_FILTER_USE_BOTH)
* were introduced in PHP 5.6.0
*
**/
function filter(Traversable $filter, Callable $callback = null, $flag = 0) {
if ($callback === null) {
$callback = 'notEmpty';
}
foreach ($filter as $key => $value) {
switch($flag) {
case ARRAY_FILTER_USE_KEY:
...
case ARRAY_FILTER_USE_BOTH:
...
default:
...
}
}
}
Cat Herding with PHP Generators – Filter
• Time Range
Where was Roman between 11:30 and 12:00?
• Geo-Fencing (“Bounding Box”)
• Inside
Did Osiris go anywhere near the main road?
• Outside
Has Lexie left the house at all?
Cat Herding with PHP Generators – Filter
// Create our initial Generator to read the gpx file
$gpxReader = new GpxReaderGpxHandler($gpxFilename);
// Define the date/time filter parameters
$startTime = new DateTime('2015-03-02 13:20:00Z');
$endTime = new DateTime('2015-03-02 13:30:00Z');
// Create the filter callback with the date/time parameters we've just defined
$timeFilter = function($timestamp) use ($startTime, $endTime) {
return $timestamp >= $startTime && $timestamp <= $endTime;
};
Cat Herding with PHP Generators – Filter
// Iterate over the trackpoint set from the gpx file,
// displaying each point detail in turn
foreach (filter($gpxReader->getElements('trkpt'), $timeFilter, ARRAY_FILTER_USE_KEY)
as $time => $element) {
printf(
'%s' . PHP_EOL . ' latitude: %7.4f longitude: %7.4f elevation: %d' . PHP_EOL,
$time->format('Y-m-d H:i:s'),
$element->position->latitude,
$element->position->longitude,
$element->elevation
);
}
Cat Herding with PHP Generators – Filter
namespace GpxReaderHelpers;
class BoundingBox {
/**
* Identify whether a trackpoint falls inside the defined bounding box
*
* @param GpxReaderGpxElement The trackpoint
* @return boolean If the trackpoint falls outside (false)
* or inside (true) the bounding box
**/
public function inside(GpxReaderGpxElement $point) {
return (($point->position->longitude >= $this->left) &&
($point->position->longitude <= $this->right) &&
($point->position->latitude >= $this->bottom) &&
($point->position->latitude <= $this->top));
}
}
Cat Herding with PHP Generators – Filter
// Create our initial Generator to read the gpx file
$gpxReader = new GpxReaderGpxHandler($gpxFilename);
// Create a bounding box defining the coordinates we want to test each point against
// This bounding box is for inside the house/garden
$boundaries = new GpxReaderHelpersBoundingBox();
$boundaries->setLatitudes(53.54382, 53.54340);
$boundaries->setLongitudes(-2.74059, -2.74005);
// We want to set the filter to include only points inside the bounding box
$boundingBoxFilter = [$boundaries, 'inside'];
Cat Herding with PHP Generators – Filter
// Iterate over the trackpoint set from the gpx file,
// displaying each point detail in turn
foreach (filter($gpxReader->getElements('trkpt'), $boundingBoxFilter) as
$time => $element) {
printf(
'%s' . PHP_EOL . ' latitude: %7.4f longitude: %7.4f elevation: %d' . PHP_EOL,
$time->format('Y-m-d H:i:s'),
$element->position->latitude,
$element->position->longitude,
$element->elevation
);
}
Cat Herding with PHP Generators – Filter
// Iterate over the trackpoint set from the gpx file,
// displaying each point detail in turn
// applying both a time filter (12:00:00-12:20:00 on 2015-11-23)
// and a bounding box filter for inside the house/garden
foreach (filter(
filter(
$gpxReader->getElements('trkpt'), $timeFilter, ARRAY_FILTER_USE_KEY
),
$boundingBoxFilter
) as $time => $element) {
printf(
...
);
}
Cat Herding with PHP Generators – Map
• A map is like a foreach loop that transforms each value in the
Traversable.
• Each input value is transformed into a new output value.
• The rules for the transformation are defined in a callback function.
Cat Herding with PHP Generators – Map
array_map()
Applies the callback to the elements of the given arrays.
array array_map ( callable $callback , array $array1 [, array $... ] )
array_map() returns an array containing all the elements of array1 after applying
the callback function to each one. The number of parameters that the callback
function accepts should match the number of arrays passed to the array_map().
Cat Herding with PHP Generators – Map
function map(Callable $callback, Traversable $iterator) {
foreach ($iterator as $key => $value) {
yield $key => $callback($value);
}
}
Cat Herding with PHP Generators – Map
namespace GpxReaderHelpers;
class DistanceCalculator {
public function setDistance(GpxReaderGpxElement $point) {
$point->distance = $this->calculateDistance($point);
return $point;
}
}
Cat Herding with PHP Generators – Map
// Create our initial Generator to read the gpx file
$gpxReader = new GpxReaderGpxHandler($gpxFilename);
// Set the mapper to calculate the distance between a trackpoint
// and the previous trackpoint
$distanceCalculator = new GpxReaderHelpersDistanceCalculator();
Cat Herding with PHP Generators – Map
// Iterate over the trackpoint set from the gpx file, mapping the distances as we go,
// displaying each point detail in turn
foreach (map([$distanceCalculator, 'setDistance'], $gpxReader->getElements('trkpt'))
as $time => $element) {
printf(
'%s' . PHP_EOL . ' latitude: %7.4f longitude: %7.4f elevation: %d' . PHP_EOL .
' distance from previous point: %5.2f m' . PHP_EOL,
$time->format('Y-m-d H:i:s'),
$element->position->latitude,
$element->position->longitude,
$element->elevation,
$element->distance
);
}
Cat Herding with PHP Generators – Map
function mmap(Callable $callback, Traversable ...$iterators) {
$mi = new MultipleIterator(MultipleIterator::MIT_NEED_ANY);
foreach($iterators as $iterator) {
$mi->attachIterator($iterator);
}
foreach($mi as $values) {
yield $callback(...$values);
}
}
http://nl3.php.net/manual/en/function.array-slice.php
...
Splat Operator
PHP >= 5.6.0
Argument Packing/Unpacking
Cat Herding with PHP Generators – Reduce
• A reduce aggregates all the values in the Traversable to a single value.
• A callback function determines the process for the aggregation.
Cat Herding with PHP Generators – Reduce
array_reduce()
Iteratively reduces the array to a single value using a callback function.
mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )
array_reduce() applies the callback function iteratively to the elements of the array,
so as to reduce the array to a single value.
Cat Herding with PHP Generators – Reduce
• Bounding Box
What Coordinates should I use for the bounding box when displaying the track
on a map?
• Distance
How far has Roman walked while he’s been out?
What is the furthest distance that Osiris has travelled from home?
Cat Herding with PHP Generators – Reduce
// Iterate over our trackpoint set from the gpx file (mapping the distance as we go)
// and reducing the results to calculate the total distance travelled
$totalDistance = reduce(
map([$distanceCalculator, 'setDistance'], $gpxReader->getElements('trkpt')),
function($runningTotal, $value) {
$runningTotal += $value->distance;
return $runningTotal;
},
0.0
);
// Display the results of our reduce
printf(
'Total distance travelled is %5.2f km' . PHP_EOL,
$totalDistance / 1000
);
Cat Herding with PHP Generators – Reduce
Total distance travelled is 4.33 km
Cat Herding with PHP Generators
iterator_apply()
Call a function for every element in an iterator.
int iterator_apply ( Traversable $iterator , callable $function [, array $args ] )
Cat Herding with PHP Generators
iterator_count()
Count the elements in an iterator.
int iterator_count ( Traversable $iterator )
Cat Herding with PHP Generators
iterator_to_array()
Copy all values from the iterator into an array.
array iterator_to_array ( Traversable $iterator [, bool $use_keys = true ] )
Cat Herding with PHP Generators
// Create an instance of the fluent Generator Helper and set our filters,
// mapper offset and limits
// and the display callback to "do" (or execute)
// In this case, the "do" is a display callback,
// but it could also be a "reduce" callback
withGenerator($trackPoints)
->filteredBy($boundingBoxFilter)
->filteredBy($timeFilter, ARRAY_FILTER_USE_KEY)
->map([$distanceCalculator, 'setDistance'])
->offset(1)
->limit(1)
->do($display);
A Functional Guide to Cat Herding with PHP Generators
https://github.com/lstrojny/functional-php
A Functional Guide to Cat Herding with PHP Generators
No cats were forced to walk anywhere that they didn't want to go
during the writing of this presentation.
Who am I?
Mark Baker
Design and Development Manager
InnovEd (Innovative Solutions for Education) Ltd
Coordinator and Developer of:
Open Source PHPOffice library
PHPExcel, PHPWord, PHPPresentation (formerly PHPPowerPoint), PHPProject, PHPVisio
Minor contributor to PHP core
@Mark_Baker
https://github.com/MarkBaker
http://uk.linkedin.com/pub/mark-baker/b/572/171
http://markbakeruk.net