Skip to content

Commit 48284cb

Browse files
committed
Add SplitCallbackIterator
Tiny, but useful, spin-off from the SplitIterator, the SplitCallbackIterator looks ready for commit as well (documentation and one test case from it).
1 parent 7b614de commit 48284cb

3 files changed

+211
-0
lines changed

SplitCallbackIterator.php

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
require_once("SplitIterator.php");
3+
4+
/**
5+
* An {@see IteratorIterator} that splits another iterator into
6+
* multiple iterators based on the decision of a callback.
7+
*
8+
* This class is a concrete implementation of a {@see SplitIterator}
9+
* that only differs in the way the decision on the split is called:
10+
* While a {@see SplitIterator} needs to be subclassed, this version
11+
* takes a callback function from the outside.
12+
*
13+
* Otherwise the documentation for {@see SplitIterator} applies.
14+
*
15+
* A code example that splits a text into paragraphs at empty lines
16+
* and formats them as simple HTML:
17+
*
18+
* <code>
19+
* // The input is an Iterator over text lines, with
20+
* // empty lines as markers for paragraph boundaries
21+
* $lines = new ArrayIterator(array(
22+
* "This is the",
23+
* "first paragraph.",
24+
* "",
25+
* "And this is",
26+
* "the second",
27+
* "one."
28+
* ));
29+
*
30+
* // We just need a function that returns TRUE for empty lines
31+
* $split = function ($key, $line) { return $line == ""; };
32+
*
33+
* // And apply this function to split up the original Iterator
34+
* // into multiple iterators (one per paragraph)
35+
* $paragraphs = new SplitCallbackIterator($lines, $split);
36+
*
37+
* // Now we can iterate over paragraphs and their lines
38+
* // already in two nested loops
39+
* foreach($paragraphs as $paragraph) {
40+
* print "<p>";
41+
* foreach($paragraph as $line)
42+
* print $line." ";
43+
* print "<\\p>";
44+
* }
45+
* </code>
46+
*
47+
* @author Beat Vontobel
48+
* @since 2015-05-20
49+
* @package MeteoNews\phplib
50+
* @subpackage Iterators
51+
*
52+
* @see SplitIterator
53+
*/
54+
class SplitCallbackIterator extends SplitIterator {
55+
private $needsSplitCallback;
56+
57+
/**
58+
* Create a new splitting iterator based on an inner iterator and a
59+
* callback for the split decision.
60+
*
61+
* The callback function gets called for every item in the inner
62+
* iterator, with each item's key and value as the two arguments. So
63+
* an example callback could look as follows:
64+
*
65+
* <code>
66+
* function needsSplit($key, $currentItem) {
67+
* return ... ? TRUE : FALSE;
68+
* }
69+
* </code>
70+
*
71+
* If the callback returns TRUE, a split is initiated for the given
72+
* item.
73+
*
74+
* @param Traversable The iterator to be split into multiple
75+
* iterators.
76+
*
77+
* @param callable The callback for the split decision.
78+
*/
79+
public function __construct(Traversable $innerIterator, callable $needsSplitCallback) {
80+
$this->needsSplitCallback = $needsSplitCallback;
81+
parent::__construct($innerIterator);
82+
}
83+
84+
final public function needsSplit($key, $value) {
85+
$callback = $this->needsSplitCallback;
86+
return $callback($key, $value);
87+
}
88+
}
89+
?>

