Skip to content

Commit d50b6ea

Browse files
authored
Merge pull request #26 from Icinga/add-utility-class-for-sequences
Introduce new class `ipl\Stdlib\Seq`
2 parents 6f8f07c + 1973833 commit d50b6ea

File tree

2 files changed

+198
-0
lines changed

2 files changed

+198
-0
lines changed

src/Seq.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
namespace ipl\Stdlib;
4+
5+
/**
6+
* Collection of utilities for traversables
7+
*/
8+
class Seq
9+
{
10+
/**
11+
* Check if the traversable contains the given needle
12+
*
13+
* @param array|iterable $traversable
14+
* @param mixed $needle Might also be a closure
15+
* @param bool $caseSensitive Whether strings should be compared case-sensitive
16+
*
17+
* @return bool
18+
*/
19+
public static function contains($traversable, $needle, $caseSensitive = true)
20+
{
21+
return self::find($traversable, $needle, $caseSensitive)[0] !== null;
22+
}
23+
24+
/**
25+
* Search in the traversable for the given needle and return its key and value
26+
*
27+
* @param array|iterable $traversable
28+
* @param mixed $needle Might also be a closure
29+
* @param bool $caseSensitive Whether strings should be compared case-sensitive
30+
*
31+
* @return array An array with two entries, the first is the key, then the value. Both are null if nothing is found.
32+
*/
33+
public static function find($traversable, $needle, $caseSensitive = true)
34+
{
35+
$usesCallback = is_callable($needle);
36+
if (! $usesCallback && $caseSensitive && is_array($traversable)) {
37+
return [array_search($needle, $traversable, true), $needle];
38+
}
39+
40+
if (! $caseSensitive && is_string($needle) && ! $usesCallback) {
41+
$needle = strtolower($needle);
42+
}
43+
44+
foreach ($traversable as $key => $item) {
45+
$originalItem = $item;
46+
if (! $caseSensitive && is_string($item)) {
47+
$item = strtolower($item);
48+
}
49+
50+
if ($usesCallback && $needle($item)) {
51+
return [$key, $originalItem];
52+
} elseif ($item === $needle) {
53+
return [$key, $originalItem];
54+
}
55+
}
56+
57+
return [null, null];
58+
}
59+
60+
/**
61+
* Search in the traversable for the given needle and return its key
62+
*
63+
* @param array|iterable $traversable
64+
* @param mixed $needle Might also be a closure
65+
* @param bool $caseSensitive Whether strings should be compared case-sensitive
66+
*
67+
* @return mixed|null Null if nothing is found
68+
*/
69+
public static function findKey($traversable, $needle, $caseSensitive = true)
70+
{
71+
return self::find($traversable, $needle, $caseSensitive)[0];
72+
}
73+
74+
/**
75+
* Search in the traversable for the given needle and return its value
76+
*
77+
* @param array|iterable $traversable
78+
* @param mixed $needle Might also be a closure
79+
* @param bool $caseSensitive Whether strings should be compared case-sensitive
80+
*
81+
* @return mixed|null Null if nothing is found
82+
*/
83+
public static function findValue($traversable, $needle, $caseSensitive = true)
84+
{
85+
$usesCallback = is_callable($needle);
86+
if (! $usesCallback && $caseSensitive && is_array($traversable)) {
87+
return isset($traversable[$needle]) ? $traversable[$needle] : null;
88+
}
89+
90+
if (! $caseSensitive && is_string($needle) && ! $usesCallback) {
91+
$needle = strtolower($needle);
92+
}
93+
94+
foreach ($traversable as $key => $item) {
95+
if (! $caseSensitive && is_string($key)) {
96+
$key = strtolower($key);
97+
}
98+
99+
if ($usesCallback && $needle($key)) {
100+
return $item;
101+
} elseif ($key === $needle) {
102+
return $item;
103+
}
104+
}
105+
106+
return null;
107+
}
108+
}

tests/SeqTest.php

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
namespace ipl\Tests\Stdlib;
4+
5+
use ArrayIterator;
6+
use ipl\Stdlib\Seq;
7+
8+
class SeqTest extends TestCase
9+
{
10+
public function testFindWithArrays()
11+
{
12+
$this->assertEquals(
13+
['oof', 'BAR'],
14+
Seq::find(['foo' => 'bar', 'oof' => 'BAR'], 'BAR')
15+
);
16+
$this->assertEquals(
17+
['foo', 'bar'],
18+
Seq::find(['foo' => 'bar', 'oof' => 'BAR'], 'BAR', false)
19+
);
20+
}
21+
22+
public function testFindWithGenerators()
23+
{
24+
$generatorCreator = function () {
25+
yield 'foo' => 'bar';
26+
yield 'oof' => 'BAR';
27+
};
28+
29+
$this->assertEquals(
30+
['oof', 'BAR'],
31+
Seq::find($generatorCreator(), 'BAR')
32+
);
33+
$this->assertEquals(
34+
['foo', 'bar'],
35+
Seq::find($generatorCreator(), 'BAR', false)
36+
);
37+
}
38+
39+
public function testFindWithIterators()
40+
{
41+
$this->assertEquals(
42+
['oof', 'BAR'],
43+
Seq::find(new ArrayIterator(['foo' => 'bar', 'oof' => 'BAR']), 'BAR')
44+
);
45+
$this->assertEquals(
46+
['foo', 'bar'],
47+
Seq::find(new ArrayIterator(['foo' => 'bar', 'oof' => 'BAR']), 'BAR', false)
48+
);
49+
}
50+
public function testFindValueWithArrays()
51+
{
52+
$this->assertEquals(
53+
'BAR',
54+
Seq::findValue(['foo' => 'bar', 'FOO' => 'BAR'], 'FOO')
55+
);
56+
$this->assertEquals(
57+
'bar',
58+
Seq::findValue(['foo' => 'bar', 'FOO' => 'BAR'], 'FOO', false)
59+
);
60+
}
61+
62+
public function testFindValueWithGenerators()
63+
{
64+
$generatorCreator = function () {
65+
yield 'foo' => 'bar';
66+
yield 'FOO' => 'BAR';
67+
};
68+
69+
$this->assertEquals(
70+
'BAR',
71+
Seq::findValue($generatorCreator(), 'FOO')
72+
);
73+
$this->assertEquals(
74+
'bar',
75+
Seq::findValue($generatorCreator(), 'FOO', false)
76+
);
77+
}
78+
79+
public function testFindValueWithIterators()
80+
{
81+
$this->assertEquals(
82+
'BAR',
83+
Seq::findValue(new ArrayIterator(['foo' => 'bar', 'FOO' => 'BAR']), 'FOO')
84+
);
85+
$this->assertEquals(
86+
'bar',
87+
Seq::findValue(new ArrayIterator(['foo' => 'bar', 'FOO' => 'BAR']), 'FOO', false)
88+
);
89+
}
90+
}

0 commit comments

Comments
 (0)