diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml deleted file mode 100644 index c79cbb6..0000000 --- a/.github/workflows/php.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: PHP Composer - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - strategy: - matrix: - phpv: [ 5.6, 7.4 ] - - steps: - - uses: actions/checkout@v2 - - - name: Setup PHP with PECL extension - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.phpv }} - - - name: Validate composer.json and composer.lock - run: composer validate - - - name: Install dependencies - run: composer install --prefer-dist --no-progress --no-suggest - - - name: Run test suite - run: composer run-script test diff --git a/.github/workflows/project-ci.yml b/.github/workflows/project-ci.yml new file mode 100644 index 0000000..88df7f0 --- /dev/null +++ b/.github/workflows/project-ci.yml @@ -0,0 +1,28 @@ +name: Unit tests & static analysis + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + check: + runs-on: ubuntu-latest + strategy: + matrix: + php_version: [ "8.2", "8.3", "8.4" ] + container: + image: ghcr.io/shoppingflux/php:${{ matrix.php_version }}-unit + env: + COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }} + XDEBUG_MODE: off + credentials: + username: ${{ secrets.CR_USER }} + password: ${{ secrets.CR_PAT }} + steps: + - name: Composer install + uses: shoppingflux/github-actions/composer-install@main + + - name: Run test script + run: composer test diff --git a/.github/workflows/project-sonar.yml b/.github/workflows/project-sonar.yml new file mode 100644 index 0000000..4dd41c2 --- /dev/null +++ b/.github/workflows/project-sonar.yml @@ -0,0 +1,26 @@ +name: Sonarcloud reporting + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + sonarcloud: + runs-on: ubuntu-latest + container: + image: ghcr.io/shoppingflux/php:8.2-unit + env: + COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }} + XDEBUG_MODE: coverage + credentials: + username: ${{ secrets.CR_USER }} + password: ${{ secrets.CR_PAT }} + + steps: + - name: Sonarcloud scan + uses: shoppingflux/github-actions/sonarcloud@main + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + sonar-token: ${{ secrets.SONARCLOUD_TOKEN }} \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 6a6eb8a..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -# Iterator Library \ No newline at end of file diff --git a/composer.json b/composer.json index 3ac637a..5dd1c15 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "license": "MIT", "description": "Provide reusable iterators", "require": { - "php": ">=5.6" + "php": ">=8.2" }, "autoload": { "psr-4": { @@ -11,17 +11,27 @@ } }, "require-dev": { - "phpunit/phpunit": "^5.7", - "shoppingfeed/coding-style-php": "~2.1.0", - "overtrue/phplint": "^1.1" + "phpunit/phpunit": "^11.0", + "shoppingfeed/coding-style-php": "^2.1.0", + "overtrue/phplint": "^9.1", + "phpstan/phpstan": "^2.1" }, "scripts": { - "phpunit": "vendor/bin/phpunit --testsuite=unit", - "phpcs": "vendor/bin/sfcs src --progress -vvv", - "phpcsfix": "vendor/bin/sfcs src --progress -vvv --autofix", + "phpunit": "vendor/bin/phpunit", + "phpcs": "vendor/bin/sfcs --progress -vvv -- src test", + "phpcsfix": "vendor/bin/sfcs --progress -vvv --autofix -- src test", + "phplint": "vendor/bin/phplint src --cache=build/phplint.cache", + "phpstan": "vendor/bin/phpstan analyse --memory-limit=1G", "test": [ + "@phplint", "@phpunit", + "@phpstan", "@phpcs" ] + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": false + } } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..dc40d2c --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,115 @@ +parameters: + ignoreErrors: + - + message: '#^Method ShoppingFeed\\Iterator\\AbstractIterator\:\:toArray\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/AbstractIterator.php + + - + message: '#^Property ShoppingFeed\\Iterator\\AbstractIterator\:\:\$items type has no value type specified in iterable type Traversable\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/AbstractIterator.php + + - + message: '#^Property ShoppingFeed\\Iterator\\AbstractIterator\:\:\$items type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/AbstractIterator.php + + - + message: '#^Property ShoppingFeed\\Iterator\\AbstractIterator\:\:\$items type has no value type specified in iterable type array\|Traversable\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/AbstractIterator.php + + - + message: '#^Method ShoppingFeed\\Iterator\\CallbackIterator\:\:__construct\(\) has parameter \$arrayOrIterator with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: src/CallbackIterator.php + + - + message: '#^Property ShoppingFeed\\Iterator\\CallbackIterator\:\:\$iterator \(Iterator\) does not accept Traversable\.$#' + identifier: assign.propertyType + count: 1 + path: src/CallbackIterator.php + + - + message: '#^Interface ShoppingFeed\\Iterator\\CountableTraversable extends generic interface Traversable but does not specify its types\: TKey, TValue$#' + identifier: missingType.generics + count: 1 + path: src/CountableTraversable.php + + - + message: '#^Interface ShoppingFeed\\Iterator\\CountableTraversable has type alias CountableIterable with no value type specified in iterable type ShoppingFeed\\Iterator\\CountableTraversable\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/CountableTraversable.php + + - + message: '#^Interface ShoppingFeed\\Iterator\\CountableTraversable has type alias CountableIterable with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/CountableTraversable.php + + - + message: '#^Interface ShoppingFeed\\Iterator\\CountableTraversable has type alias CountableIterable with no value type specified in iterable type array\|ShoppingFeed\\Iterator\\CountableTraversable\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/CountableTraversable.php + + - + message: '#^Method ShoppingFeed\\Iterator\\FilterAggregateIterator\:\:__construct\(\) has parameter \$arrayOrTraversable with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: src/FilterAggregateIterator.php + + - + message: '#^Parameter \#1 \$value of function count expects array\|Countable, array\|Traversable given\.$#' + identifier: argument.type + count: 1 + path: src/FilterAggregateIterator.php + + - + message: '#^Interface ShoppingFeed\\Iterator\\IteratorInterface extends generic interface IteratorAggregate but does not specify its types\: TKey, TValue$#' + identifier: missingType.generics + count: 1 + path: src/IteratorInterface.php + + - + message: '#^Method ShoppingFeed\\Iterator\\IteratorInterface\:\:toArray\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/IteratorInterface.php + + - + message: '#^Instanceof between Traversable and Traversable will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue + count: 1 + path: src/KeyThenValueIterator.php + + - + message: '#^Method ShoppingFeed\\Iterator\\KeyThenValueIterator\:\:__construct\(\) has parameter \$iterable with no value type specified in iterable type Traversable\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/KeyThenValueIterator.php + + - + message: '#^Method ShoppingFeed\\Iterator\\KeyThenValueIterator\:\:__construct\(\) has parameter \$iterable with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/KeyThenValueIterator.php + + - + message: '#^Method ShoppingFeed\\Iterator\\KeyThenValueIterator\:\:__construct\(\) has parameter \$iterable with no value type specified in iterable type array\|Traversable\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/KeyThenValueIterator.php + + - + message: '#^Result of && is always false\.$#' + identifier: booleanAnd.alwaysFalse + count: 1 + path: src/KeyThenValueIterator.php \ No newline at end of file diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..672cb44 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,7 @@ +parameters: + paths: + - src + level: 8 + +includes: + - phpstan-baseline.neon diff --git a/phpunit.xml b/phpunit.xml index 704aab8..d11f31e 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,13 +1,13 @@ - - - - ./tests/unit - - - - - - src - - + + + + + test/unit + + + + + src/ + + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..4ff5853 --- /dev/null +++ b/readme.md @@ -0,0 +1,19 @@ +# shoppingfeed/iterator + +### Installation + +``` +composer require shoppingfeed/iterator +``` + +### Contributing + +To connect to a php 8.2 container correctly configured + +- Create a container : `docker run --name iterator-php -v $PWD:/var/www -d ghcr.io/shoppingflux/php:8.2-unit` +- Connect to container : `docker exec -it iterator-php bash` + +Once connected to the container you can : + +- Update composer dependencies : `composer update` +- Run test : `composer test` diff --git a/sonar-project.properties b/sonar-project.properties index a3bd6a6..13c3507 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,3 +1,5 @@ +sonar.organization=shoppingfeed +sonar.projectKey=shoppingflux_iterator sonar.sources=./src sonar.php.coverage.reportPaths=build/clover.xml sonar.php.tests.reportPath=build/junit.xml diff --git a/src/AbstractIterator.php b/src/AbstractIterator.php index d676109..2944f24 100644 --- a/src/AbstractIterator.php +++ b/src/AbstractIterator.php @@ -1,11 +1,10 @@ callback = $callback; } - #[\ReturnTypeWillChange] + #[ReturnTypeWillChange] public function getIterator() { foreach ($this->iterator as $key => $item) { diff --git a/src/CountableTraversable.php b/src/CountableTraversable.php index 03be92a..88bf18d 100644 --- a/src/CountableTraversable.php +++ b/src/CountableTraversable.php @@ -15,5 +15,4 @@ */ interface CountableTraversable extends Traversable, Countable { - } diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index a6270a2..f9e38cb 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -1,6 +1,9 @@ items = $arrayOrTraversable; @@ -21,7 +20,7 @@ public function __construct($arrayOrTraversable) /** * @return \Generator */ - #[\ReturnTypeWillChange] + #[ReturnTypeWillChange] public function getIterator() { foreach ($this->items as $key => $item) { @@ -36,7 +35,7 @@ public function getIterator() /** * @return int */ - #[\ReturnTypeWillChange] + #[ReturnTypeWillChange] public function count() { return count($this->items); diff --git a/src/FilterAggregateIteratorInterface.php b/src/FilterAggregateIteratorInterface.php index dcd024e..fbef6e9 100644 --- a/src/FilterAggregateIteratorInterface.php +++ b/src/FilterAggregateIteratorInterface.php @@ -1,4 +1,5 @@ items = $iterable; } - #[\ReturnTypeWillChange] + #[ReturnTypeWillChange] public function getIterator() { foreach ($this->items as $key => $value) { diff --git a/tests/unit/CallbackIteratorTest.php b/test/unit/CallbackIteratorTest.php similarity index 66% rename from tests/unit/CallbackIteratorTest.php rename to test/unit/CallbackIteratorTest.php index eb8ab23..92d7e59 100644 --- a/tests/unit/CallbackIteratorTest.php +++ b/test/unit/CallbackIteratorTest.php @@ -1,22 +1,27 @@ iterator = $this->createMock('\Iterator'); $this->instance = new CallbackIterator( $this->iterator, - [$this, 'toLowerCallback'] + [$this, 'toLowerCallback'], ); } @@ -24,36 +29,35 @@ public function testConstructWithArray() { $instance = new CallbackIterator( ['element1', 'element2'], - [$this, 'toLowerCallback'] + [$this, 'toLowerCallback'], ); $this->assertInstanceOf( CallbackIterator::class, - $instance + $instance, ); } public function testConstructWithIteratorAggregate() { $expected = ['foo', 'bar', 'baz']; - $iteratorAggregate = $this->createMock(\IteratorAggregate::class); + $iteratorAggregate = $this->createMock(IteratorAggregate::class); $iteratorAggregate ->expects($this->once()) ->method('getIterator') - ->willReturn(new \ArrayIterator($expected)); + ->willReturn(new ArrayIterator($expected)); $instance = new CallbackIterator($iteratorAggregate, [$this, 'toLowerCallback']); $this->assertSame($expected, $instance->toArray()); } - /** - * @expectedException \InvalidArgumentException - */ public function testConstructWithInvalidValidator() { + $this->expectException(InvalidArgumentException::class); + new CallbackIterator( 'invalidParameter', - [$this, 'toLowerCallback'] + [$this, 'toLowerCallback'], ); } @@ -61,17 +65,17 @@ public function testImplementsIteratorAggregate() { $iterator = new CallbackIterator( ['TOTO', 'TITI'], - self::class . '::toLowerStatic' + self::class . '::toLowerStatic', ); - $this->assertInstanceOf(\IteratorAggregate::class, $iterator); + $this->assertInstanceOf(IteratorAggregate::class, $iterator); } public function testIteratesWithCallback() { $iterator = new CallbackIterator( ['TOTO', 'TITI'], - self::class . '::toLowerStatic' + self::class . '::toLowerStatic', ); $this->assertSame(['toto', 'titi'], $iterator->toArray()); diff --git a/tests/unit/FilterAggregateIteratorTest.php b/test/unit/FilterAggregateIteratorTest.php similarity index 88% rename from tests/unit/FilterAggregateIteratorTest.php rename to test/unit/FilterAggregateIteratorTest.php index ae26d13..60f9c1d 100644 --- a/tests/unit/FilterAggregateIteratorTest.php +++ b/test/unit/FilterAggregateIteratorTest.php @@ -1,7 +1,11 @@ assertSame($array, (new FilterAggregateIterator(new \ArrayObject($array)))->toArray()); + $this->assertSame($array, (new FilterAggregateIterator(new ArrayObject($array)))->toArray()); } - public function testAddFilterIsFluent() { $instance = new FilterAggregateIterator([]); diff --git a/tests/unit/KeyThenValueIteratorTest.php b/test/unit/KeyThenValueIteratorTest.php similarity index 77% rename from tests/unit/KeyThenValueIteratorTest.php rename to test/unit/KeyThenValueIteratorTest.php index ee1ff8b..5079702 100644 --- a/tests/unit/KeyThenValueIteratorTest.php +++ b/test/unit/KeyThenValueIteratorTest.php @@ -1,7 +1,10 @@ expectException(Exception\InvalidArgumentException::class); $this->expectExceptionMessage( - 'Argument 1 passed to ShoppingFeed\Iterator\KeyThenValueIterator::__construct '. - 'must be an array or an instance of \Traversable' + 'Argument 1 passed to ShoppingFeed\Iterator\KeyThenValueIterator::__construct ' . + 'must be an array or an instance of \Traversable', ); new KeyThenValueIterator('foo'); diff --git a/tests/unit/bootstrap.php b/tests/unit/bootstrap.php deleted file mode 100644 index fc4ceba..0000000 --- a/tests/unit/bootstrap.php +++ /dev/null @@ -1,5 +0,0 @@ -