tests/SplitCallbackIterator_000.phpt

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
--TEST--
2+
SplitCallbackIterator: Automatic smoke test (syntax, warnings, silence)
3+
--FILE--
4+
<?php
5+
$source = FALSE;
6+
$output = FALSE;
7+
$global_vars = array();
8+
$cr_okay = FALSE;
9+
10+
$source = file_get_contents("SplitCallbackIterator.php");
11+
$lines = preg_split('/(\\x0a|\\x0d)+/', $source, 0, PREG_SPLIT_NO_EMPTY);
12+
13+
if(preg_match('/\?>\s*$/', $source))
14+
echo "SplitCallbackIterator has closing PHP tag at end\n";
15+
else
16+
echo "SplitCallbackIterator is missing closing PHP tag at end\n";
17+
18+
if(file_exists(preg_replace("|[^/]+$|", "DOS_LINE_ENDINGS", "SplitCallbackIterator"))) {
19+
// Check for a consistent use of only CRLF if a "DOS_LINE_ENDINGS" marker
20+
// file exists in the containing folder
21+
$cr_okay = (preg_match_all('/\x0d\x0a/', $source) == preg_match_all('/\x0d/', $source)
22+
&& preg_match_all('/\x0d\x0a/', $source) == preg_match_all('/\x0a/', $source));
23+
} else {
24+
// Otherwise (normal situation) any CR character is treated as a bug
25+
$cr_okay = !preg_match('/\x0d/', $source);
26+
}
27+
28+
if($cr_okay)
29+
echo "SplitCallbackIterator does not contain CR characters (or is consistent and in a folder marked with DOS_LINE_ENDINGS)\n";
30+
else
31+
echo "SplitCallbackIterator does contain CR characters (and is not consistent or not in a folder marked with DOS_LINE_ENDINGS)\n";
32+
33+
$global_vars = array_keys($GLOBALS);
34+
35+
ob_start();
36+
require_once("SplitCallbackIterator.php");
37+
$output = ob_get_contents();
38+
ob_end_clean();
39+
if($output === "")
40+
echo "Parsing of SplitCallbackIterator was silent\n";
41+
else
42+
echo "Parsing of SplitCallbackIterator was not silent:\n$output";
43+
44+
// XXX: Currently, we have these exceptions of global variables being added...
45+
$global_vars = array_merge($global_vars,
46+
array('db_host', 'db_user', 'db_pass', 'db_name',
47+
'memcache_hostconfig',
48+
'global_script_resources_object'));
49+
50+
if(count(array_diff(array_keys($GLOBALS), $global_vars)) == 0)
51+
print "SplitCallbackIterator did not pollute global variable space\n";
52+
else
53+
print "SplitCallbackIterator added these variables to global space: ".
54+
implode(", ", array_diff(array_keys($GLOBALS), $global_vars)).
55+
"\n";
56+
57+
$class_defs = array();
58+
foreach($lines as $line) {
59+
if(preg_match('/^\s*((abstract)\s+)?(class|interface|trait)\s+([a-z0-9_]+)/i', $line, $matches)) {
60+
$new_def = "$matches[3] $matches[4]";
61+
if($matches[3] == 'class'
62+
&& preg_match('/Exception$/', $matches[4])) {
63+
foreach($class_defs as $class_def) {
64+
if(preg_match("/^".preg_replace("/^(trait|interface)/", "class", $class_def)."/", $new_def))
65+
continue 2;
66+
}
67+
}
68+
$class_defs[] = $new_def;
69+
}
70+
}
71+
if(count($class_defs) <= 1) {
72+
print "SplitCallbackIterator contains at most one class, interface, or trait (except for Exception classes)\n";
73+
} else {
74+
print "SplitCallbackIterator contains multiple classes/interfaces/traits:\n";
75+
print " ".implode("\n ", $class_defs)."\n";
76+
}
77+
?>
78+
--EXPECT--
79+
SplitCallbackIterator has closing PHP tag at end
80+
SplitCallbackIterator does not contain CR characters (or is consistent and in a folder marked with DOS_LINE_ENDINGS)
81+
Parsing of SplitCallbackIterator was silent
82+
SplitCallbackIterator did not pollute global variable space
83+
SplitCallbackIterator contains at most one class, interface, or trait (except for Exception classes)

tests/SplitCallbackIterator_001.phpt

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
--TEST--
2+
SplitCallbackIterator: Documentation example
3+
--FILE--
4+
<?php
5+
require_once("SplitCallbackIterator.php");
6+
7+
$lines = new ArrayIterator(array("First",
8+
"and second line, and",
9+
"3rd in 1st paragraph.",
10+
"",
11+
"A 2nd paragraph.",
12+
"",
13+
"And the final one,",
14+
"again with two lines."));
15+
16+
$paragraphs = TestHelpers::pnewdebug("SplitCallbackIterator",
17+
$lines,
18+
function ($key, $line) {
19+
return $line == "";
20+
});
21+
22+
foreach($paragraphs as $paragraph) {
23+
print "<p>\n ";
24+
foreach($paragraph as $line)
25+
print $line." ";
26+
print "\n<\\p>\n";
27+
}
28+
?>
29+
--EXPECT--
30+
new SplitCallbackIterator(object(ArrayIterator), object(Closure)) returns object(SplitCallbackIterator)
31+
<p>
32+
First and second line, and 3rd in 1st paragraph.
33+
<\p>
34+
<p>
35+
A 2nd paragraph.
36+
<\p>
37+
<p>
38+
And the final one, again with two lines.
39+
<\p>

0 commit comments

Comments
 (0)