diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..ed17d00
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,12 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text eol=lf
+/.gitattributes export-ignore
+/.gitignore export-ignore
+/.github export-ignore
+/phpcs.xml.dist export-ignore
+/phpstan.neon.dist export-ignore
+/phpstan-baseline.neon export-ignore
+/phpunit.xml.dist export-ignore
+/psalm.xml.dist export-ignore
+/psalm-baseline.xml export-ignore
+/tests export-ignore
diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml
new file mode 100644
index 0000000..2582648
--- /dev/null
+++ b/.github/workflows/coding-standards.yml
@@ -0,0 +1,34 @@
+name: "Coding Standards"
+
+on:
+ pull_request:
+ push:
+ branches:
+ - "master"
+
+jobs:
+ coding-standards:
+ name: "Coding Standards"
+ runs-on: "ubuntu-20.04"
+
+ strategy:
+ matrix:
+ php-version:
+ - "7.4"
+
+ steps:
+ - name: "Checkout"
+ uses: "actions/checkout@v2"
+
+ - name: "Install PHP"
+ uses: "shivammathur/setup-php@v2"
+ with:
+ coverage: "none"
+ php-version: "${{ matrix.php-version }}"
+ tools: "cs2pr"
+
+ - name: "Install dependencies with Composer"
+ uses: "ramsey/composer-install@v1"
+
+ - name: "Run PHP_CodeSniffer"
+ run: "vendor/bin/phpcs -q --no-colors --report=checkstyle | cs2pr"
diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
new file mode 100644
index 0000000..9e4bf6c
--- /dev/null
+++ b/.github/workflows/continuous-integration.yml
@@ -0,0 +1,72 @@
+name: "Continuous Integration"
+
+on:
+ pull_request:
+ push:
+ schedule:
+ - cron: "0 0 1 * *"
+
+jobs:
+ unit-tests:
+ name: "Unit Tests"
+ runs-on: "ubuntu-20.04"
+
+ strategy:
+ matrix:
+ php-version:
+ - "7.4"
+ - "8.0"
+ dependencies:
+ - "highest"
+ include:
+ - dependencies: "lowest"
+ php-version: "7.4"
+
+ steps:
+ - name: "Checkout"
+ uses: "actions/checkout@v2"
+ with:
+ fetch-depth: 2
+
+ - name: "Install PHP"
+ uses: "shivammathur/setup-php@v2"
+ with:
+ php-version: "${{ matrix.php-version }}"
+ coverage: "pcov"
+ ini-values: "zend.assertions=1"
+
+ - name: "Install dependencies with Composer"
+ uses: "ramsey/composer-install@v1"
+ with:
+ dependency-versions: "${{ matrix.dependencies }}"
+
+ - name: "Run unit tests"
+ run: "vendor/bin/pest --coverage-clover=coverage.xml"
+
+ - name: "Upload coverage file"
+ uses: "actions/upload-artifact@v2"
+ with:
+ name: "pest-${{ matrix.deps }}-${{ matrix.php-version }}.coverage"
+ path: "coverage.xml"
+
+ upload-coverage:
+ name: "Upload coverage to Codecov"
+ runs-on: "ubuntu-20.04"
+ needs:
+ - "unit-tests"
+
+ steps:
+ - name: "Checkout"
+ uses: "actions/checkout@v2"
+ with:
+ fetch-depth: 2
+
+ - name: "Download coverage files"
+ uses: "actions/download-artifact@v2"
+ with:
+ path: "reports"
+
+ - name: "Upload to Codecov"
+ uses: "codecov/codecov-action@v1"
+ with:
+ directory: reports
diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml
new file mode 100644
index 0000000..23cd0e1
--- /dev/null
+++ b/.github/workflows/static-analysis.yml
@@ -0,0 +1,58 @@
+name: "Static Analysis"
+
+on:
+ pull_request:
+ push:
+ branches:
+ - "master"
+
+jobs:
+ static-analysis-phpstan:
+ name: "Static Analysis with PHPStan"
+ runs-on: "ubuntu-20.04"
+
+ strategy:
+ matrix:
+ php-version:
+ - "7.4"
+
+ steps:
+ - name: "Checkout code"
+ uses: "actions/checkout@v2"
+
+ - name: "Install PHP"
+ uses: "shivammathur/setup-php@v2"
+ with:
+ coverage: "none"
+ php-version: "${{ matrix.php-version }}"
+ tools: "cs2pr"
+
+ - name: "Install dependencies with Composer"
+ uses: "ramsey/composer-install@v1"
+
+ - name: "Run a static analysis with phpstan/phpstan"
+ run: "vendor/bin/phpstan analyse --error-format=checkstyle | cs2pr"
+
+ static-analysis-psalm:
+ name: "Static Analysis with Psalm"
+ runs-on: "ubuntu-20.04"
+
+ strategy:
+ matrix:
+ php-version:
+ - "7.4"
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ - name: Psalm
+ uses: docker://vimeo/psalm-github-actions:4.4.1
+ with:
+ composer_require_dev: true
+ security_analysis: true
+ report_file: results.sarif
+ - name: Upload Security Analysis results to GitHub
+ uses: github/codeql-action/upload-sarif@v1
+ with:
+ sarif_file: results.sarif
diff --git a/.gitignore b/.gitignore
index c23da7c..56ed85a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,8 @@
-/vendor
-
+/.phpcs-cache
+/.phpunit.result.cache
/composer.lock
+/phpcs.xml
+/phpstan.neon
+/psalm.xml
/phpunit.xml
+/vendor/
diff --git a/.scrutinizer.yml b/.scrutinizer.yml
deleted file mode 100644
index 5f9e672..0000000
--- a/.scrutinizer.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-filter:
- excluded_paths: [tests/*]
-checks:
- php:
- code_rating: true
- remove_extra_empty_lines: true
- remove_php_closing_tag: true
- remove_trailing_whitespace: true
- fix_use_statements:
- remove_unused: true
- preserve_multiple: false
- preserve_blanklines: true
- order_alphabetically: true
- fix_php_opening_tag: true
- fix_linefeed: true
- fix_line_ending: true
- fix_identation_4spaces: true
- fix_doc_comments: true
-tools:
- php_analyzer: true
- php_code_coverage: false
- php_code_sniffer:
- config:
- standard: PSR2
- filter:
- paths: ['src']
- php_loc:
- enabled: true
- excluded_dirs: [vendor, tests]
- php_cpd:
- enabled: true
- excluded_dirs: [vendor, tests]
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index d2408be..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-language: php
-
-php:
- - '5.6'
- - '7.0'
- - '7.1'
- - '7.2'
- - '7.3'
- - '7.4'
- - 'nightly'
-
-matrix:
- include:
- - php: '5.3'
- dist: precise
- - php: '5.4'
- dist: trusty
- - php: '5.5'
- dist: trusty
- allow_failures:
- - php: 'nightly'
-
-before_script:
- - travis_retry composer self-update
- - travis_retry composer global require hirak/prestissimo
- - travis_retry composer install --no-interaction --prefer-dist
- - travis_retry phpenv rehash
-
-script:
- - if [[ ${TRAVIS_PHP_VERSION:0:3} == "5.3" ]]; then composer require --dev symfony/polyfill-php54; fi
- - if [[ $TRAVIS_PHP_VERSION = 5.5.* || $TRAVIS_PHP_VERSION = 5.6.* || $TRAVIS_PHP_VERSION = 7.* ]]; then composer require --dev php-coveralls/php-coveralls; fi
- - if [[ $TRAVIS_PHP_VERSION = 5.5.* || $TRAVIS_PHP_VERSION = 5.6.* || $TRAVIS_PHP_VERSION = 7.* ]]; then ./vendor/bin/phpunit --coverage-clover build/logs/clover.xml; else ./vendor/bin/phpunit; fi
- - ./vendor/bin/phpcs --standard=psr2 -n src/
- - mkdir -p build/logs
-
-after_script:
- - php vendor/bin/php-coveralls -v
diff --git a/README.md b/README.md
index 1bb43b4..d1a5801 100644
--- a/README.md
+++ b/README.md
@@ -1,29 +1,20 @@
[](https://packagist.org/packages/bentools/iterable-functions)
-[](https://packagist.org/packages/bentools/iterable-functions)
-[](https://travis-ci.org/bpolaszek/php-iterable-functions)
-[](https://coveralls.io/github/bpolaszek/php-iterable-functions?branch=master)
-[](https://scrutinizer-ci.com/g/bpolaszek/php-iterable-functions)
+[![GitHub Actions][GA master image]][GA master]
+[![Code Coverage][Coverage image]][CodeCov Master]
+[![Shepherd Type][Shepherd Image]][Shepherd Link]
[](https://packagist.org/packages/bentools/iterable-functions)
Iterable functions
==================
-Provides additional functions to work with [iterable](https://wiki.php.net/rfc/iterable) variables (even on PHP5.3+).
+This package provides functions to work with [iterables](https://wiki.php.net/rfc/iterable), as you usually do with arrays:
-is_iterable()
--------------
-To check wether or not a PHP variable can be looped over in a `foreach` statement, PHP provides an `is_iterable()` function.
-
-**But this function only works on PHP7.1+**.
-
-This library ships a polyfill of this function for previous PHP versions.
-
-Usage:
-```php
-var_dump(is_iterable(array('foo', 'bar'))); // true
-var_dump(is_iterable(new DirectoryIterator(__DIR__))); // true
-var_dump(is_iterable('foobar')); // false
-```
+- [iterable_to_array()](#iterable_to_array)
+- [iterable_to_traversable()](#iterable_to_traversable)
+- [iterable_map()](#iterable_map)
+- [iterable_reduce()](#iterable_reduce)
+- [iterable_filter()](#iterable_filter)
+- [iterable_values()](#iterable_values)
iterable_to_array()
-------------------
@@ -32,13 +23,15 @@ PHP offers an `iterator_to_array()` function to export any iterator into an arra
**But when you want to transform an `iterable` to an array, the `iterable` itself can already be an array.**
-When using `iterator_to_array()` with an array, PHP5 triggers a E_RECOVERABLE_ERROR while PHP7 throws a `TypeError`.
+When using `iterator_to_array()` with an iterable, that happens to be an array, PHP will throw a `TypeError`.
If you need an iterable-agnostic function, try our `iterable_to_array()`:
```php
-var_dump(iterable_to_array(new ArrayIterator(array('foo', 'bar')))); // ['foo', 'bar']
-var_dump(iterable_to_array(array('foo', 'bar'))); // ['foo', 'bar']
+use function BenTools\IterableFunctions\iterable_to_array;
+
+var_dump(iterable_to_array(new \ArrayIterator(['foo', 'bar']))); // ['foo', 'bar']
+var_dump(iterable_to_array(['foo', 'bar'])); // ['foo', 'bar']
```
iterable_to_traversable()
@@ -50,11 +43,13 @@ If your variable is already an instance of `Traversable` (i.e. an `Iterator`, an
If your variable is an array, the function converts it to an `ArrayIterator`.
Usage:
+
```php
-var_dump(iterable_to_traversable(array('foo', 'bar'))); // ArrayIterator(array('foo', 'bar'))
-var_dump(iterable_to_traversable(new ArrayIterator(array('foo', 'bar')))); // ArrayIterator(array('foo', 'bar'))
-```
+use function BenTools\IterableFunctions\iterable_to_traversable;
+var_dump(iterable_to_traversable(['foo', 'bar'])); // \ArrayIterator(['foo', 'bar'])
+var_dump(iterable_to_traversable(new \ArrayIterator(['foo', 'bar']))); // \ArrayIterator(['foo', 'bar'])
+```
iterable_map()
--------------
@@ -62,6 +57,8 @@ iterable_map()
Works like an `array_map` with an `array` or a `Traversable`.
```php
+use function BenTools\IterableFunctions\iterable_map;
+
$generator = function () {
yield 'foo';
yield 'bar';
@@ -78,6 +75,8 @@ iterable_reduce()
Works like an `reduce` with an `iterable`.
```php
+use function BenTools\IterableFunctions\iterable_reduce;
+
$generator = function () {
yield 1;
yield 2;
@@ -98,6 +97,8 @@ iterable_filter()
Works like an `array_filter` with an `array` or a `Traversable`.
```php
+use function BenTools\IterableFunctions\iterable_filter;
+
$generator = function () {
yield 0;
yield 1;
@@ -109,7 +110,10 @@ foreach (iterable_filter($generator()) as $item) {
```
Of course you can define your own filter:
+
```php
+use function BenTools\IterableFunctions\iterable_filter;
+
$generator = function () {
yield 'foo';
yield 'bar';
@@ -125,69 +129,89 @@ foreach (iterable_filter($generator(), $filter) as $item) {
}
```
+iterable_values()
+--------------
-Iterable factory
-================
-
-When you have an `iterable` type-hint somewhere, and don't know in advance wether you'll pass an `array` or a `Traversable`, just call the magic `iterable()` factory:
+Works like an `array_values` with an `array` or a `Traversable`.
```php
-interface SomeInterface
-{
- /**
- * Return an iterable list of items
- *
- * @return iterable
- */
- public function getItems(): iterable;
-}
+use function BenTools\IterableFunctions\iterable_values;
-class MyService implements SomeInterface
-{
- /**
- * @inheritdoc
- */
- public function getItems(): iterable
- {
- return iterable($this->someOtherService->findAll()):
- }
+$generator = function () {
+ yield 'a' => 'a';
+ yield 'b' => 'b';
+};
+foreach (iterable_values($generator()) as $key => $value) {
+ var_dump($key); // 0, 1
+ var_dump($value); // a, b
}
```
-It even accepts a `null` value (then converting it to an `EmptyIterator`).
+Iterable fluent interface
+=========================
+
+The `iterable` function allows you to wrap an iterable and apply some common operations.
-You may add a `filter` callable and a `map` callable to make your life easier:
+With an array input:
```php
+use function BenTools\IterableFunctions\iterable;
$data = [
'banana',
'pineapple',
- 'potato',
+ 'rock',
];
-$isFruit = function ($eatable) {
- return 'potato' !== $eatable;
-};
+$iterable = iterable($data)->filter(fn($eatable) => 'rock' !== $eatable)->map('strtoupper'); // Traversable of ['banana', 'pineapple']
+```
-var_dump(iterator_to_array(iterable($data)->filter($isFruit)->map('strtoupper'))); // ['banana', 'pineapple']
+With a traversable input:
+
+```php
+use function BenTools\IterableFunctions\iterable;
+$data = [
+ 'banana',
+ 'pineapple',
+ 'rock',
+];
+
+$data = fn() => yield from $data;
+
+$iterable = iterable($data())->filter(fn($eatable) => 'rock' !== $eatable)->map('strtoupper'); // Traversable of ['banana', 'pineapple']
+```
+
+Array output:
+
+```php
+$iterable->asArray(); // array ['banana', 'pineapple']
```
Installation
============
-With composer (they'll be autoloaded):
```
-composer require bentools/iterable-functions
+composer require bentools/iterable-functions:^2.0
```
-Or manually:
-```php
-require_once '/path/to/this/library/src/iterable-functions.php';
-```
+For PHP5+ compatibility, check out the [1.x branch](https://github.com/bpolaszek/php-iterable-functions/tree/1.x).
+
Unit tests
==========
+
```
-./vendor/bin/phpunit
+php vendor/bin/pest
```
+
+[GA master]: https://github.com/bpolaszek/php-iterable-functions/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A2.0.x-dev
+
+[GA master image]: https://github.com/bpolaszek/php-iterable-functions/workflows/Continuous%20Integration/badge.svg
+
+[CodeCov Master]: https://codecov.io/gh/bpolaszek/php-iterable-functions/branch/2.0.x-dev
+
+[Coverage image]: https://codecov.io/gh/bpolaszek/php-iterable-functions/branch/2.0.x-dev/graph/badge.svg
+
+[Shepherd Image]: https://shepherd.dev/github/bpolaszek/php-iterable-functions/coverage.svg
+
+[Shepherd Link]: https://shepherd.dev/github/bpolaszek/php-iterable-functions
diff --git a/composer.json b/composer.json
index 31041a7..b04bdf7 100644
--- a/composer.json
+++ b/composer.json
@@ -10,16 +10,27 @@
"files": ["src/iterable-functions.php"]
},
"autoload-dev": {
+ "psr-4": {
+ "BenTools\\IterableFunctions\\Tests\\": "tests"
+ },
"files": [
"vendor/symfony/var-dumper/Resources/functions/dump.php"
]
},
"require": {
- "php": ">=5.3"
+ "php": "^7.4 || ^8.0"
},
"require-dev": {
- "phpunit/phpunit": "^4.0|^5.0|^6.0|^7.0",
- "squizlabs/php_codesniffer": "^2.0|^3.4",
- "symfony/var-dumper": "@stable"
+ "bentools/cartesian-product": "^1.3",
+ "doctrine/coding-standard": "^8.2",
+ "pestphp/pest": "^1.0",
+ "phpstan/extension-installer": "^1.1",
+ "phpstan/phpstan": "^0.12.67",
+ "phpstan/phpstan-strict-rules": "^0.12.9",
+ "symfony/var-dumper": "^5.2",
+ "vimeo/psalm": "^4.4"
+ },
+ "config": {
+ "sort-packages": true
}
}
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
new file mode 100644
index 0000000..82c4a0e
--- /dev/null
+++ b/phpcs.xml.dist
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ src/
+ tests/
+
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
new file mode 100644
index 0000000..1e018d5
--- /dev/null
+++ b/phpstan.neon.dist
@@ -0,0 +1,6 @@
+parameters:
+ level: max
+ paths:
+ - %currentWorkingDirectory%/src
+ - %currentWorkingDirectory%/tests
+
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 9ca55d7..9247927 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,37 +1,21 @@
-
- tests/TestIsIterable.php
- tests/TestIterableToArray.php
- tests/TestIterableToTraversable.php
- tests/TestIterableFilter.php
- tests/TestIterableMap.php
- tests/TestIterableObject.php
+ tests
-
-
- ./src
-
- src/iterable-map-php53.php
- src/iterable-map-php55.php
-
-
-
+
+
+ src
+
+
diff --git a/psalm-baseline.xml b/psalm-baseline.xml
new file mode 100644
index 0000000..555826d
--- /dev/null
+++ b/psalm-baseline.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ is_array($iterable) ? $filtered->asArray() : $filtered
+ is_array($iterable) ? $mapped->asArray() : $mapped
+
+
+ iterable<TKey, TResult>
+ iterable<TKey, TValue>
+
+
+
diff --git a/psalm.xml.dist b/psalm.xml.dist
new file mode 100644
index 0000000..02ceeb4
--- /dev/null
+++ b/psalm.xml.dist
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/IterableObject.php b/src/IterableObject.php
index c0caeea..c3526f1 100644
--- a/src/IterableObject.php
+++ b/src/IterableObject.php
@@ -1,131 +1,92 @@
+ */
final class IterableObject implements IteratorAggregate
{
- /**
- * @var iterable|array|Traversable
- */
+ /** @var iterable */
private $iterable;
- /**
- * @var callable
- */
- private $filter;
-
- /**
- * @var callable
- */
- private $map;
+ /** @param iterable $iterable */
+ public function __construct(iterable $iterable)
+ {
+ $this->iterable = $iterable;
+ }
/**
- * IterableObject constructor.
- * @param iterable|array|Traversable $iterable
- * @param callable|null $filter
- * @param callable|null $map
- * @throws InvalidArgumentException
+ * @param (callable(TValue):bool)|null $filter
+ *
+ * @return self
*/
- public function __construct($iterable, $filter = null, $map = null)
+ public function filter(?callable $filter = null): self
{
- if (null === $iterable) {
- $iterable = new EmptyIterator();
- }
- if (!is_iterable($iterable)) {
- throw new InvalidArgumentException(
- sprintf('Expected array or Traversable, got %s', is_object($iterable) ? get_class($iterable) : gettype($iterable))
- );
- }
-
- // Cannot rely on callable type-hint on PHP 5.3
- if (null !== $filter && !is_callable($filter) && !$filter instanceof Closure) {
- throw new InvalidArgumentException(
- sprintf('Expected callable, got %s', is_object($filter) ? get_class($filter) : gettype($filter))
- );
- }
+ if ($this->iterable instanceof Traversable) {
+ $filter ??=
+ /** @param mixed $value */
+ static function ($value): bool {
+ return (bool) $value;
+ };
- if (null !== $map && !is_callable($map) && !$map instanceof Closure) {
- throw new InvalidArgumentException(
- sprintf('Expected callable, got %s', is_object($map) ? get_class($map) : gettype($map))
- );
+ return new self(new CallbackFilterIterator(new IteratorIterator($this->iterable), $filter));
}
- $this->iterable = $iterable;
- $this->filter = $filter;
- $this->map = $map;
- }
+ $filtered = $filter === null ? array_filter($this->iterable) : array_filter($this->iterable, $filter);
- /**
- * @param callable $filter
- * @return self
- */
- public function filter($filter)
- {
- return new self($this->iterable, $filter, $this->map);
+ return new self($filtered);
}
/**
- * @param callable $map
- * @return self
+ * @param callable(TValue):TResult $mapper
+ *
+ * @return self
+ *
+ * @template TResult
*/
- public function map($map)
+ public function map(callable $mapper): self
{
- return new self($this->iterable, $this->filter, $map);
- }
+ if ($this->iterable instanceof Traversable) {
+ return new self(new MappedTraversable($this->iterable, $mapper));
+ }
- /**
- * @param callable $filter
- * @return self
- * @deprecated Use IterableObject::filter instead.
- */
- public function withFilter($filter)
- {
- return $this->filter($filter);
+ return new self(array_map($mapper, $this->iterable));
}
/**
- * @param callable $map
- * @return self
- * @deprecated Use IterableObject::map instead.
+ * @return self
*/
- public function withMap($map)
+ public function values(): self
{
- return $this->map($map);
+ return new self(new WithoutKeysTraversable($this->iterable));
}
- /**
- * @inheritdoc
- */
- public function getIterator()
+ /** @return Traversable */
+ public function getIterator(): Traversable
{
- $iterable = $this->iterable;
- if (null !== $this->filter) {
- $iterable = iterable_filter($iterable, $this->filter);
- }
- if (null !== $this->map) {
- $iterable = iterable_map($iterable, $this->map);
- }
- return iterable_to_traversable($iterable);
+ yield from $this->iterable;
}
- /**
- * @return array
- */
- public function asArray()
+ /** @return array */
+ public function asArray(): array
{
- $iterable = $this->iterable;
- if (null !== $this->filter) {
- $iterable = iterable_filter($iterable, $this->filter);
- }
- if (null !== $this->map) {
- $iterable = iterable_map($iterable, $this->map);
- }
- return iterable_to_array($iterable);
+ return $this->iterable instanceof Traversable ? iterator_to_array($this->iterable) : $this->iterable;
}
}
diff --git a/src/MappedTraversable.php b/src/MappedTraversable.php
new file mode 100644
index 0000000..d992c76
--- /dev/null
+++ b/src/MappedTraversable.php
@@ -0,0 +1,41 @@
+
+ */
+final class MappedTraversable implements IteratorAggregate
+{
+ /** @var Traversable */
+ private $traversable;
+
+ /** @var callable */
+ private $mapper;
+
+ /**
+ * @param Traversable $traversable
+ */
+ public function __construct(Traversable $traversable, callable $mapper)
+ {
+ $this->traversable = $traversable;
+ $this->mapper = $mapper;
+ }
+
+ /**
+ * @return Traversable
+ */
+ public function getIterator(): Traversable
+ {
+ foreach ($this->traversable as $key => $value) {
+ yield $key => ($this->mapper)($value);
+ }
+ }
+}
diff --git a/src/WithoutKeysTraversable.php b/src/WithoutKeysTraversable.php
new file mode 100644
index 0000000..6a97776
--- /dev/null
+++ b/src/WithoutKeysTraversable.php
@@ -0,0 +1,39 @@
+
+ */
+final class WithoutKeysTraversable implements IteratorAggregate
+{
+ /** @var iterable */
+ private $iterable;
+
+ /**
+ * @param iterable $iterable
+ */
+ public function __construct(iterable $iterable)
+ {
+ $this->iterable = $iterable;
+ }
+
+ /**
+ * @return Generator
+ */
+ public function getIterator(): Generator
+ {
+ foreach ($this->iterable as $value) {
+ yield $value;
+ }
+ }
+}
diff --git a/src/iterable-functions.php b/src/iterable-functions.php
index de15eeb..981332e 100644
--- a/src/iterable-functions.php
+++ b/src/iterable-functions.php
@@ -1,145 +1,137 @@
= 0) {
- include_once __DIR__ . '/iterable-map-php55.php';
-} else {
- include_once __DIR__ . '/iterable-map-php53.php';
-}
-
-if (!function_exists('is_iterable')) {
+namespace BenTools\IterableFunctions;
- /**
- * Check wether or not a variable is iterable (i.e array or \Traversable)
- *
- * @param mixed $iterable
- * @return bool
- */
- function is_iterable($iterable)
- {
- return is_array($iterable) || $iterable instanceof \Traversable;
- }
-}
+use ArrayIterator;
+use EmptyIterator;
+use Traversable;
-if (!function_exists('iterable_to_array')) {
+use function array_values;
+use function iterator_to_array;
- /**
- * Copy the iterable into an array. If the iterable is already an array, return it.
- *
- * @param iterable|array|\Traversable $iterable
- * @param bool $use_keys [optional] Whether to use the iterator element keys as index.
- * @return array
- */
- function iterable_to_array($iterable, $use_keys = true)
- {
- return is_array($iterable) ? ($use_keys ? $iterable : array_values($iterable)) : iterator_to_array($iterable, $use_keys);
- }
+/**
+ * Maps a callable to an iterable.
+ *
+ * @param iterable $iterable
+ * @param callable(TValue):TResult $mapper
+ *
+ * @return iterable
+ *
+ * @template TKey
+ * @template TValue
+ * @template TResult
+ */
+function iterable_map(iterable $iterable, callable $mapper): iterable
+{
+ return iterable($iterable)->map($mapper);
}
-if (!function_exists('iterable_to_traversable')) {
-
- /**
- * If the iterable is not intance of \Traversable, it is an array => convert it to an ArrayIterator.
- *
- * @param iterable|array|\Traversable $iterable
- * @return \Traversable
- */
- function iterable_to_traversable($iterable)
- {
- if ($iterable instanceof Traversable) {
- return $iterable;
- } elseif (is_array($iterable)) {
- return new ArrayIterator($iterable);
- } else {
- throw new \InvalidArgumentException(
- sprintf(
- 'Expected array or \\Traversable, got %s',
- is_object($iterable) ? get_class($iterable) : gettype($iterable)
- )
- );
- }
+/**
+ * Copy the iterable into an array.
+ *
+ * @param iterable $iterable
+ * @param bool $preserveKeys [optional] Whether to use the iterator element keys as index.
+ *
+ * @return array
+ *
+ * @psalm-return ($preserveKeys is true ? array : array)
+ * @psalm-template TKey as array-key
+ * @phpstan-template TKey
+ * @template TValue
+ */
+function iterable_to_array(iterable $iterable, bool $preserveKeys = true): array
+{
+ if ($iterable instanceof Traversable) {
+ return iterator_to_array($iterable, $preserveKeys);
}
-}
-
-if (!function_exists('iterable_filter')) {
- /**
- * Filters an iterable.
- *
- * @param iterable|array|\Traversable $iterable
- * @param callable $filter
- * @return array|CallbackFilterIterator
- * @throws InvalidArgumentException
- */
- function iterable_filter($iterable, $filter = null)
- {
- if (!is_iterable($iterable)) {
- throw new \InvalidArgumentException(
- sprintf('Expected array or Traversable, got %s', is_object($iterable) ? get_class($iterable) : gettype($iterable))
- );
- }
+ return $preserveKeys ? $iterable : array_values($iterable);
+}
- // Cannot rely on callable type-hint on PHP 5.3
- if (null !== $filter && !is_callable($filter) && !$filter instanceof Closure) {
- throw new InvalidArgumentException(
- sprintf('Expected callable, got %s', is_object($filter) ? get_class($filter) : gettype($filter))
- );
- }
+/**
+ * If the iterable is not instance of Traversable, it is an array => convert it to an ArrayIterator.
+ *
+ * @param iterable $iterable
+ *
+ * @return Traversable
+ *
+ * @template TKey
+ * @template TValue
+ */
+function iterable_to_traversable(iterable $iterable): Traversable
+{
+ if ($iterable instanceof Traversable) {
+ return $iterable;
+ }
- if (null === $filter) {
- $filter = function ($value) {
- return (bool) $value;
- };
- }
+ return new ArrayIterator($iterable);
+}
- if ($iterable instanceof Traversable) {
- if (!class_exists('CallbackFilterIterator')) {
- throw new \RuntimeException('Class CallbackFilterIterator not found. Try using a polyfill, like symfony/polyfill-php54');
- }
- return new CallbackFilterIterator(new IteratorIterator($iterable), $filter);
- }
+/**
+ * Filters an iterable.
+ *
+ * @param (callable(TValue):bool)|null $filter
+ *
+ * @psalm-param iterable $iterable
+ * @phpstan-param iterable $iterable https://github.com/phpstan/phpstan/issues/4498
+ * @psalm-return iterable
+ * @phpstan-return iterable https://github.com/phpstan/phpstan/issues/4498
+ * @template TKey
+ * @template TValue
+ */
+function iterable_filter(iterable $iterable, ?callable $filter = null): iterable
+{
+ return iterable($iterable)->filter($filter);
+}
- return array_filter($iterable, $filter);
+/**
+ * Reduces an iterable.
+ *
+ * @param iterable $iterable
+ * @param TResult $initial
+ * @param callable(TResult, TValue):TResult $reduce
+ *
+ * @return TResult
+ *
+ * @template TValue
+ * @template TResult
+ * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
+ * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint
+ */
+function iterable_reduce(iterable $iterable, callable $reduce, $initial = null)
+{
+ foreach ($iterable as $item) {
+ $initial = $reduce($initial, $item);
}
+ return $initial;
}
-if (!function_exists('iterable_reduce')) {
- /**
- * Reduces an iterable.
- *
- * @param iterable $iterable
- * @param callable(mixed, mixed) $reduce
- * @return mixed
- *
- * @psalm-template TValue
- * @psalm-template TResult
- *
- * @psalm-param iterable $iterable
- * @psalm-param callable(TResult|null, TValue) $reduce
- * @psalm-param TResult|null $initial
- *
- * @psalm-return TResult|null
- */
- function iterable_reduce($iterable, $reduce, $initial = null)
- {
- foreach ($iterable as $item) {
- $initial = $reduce($initial, $item);
- }
-
- return $initial;
- }
+/**
+ * Yields iterable values (leaving out keys).
+ *
+ * @param iterable $iterable
+ *
+ * @return iterable
+ *
+ * @template TValue
+ */
+function iterable_values(iterable $iterable): iterable
+{
+ return iterable($iterable)->values();
}
/**
- * @param iterable|array|\Traversable $iterable
- * @param callable|null $filter
- * @param callable|null $map
- * @return Traversable|IterableObject
- * @throws InvalidArgumentException
+ * @param iterable|null $iterable
+ *
+ * @return IterableObject
+ *
+ * @template TKey
+ * @template TValue
*/
-function iterable($iterable, $filter = null, $map = null)
+function iterable(?iterable $iterable): IterableObject
{
- return new IterableObject($iterable, $filter, $map);
+ return new IterableObject($iterable ?? new EmptyIterator());
}
diff --git a/src/iterable-map-php53.php b/src/iterable-map-php53.php
deleted file mode 100644
index 3918de9..0000000
--- a/src/iterable-map-php53.php
+++ /dev/null
@@ -1,35 +0,0 @@
- $value) {
- yield $key => $map($value);
- }
- };
-
- return $generator($iterable, $map);
- }
-
- return array_map($map, $iterable);
- }
-
-}
diff --git a/tests/IterableFilterTest.php b/tests/IterableFilterTest.php
new file mode 100644
index 0000000..ea0b4ca
--- /dev/null
+++ b/tests/IterableFilterTest.php
@@ -0,0 +1,49 @@
+ true], iterable_to_array(iterable_filter($iterable)));
+});
+
+it('filters a Traversable object', function (): void {
+ $iterable = SplFixedArray::fromArray([false, true]);
+ $filtered = iterable_filter($iterable);
+ assert($filtered instanceof Traversable);
+ assertSame([1 => true], iterator_to_array($filtered));
+});
+
+it('filters an array with a callback', function (): void {
+ $iterable = ['foo', 'bar'];
+ $filter =
+ /** @param mixed $input */
+ static function ($input): bool {
+ return $input === 'bar';
+ };
+ assertSame([1 => 'bar'], iterable_to_array(iterable_filter($iterable, $filter)));
+});
+
+it('filters a Travsersable object with a callback', function (): void {
+ $iterable = SplFixedArray::fromArray(['foo', 'bar']);
+ $filter =
+ /** @param mixed $input */
+ static function ($input): bool {
+ return $input === 'bar';
+ };
+ $filtered = iterable_filter($iterable, $filter);
+ assert($filtered instanceof Traversable);
+ assertSame([1 => 'bar'], iterator_to_array($filtered));
+});
diff --git a/tests/IterableMapTest.php b/tests/IterableMapTest.php
new file mode 100644
index 0000000..618a164
--- /dev/null
+++ b/tests/IterableMapTest.php
@@ -0,0 +1,49 @@
+ $item) {
+ assertInstanceOf(stdClass::class, $key);
+ assertSame('FOO', $item);
+
+ return;
+ }
+
+ Assert::fail('Did not iterate');
+});
+
+/** @return Generator */
+function iterableWithObjectKeys(): Generator
+{
+ yield new stdClass() => 'foo';
+}
diff --git a/tests/IterableObjectTest.php b/tests/IterableObjectTest.php
new file mode 100644
index 0000000..ded2efe
--- /dev/null
+++ b/tests/IterableObjectTest.php
@@ -0,0 +1,126 @@
+ [
+ null,
+ ['', 'foo', 'bar'],
+ new class implements IteratorAggregate {
+ /** @return Traversable */
+ public function getIterator(): Traversable
+ {
+ yield '';
+ yield 'foo';
+ yield 'bar';
+ }
+ },
+ ],
+ 'mapper' => [
+ null,
+ static function (): callable {
+ return static function (string $value): string {
+ return strtoupper($value);
+ };
+ },
+ ],
+ 'filtered' => [
+ false,
+ true,
+ ],
+ 'filter' => [
+ null,
+ static function (): callable {
+ return static function (string $value): bool {
+ return strtolower($value) === 'bar';
+ };
+ },
+ ],
+]);
+
+
+it(
+ 'produces the expected result',
+ function (?iterable $input, ?callable $mapper, bool $filtered, ?callable $filter): void {
+ $iterable = iterable($input);
+
+ if ($input === null) {
+ assertSame([], $iterable->asArray());
+
+ return;
+ }
+
+ // Default expectation
+ $expected = ['', 'foo', 'bar'];
+
+ // Expectation when iterable is mapped
+ if ($mapper !== null) {
+ $iterable = $iterable->map($mapper);
+ $expected = ['', 'FOO', 'BAR'];
+ }
+
+ // Expectation when iterable is filtered
+ if ($filtered === true) {
+ $iterable = $iterable->filter($filter);
+
+ // empty string should be removed when iterable is filtered without callable
+ unset($expected[0]);
+
+ // empty string and "foo" should be removed otherwise
+ if (is_callable($filter)) {
+ unset($expected[1]);
+ }
+ }
+
+ assertSame($expected, $iterable->asArray());
+ }
+)->with($combinations);
+
+it('can filter first, then map', function (iterable $input): void {
+ $map =
+ /** @return mixed */
+ static function (string $value) {
+ $map = ['zero' => 0, 'one' => 1, 'two' => 2];
+
+ return $map[$value];
+ };
+
+ $iterableObject = iterable($input)->filter()->map($map);
+ assertInstanceOf(IterableObject::class, $iterableObject);
+ assertEquals([0, 1, 2], array_values($iterableObject->asArray()));
+})->with(function (): Generator {
+ $input = ['zero', 'one', 'two'];
+ yield [$input];
+ yield [
+ /** @return Generator */
+ (static function (array $input): Generator {
+ yield from $input;
+ })($input),
+ ];
+});
+
+it('strips key through values()', function (): void {
+ $input = ['x' => 'zero', 'y' => 'one', 'z' => 'two'];
+
+ $iterableObject = iterable($input)->values();
+ assertInstanceOf(IterableObject::class, $iterableObject);
+ assertEquals(['zero', 'one', 'two'], $iterableObject->asArray());
+});
diff --git a/tests/IterableReduceTest.php b/tests/IterableReduceTest.php
new file mode 100644
index 0000000..0001cc4
--- /dev/null
+++ b/tests/IterableReduceTest.php
@@ -0,0 +1,27 @@
+ 'foo', 2 => 'bar']);
+ assertEquals([0 => 'foo', 1 => 'bar'], iterable_to_array($iterator, false));
+});
+
+it('keeps the same array', function (): void {
+ $array = ['foo', 'bar'];
+ assertSame(['foo', 'bar'], iterable_to_array($array));
+});
+
+it('removes the keys of an array', function (): void {
+ $array = [1 => 'foo', 2 => 'bar'];
+ assertEquals([0 => 'foo', 1 => 'bar'], iterable_to_array($array, false));
+});
diff --git a/tests/IterableToTraversableTest.php b/tests/IterableToTraversableTest.php
new file mode 100644
index 0000000..688aa57
--- /dev/null
+++ b/tests/IterableToTraversableTest.php
@@ -0,0 +1,24 @@
+ 'bar']);
+ $traversable = iterable_to_traversable($iterator);
+ assertSame($iterator, $traversable);
+});
+
+it('converts an array to a traversable object', function (): void {
+ $array = ['foo' => 'bar'];
+ $traversable = iterable_to_traversable($array);
+ assertEquals(new ArrayIterator(['foo' => 'bar']), $traversable);
+});
diff --git a/tests/IterableValuesTest.php b/tests/IterableValuesTest.php
new file mode 100644
index 0000000..4a0c55b
--- /dev/null
+++ b/tests/IterableValuesTest.php
@@ -0,0 +1,48 @@
+ true];
+
+ // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedForeach
+ foreach (iterable_values($iterable) as $key => $value) {
+ }
+
+ if (! isset($key, $value)) {
+ Assert::fail('No values were returned');
+ }
+
+ assertSame(0, $key);
+ assertSame(true, $value);
+ }
+);
+
+it(
+ 'gets values of Traversable object',
+ function (): void {
+ $iterable = new ArrayIterator(['b' => true]);
+
+ // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedForeach
+ foreach (iterable_values($iterable) as $key => $value) {
+ }
+
+ if (! isset($key, $value)) {
+ Assert::fail('No values were returned');
+ }
+
+ assertSame(0, $key);
+ assertSame(true, $value);
+ }
+);
diff --git a/tests/TestIsIterable.php b/tests/TestIsIterable.php
deleted file mode 100644
index 8426ed8..0000000
--- a/tests/TestIsIterable.php
+++ /dev/null
@@ -1,43 +0,0 @@
-assertTrue(function_exists('is_iterable'));
- }
-
- public function testArrayIsIterable()
- {
- $array = array('foo', 'bar');
- $this->assertTrue(is_iterable($array));
- }
-
- public function testIteratorIsIterable()
- {
- $iterator = new DirectoryIterator(__DIR__);
- $this->assertTrue(is_iterable($iterator));
- }
-
- public function testScalarIsNotIterable()
- {
- $scalar = 'foobar';
- $this->assertFalse(is_iterable($scalar));
- }
-
- public function testObjectIsNotIterable()
- {
- $object = new \stdClass();
- $this->assertFalse(is_iterable($object));
- }
-
- public function testResourceIsNotIterable()
- {
- $resource = fopen('php://temp', 'rb');
- $this->assertFalse(is_iterable($resource));
- }
-
-}
diff --git a/tests/TestIterableFilter.php b/tests/TestIterableFilter.php
deleted file mode 100644
index cd9d22e..0000000
--- a/tests/TestIterableFilter.php
+++ /dev/null
@@ -1,34 +0,0 @@
-assertEquals(array(1 => 'bar'), iterable_to_array(iterable_filter($iterable, $filter)));
- }
-
- public function testTraversableFilter()
- {
- $iterable = SplFixedArray::fromArray(array('foo', 'bar'));
- $filter = function ($input) {
- return 'bar' === $input;
- };
- $this->assertEquals(array(1 => 'bar'), iterable_to_array(iterable_filter($iterable, $filter)));
- }
-
- /**
- * @expectedException InvalidArgumentException
- */
- public function testInvalidIterable()
- {
- $filter = function () {
- return true;
- };
- iterable_filter('foo', $filter);
- }
-}
\ No newline at end of file
diff --git a/tests/TestIterableMap.php b/tests/TestIterableMap.php
deleted file mode 100644
index 26d2811..0000000
--- a/tests/TestIterableMap.php
+++ /dev/null
@@ -1,30 +0,0 @@
-assertEquals(array('FOO', 'BAR'), iterable_to_array(iterable_map($iterable, $map)));
- }
-
- public function testTraversableMap()
- {
- $iterable = SplFixedArray::fromArray(array('foo', 'bar'));
- $map = 'strtoupper';
- $this->assertEquals(array('FOO', 'BAR'), iterable_to_array(iterable_map($iterable, $map)));
- }
-
- /**
- * @expectedException InvalidArgumentException
- */
- public function testInvalidIterable()
- {
- $filter = function () {
- return true;
- };
- iterable_map('foo', $filter);
- }
-}
\ No newline at end of file
diff --git a/tests/TestIterableObject.php b/tests/TestIterableObject.php
deleted file mode 100644
index 108a4ee..0000000
--- a/tests/TestIterableObject.php
+++ /dev/null
@@ -1,114 +0,0 @@
-assertInstanceOf('BenTools\IterableFunctions\IterableObject', $iterableObject);
- $this->assertEquals($expectedResult, iterator_to_array($iterableObject));
- }
- /**
- * @dataProvider dataProvider
- */
- public function testFromArrayToArray($data, $filter = null, $map = null, $expectedResult)
- {
- $iterableObject = iterable($data, $filter, $map);
- $this->assertInstanceOf('BenTools\IterableFunctions\IterableObject', $iterableObject);
- $this->assertEquals($expectedResult, $iterableObject->asArray());
- }
-
- /**
- * @dataProvider dataProvider
- */
- public function testFromTraversableToIterator($data, $filter = null, $map = null, $expectedResult)
- {
- $data = SplFixedArray::fromArray($data);
- $iterableObject = iterable($data, $filter, $map);
- $this->assertInstanceOf('BenTools\IterableFunctions\IterableObject', $iterableObject);
- $this->assertEquals($expectedResult, iterator_to_array($iterableObject));
- }
- /**
- * @dataProvider dataProvider
- */
- public function testFromTraversableToArray($data, $filter = null, $map = null, $expectedResult)
- {
- $data = SplFixedArray::fromArray($data);
- $iterableObject = iterable($data, $filter, $map);
- $this->assertInstanceOf('BenTools\IterableFunctions\IterableObject', $iterableObject);
- $this->assertEquals($expectedResult, $iterableObject->asArray());
- }
-
- public function testFilterMutator()
- {
- $filter = function ($value) {
- return 'bar' === $value;
- };
- $iterableObject = iterable(array('foo', 'bar'))->withFilter($filter);
- $this->assertEquals(array(1 => 'bar'), iterator_to_array($iterableObject));
- }
-
- public function testMapMutator()
- {
- $map = 'strtoupper';
- $iterableObject = iterable(array('foo', 'bar'))->withMap($map);
- $this->assertEquals(array('FOO', 'BAR'), iterator_to_array($iterableObject));
- }
-
- public function testFilterAndMapMutators()
- {
- $filter = function ($value) {
- return 'bar' === $value;
- };
- $map = 'strtoupper';
- $iterableObject = iterable(array('foo', 'bar'))->withMap($map)->withFilter($filter);
- $this->assertEquals(array(1 => 'BAR'), iterator_to_array($iterableObject));
- $iterableObject = iterable(array('foo', 'bar'))->map($map)->filter($filter);
- $this->assertEquals(array(1 => 'BAR'), iterator_to_array($iterableObject));
- $iterableObject = iterable(array('foo', 'bar'))->withFilter($filter)->withMap($map);
- $this->assertEquals(array(1 => 'BAR'), iterator_to_array($iterableObject));
- $iterableObject = iterable(array('foo', 'bar'))->filter($filter)->map($map);
- $this->assertEquals(array(1 => 'BAR'), iterator_to_array($iterableObject));
- }
-
- public function dataProvider()
- {
- $data = array('foo', 'bar');
- $filter = function ($value) {
- return 'bar' === $value;
- };
- $map = 'strtoupper';
-
- return array(
- array(
- $data,
- null,
- null,
- array('foo', 'bar')
- ),
- array(
- $data,
- $filter,
- null,
- array(1 => 'bar')
- ),
- array(
- $data,
- null,
- $map,
- array('FOO', 'BAR')
- ),
- array(
- $data,
- $filter,
- $map,
- array(1 => 'BAR')
- ),
- );
- }
-
-}
diff --git a/tests/TestIterableReduce.php b/tests/TestIterableReduce.php
deleted file mode 100644
index 99b7709..0000000
--- a/tests/TestIterableReduce.php
+++ /dev/null
@@ -1,24 +0,0 @@
-assertTrue(function_exists('iterable_to_array'));
- }
-
- public function testIteratorToArray()
- {
- $iterator = new ArrayIterator(array('foo', 'bar'));
- $this->assertEquals(array('foo', 'bar'), iterable_to_array($iterator));
- }
-
- public function testIteratorWithoutKeysToArray()
- {
- $iterator = new ArrayIterator(array(1 => 'foo', 2 => 'bar'));
- $this->assertEquals(array(0 => 'foo', 1 => 'bar'), iterable_to_array($iterator, false));
- }
-
- public function testArrayToArray()
- {
- $array = array('foo', 'bar');
- $this->assertEquals(array('foo', 'bar'), iterable_to_array($array));
- }
-
- public function testArrayWithoutKeysToArray()
- {
- $array = array(1 => 'foo', 2 => 'bar');
- $this->assertEquals(array(0 => 'foo', 1 => 'bar'), iterable_to_array($array, false));
- }
-
- public function testScalarToArray()
- {
- $scalar = 'foobar';
- $this->assertTrue($this->triggersError($scalar));
- }
-
- public function testObjectToArray()
- {
- $object = new stdClass();
- $this->assertTrue($this->triggersError($object));
- }
-
- public function testResourceToArray()
- {
- $resource = fopen('php://temp', 'rb');
- $this->assertTrue($this->triggersError($resource));
- }
-
- private function triggersError($input)
- {
- return version_compare(PHP_VERSION, '7.0.0') >= 0 ? $this->triggersErrorPHP7($input) : $this->triggersErrorPHP5($input);
- }
-
- private function triggersErrorPHP7($input)
- {
- $errorOccured = false;
-
- try {
- iterable_to_array($input);
- }
- catch (\TypeError $e) {
- $errorOccured = true;
- }
-
- return $errorOccured;
- }
-
- private function triggersErrorPHP5($input)
- {
- $errorOccured = false;
-
- set_error_handler(function ($errno) {
- return E_RECOVERABLE_ERROR === $errno;
- });
-
- if (false === @iterable_to_array($input)) {
- $errorOccured = true;
- }
-
- restore_error_handler();
-
- return $errorOccured;
- }
-
-}
diff --git a/tests/TestIterableToTraversable.php b/tests/TestIterableToTraversable.php
deleted file mode 100644
index 2038065..0000000
--- a/tests/TestIterableToTraversable.php
+++ /dev/null
@@ -1,38 +0,0 @@
-assertTrue(function_exists('iterable_to_traversable'));
- }
-
- public function testIteratorToTraversable()
- {
- $iterator = new ArrayIterator(array('foo' => 'bar'));
- $traversable = iterable_to_traversable($iterator);
- $this->assertSame($iterator, $traversable);
- $this->assertInstanceOf('Traversable', $iterator);
- }
-
- public function testArrayToTraversable()
- {
- $array = array('foo' => 'bar');
- $traversable = iterable_to_traversable($array);
- $this->assertEquals(new ArrayIterator(array('foo' => 'bar')), $traversable);
- $this->assertInstanceOf('Traversable', $traversable);
- }
-
- /**
- * @expectedException InvalidArgumentException
- */
- public function testInvalidArgument()
- {
- $string = 'foo';
- iterable_to_traversable($string);
- var_dump(iterable_to_traversable(array('foo', 'bar')));
- }
-}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..b0ccdc7
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,7 @@
+