From e0244cf537e634318691cce22bcf25a06b7293fd Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Sun, 10 Mar 2024 15:00:39 +0400 Subject: [PATCH 01/25] Init commit --- .editorconfig | 24 ++ .gitattributes | 33 ++ .github/workflows/main.yml | 140 ++++++ .gitignore | 19 + .phan.php | 23 + LICENSE | 2 +- Makefile | 27 ++ README.md | 37 +- build/.gitkeep | 0 composer.json | 61 +++ csv-blueprint | 43 ++ phpunit.xml.dist | 53 +++ src/Commands/CreateCsv.php | 61 +++ src/Commands/CreateSchema.php | 61 +++ src/Commands/ValidateDir.php | 52 +++ src/Commands/ValidateFile.php | 52 +++ src/Csv/Column.php | 128 ++++++ src/Csv/CsvFile.php | 159 +++++++ src/Csv/Exception.php | 21 + src/Csv/ParseConfig.php | 135 ++++++ src/Exception.php | 21 + src/Schema.php | 142 ++++++ src/Utils.php | 47 ++ src/Validators/Exception.php | 21 + src/Validators/Rules/AbstarctRule.php | 92 ++++ src/Validators/Rules/AllowValues.php | 36 ++ src/Validators/Rules/DateFormat.php | 39 ++ src/Validators/Rules/ExactValue.php | 29 ++ src/Validators/Rules/IsBool.php | 30 ++ src/Validators/Rules/IsDomain.php | 31 ++ src/Validators/Rules/IsEmail.php | 29 ++ src/Validators/Rules/IsFloat.php | 29 ++ src/Validators/Rules/IsInt.php | 29 ++ src/Validators/Rules/IsIp.php | 29 ++ src/Validators/Rules/IsLatitude.php | 35 ++ src/Validators/Rules/IsLongitude.php | 35 ++ src/Validators/Rules/IsUrl.php | 29 ++ src/Validators/Rules/Max.php | 34 ++ src/Validators/Rules/MaxDate.php | 33 ++ src/Validators/Rules/MaxLength.php | 31 ++ src/Validators/Rules/Min.php | 34 ++ src/Validators/Rules/MinDate.php | 33 ++ src/Validators/Rules/MinLength.php | 31 ++ src/Validators/Rules/NotEmpty.php | 29 ++ src/Validators/Rules/OnlyCapitalize.php | 29 ++ src/Validators/Rules/OnlyLowercase.php | 29 ++ src/Validators/Rules/OnlyTrimed.php | 29 ++ src/Validators/Rules/OnlyUppercase.php | 29 ++ src/Validators/Rules/Precision.php | 43 ++ src/Validators/Rules/Regex.php | 32 ++ src/Validators/Rules/RuleException.php | 21 + src/Validators/Ruleset.php | 61 +++ src/Validators/Validator.php | 35 ++ tests/CsvBlueprintPackageTest.php | 22 + tests/CsvBlueprintPhpStormProxyTest.php | 21 + tests/CsvReaderTest.php | 79 ++++ tests/RulesTest.php | 552 ++++++++++++++++++++++++ tests/SchemaTest.php | 224 ++++++++++ tests/UtilsTest.php | 45 ++ tests/ValidatorTest.php | 436 +++++++++++++++++++ tests/autoload.php | 25 ++ tests/fixtures/complex_header.csv | 101 +++++ tests/fixtures/complex_no_header.csv | 100 +++++ tests/fixtures/empty_header.csv | 1 + tests/fixtures/empty_no_header.csv | 0 tests/fixtures/simple_header.csv | 4 + tests/fixtures/simple_no_header.csv | 3 + tests/schemas/example_empty.yml | 17 + tests/schemas/example_full.yml | 201 +++++++++ tests/schemas/ready.yml | 55 +++ tests/schemas/simple_header.yml | 19 + tests/schemas/simple_no_header.yml | 20 + 72 files changed, 4290 insertions(+), 2 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/workflows/main.yml create mode 100644 .gitignore create mode 100644 .phan.php create mode 100644 Makefile create mode 100644 build/.gitkeep create mode 100644 composer.json create mode 100644 csv-blueprint create mode 100644 phpunit.xml.dist create mode 100644 src/Commands/CreateCsv.php create mode 100644 src/Commands/CreateSchema.php create mode 100644 src/Commands/ValidateDir.php create mode 100644 src/Commands/ValidateFile.php create mode 100644 src/Csv/Column.php create mode 100644 src/Csv/CsvFile.php create mode 100644 src/Csv/Exception.php create mode 100644 src/Csv/ParseConfig.php create mode 100644 src/Exception.php create mode 100644 src/Schema.php create mode 100644 src/Utils.php create mode 100644 src/Validators/Exception.php create mode 100644 src/Validators/Rules/AbstarctRule.php create mode 100644 src/Validators/Rules/AllowValues.php create mode 100644 src/Validators/Rules/DateFormat.php create mode 100644 src/Validators/Rules/ExactValue.php create mode 100644 src/Validators/Rules/IsBool.php create mode 100644 src/Validators/Rules/IsDomain.php create mode 100644 src/Validators/Rules/IsEmail.php create mode 100644 src/Validators/Rules/IsFloat.php create mode 100644 src/Validators/Rules/IsInt.php create mode 100644 src/Validators/Rules/IsIp.php create mode 100644 src/Validators/Rules/IsLatitude.php create mode 100644 src/Validators/Rules/IsLongitude.php create mode 100644 src/Validators/Rules/IsUrl.php create mode 100644 src/Validators/Rules/Max.php create mode 100644 src/Validators/Rules/MaxDate.php create mode 100644 src/Validators/Rules/MaxLength.php create mode 100644 src/Validators/Rules/Min.php create mode 100644 src/Validators/Rules/MinDate.php create mode 100644 src/Validators/Rules/MinLength.php create mode 100644 src/Validators/Rules/NotEmpty.php create mode 100644 src/Validators/Rules/OnlyCapitalize.php create mode 100644 src/Validators/Rules/OnlyLowercase.php create mode 100644 src/Validators/Rules/OnlyTrimed.php create mode 100644 src/Validators/Rules/OnlyUppercase.php create mode 100644 src/Validators/Rules/Precision.php create mode 100644 src/Validators/Rules/Regex.php create mode 100644 src/Validators/Rules/RuleException.php create mode 100644 src/Validators/Ruleset.php create mode 100644 src/Validators/Validator.php create mode 100644 tests/CsvBlueprintPackageTest.php create mode 100644 tests/CsvBlueprintPhpStormProxyTest.php create mode 100644 tests/CsvReaderTest.php create mode 100644 tests/RulesTest.php create mode 100644 tests/SchemaTest.php create mode 100644 tests/UtilsTest.php create mode 100644 tests/ValidatorTest.php create mode 100644 tests/autoload.php create mode 100644 tests/fixtures/complex_header.csv create mode 100644 tests/fixtures/complex_no_header.csv create mode 100644 tests/fixtures/empty_header.csv create mode 100644 tests/fixtures/empty_no_header.csv create mode 100644 tests/fixtures/simple_header.csv create mode 100644 tests/fixtures/simple_no_header.csv create mode 100644 tests/schemas/example_empty.yml create mode 100644 tests/schemas/example_full.yml create mode 100644 tests/schemas/ready.yml create mode 100644 tests/schemas/simple_header.yml create mode 100644 tests/schemas/simple_no_header.yml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..03ed54b6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,24 @@ +# +# JBZoo Toolbox - Csv-Blueprint. +# +# This file is part of the JBZoo Toolbox project. +# For the full copyright and license information, please view the LICENSE +# file that was distributed with this source code. +# +# @license MIT +# @copyright Copyright (C) JBZoo.com, All rights reserved. +# @see https://github.com/JBZoo/Csv-Blueprint +# + +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..0e97e3d1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,33 @@ +# +# JBZoo Toolbox - Csv-Blueprint. +# +# This file is part of the JBZoo Toolbox project. +# For the full copyright and license information, please view the LICENSE +# file that was distributed with this source code. +# +# @license MIT +# @copyright Copyright (C) JBZoo.com, All rights reserved. +# @see https://github.com/JBZoo/Csv-Blueprint +# + +/.github export-ignore +/build export-ignore +/tests export-ignore +/.editorconfig export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.phan.php export-ignore +/.travis.yml export-ignore +/composer.lock export-ignore +/phpunit.xml.dist export-ignore +/Makefile export-ignore + +* text eol=lf + +# (binary is a macro for -text -diff) +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.ttf binary diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..5cf213ea --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,140 @@ +# +# JBZoo Toolbox - Csv-Blueprint. +# +# This file is part of the JBZoo Toolbox project. +# For the full copyright and license information, please view the LICENSE +# file that was distributed with this source code. +# +# @license MIT +# @copyright Copyright (C) JBZoo.com, All rights reserved. +# @see https://github.com/JBZoo/Csv-Blueprint +# + +name: CI + +on: + pull_request: + branches: + - "*" + push: + branches: + - 'master' + schedule: + - cron: '12 */8 * * *' + +env: + COLUMNS: 120 + TERM_PROGRAM: Hyper + +jobs: + phpunit: + name: PHPUnit + runs-on: ubuntu-latest + env: + JBZOO_COMPOSER_UPDATE_FLAGS: ${{ matrix.composer_flags }} + strategy: + matrix: + php-version: [ 8.1, 8.2, 8.3 ] + coverage: [ xdebug, none ] + composer_flags: [ "--prefer-lowest", "" ] + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + coverage: ${{ matrix.coverage }} + tools: composer + extensions: ast + + - name: Build the Project + run: make update --no-print-directory + + - name: ๐Ÿงช PHPUnit Tests + run: make test --no-print-directory + + - name: Uploading coverage to coveralls + if: ${{ matrix.coverage == 'xdebug' }} + continue-on-error: true + env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: make report-coveralls --no-print-directory || true + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + continue-on-error: true + with: + name: PHPUnit - ${{ matrix.php-version }} - ${{ matrix.coverage }} + path: build/ + + + linters: + name: Linters + runs-on: ubuntu-latest + strategy: + matrix: + php-version: [ 8.1, 8.2, 8.3 ] + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + coverage: none + tools: composer + extensions: ast + + - name: Build the Project + run: make update --no-print-directory + + - name: ๐Ÿ‘ Code Quality + run: make codestyle --no-print-directory + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + continue-on-error: true + with: + name: Linters - ${{ matrix.php-version }} + path: build/ + + + report: + name: Reports + runs-on: ubuntu-latest + strategy: + matrix: + php-version: [ 8.1, 8.2, 8.3 ] + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + coverage: xdebug + tools: composer + extensions: ast + + - name: Build the Project + run: make update --no-print-directory + + - name: ๐Ÿ“ Build Reports + run: make report-all --no-print-directory + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + continue-on-error: true + with: + name: Reports - ${{ matrix.php-version }} + path: build/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9fbada9a --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# +# JBZoo Toolbox - Csv-Blueprint. +# +# This file is part of the JBZoo Toolbox project. +# For the full copyright and license information, please view the LICENSE +# file that was distributed with this source code. +# +# @license MIT +# @copyright Copyright (C) JBZoo.com, All rights reserved. +# @see https://github.com/JBZoo/Csv-Blueprint +# + +.idea +.DS_Store +build +vendor +phpunit.xml +composer.lock +*.cache diff --git a/.phan.php b/.phan.php new file mode 100644 index 00000000..64820b43 --- /dev/null +++ b/.phan.php @@ -0,0 +1,23 @@ + [ + 'src', + ], +]); diff --git a/LICENSE b/LICENSE index fcc81725..1183d723 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 JBZoo Toolbox +Copyright (c) 2020 JBZoo Toolbox for developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..0cbdad5b --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +# +# JBZoo Toolbox - Csv-Blueprint. +# +# This file is part of the JBZoo Toolbox project. +# For the full copyright and license information, please view the LICENSE +# file that was distributed with this source code. +# +# @license MIT +# @copyright Copyright (C) JBZoo.com, All rights reserved. +# @see https://github.com/JBZoo/Csv-Blueprint +# + + +ifneq (, $(wildcard ./vendor/jbzoo/codestyle/src/init.Makefile)) + include ./vendor/jbzoo/codestyle/src/init.Makefile +endif + + +update: ##@Project Install/Update all 3rd party dependencies + $(call title,"Install/Update all 3rd party dependencies") + @echo "Composer flags: $(JBZOO_COMPOSER_UPDATE_FLAGS)" + @composer update $(JBZOO_COMPOSER_UPDATE_FLAGS) + + +test-all: ##@Project Run all project tests at once + @make test + @make codestyle diff --git a/README.md b/README.md index 7483a948..48be9939 100644 --- a/README.md +++ b/README.md @@ -1 +1,36 @@ -# csv-validator \ No newline at end of file +# JBZoo / Csv-Blueprint + +[![CI](https://github.com/JBZoo/Csv-Blueprint/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/JBZoo/Csv-Blueprint/actions/workflows/main.yml?query=branch%3Amaster) [![Coverage Status](https://coveralls.io/repos/github/JBZoo/Csv-Blueprint/badge.svg?branch=master)](https://coveralls.io/github/JBZoo/Csv-Blueprint?branch=master) [![Psalm Coverage](https://shepherd.dev/github/JBZoo/Csv-Blueprint/coverage.svg)](https://shepherd.dev/github/JBZoo/Csv-Blueprint) [![Psalm Level](https://shepherd.dev/github/JBZoo/Csv-Blueprint/level.svg)](https://shepherd.dev/github/JBZoo/Csv-Blueprint) [![CodeFactor](https://www.codefactor.io/repository/github/jbzoo/csv-blueprint/badge)](https://www.codefactor.io/repository/github/jbzoo/csv-blueprint/issues) +[![Stable Version](https://poser.pugx.org/jbzoo/csv-blueprint/version)](https://packagist.org/packages/jbzoo/csv-blueprint/) [![Total Downloads](https://poser.pugx.org/jbzoo/csv-blueprint/downloads)](https://packagist.org/packages/jbzoo/csv-blueprint/stats) [![Dependents](https://poser.pugx.org/jbzoo/csv-blueprint/dependents)](https://packagist.org/packages/jbzoo/csv-blueprint/dependents?order_by=downloads) [![GitHub License](https://img.shields.io/github/license/jbzoo/csv-blueprint)](https://github.com/JBZoo/Csv-Blueprint/blob/master/LICENSE) + + + + +### Installing + +```sh +composer require jbzoo/csv-blueprint +``` + + +### Usage + +```php +use JBZoo\CsvBlueprint\Csv-Blueprint; + +// Just use it! +$object = new Csv-Blueprint(); +$object->doSomeStreetMagic(':)'); +``` + + +## Unit tests and check code style +```sh +make update +make test-all +``` + + +### License + +MIT diff --git a/build/.gitkeep b/build/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..3d98946e --- /dev/null +++ b/composer.json @@ -0,0 +1,61 @@ +{ + "name" : "jbzoo/csv-blueprint", + "type" : "library", + "description" : "CLI Utility for Validating and Generating CSV Files Based on Custom Rules. It ensures your data meets specified criteria, streamlining data management and integrity checks.", + "license" : "MIT", + "keywords" : [ + "jbzoo", + "csv", + "csv-validator", + "csv-validation", + "csv-generation", + "csv-format", + "csv-rules", + "csv-schema" + ], + + "authors" : [ + { + "name" : "Denis Smetannikov", + "email" : "admin@jbzoo.com", + "role" : "lead" + } + ], + + "minimum-stability" : "dev", + "prefer-stable" : true, + + "require" : { + "php" : "^8.1", + "ext-mbstring" : "*", + "jbzoo/data" : "^7.1", + "jbzoo/cli" : "^7.1", + "league/csv" : "^9.15", + "fakerphp/faker" : "^1.23", + "jbzoo/utils" : "^7.1" + }, + + "require-dev" : { + "roave/security-advisories" : "dev-latest", + "jbzoo/toolbox-dev" : "^7.1" + }, + + "autoload" : { + "psr-4" : {"JBZoo\\CsvBlueprint\\" : "src"} + }, + + "autoload-dev" : { + "psr-4" : {"JBZoo\\PHPUnit\\" : "tests"} + }, + + "config" : { + "optimize-autoloader" : true, + "allow-plugins" : {"composer/package-versions-deprecated" : true} + }, + + "extra" : { + "branch-alias" : { + "dev-master" : "7.x-dev" + } + } +} diff --git a/csv-blueprint b/csv-blueprint new file mode 100644 index 00000000..c98a086a --- /dev/null +++ b/csv-blueprint @@ -0,0 +1,43 @@ +#!/usr/bin/env php +registerCommandsByPath(PATH_ROOT . '/src/Commands', __NAMESPACE__) + ->setLogo( + <<<'EOF' + _____ ______ _ _ _ + / __ \ | ___ \ | (_) | | + | / \/_____ __ | |_/ / |_ _ ___ _ __ _ __ _ _ __ | |_ + | | / __\ \ / / | ___ \ | | | |/ _ \ '_ \| '__| | '_ \| __| + | \__/\__ \\ V / | |_/ / | |_| | __/ |_) | | | | | | | |_ + \____/___/ \_/ \____/|_|\__,_|\___| .__/|_| |_|_| |_|\__| + | | + |_| + EOF, + ) + ->run(); diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 00000000..9ca3124e --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,53 @@ + + + + + + src + + + + + + + + + + + + tests + + + + + + + diff --git a/src/Commands/CreateCsv.php b/src/Commands/CreateCsv.php new file mode 100644 index 00000000..a0591666 --- /dev/null +++ b/src/Commands/CreateCsv.php @@ -0,0 +1,61 @@ +setName('create:csv') + ->setDescription('Generate random CSV file by rules from yml file. Data based on fakerphp/faker library.') + ->addArgument( + 'rule-file', + InputArgument::REQUIRED, + 'Path to rule file (yml)', + ) + ->addOption( + 'seed', + null, + InputOption::VALUE_OPTIONAL, + 'Seed for random data generation (fakerphp/faker library)', + ) + ->addOption( + 'to-file', + null, + InputOption::VALUE_OPTIONAL, + 'If set, the generated CSV will be saved to the specified file. ' + . 'Otherwise, the result will be output to the console as STDOUT.', + ); + + parent::configure(); + } + + protected function executeAction(): int + { + $yml = '/Users/smetdenis/Work/projects/jbzoo-csv-validator/tests/rules/example.yml'; + dump(yml($yml)); + + return self::SUCCESS; + } +} diff --git a/src/Commands/CreateSchema.php b/src/Commands/CreateSchema.php new file mode 100644 index 00000000..183703a2 --- /dev/null +++ b/src/Commands/CreateSchema.php @@ -0,0 +1,61 @@ +setName('create:schema') + ->setDescription('Generate random CSV file by rules from yml file. Data based on fakerphp/faker library.') + ->addArgument( + 'rule-file', + InputArgument::REQUIRED, + 'Path to rule file (yml)', + ) + ->addOption( + 'seed', + null, + InputOption::VALUE_OPTIONAL, + 'Seed for random data generation (fakerphp/faker library)', + ) + ->addOption( + 'to-file', + null, + InputOption::VALUE_OPTIONAL, + 'If set, the generated CSV will be saved to the specified file. ' + . 'Otherwise, the result will be output to the console as STDOUT.', + ); + + parent::configure(); + } + + protected function executeAction(): int + { + $yml = '/Users/smetdenis/Work/projects/jbzoo-csv-validator/tests/rules/example.yml'; + dump(yml($yml)); + + return self::SUCCESS; + } +} diff --git a/src/Commands/ValidateDir.php b/src/Commands/ValidateDir.php new file mode 100644 index 00000000..fc10ec1d --- /dev/null +++ b/src/Commands/ValidateDir.php @@ -0,0 +1,52 @@ +setName('validate:dir') + ->setDescription('Validate CSV file(s) by rules from yml file(s)') + ->addArgument( + 'csv-file-or-dir', + InputArgument::REQUIRED, + 'Path to CSV file or directory with CSV files', + ) + ->addArgument( + 'rule-file-or-dir', + InputArgument::REQUIRED, + 'Path to rule file (yml) or directory with rule files', + ); + + parent::configure(); + } + + protected function executeAction(): int + { + $yml = '/Users/smetdenis/Work/projects/jbzoo-csv-validator/tests/rules/example.yml'; + dump(yml($yml)); + + return self::SUCCESS; + } +} diff --git a/src/Commands/ValidateFile.php b/src/Commands/ValidateFile.php new file mode 100644 index 00000000..b992c96a --- /dev/null +++ b/src/Commands/ValidateFile.php @@ -0,0 +1,52 @@ +setName('validate:file') + ->setDescription('Validate CSV file(s) by rules from yml file(s)') + ->addArgument( + 'csv-file-or-dir', + InputArgument::REQUIRED, + 'Path to CSV file or directory with CSV files', + ) + ->addArgument( + 'rule-file-or-dir', + InputArgument::REQUIRED, + 'Path to rule file (yml) or directory with rule files', + ); + + parent::configure(); + } + + protected function executeAction(): int + { + $yml = '/Users/smetdenis/Work/projects/jbzoo-csv-validator/tests/rules/example.yml'; + dump(yml($yml)); + + return self::SUCCESS; + } +} diff --git a/src/Csv/Column.php b/src/Csv/Column.php new file mode 100644 index 00000000..9030801f --- /dev/null +++ b/src/Csv/Column.php @@ -0,0 +1,128 @@ + '', + 'name' => '', + 'description' => '', + 'type' => 'base', // TODO: class + 'required' => false, + 'allow_empty' => false, + 'regex' => null, + 'rules' => [], + 'aggregate_rules' => [], + ]; + + private int $id; + private Data $column; + private array $rules; + private array $aggregateRules; + + public function __construct(int $id, array $config) + { + $this->id = $id; + $this->column = new Data($config); + $this->rules = $this->prepareRuleSet('rules'); + $this->aggregateRules = $this->prepareRuleSet('aggregate_rules'); + } + + public function getName(): string + { + return $this->column->getString('name', self::FALLBACK_VALUES['name']); + } + + public function getId(): int + { + return $this->id; + } + + public function getDescription(): string + { + return $this->column->getString('description', self::FALLBACK_VALUES['description']); + } + + public function getHumanName(): string + { + return \trim($this->getName() . ' (' . $this->getId() . ')'); + } + + public function getKey(): string + { + return $this->getName() ?: (string)$this->getId(); + } + + public function getType(): string + { + return $this->column->getString('type', self::FALLBACK_VALUES['type']); + } + + public function isRequired(): bool + { + return $this->column->getBool('required', self::FALLBACK_VALUES['required']); + } + + public function getRegex(): ?string + { + $regex = $this->column->getStringNull('regex', self::FALLBACK_VALUES['regex']); + + return Utils::prepareRegex($regex); + } + + public function getRules(): array + { + return $this->rules; + } + + public function getAggregateRules(): array + { + return $this->aggregateRules; + } + + public function getInherit(): string + { + return $this->column->getString('inherit', self::FALLBACK_VALUES['inherit']); + } + + public function validate(string $cellValue, int $line): array + { + return (new Validator($this))->validate($cellValue, $line); + } + + private function prepareRuleSet(string $schemaKey): array + { + $rules = []; + + $ruleSetConfig = $this->column->getSelf($schemaKey, self::FALLBACK_VALUES[$schemaKey]); + + foreach ($ruleSetConfig as $ruleName => $ruleValue) { + if (\str_starts_with($ruleName, 'custom_')) { + $rules[$ruleName] = \array_merge(['class' => '', 'args' => []], $ruleValue); + } else { + $rules[$ruleName] = $ruleValue; + } + } + + return $rules; + } +} diff --git a/src/Csv/CsvFile.php b/src/Csv/CsvFile.php new file mode 100644 index 00000000..95269ad1 --- /dev/null +++ b/src/Csv/CsvFile.php @@ -0,0 +1,159 @@ +csvFilename = \realpath($csvFilename); + $this->schema = new Schema($csvSchemaFilenameOrArray); + $this->structure = $this->schema->getCsvStructure(); + $this->reader = $this->prepareReader(); + } + + public function getCsvFilename(): string + { + return $this->csvFilename; + } + + public function getCsvStructure(): ParseConfig + { + return $this->structure; + } + + /** + * @return string[] + */ + public function getHeader(): array + { + if ($this->structure->isHeader()) { + return $this->reader->getHeader(); + } + + return []; + } + + public function getRecords(): \League\Csv\MapIterator + { + return $this->reader->getRecords($this->getHeader()); + } + + public function getRecordsChunk(int $offset = 0, int $limit = -1): \League\Csv\ResultSet + { + return Statement::create(null, $offset, $limit)->process($this->reader, $this->getHeader()); + } + + public function validate(bool $quickStop = false): array + { + return $this->validateHeader() + + $this->validateEachCell($quickStop) + + $this->validateAggregateRules($quickStop); + } + + private function prepareReader(): LeagueReader + { + $reader = LeagueReader::createFromPath($this->csvFilename) + ->setDelimiter($this->structure->getDelimiter()) + ->setEnclosure($this->structure->getEnclosure()) + ->setEscape($this->structure->getQuoteChar()) + ->setHeaderOffset($this->structure->isHeader() ? 0 : null); + + if ($this->structure->isBom()) { + $reader->includeInputBOM(); + + $encoding = $this->structure->getEncoding(); + if ($encoding === CsvStructure::ENCODING_UTF8) { + $reader->setOutputBOM(ByteSequence::BOM_UTF8); + } elseif ($encoding === CsvStructure::ENCODING_UTF16) { + $reader->setOutputBOM(ByteSequence::BOM_UTF16_LE); + } elseif ($encoding === CsvStructure::ENCODING_UTF32) { + $reader->setOutputBOM(ByteSequence::BOM_UTF32_LE); + } + } else { + $reader->skipInputBOM(); + } + + return $reader; + } + + private function validateHeader(): array + { + if (!$this->getCsvStructure()->isHeader()) { + return []; + } + + $errorAcc = []; + + foreach ($this->schema->getColumns() as $column) { + if ($column->getName() === '') { + $errorAcc[] = "Property \"name\" is not defined for column id={$column->getId()} " . + "in schema: {$this->schema->getFilename()}"; + } + } + + return $errorAcc; + } + + private function validateEachCell(bool $quickStop = false): array + { + $errorAcc = []; + + foreach ($this->getRecords() as $line => $record) { + $columns = $this->schema->getColumnsMappedByHeader($this->getHeader()); + + foreach ($columns as $column) { + if ($column === null) { + continue; + } + + $errorAcc = $this->appendErrors($errorAcc, $column->validate($record[$column->getKey()], $line)); + if ($quickStop && \count($errorAcc) > 0) { + return $errorAcc; + } + } + } + + return $errorAcc; + } + + private function validateAggregateRules(bool $quickStop = false): array + { + return []; + } + + private function appendErrors(array $errorAcc, array $newErrors): array + { + $errorAcc += \array_filter($newErrors); + + return $errorAcc; + } +} diff --git a/src/Csv/Exception.php b/src/Csv/Exception.php new file mode 100644 index 00000000..2e1cca41 --- /dev/null +++ b/src/Csv/Exception.php @@ -0,0 +1,21 @@ + null, + 'bom' => false, + 'delimiter' => ',', + 'quote_char' => '\\', + 'enclosure' => '"', + 'encoding' => 'utf-8', + 'header' => true, + 'strict_column_order' => false, + 'other_columns_possible' => false, + ]; + + private Data $structure; + + public function __construct(array $config) + { + $this->structure = new Data($config); + } + + public function getInherit(): ?string + { + return $this->structure->getStringNull('inherit', self::FALLBACK_VALUES['inherit']); + } + + public function isBom(): bool + { + return $this->structure->getBool('bom', self::FALLBACK_VALUES['bom']); + } + + public function getDelimiter(): string + { + $value = $this->structure->getString('delimiter', self::FALLBACK_VALUES['delimiter']); + if (\strlen($value) === 1) { + return $value; + } + + throw new \InvalidArgumentException('Delimiter must be a single character'); + } + + public function getQuoteChar(): string + { + $value = $this->structure->getString('quote_char', self::FALLBACK_VALUES['quote_char']); + if (\strlen($value) === 1) { + return $value; + } + + throw new \InvalidArgumentException('Quote char must be a single character'); + } + + public function getEnclosure(): string + { + $value = $this->structure->getString('enclosure', self::FALLBACK_VALUES['enclosure']); + + if (\strlen($value) === 1) { + return $value; + } + + throw new \InvalidArgumentException('Enclosure must be a single character'); + } + + public function getEncoding(): string + { + $encoding = \strtolower(\trim($this->structure->getString('encoding', self::FALLBACK_VALUES['encoding']))); + $availableOptions = [ // TODO: add flexible handler for this + self::ENCODING_UTF8, + self::ENCODING_UTF16, + self::ENCODING_UTF32, + ]; + + $result = \in_array($encoding, $availableOptions, true) ? $encoding : null; + if ($result) { + return $result; + } + + throw new \InvalidArgumentException("Invalid encoding: {$encoding}"); + } + + public function isHeader(): bool + { + return $this->structure->getBool('header', self::FALLBACK_VALUES['header']); + } + + public function isStrictColumnOrder(): bool + { + return $this->structure->getBool('strict_column_order', self::FALLBACK_VALUES['strict_column_order']); + } + + public function isOtherColumnsPossible(): bool + { + return $this->structure->getBool('other_columns_possible', self::FALLBACK_VALUES['other_columns_possible']); + } + + public function getArrayCopy(): array + { + return [ + // System rules + 'inherit' => $this->getInherit(), + // Reading rules + 'bom' => $this->isBom(), + 'delimiter' => $this->getDelimiter(), + 'quote_char' => $this->getQuoteChar(), + 'enclosure' => $this->getEnclosure(), + 'encoding' => $this->getEncoding(), + 'header' => $this->isHeader(), + // Global validation rules + 'strict_column_order' => $this->isStrictColumnOrder(), + 'other_columns_possible' => $this->isOtherColumnsPossible(), + ]; + } +} diff --git a/src/Exception.php b/src/Exception.php new file mode 100644 index 00000000..1780922a --- /dev/null +++ b/src/Exception.php @@ -0,0 +1,21 @@ +filename = '_custom_array_'; + $this->data = new Data($csvSchemaFilenameOrArray); + } elseif (\file_exists($csvSchemaFilenameOrArray)) { + $this->filename = $csvSchemaFilenameOrArray; + $fileExtension = \pathinfo($csvSchemaFilenameOrArray, \PATHINFO_EXTENSION); + if ($fileExtension === 'yml' || $fileExtension === 'yaml') { + $this->data = yml($csvSchemaFilenameOrArray); + } elseif ($fileExtension === 'json') { + $this->data = json($csvSchemaFilenameOrArray); + } elseif ($fileExtension === 'php') { + $this->data = phpArray($csvSchemaFilenameOrArray); + } else { + throw new \InvalidArgumentException("Unsupported file extension: {$fileExtension}"); + } + } + + $this->columns = $this->prepareColumns(); + } + + public function getFilename(): string + { + return $this->filename; + } + + public function getCsvStructure(): ParseConfig + { + return new ParseConfig($this->data->getArray('csv_structure')); + } + + /** + * @return Column[] + */ + public function getColumns(): array + { + return $this->columns; + } + + /** + * @return Column[] + */ + public function getColumnsMappedByHeader(array $header): array + { + $map = []; + + foreach ($header as $headerName) { + $column = $this->columns[$headerName] ?? null; + $map[$headerName] = $column; + } + + return $map; + } + + public function getColumn(int|string $columNameOrId) + { + if (\is_int($columNameOrId)) { + $column = \array_values($this->getColumns())[$columNameOrId] ?? null; + } else { + $column = $this->getColumns()[$columNameOrId] ?? null; + } + + if (!$column) { + throw new Exception("Column \"{$columNameOrId}\" not found in schema \"{$this->filename}\""); + } + + return $column; + } + + public function getFinenamePattern(): ?string + { + return $this->data->getStringNull('finename_pattern'); + } + + public function getIncludes(): array + { + $result = []; + + foreach ($this->data->getArray('includes') as $includedPath) { + [$schemaPath, $alias] = \explode(' as ', $includedPath); + + $schemaPath = \trim($schemaPath); + $alias = \trim($alias); + + $result[$alias] = $schemaPath; + } + + return $result; + } + + /** + * @return Column[] + */ + private function prepareColumns(): array + { + $result = []; + + foreach ($this->data->getArray('columns') as $columnId => $columnPreferences) { + $column = new Column($columnId, $columnPreferences); + + $result[$column->getKey()] = $column; + } + + return $result; + } +} diff --git a/src/Utils.php b/src/Utils.php new file mode 100644 index 00000000..2b81c42f --- /dev/null +++ b/src/Utils.php @@ -0,0 +1,47 @@ +columnNameId = $columnNameId; + $this->options = $options; + $this->ruleCode = $this->getRuleCode(); + } + + public function validate(?string $cellValue, int $line = 0): ?string + { + if ($error = $this->validateRule($cellValue)) { + $error = \rtrim($error, '.'); // Remove last dot to make the final message nice. + + return "Error \"{$this->ruleCode}\" at line {$line}, column \"{$this->columnNameId}\". {$error}."; + } + + return null; + } + + protected function getOptionAsBool(): bool + { + return bool($this->options); + } + + protected function getOptionAsString(): string + { + return (string)$this->options; + } + + protected function getOptionAsInt(): int + { + return int($this->options); + } + + protected function getOptionAsFloat(): float + { + return float($this->options); + } + + protected function getOptionAsArray(): array + { + return (array)$this->options; + } + + protected function getOptionAsData(): array + { + return data($this->options); + } + + protected function getOptionAsDate(): \DateTimeImmutable + { + return new \DateTimeImmutable($this->getOptionAsString(), new \DateTimeZone('UTC')); + } + + private function getRuleCode(): string + { + return Utils::camelToKebabCase((new \ReflectionClass($this))->getShortName()); + } +} diff --git a/src/Validators/Rules/AllowValues.php b/src/Validators/Rules/AllowValues.php new file mode 100644 index 00000000..ed83ecd4 --- /dev/null +++ b/src/Validators/Rules/AllowValues.php @@ -0,0 +1,36 @@ +getOptionAsArray(); + + if (\count($allowedValues) === 0) { + return 'Allowed values are not defined'; + } + + if (!\in_array($cellValue, $allowedValues, true)) { + return "Value \"{$cellValue}\" is not allowed. " . + 'Allowed values: ["' . \implode('", "', $allowedValues) . '"]'; + } + + return null; + } +} diff --git a/src/Validators/Rules/DateFormat.php b/src/Validators/Rules/DateFormat.php new file mode 100644 index 00000000..4e1670e3 --- /dev/null +++ b/src/Validators/Rules/DateFormat.php @@ -0,0 +1,39 @@ +getOptionAsString(); + if (!$expectedDateFormat) { + return 'Date format is not defined'; + } + + if (!$cellValue) { + return 'Date format of value "" is not valid. Expected format: "' . $expectedDateFormat . '"'; + } + + $date = \DateTimeImmutable::createFromFormat($expectedDateFormat, $cellValue); + if (!$date || $date->format($expectedDateFormat) !== $cellValue) { + return "Date format of value \"{$cellValue}\" is not valid. Expected format: \"{$expectedDateFormat}\""; + } + + return null; + } +} diff --git a/src/Validators/Rules/ExactValue.php b/src/Validators/Rules/ExactValue.php new file mode 100644 index 00000000..31f71d80 --- /dev/null +++ b/src/Validators/Rules/ExactValue.php @@ -0,0 +1,29 @@ +getOptionAsString() !== (string)$cellValue) { + return "Value \"{$cellValue}\" is not strict equal to \"{$this->getOptionAsString()}\""; + } + + return null; + } +} diff --git a/src/Validators/Rules/IsBool.php b/src/Validators/Rules/IsBool.php new file mode 100644 index 00000000..f05dfa79 --- /dev/null +++ b/src/Validators/Rules/IsBool.php @@ -0,0 +1,30 @@ + 90.0) { + return "Value \"{$cellValue}\" is not a valid latitude (-90 <= x <= 90)"; + } + + return null; + } +} diff --git a/src/Validators/Rules/IsLongitude.php b/src/Validators/Rules/IsLongitude.php new file mode 100644 index 00000000..676458ed --- /dev/null +++ b/src/Validators/Rules/IsLongitude.php @@ -0,0 +1,35 @@ + 180.0) { + return "Value \"{$cellValue}\" is not a valid longitude (-180 <= x <= 180)"; + } + + return null; + } +} diff --git a/src/Validators/Rules/IsUrl.php b/src/Validators/Rules/IsUrl.php new file mode 100644 index 00000000..0de775ac --- /dev/null +++ b/src/Validators/Rules/IsUrl.php @@ -0,0 +1,29 @@ +getOptionAsFloat() < (float)$cellValue) { + return "Value \"{$cellValue}\" is greater than \"{$this->getOptionAsFloat()}\""; + } + + return null; + } +} diff --git a/src/Validators/Rules/MaxDate.php b/src/Validators/Rules/MaxDate.php new file mode 100644 index 00000000..5289aa20 --- /dev/null +++ b/src/Validators/Rules/MaxDate.php @@ -0,0 +1,33 @@ +getOptionAsDate(); + $cellDate = new \DateTimeImmutable($cellValue); + + if ($cellDate > $minDate) { + return "Value \"{$cellValue}\" is more than the maximum " . + "date \"{$minDate->format(\DATE_RFC3339_EXTENDED)}\""; + } + + return null; + } +} diff --git a/src/Validators/Rules/MaxLength.php b/src/Validators/Rules/MaxLength.php new file mode 100644 index 00000000..7b32ddc1 --- /dev/null +++ b/src/Validators/Rules/MaxLength.php @@ -0,0 +1,31 @@ +getOptionAsInt(); + $length = \mb_strlen($cellValue); + if (\mb_strlen($cellValue) > $minLength) { + return "Value \"{$cellValue}\" (legth: {$length}) is too long. Max length is {$minLength}"; + } + + return null; + } +} diff --git a/src/Validators/Rules/Min.php b/src/Validators/Rules/Min.php new file mode 100644 index 00000000..937a3220 --- /dev/null +++ b/src/Validators/Rules/Min.php @@ -0,0 +1,34 @@ +getOptionAsFloat() > (float)$cellValue) { + return "Value \"{$cellValue}\" is less than \"{$this->getOptionAsFloat()}\""; + } + + return null; + } +} diff --git a/src/Validators/Rules/MinDate.php b/src/Validators/Rules/MinDate.php new file mode 100644 index 00000000..813ad3b0 --- /dev/null +++ b/src/Validators/Rules/MinDate.php @@ -0,0 +1,33 @@ +getOptionAsDate(); + $cellDate = new \DateTimeImmutable($cellValue); + + if ($cellDate < $minDate) { + return "Value \"{$cellValue}\" is less than the minimum " . + "date \"{$minDate->format(\DATE_RFC3339_EXTENDED)}\""; + } + + return null; + } +} diff --git a/src/Validators/Rules/MinLength.php b/src/Validators/Rules/MinLength.php new file mode 100644 index 00000000..64892e9d --- /dev/null +++ b/src/Validators/Rules/MinLength.php @@ -0,0 +1,31 @@ +getOptionAsInt(); + $length = \mb_strlen($cellValue); + if ($length < $minLength) { + return "Value \"{$cellValue}\" (legth: {$length}) is too short. Min length is {$minLength}"; + } + + return null; + } +} diff --git a/src/Validators/Rules/NotEmpty.php b/src/Validators/Rules/NotEmpty.php new file mode 100644 index 00000000..4812e7ee --- /dev/null +++ b/src/Validators/Rules/NotEmpty.php @@ -0,0 +1,29 @@ +getOptionAsInt() !== $this->getFloatPrecision($cellValue)) { + return "Value \"{$cellValue}\" has a precision of " . $this->getFloatPrecision( + $cellValue, + ) . ' but should have a precision of ' . $this->getOptionAsInt(); + } + + return null; + } + + private function getFloatPrecision(?string $cellValue): int + { + $floatAsString = (string)$cellValue; + $dotPosition = \strpos($floatAsString, '.'); + + if ($dotPosition === false) { + return 0; + } + + return \strlen($floatAsString) - $dotPosition - 1; + } +} diff --git a/src/Validators/Rules/Regex.php b/src/Validators/Rules/Regex.php new file mode 100644 index 00000000..9245da3b --- /dev/null +++ b/src/Validators/Rules/Regex.php @@ -0,0 +1,32 @@ +getOptionAsString()); + if (!\preg_match($regex, $cellValue)) { + return "Value \"{$cellValue}\" does not match the pattern \"{$regex}\""; + } + + return null; + } +} diff --git a/src/Validators/Rules/RuleException.php b/src/Validators/Rules/RuleException.php new file mode 100644 index 00000000..48ed9cb6 --- /dev/null +++ b/src/Validators/Rules/RuleException.php @@ -0,0 +1,21 @@ +columnNameId = $columnNameId; + + foreach ($rules as $ruleName => $options) { + $this->rules[] = $this->createRule($ruleName, $options); + } + } + + public function createRule(string $ruleName, null|array|bool|float|int|string $options = null): AbstarctRule + { + if (\class_exists($ruleName)) { + return new $ruleName($this->columnNameId, $options); + } + + $classname = __NAMESPACE__ . '\\Rules\\' . Utils::kebabToCamelCase($ruleName); + if (\class_exists($classname)) { + return new $classname($this->columnNameId, $options); + } + + throw new Exception("Rule \"{$ruleName}\" not found. Expected class: {$classname}"); + } + + public function validate(?string $cellValue, int $line): array + { + $errors = []; + + foreach ($this->rules as $rule) { + $errors[] = $rule->validate($cellValue, $line); + } + + return $errors; + } +} diff --git a/src/Validators/Validator.php b/src/Validators/Validator.php new file mode 100644 index 00000000..3359958b --- /dev/null +++ b/src/Validators/Validator.php @@ -0,0 +1,35 @@ +ruleset = new Ruleset($column->getRules(), $column->getHumanName()); + } + + public function validate(?string $cellValue, int $line): array + { + return $this->ruleset->validate($cellValue, $line); + } +} diff --git a/tests/CsvBlueprintPackageTest.php b/tests/CsvBlueprintPackageTest.php new file mode 100644 index 00000000..e88aca12 --- /dev/null +++ b/tests/CsvBlueprintPackageTest.php @@ -0,0 +1,22 @@ +getCsvFilename()); + + isSame([], $csv->getHeader()); + + isSame([ + ['1', 'true'], + ['2', 'true'], + ['3', 'false'], + ], $this->fetchRows($csv->getRecords())); + + isSame([['2', 'true']], $this->fetchRows($csv->getRecordsChunk(1, 1))); + + isSame([['2', 'true'], ['3', 'false']], $this->fetchRows($csv->getRecordsChunk(1))); + } + + public function testReadCsvFileWithHeader(): void + { + $csv = new CsvFile(self::CSV_SIMPLE_HEADER, self::SCHEMA_SIMPLE_HEADER); + isSame(self::CSV_SIMPLE_HEADER, $csv->getCsvFilename()); + + isSame(['seq', 'bool', 'exact'], $csv->getHeader()); + + isSame([ + ['seq' => '1', 'bool' => 'true', 'exact' => '1'], + ['seq' => '2', 'bool' => 'true', 'exact' => '1'], + ['seq' => '3', 'bool' => 'false', 'exact' => '1'], + ], $this->fetchRows($csv->getRecords())); + + isSame( + [['seq' => '2', 'bool' => 'true', 'exact' => '1']], + $this->fetchRows($csv->getRecordsChunk(1, 1)), + ); + + isSame( + [['seq' => '2', 'bool' => 'true', 'exact' => '1'], ['seq' => '3', 'bool' => 'false', 'exact' => '1']], + $this->fetchRows($csv->getRecordsChunk(1, 2)), + ); + } + + private function fetchRows(iterable $records): array + { + return \array_reduce(\iterator_to_array($records), static function ($acc, $record) { + $acc[] = $record; + + return $acc; + }, []); + } +} diff --git a/tests/RulesTest.php b/tests/RulesTest.php new file mode 100644 index 00000000..ae95e344 --- /dev/null +++ b/tests/RulesTest.php @@ -0,0 +1,552 @@ +validate('1')); + isSame(null, $rule->validate('2')); + isSame(null, $rule->validate('3')); + isSame( + 'Error "allow_values" at line 0, column "prop". ' . + 'Value "" is not allowed. Allowed values: ["1", "2", "3"].', + $rule->validate(''), + ); + isSame( + 'Error "allow_values" at line 0, column "prop". ' . + 'Value "invalid" is not allowed. Allowed values: ["1", "2", "3"].', + $rule->validate('invalid'), + ); + + $rule = new AllowValues('prop', ['1', '2', '3', '']); + isSame(null, $rule->validate('')); + + $rule = new AllowValues('prop', ['1', '2', '3', ' ']); + isSame(null, $rule->validate(' ')); + } + + public function testDateFormat(): void + { + $rule = new DateFormat('prop', 'Y-m-d'); + isSame(null, $rule->validate('2000-12-31')); + isSame( + 'Error "date_format" at line 0, column "prop". ' . + 'Date format of value "" is not valid. Expected format: "Y-m-d".', + $rule->validate(''), + ); + isSame( + 'Error "date_format" at line 0, column "prop". ' . + 'Date format of value "2000-01-02 12:34:56" is not valid. Expected format: "Y-m-d".', + $rule->validate('2000-01-02 12:34:56'), + ); + } + + public function testExactValue(): void + { + $rule = new ExactValue('prop', '123'); + isSame(null, $rule->validate('123')); + isSame( + 'Error "exact_value" at line 0, column "prop". Value "" is not strict equal to "123".', + $rule->validate(''), + ); + isSame( + 'Error "exact_value" at line 0, column "prop". Value "2000-01-02" is not strict equal to "123".', + $rule->validate('2000-01-02'), + ); + } + + public function testIsBool(): void + { + $rule = new IsBool('prop'); + isSame(null, $rule->validate('true')); + isSame(null, $rule->validate('false')); + isSame(null, $rule->validate('TRUE')); + isSame(null, $rule->validate('FALSE')); + isSame(null, $rule->validate('True')); + isSame(null, $rule->validate('False')); + isSame( + 'Error "is_bool" at line 0, column "prop". Value "" is not allowed. Allowed values: ["true", "false"].', + $rule->validate(''), + ); + isSame( + 'Error "is_bool" at line 0, column "prop". Value "1" is not allowed. Allowed values: ["true", "false"].', + $rule->validate('1'), + ); + } + + public function testIsDomain(): void + { + $rule = new IsDomain('prop'); + isSame(null, $rule->validate('example.com')); + isSame(null, $rule->validate('sub.example.com')); + isSame(null, $rule->validate('sub.sub.example.com')); + isSame(null, $rule->validate('sub.sub-example.com')); + isSame(null, $rule->validate('sub-sub-example.com')); + isSame(null, $rule->validate('sub-sub-example.qwerty')); + isSame( + 'Error "is_domain" at line 0, column "prop". Value "example" is not a valid domain.', + $rule->validate('example'), + ); + isSame( + 'Error "is_domain" at line 0, column "prop". Value "" is not a valid domain.', + $rule->validate(''), + ); + } + + public function testIsEmail(): void + { + $rule = new IsEmail('prop'); + isSame(null, $rule->validate('user@example.com')); + isSame(null, $rule->validate('user@sub.example.com')); + isSame( + 'Error "is_email" at line 0, column "prop". Value "user:pass@example.com" is not a valid email.', + $rule->validate('user:pass@example.com'), + ); + } + + public function testIsFloat(): void + { + $rule = new IsFloat('prop'); + isSame(null, $rule->validate('1')); + isSame(null, $rule->validate('1.0')); + isSame(null, $rule->validate('-1')); + isSame(null, $rule->validate('-1.0')); + isSame( + 'Error "is_float" at line 0, column "prop". Value "1.000.000" is not a float number.', + $rule->validate('1.000.000'), + ); + isSame( + 'Error "is_float" at line 0, column "prop". Value "" is not a float number.', + $rule->validate(''), + ); + isSame( + 'Error "is_float" at line 0, column "prop". Value " 1" is not a float number.', + $rule->validate(' 1'), + ); + } + + public function testIsInt(): void + { + $rule = new IsInt('prop'); + isSame(null, $rule->validate('1')); + isSame(null, $rule->validate('0')); + isSame(null, $rule->validate('-1')); + isSame( + 'Error "is_int" at line 0, column "prop". Value "1.000.000" is not an integer.', + $rule->validate('1.000.000'), + ); + isSame( + 'Error "is_int" at line 0, column "prop". Value "1.1" is not an integer.', + $rule->validate('1.1'), + ); + isSame( + 'Error "is_int" at line 0, column "prop". Value "1.0" is not an integer.', + $rule->validate('1.0'), + ); + isSame( + 'Error "is_int" at line 0, column "prop". Value "" is not an integer.', + $rule->validate(''), + ); + isSame( + 'Error "is_int" at line 0, column "prop". Value " 1" is not an integer.', + $rule->validate(' 1'), + ); + isSame( + 'Error "is_int" at line 0, column "prop". Value "1 " is not an integer.', + $rule->validate('1 '), + ); + } + + public function testIsIp(): void + { + $rule = new IsIp('prop'); + isSame(null, $rule->validate('127.0.0.1')); + isSame(null, $rule->validate('0.0.0.0')); + isSame( + 'Error "is_ip" at line 0, column "prop". Value "1.2.3" is not a valid IP.', + $rule->validate('1.2.3'), + ); + } + + public function testIsLatitude(): void + { + $rule = new IsLatitude('prop'); + isSame(null, $rule->validate('0')); + isSame(null, $rule->validate('90')); + isSame(null, $rule->validate('-90')); + isSame( + 'Error "is_latitude" at line 0, column "prop". Value "123" is not a valid latitude (-90 <= x <= 90).', + $rule->validate('123'), + ); + isSame( + 'Error "is_latitude" at line 0, column "prop". Value "90.1" is not a valid latitude (-90 <= x <= 90).', + $rule->validate('90.1'), + ); + isSame( + 'Error "is_latitude" at line 0, column "prop". Value "90.1.1.1.1" is not a float number.', + $rule->validate('90.1.1.1.1'), + ); + } + + public function testIsLongitude(): void + { + $rule = new IsLongitude('prop'); + isSame(null, $rule->validate('0')); + isSame(null, $rule->validate('180')); + isSame(null, $rule->validate('-180')); + isSame( + 'Error "is_longitude" at line 0, column "prop". Value "1230" is not a valid longitude (-180 <= x <= 180).', + $rule->validate('1230'), + ); + isSame( + 'Error "is_longitude" at line 0, column "prop". ' . + 'Value "180.0001" is not a valid longitude (-180 <= x <= 180).', + $rule->validate('180.0001'), + ); + isSame( + 'Error "is_longitude" at line 0, column "prop". Value "-180.1" is not a valid longitude (-180 <= x <= 180).', + $rule->validate('-180.1'), + ); + isSame( + 'Error "is_longitude" at line 0, column "prop". Value "1.0.0.0" is not a float number.', + $rule->validate('1.0.0.0'), + ); + } + + public function testIsUrl(): void + { + $rule = new IsUrl('prop'); + isSame(null, $rule->validate('http://example.com')); + isSame(null, $rule->validate('http://example.com/home-page')); + isSame(null, $rule->validate('ftp://user:pass@example.com/home-page?param=value&v=asd#anchor')); + isSame( + 'Error "is_url" at line 0, column "prop". Value "123" is not a valid URL.', + $rule->validate('123'), + ); + isSame( + 'Error "is_url" at line 0, column "prop". Value "//example.com" is not a valid URL.', + $rule->validate('//example.com'), + ); + } + + public function testMin(): void + { + $rule = new Min('prop', 10); + isSame(null, $rule->validate('10')); + isSame(null, $rule->validate('11')); + isSame( + 'Error "min" at line 0, column "prop". Value "9" is less than "10".', + $rule->validate('9'), + ); + isSame( + 'Error "min" at line 0, column "prop". Value "example.com" is not a float number.', + $rule->validate('example.com'), + ); + + $rule = new Min('prop', 10.1); + isSame(null, $rule->validate('10.1')); + isSame(null, $rule->validate('11')); + isSame( + 'Error "min" at line 0, column "prop". Value "9" is less than "10.1".', + $rule->validate('9'), + ); + isSame( + 'Error "min" at line 0, column "prop". Value "example.com" is not a float number.', + $rule->validate('example.com'), + ); + } + + public function testMax(): void + { + $rule = new Max('prop', 10); + isSame(null, $rule->validate('9')); + isSame(null, $rule->validate('10')); + isSame( + 'Error "max" at line 0, column "prop". Value "123" is greater than "10".', + $rule->validate('123'), + ); + isSame( + 'Error "max" at line 0, column "prop". Value "example.com" is not a float number.', + $rule->validate('example.com'), + ); + + $rule = new Max('prop', 10.1); + isSame(null, $rule->validate('9')); + isSame(null, $rule->validate('10.0')); + isSame(null, $rule->validate('10.1')); + isSame( + 'Error "max" at line 0, column "prop". Value "10.2" is greater than "10.1".', + $rule->validate('10.2'), + ); + } + + public function testMinDate(): void + { + $rule = new MinDate('prop', '2000-01-10'); + isSame(null, $rule->validate('2000-01-10')); + isSame( + 'Error "min_date" at line 0, column "prop". ' . + 'Value "2000-01-09" is less than the minimum date "2000-01-10T00:00:00.000+00:00".', + $rule->validate('2000-01-09'), + ); + + $rule = new MinDate('prop', '2000-01-10 00:00:00 +01:00'); + isSame(null, $rule->validate('2000-01-10 00:00:00 +01:00')); + isSame( + 'Error "min_date" at line 0, column "prop". ' . + 'Value "2000-01-09 23:59:59 Europe/Berlin" is less than the minimum date "2000-01-10T00:00:00.000+01:00".', + $rule->validate('2000-01-09 23:59:59 Europe/Berlin'), + ); + + $rule = new MinDate('prop', '-1000 years'); + isSame(null, $rule->validate('2000-01-10 00:00:00 +01:00')); + } + + public function testMaxDate(): void + { + $rule = new MaxDate('prop', '2000-01-10'); + isSame(null, $rule->validate('2000-01-09')); + isSame( + 'Error "max_date" at line 0, column "prop". ' . + 'Value "2000-01-11" is more than the maximum date "2000-01-10T00:00:00.000+00:00".', + $rule->validate('2000-01-11'), + ); + + $rule = new MaxDate('prop', '2000-01-10 00:00:00'); + isSame(null, $rule->validate('2000-01-10 00:00:00')); + isSame( + 'Error "max_date" at line 0, column "prop". ' . + 'Value "2000-01-10 00:00:01" is more than the maximum date "2000-01-10T00:00:00.000+00:00".', + $rule->validate('2000-01-10 00:00:01'), + ); + + $rule = new MaxDate('prop', '+1 day'); + isSame(null, $rule->validate('2000-01-10 00:00:00 +01:00')); + } + + public function testMinLength(): void + { + $rule = new MinLength('prop', 5); + isSame(null, $rule->validate('12345')); + isSame(null, $rule->validate(' ')); + isSame(null, $rule->validate(' 1 ')); + isSame( + 'Error "min_length" at line 0, column "prop". Value "1234" (legth: 4) is too short. Min length is 5.', + $rule->validate('1234'), + ); + isSame( + 'Error "min_length" at line 0, column "prop". Value "123 " (legth: 4) is too short. Min length is 5.', + $rule->validate('123 '), + ); + isSame( + 'Error "min_length" at line 0, column "prop". Value "" (legth: 0) is too short. Min length is 5.', + $rule->validate(''), + ); + } + + public function testMaxLength(): void + { + $rule = new MaxLength('prop', 5); + isSame(null, $rule->validate('')); + isSame(null, $rule->validate('1234')); + isSame(null, $rule->validate('12345')); + isSame(null, $rule->validate(' ')); + isSame(null, $rule->validate(' 1 ')); + isSame( + 'Error "max_length" at line 0, column "prop". Value "123456" (legth: 6) is too long. Max length is 5.', + $rule->validate('123456'), + ); + isSame( + 'Error "max_length" at line 0, column "prop". Value "12345 " (legth: 6) is too long. Max length is 5.', + $rule->validate('12345 '), + ); + } + + public function testNotEmpty(): void + { + $rule = new NotEmpty('prop'); + isSame(null, $rule->validate('0')); + isSame(null, $rule->validate('false')); + isSame(null, $rule->validate('1')); + isSame(null, $rule->validate(' 0')); + isSame(null, $rule->validate(' ')); + isSame( + 'Error "not_empty" at line 0, column "prop". Value is empty.', + $rule->validate(''), + ); + isSame( + 'Error "not_empty" at line 0, column "prop". Value is empty.', + $rule->validate(null), + ); + } + + public function testOnlyCapitalize(): void + { + $rule = new OnlyCapitalize('prop'); + isSame(null, $rule->validate('0')); + isSame(null, $rule->validate('False')); + isSame(null, $rule->validate('Qwe Rty')); + isSame(null, $rule->validate(' Qwe Rty')); + isSame(null, $rule->validate(' ')); + isSame( + 'Error "only_capitalize" at line 0, column "prop". Value "qwerty" should be in capitalize.', + $rule->validate('qwerty'), + ); + isSame( + 'Error "only_capitalize" at line 0, column "prop". Value "qwe Rty" should be in capitalize.', + $rule->validate('qwe Rty'), + ); + } + + public function testOnlyLowercase(): void + { + $rule = new OnlyLowercase('prop'); + isSame(null, $rule->validate('0')); + isSame(null, $rule->validate('false')); + isSame(null, $rule->validate('qwe rty')); + isSame(null, $rule->validate(' qwe rty')); + isSame(null, $rule->validate(' ')); + isSame( + 'Error "only_lowercase" at line 0, column "prop". Value "Qwerty" should be in lowercase.', + $rule->validate('Qwerty'), + ); + isSame( + 'Error "only_lowercase" at line 0, column "prop". Value "qwe Rty" should be in lowercase.', + $rule->validate('qwe Rty'), + ); + } + + public function testOnlyUppercase(): void + { + $rule = new OnlyUppercase('prop'); + isSame(null, $rule->validate('0')); + isSame(null, $rule->validate('FALSE')); + isSame(null, $rule->validate('QWE RTY')); + isSame(null, $rule->validate(' ')); + isSame( + 'Error "only_uppercase" at line 0, column "prop". Value "Qwerty" is not uppercase.', + $rule->validate('Qwerty'), + ); + isSame( + 'Error "only_uppercase" at line 0, column "prop". Value "qwe Rty" is not uppercase.', + $rule->validate('qwe Rty'), + ); + } + + public function testPrecision(): void + { + $rule = new Precision('prop', 0); + isSame(null, $rule->validate('0')); + isSame(null, $rule->validate('10')); + isSame(null, $rule->validate('-10')); + isSame( + 'Error "precision" at line 0, column "prop". ' . + 'Value "1.1" has a precision of 1 but should have a precision of 0.', + $rule->validate('1.1'), + ); + isSame( + 'Error "precision" at line 0, column "prop". ' . + 'Value "1.0" has a precision of 1 but should have a precision of 0.', + $rule->validate('1.0'), + ); + + $rule = new Precision('prop', 1); + isSame(null, $rule->validate('0.0')); + isSame(null, $rule->validate('10.0')); + isSame(null, $rule->validate('-10.0')); + isSame( + 'Error "precision" at line 0, column "prop". ' . + 'Value "1" has a precision of 0 but should have a precision of 1.', + $rule->validate('1'), + ); + isSame( + 'Error "precision" at line 0, column "prop". ' . + 'Value "1.01" has a precision of 2 but should have a precision of 1.', + $rule->validate('1.01'), + ); + + $rule = new Precision('prop', 2); + isSame(null, $rule->validate('0.01')); + isSame(null, $rule->validate('10.00')); + isSame(null, $rule->validate('-10.00')); + isSame( + 'Error "precision" at line 0, column "prop". ' . + 'Value "2.0" has a precision of 1 but should have a precision of 2.', + $rule->validate('2.0'), + ); + isSame( + 'Error "precision" at line 0, column "prop". ' . + 'Value "1.000" has a precision of 3 but should have a precision of 2.', + $rule->validate('1.000'), + ); + } + + public function testRegex(): void + { + $rule = new Regex('prop', '/^a/'); + isSame(null, $rule->validate('abc')); + isSame(null, $rule->validate('aaa')); + isSame(null, $rule->validate('a')); + isSame( + 'Error "regex" at line 0, column "prop". Value "1bc" does not match the pattern "/^a/".', + $rule->validate('1bc'), + ); + + $rule = new Regex('prop', '^a'); + isSame(null, $rule->validate('abc')); + isSame(null, $rule->validate('aaa')); + isSame(null, $rule->validate('a')); + isSame( + 'Error "regex" at line 0, column "prop". Value "1bc" does not match the pattern "/^a/u".', + $rule->validate('1bc'), + ); + } +} diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php new file mode 100644 index 00000000..fb6bf51d --- /dev/null +++ b/tests/SchemaTest.php @@ -0,0 +1,224 @@ +getFilename()); + + $schemaFull = new Schema(self::SCHEMA_EXAMPLE_FULL); + isSame(self::SCHEMA_EXAMPLE_FULL, $schemaFull->getFilename()); + } + + public function testGetFinenamePattern(): void + { + $schemaEmpty = new Schema(self::SCHEMA_EXAMPLE_EMPTY); + isSame(null, $schemaEmpty->getFinenamePattern()); + + $schemaFull = new Schema(self::SCHEMA_EXAMPLE_FULL); + isSame('^example\.csv$', $schemaFull->getFinenamePattern()); + } + + public function testScvStruture(): void + { + $schemaEmpty = new Schema(self::SCHEMA_EXAMPLE_EMPTY); + isSame([ + 'inherit' => null, + 'bom' => false, + 'delimiter' => ',', + 'quote_char' => '\\', + 'enclosure' => '"', + 'encoding' => 'utf-8', + 'header' => true, + 'strict_column_order' => false, + 'other_columns_possible' => false, + ], $schemaEmpty->getCsvStructure()->getArrayCopy()); + + $schemaFull = new Schema(self::SCHEMA_EXAMPLE_FULL); + isSame([ + 'inherit' => 'alias_1', + 'bom' => false, + 'delimiter' => ',', + 'quote_char' => '\\', + 'enclosure' => '"', + 'encoding' => 'utf-8', + 'header' => true, + 'strict_column_order' => true, + 'other_columns_possible' => true, + ], $schemaFull->getCsvStructure()->getArrayCopy()); + } + + public function testColumns(): void + { + $schemaEmpty = new Schema(self::SCHEMA_EXAMPLE_EMPTY); + isSame([], $schemaEmpty->getColumns()); + + $schemaFull = new Schema(self::SCHEMA_EXAMPLE_FULL); + isSame([ + 0 => 0, + 1 => 'General available options', + 2 => 'Some String', + 3 => 'Some Integer', + 4 => 'Some Float', + 5 => 'Some Date', + 6 => 'Some Enum', + 7 => 'Some Boolean', + 8 => 'Some Inherited', + 9 => 'Some Latitude', + 10 => 'Some Longitude', + 11 => 'Some URL', + 12 => 'Some Email', + 13 => 'Some IP', + 14 => 'Some UUID', + 15 => 'Some Custom Rule', + ], \array_keys($schemaFull->getColumns())); + } + + public function testColumnByNameAndId(): void + { + $schemaFull = new Schema(self::SCHEMA_EXAMPLE_FULL); + isNotNull($schemaFull->getColumn(1)); + isNotNull($schemaFull->getColumn('General available options')); + + isSame( + $schemaFull->getColumn(1), + $schemaFull->getColumn('General available options'), + ); + } + + public function testIncludes(): void + { + $schemaEmpty = new Schema(self::SCHEMA_EXAMPLE_EMPTY); + isSame([], $schemaEmpty->getIncludes()); + + $schemaFull = new Schema(self::SCHEMA_EXAMPLE_FULL); + isSame([ + 'alias_1' => '/path/schema_1.yml', + 'alias_2' => './path/schema_2.yml', + 'alias_3' => '../path/schema_3.yml', + ], $schemaFull->getIncludes()); + } + + public function testGetUndefinedColumnById(): void + { + $this->expectExceptionMessage( + 'Column "1000" not found in schema "' . self::SCHEMA_EXAMPLE_EMPTY . '"', + ); + $schemaFull = new Schema(self::SCHEMA_EXAMPLE_EMPTY); + isNull($schemaFull->getColumn(1000)); + } + + public function testGetUndefinedColumnByName(): void + { + $this->expectExceptionMessage( + 'Column "undefined_column" not found in schema "' . self::SCHEMA_EXAMPLE_EMPTY . '"', + ); + $schemaFull = new Schema(self::SCHEMA_EXAMPLE_EMPTY); + isNull($schemaFull->getColumn('undefined_column')); + } + + public function testGetColumnMinimal(): void + { + $schemaFull = new Schema(self::SCHEMA_EXAMPLE_FULL); + $column = $schemaFull->getColumn(0); + + isSame('', $column->getName()); + isSame('', $column->getDescription()); + isSame('base', $column->getType()); + isSame(null, $column->getRegex()); + isSame('', $column->getInherit()); + isFalse($column->isRequired()); + + isTrue(\is_array($column->getRules())); + isEmpty($column->getRules()); + + isTrue(\is_array($column->getAggregateRules())); + isEmpty($column->getAggregateRules()); + } + + public function testGetColumnProps(): void + { + $schemaFull = new Schema(self::SCHEMA_EXAMPLE_FULL); + $column = $schemaFull->getColumn(1); + + isSame('General available options', $column->getName()); + isSame('Some description', $column->getDescription()); + isSame('some_type', $column->getType()); + isSame('', $column->getInherit()); + + isTrue($column->isRequired()); + + isTrue(\is_array($column->getRules())); + isNotEmpty($column->getRules()); + + isTrue(\is_array($column->getAggregateRules())); + isNotEmpty($column->getAggregateRules()); + } + + public function testGetColumnRules(): void + { + $schemaFull = new Schema(self::SCHEMA_EXAMPLE_FULL); + $column = $schemaFull->getColumn('Some String'); + + isSame([ + 'min_length' => 1, + 'max_length' => 10, + 'only_trimed' => false, + 'only_uppercase' => false, + 'only_lowercase' => false, + 'only_capitalize' => false, + ], $column->getRules()); + } + + public function testGetColumnAggregateRules(): void + { + $schemaFull = new Schema(self::SCHEMA_EXAMPLE_FULL); + $column = $schemaFull->getColumn(1); + + isSame([ + 'unique' => false, + 'sorted' => 'asc', + 'sorted_flag' => 'SORT_NATURAL', + 'count_min' => 1, + 'count_max' => 10, + 'count_empty_min' => 1, + 'count_empty_max' => 10, + 'count_filled_min' => 1, + 'count_filled_max' => 10, + 'custom_1' => [ + 'class' => 'My\\Aggregate\\Rules1', + 'args' => ['value'], + ], + 'custom_2' => [ + 'class' => 'My\\Aggregate\\Rules2', + 'args' => ['value1', 'value2'], + ], + 'custom_my_favorite_name' => [ + 'class' => 'My\\Aggregate\\RulesXXX', + 'args' => [], + ], + ], $column->getAggregateRules()); + } +} diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php new file mode 100644 index 00000000..f4e31e45 --- /dev/null +++ b/tests/UtilsTest.php @@ -0,0 +1,45 @@ +expectExceptionMessage( + 'Rule "undefined_rule" not found. Expected class: JBZoo\CsvBlueprint\Validators\Rules\UndefinedRule', + ); + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'undefined_rule', true)); + $csv->validate(); + } + + public function testValidWithHeader(): void + { + $csv = new CsvFile(self::CSV_SIMPLE_HEADER, self::SCHEMA_SIMPLE_HEADER); + isSame([], $csv->validate()); + } + + public function testValidWithoutHeader(): void + { + $csv = new CsvFile(self::CSV_SIMPLE_NO_HEADER, self::SCHEMA_SIMPLE_NO_HEADER); + isSame([], $csv->validate()); + } + + public function testNotEmptyMessage(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'not_empty', true)); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('integer', 'not_empty', true)); + isSame('Error "not_empty" at line 18, column "integer (0)". Value is empty.', $csv->validate()[0]); + } + + public function testNoName(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule(null, 'not_empty', true)); + isSame('Property "name" is not defined for column id=0 in schema: _custom_array_', $csv->validate()[0]); + } + + public function testMin(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'min', -10)); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'min', 10)); + isSame('Error "min" at line 1, column "seq (0)". Value "1" is less than "10".', $csv->validate()[0]); + } + + public function testMax(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'max', 10000)); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'max', 10)); + isSame( + 'Error "max" at line 11, column "seq (0)". Value "11" is greater than "10".', + $csv->validate()[0], + ); + } + + public function testRegex(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'regex', '.*')); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'regex', '^[a-zA-Z0-9]+$')); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'regex', '[a-z]')); + isSame( + 'Error "regex" at line 1, column "seq (0)". Value "1" does not match the pattern "/[a-z]/u".', + $csv->validate()[0], + ); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'regex', '/[a-z]/')); + isSame( + 'Error "regex" at line 1, column "seq (0)". Value "1" does not match the pattern "/[a-z]/".', + $csv->validate()[0], + ); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'regex', '/[a-z]/i')); + isSame( + 'Error "regex" at line 1, column "seq (0)". Value "1" does not match the pattern "/[a-z]/i".', + $csv->validate()[0], + ); + } + + public function testMinLength(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'min_length', 1)); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'min_length', 1000)); + isSame( + 'Error "min_length" at line 1, column "seq (0)". Value "1" (legth: 1) is too short. Min length is 1000.', + $csv->validate()[0], + ); + } + + public function testMaxLength(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'max_length', 10)); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'max_length', 1)); + isSame( + 'Error "max_length" at line 10, column "seq (0)". Value "10" (legth: 2) is too long. Max length is 1.', + $csv->validate()[0], + ); + } + + public function testOnlyTrimed(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'only_trimed', true)); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('sentence', 'only_trimed', true)); + isSame( + 'Error "only_trimed" at line 13, column "sentence (0)". Value " Urecam" is not trimmed.', + $csv->validate()[0], + ); + } + + public function testOnlyUppercase(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'only_uppercase', true)); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'only_uppercase', true)); + isSame( + 'Error "only_uppercase" at line 1, column "bool (0)". Value "true" is not uppercase.', + $csv->validate()[0], + ); + } + + public function testOnlyLowercase(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'only_lowercase', true)); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'only_lowercase', true)); + isSame( + 'Error "only_lowercase" at line 7, column "bool (0)". Value "False" should be in lowercase.', + $csv->validate()[0], + ); + } + + public function testOnlyCapitalize(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'only_capitalize', true)); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'only_capitalize', true)); + isSame( + 'Error "only_capitalize" at line 1, column "bool (0)". Value "true" should be in capitalize.', + $csv->validate()[0], + ); + } + + public function testPrecision(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'precision', 0)); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'precision', 1)); + isSame( + 'Error "precision" at line 1, column "seq (0)". ' . + 'Value "1" has a precision of 0 but should have a precision of 1.', + $csv->validate()[0], + ); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('float', 'precision', 3)); + isSame( + 'Error "precision" at line 2, column "float (0)". ' . + 'Value "506847750940.2624" has a precision of 4 but should have a precision of 3.', + $csv->validate()[0], + ); + } + + public function testMinDate(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'min_date', '2000-01-01')); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'min_date', '2120-01-01')); + isSame( + 'Error "min_date" at line 1, column "date (0)". ' . + 'Value "2042/11/18" is less than the minimum date "2120-01-01T00:00:00.000+00:00".', + $csv->validate()[0], + ); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'min_date', '2042/11/17')); + isSame( + 'Error "min_date" at line 4, column "date (0)". ' . + 'Value "2032/09/09" is less than the minimum date "2042-11-17T00:00:00.000+00:00".', + $csv->validate()[0], + ); + } + + public function testMaxDate(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'max_date', '2200-01-01')); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'max_date', '2120-01-01')); + isSame( + 'Error "max_date" at line 22, column "date (0)". ' . + 'Value "2120/02/01" is more than the maximum date "2120-01-01T00:00:00.000+00:00".', + $csv->validate()[0], + ); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'max_date', '2042/11/17')); + isSame( + 'Error "max_date" at line 1, column "date (0)". ' . + 'Value "2042/11/18" is more than the maximum date "2042-11-17T00:00:00.000+00:00".', + $csv->validate()[0], + ); + } + + public function testDateFormat(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'date_format', 'Y/m/d')); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'date_format', 'Y/m/d H:i:s')); + isSame( + 'Error "date_format" at line 1, column "date (0)". ' . + 'Date format of value "2042/11/18" is not valid. Expected format: "Y/m/d H:i:s".', + $csv->validate()[0], + ); + } + + public function testAllowValues(): void + { + $csv = new CsvFile( + self::CSV_COMPLEX, + $this->getRule( + 'bool', + 'allow_values', + ['true', 'false', 'False', 'True'], + ), + ); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'allow_values', ['true', 'false'])); + isSame( + 'Error "allow_values" at line 7, column "bool (0)". ' . + 'Value "False" is not allowed. Allowed values: ["true", "false"].', + $csv->validate()[0], + ); + } + + public function testExactValue(): void + { + $csv = new CsvFile(self::CSV_SIMPLE_HEADER, $this->getRule('exact', 'exact_value', '1')); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_SIMPLE_HEADER, $this->getRule('exact', 'exact_value', '2')); + isSame( + 'Error "exact_value" at line 1, column "exact (0)". Value "1" is not strict equal to "2".', + $csv->validate()[0], + ); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'exact_value', 'true')); + isSame( + 'Error "exact_value" at line 3, column "bool (0)". Value "false" is not strict equal to "true".', + $csv->validate()[0], + ); + } + + public function testIsInt(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'is_int', true)); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'is_int', true)); + isSame( + 'Error "is_int" at line 1, column "bool (0)". Value "true" is not an integer.', + $csv->validate()[0], + ); + } + + public function testIsFloat(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'is_float', true)); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'is_float', true)); + isSame( + 'Error "is_float" at line 1, column "bool (0)". Value "true" is not a float number.', + $csv->validate()[0], + ); + } + + public function testIsBool(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'is_bool', true)); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('yn', 'is_bool', true)); + isSame( + 'Error "is_bool" at line 1, column "yn (0)". Value "n" is not allowed. Allowed values: ["true", "false"].', + $csv->validate()[0], + ); + } + + public function testIsEmail(): void + { + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('email', 'is_email', true)); + isSame([], $csv->validate()); + + $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('yn', 'is_email', true)); + isSame( + 'Error "is_email" at line 1, column "yn (0)". Value "N" is not a valid email.', + $csv->validate()[0], + ); + } + + public function testIsEmailPure(): void + { + $rule = new IsEmail('prop', true); + isSame(null, $rule->validate('user@example.com')); + isSame( + 'Error "is_email" at line 0, column "prop". Value "example.com" is not a valid email.', + $rule->validate('example.com'), + ); + } + + public function testIsDomain(): void + { + $rule = new IsDomain('prop', true); + isSame(null, $rule->validate('example.com')); + isSame(null, $rule->validate('sub.example.com')); + isSame(null, $rule->validate('sub.sub.example.com')); + isSame( + 'Error "is_domain" at line 0, column "prop". Value "123" is not a valid domain.', + $rule->validate('123'), + ); + } + + public function testIsIp(): void + { + $rule = new IsIp('prop', true); + isSame(null, $rule->validate('127.0.0.1')); + isSame( + 'Error "is_ip" at line 0, column "prop". Value "123" is not a valid IP.', + $rule->validate('123'), + ); + } + + public function testIsUrl(): void + { + $rule = new IsUrl('prop', true); + isSame(null, $rule->validate('http://example.com')); + isSame(null, $rule->validate('http://example.com/home-page')); + isSame(null, $rule->validate('ftp://example.com/home-page?param=value')); + isSame( + 'Error "is_url" at line 0, column "prop". Value "123" is not a valid URL.', + $rule->validate('123'), + ); + } + + public function testIsLatitude(): void + { + $rule = new IsLatitude('prop', true); + isSame(null, $rule->validate('0')); + isSame(null, $rule->validate('90')); + isSame(null, $rule->validate('-90')); + isSame( + 'Error "is_latitude" at line 0, column "prop". Value "123" is not a valid latitude (-90 <= x <= 90).', + $rule->validate('123'), + ); + isSame( + 'Error "is_latitude" at line 0, column "prop". Value "1.0.0.0" is not a float number.', + $rule->validate('1.0.0.0'), + ); + } + + public function testIsLongitude(): void + { + $rule = new IsLongitude('prop', true); + isSame(null, $rule->validate('0')); + isSame(null, $rule->validate('180')); + isSame(null, $rule->validate('-180')); + isSame( + 'Error "is_longitude" at line 0, column "prop". Value "1230" is not a valid longitude (-180 <= x <= 180).', + $rule->validate('1230'), + ); + isSame( + 'Error "is_longitude" at line 0, column "prop". Value "1.0.0.0" is not a float number.', + $rule->validate('1.0.0.0'), + ); + } + + private function getRule(?string $columnName, ?string $ruleName, array|bool|float|int|string $options): array + { + return ['columns' => [['name' => $columnName, 'rules' => [$ruleName => $options]]]]; + } +} diff --git a/tests/autoload.php b/tests/autoload.php new file mode 100644 index 00000000..90acf384 --- /dev/null +++ b/tests/autoload.php @@ -0,0 +1,25 @@ + Date: Sun, 10 Mar 2024 15:12:40 +0400 Subject: [PATCH 02/25] Refactor UnitFacing rule to accept specific values. Add unit facing options and update test cases. --- src/Validators/Rules/UnitFacing.php | 41 +++++++++++++++++++++++++++++ tests/RulesTest.php | 20 ++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/Validators/Rules/UnitFacing.php diff --git a/src/Validators/Rules/UnitFacing.php b/src/Validators/Rules/UnitFacing.php new file mode 100644 index 00000000..14f4c5e6 --- /dev/null +++ b/src/Validators/Rules/UnitFacing.php @@ -0,0 +1,41 @@ +validate('1bc'), ); } + + public function testUnitFacing(): void + { + $rule = new UnitFacing('prop'); + isSame(null, $rule->validate('N')); + isSame(null, $rule->validate('S')); + isSame(null, $rule->validate('E')); + isSame(null, $rule->validate('W')); + isSame(null, $rule->validate('NE')); + isSame(null, $rule->validate('SE')); + isSame(null, $rule->validate('NW')); + isSame(null, $rule->validate('SW')); + isSame(null, $rule->validate('none')); + isSame( + 'Error "unit_facing" at line 0, column "prop". Value "qwe" is not allowed. ' . + 'Allowed values: ["N", "S", "E", "W", "NE", "SE", "NW", "SW", "none", ""].', + $rule->validate('qwe'), + ); + } } From 871c93572be1acd647b583644603d1d1a7461754 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Sun, 10 Mar 2024 18:08:15 +0400 Subject: [PATCH 03/25] Refactor CSV and schema file paths in the test cases. --- src/Validators/Error.php | 63 +++++++++ src/Validators/Rules/AbstarctRule.php | 7 +- src/Validators/Rules/IsBool.php | 4 + src/Validators/Rules/IsDomain.php | 4 + src/Validators/Rules/IsEmail.php | 4 + src/Validators/Rules/IsFloat.php | 4 + src/Validators/Rules/IsInt.php | 4 + src/Validators/Rules/IsIp.php | 4 + src/Validators/Rules/IsLatitude.php | 4 + src/Validators/Rules/IsLongitude.php | 4 + src/Validators/Rules/IsUrl.php | 4 + src/Validators/Rules/NotEmpty.php | 4 + src/Validators/Rules/OnlyCapitalize.php | 4 + src/Validators/Rules/OnlyLowercase.php | 4 + src/Validators/Rules/OnlyTrimed.php | 4 + src/Validators/Rules/OnlyUppercase.php | 4 + src/Validators/Rules/UnitFacing.php | 4 + src/Validators/Rules/UsaMarketName.php | 34 +++++ src/Validators/Validator.php | 3 + tests/{ => Blueprint}/CsvReaderTest.php | 13 +- tests/{ => Blueprint}/RulesTest.php | 170 +++++++++++++----------- tests/{ => Blueprint}/SchemaTest.php | 15 ++- tests/{ => Blueprint}/UtilsTest.php | 5 +- tests/{ => Blueprint}/ValidatorTest.php | 88 ++++++------ 24 files changed, 327 insertions(+), 131 deletions(-) create mode 100644 src/Validators/Error.php create mode 100644 src/Validators/Rules/UsaMarketName.php rename tests/{ => Blueprint}/CsvReaderTest.php (82%) rename tests/{ => Blueprint}/RulesTest.php (81%) rename tests/{ => Blueprint}/SchemaTest.php (93%) rename tests/{ => Blueprint}/UtilsTest.php (93%) rename tests/{ => Blueprint}/ValidatorTest.php (86%) diff --git a/src/Validators/Error.php b/src/Validators/Error.php new file mode 100644 index 00000000..51c314ce --- /dev/null +++ b/src/Validators/Error.php @@ -0,0 +1,63 @@ +getRuleCode()}\" at line {$this->getLine()}, column \"{$this->getColumnName()}\". {$this->getMessage()}."; + } + + public function getRuleCode(): string + { + return $this->ruleCode; + } + + public function getMessage(): string + { + return \rtrim($this->message, '.'); + } + + public function getColumnName(): string + { + return $this->columnName; + } + + public function getLine(): int + { + return $this->line; + } + + public function toArray(): array + { + return [ + 'ruleCode' => $this->ruleCode, + 'message' => $this->message, + 'columnName' => $this->columnName, + 'line' => $this->line, + ]; + } +} diff --git a/src/Validators/Rules/AbstarctRule.php b/src/Validators/Rules/AbstarctRule.php index cf939f65..d8ab42c4 100644 --- a/src/Validators/Rules/AbstarctRule.php +++ b/src/Validators/Rules/AbstarctRule.php @@ -17,6 +17,7 @@ namespace JBZoo\CsvBlueprint\Validators\Rules; use JBZoo\CsvBlueprint\Utils; +use JBZoo\CsvBlueprint\Validators\Error; use function JBZoo\Data\data; use function JBZoo\Utils\bool; @@ -39,12 +40,10 @@ public function __construct(?string $columnNameId, null|array|bool|float|int|str $this->ruleCode = $this->getRuleCode(); } - public function validate(?string $cellValue, int $line = 0): ?string + public function validate(?string $cellValue, int $line = 0): ?Error { if ($error = $this->validateRule($cellValue)) { - $error = \rtrim($error, '.'); // Remove last dot to make the final message nice. - - return "Error \"{$this->ruleCode}\" at line {$line}, column \"{$this->columnNameId}\". {$error}."; + return new Error($this->ruleCode, $error, $this->columnNameId, $line); } return null; diff --git a/src/Validators/Rules/IsBool.php b/src/Validators/Rules/IsBool.php index f05dfa79..e4260f5b 100644 --- a/src/Validators/Rules/IsBool.php +++ b/src/Validators/Rules/IsBool.php @@ -20,6 +20,10 @@ final class IsBool extends AllowValues { public function validateRule(?string $cellValue): ?string { + if (!$this->getOptionAsBool()) { + return null; + } + return parent::validateRule(\strtolower((string)$cellValue)); } diff --git a/src/Validators/Rules/IsDomain.php b/src/Validators/Rules/IsDomain.php index a39caf33..70bf6f92 100644 --- a/src/Validators/Rules/IsDomain.php +++ b/src/Validators/Rules/IsDomain.php @@ -20,6 +20,10 @@ final class IsDomain extends AbstarctRule { public function validateRule(?string $cellValue): ?string { + if (!$this->getOptionAsBool()) { + return null; + } + $domainPattern = '/^(?!-)[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(\.[A-Za-z]{2,})$/'; if (!\preg_match($domainPattern, $cellValue)) { diff --git a/src/Validators/Rules/IsEmail.php b/src/Validators/Rules/IsEmail.php index 9d0cc6ec..11c44eed 100644 --- a/src/Validators/Rules/IsEmail.php +++ b/src/Validators/Rules/IsEmail.php @@ -20,6 +20,10 @@ final class IsEmail extends AbstarctRule { public function validateRule(?string $cellValue): ?string { + if (!$this->getOptionAsBool()) { + return null; + } + if (!\filter_var($cellValue, \FILTER_VALIDATE_EMAIL)) { return "Value \"{$cellValue}\" is not a valid email"; } diff --git a/src/Validators/Rules/IsFloat.php b/src/Validators/Rules/IsFloat.php index f60134c8..7d05d901 100644 --- a/src/Validators/Rules/IsFloat.php +++ b/src/Validators/Rules/IsFloat.php @@ -20,6 +20,10 @@ class IsFloat extends AbstarctRule { public function validateRule(?string $cellValue): ?string { + if (!$this->getOptionAsBool()) { + return null; + } + if (!\preg_match('/^-?\d+(\.\d+)?$/', $cellValue)) { return "Value \"{$cellValue}\" is not a float number"; } diff --git a/src/Validators/Rules/IsInt.php b/src/Validators/Rules/IsInt.php index 23c4f137..e5919607 100644 --- a/src/Validators/Rules/IsInt.php +++ b/src/Validators/Rules/IsInt.php @@ -20,6 +20,10 @@ final class IsInt extends AbstarctRule { public function validateRule(?string $cellValue): ?string { + if (!$this->getOptionAsBool()) { + return null; + } + if (!\preg_match('/^-?\d+$/', $cellValue)) { return "Value \"{$cellValue}\" is not an integer"; } diff --git a/src/Validators/Rules/IsIp.php b/src/Validators/Rules/IsIp.php index 052595c0..746acf7c 100644 --- a/src/Validators/Rules/IsIp.php +++ b/src/Validators/Rules/IsIp.php @@ -20,6 +20,10 @@ final class IsIp extends AbstarctRule { public function validateRule(?string $cellValue): ?string { + if (!$this->getOptionAsBool()) { + return null; + } + if (!\filter_var($cellValue, \FILTER_VALIDATE_IP)) { return "Value \"{$cellValue}\" is not a valid IP"; } diff --git a/src/Validators/Rules/IsLatitude.php b/src/Validators/Rules/IsLatitude.php index 6e17e8dc..d2052890 100644 --- a/src/Validators/Rules/IsLatitude.php +++ b/src/Validators/Rules/IsLatitude.php @@ -20,6 +20,10 @@ final class IsLatitude extends IsFloat { public function validateRule(?string $cellValue): ?string { + if (!$this->getOptionAsBool()) { + return null; + } + $result = parent::validateRule($cellValue); if ($result) { return $result; diff --git a/src/Validators/Rules/IsLongitude.php b/src/Validators/Rules/IsLongitude.php index 676458ed..a91ffd3a 100644 --- a/src/Validators/Rules/IsLongitude.php +++ b/src/Validators/Rules/IsLongitude.php @@ -20,6 +20,10 @@ final class IsLongitude extends IsFloat { public function validateRule(?string $cellValue): ?string { + if (!$this->getOptionAsBool()) { + return null; + } + $result = parent::validateRule($cellValue); if ($result) { return $result; diff --git a/src/Validators/Rules/IsUrl.php b/src/Validators/Rules/IsUrl.php index 0de775ac..f9e2e30f 100644 --- a/src/Validators/Rules/IsUrl.php +++ b/src/Validators/Rules/IsUrl.php @@ -20,6 +20,10 @@ final class IsUrl extends AbstarctRule { public function validateRule(?string $cellValue): ?string { + if (!$this->getOptionAsBool()) { + return null; + } + if (!\filter_var($cellValue, \FILTER_VALIDATE_URL)) { return "Value \"{$cellValue}\" is not a valid URL"; } diff --git a/src/Validators/Rules/NotEmpty.php b/src/Validators/Rules/NotEmpty.php index 4812e7ee..b99339d7 100644 --- a/src/Validators/Rules/NotEmpty.php +++ b/src/Validators/Rules/NotEmpty.php @@ -20,6 +20,10 @@ final class NotEmpty extends AbstarctRule { public function validateRule(?string $cellValue): ?string { + if (!$this->getOptionAsBool()) { + return null; + } + if ($cellValue === null || $cellValue === '') { return 'Value is empty'; } diff --git a/src/Validators/Rules/OnlyCapitalize.php b/src/Validators/Rules/OnlyCapitalize.php index beebb631..49771bfd 100644 --- a/src/Validators/Rules/OnlyCapitalize.php +++ b/src/Validators/Rules/OnlyCapitalize.php @@ -20,6 +20,10 @@ final class OnlyCapitalize extends AbstarctRule { public function validateRule(?string $cellValue): ?string { + if (!$this->getOptionAsBool()) { + return null; + } + if ($cellValue !== null && $cellValue !== \ucfirst($cellValue)) { return "Value \"{$cellValue}\" should be in capitalize"; } diff --git a/src/Validators/Rules/OnlyLowercase.php b/src/Validators/Rules/OnlyLowercase.php index a81610b6..3e4c8e4c 100644 --- a/src/Validators/Rules/OnlyLowercase.php +++ b/src/Validators/Rules/OnlyLowercase.php @@ -20,6 +20,10 @@ final class OnlyLowercase extends AbstarctRule { public function validateRule(?string $cellValue): ?string { + if (!$this->getOptionAsBool()) { + return null; + } + if ($cellValue !== null && $cellValue !== \mb_strtolower($cellValue)) { return "Value \"{$cellValue}\" should be in lowercase"; } diff --git a/src/Validators/Rules/OnlyTrimed.php b/src/Validators/Rules/OnlyTrimed.php index 12bf539a..125ea168 100644 --- a/src/Validators/Rules/OnlyTrimed.php +++ b/src/Validators/Rules/OnlyTrimed.php @@ -20,6 +20,10 @@ final class OnlyTrimed extends AbstarctRule { public function validateRule(?string $cellValue): ?string { + if (!$this->getOptionAsBool()) { + return null; + } + if (\trim($cellValue) !== $cellValue) { return "Value \"{$cellValue}\" is not trimmed"; } diff --git a/src/Validators/Rules/OnlyUppercase.php b/src/Validators/Rules/OnlyUppercase.php index 901c117a..f3014b36 100644 --- a/src/Validators/Rules/OnlyUppercase.php +++ b/src/Validators/Rules/OnlyUppercase.php @@ -20,6 +20,10 @@ final class OnlyUppercase extends AbstarctRule { public function validateRule(?string $cellValue): ?string { + if (!$this->getOptionAsBool()) { + return null; + } + if ($cellValue !== null && \mb_strtoupper($cellValue, 'UTF-8') !== $cellValue) { return "Value \"{$cellValue}\" is not uppercase"; } diff --git a/src/Validators/Rules/UnitFacing.php b/src/Validators/Rules/UnitFacing.php index 14f4c5e6..caf7f7eb 100644 --- a/src/Validators/Rules/UnitFacing.php +++ b/src/Validators/Rules/UnitFacing.php @@ -20,6 +20,10 @@ class UnitFacing extends AllowValues { public function validateRule(?string $cellValue): ?string { + if (!$this->getOptionAsBool()) { + return null; + } + return parent::validateRule((string)$cellValue); } diff --git a/src/Validators/Rules/UsaMarketName.php b/src/Validators/Rules/UsaMarketName.php new file mode 100644 index 00000000..24f0abc3 --- /dev/null +++ b/src/Validators/Rules/UsaMarketName.php @@ -0,0 +1,34 @@ +getOptionAsBool()) { + return null; + } + + if (!\preg_match('/^[A-Za-z0-9\s-]+, [A-Z]{2}$/u', $cellValue)) { + return 'Invalid market name format for value "' . $cellValue . '". ' . + 'Market name must have format "New York, NY"'; + } + + return null; + } +} diff --git a/src/Validators/Validator.php b/src/Validators/Validator.php index 3359958b..4ff3adbb 100644 --- a/src/Validators/Validator.php +++ b/src/Validators/Validator.php @@ -28,6 +28,9 @@ public function __construct(Column $column) $this->ruleset = new Ruleset($column->getRules(), $column->getHumanName()); } + /** + * @return Error[] + */ public function validate(?string $cellValue, int $line): array { return $this->ruleset->validate($cellValue, $line); diff --git a/tests/CsvReaderTest.php b/tests/Blueprint/CsvReaderTest.php similarity index 82% rename from tests/CsvReaderTest.php rename to tests/Blueprint/CsvReaderTest.php index d26711d1..c80ef60f 100644 --- a/tests/CsvReaderTest.php +++ b/tests/Blueprint/CsvReaderTest.php @@ -14,17 +14,20 @@ declare(strict_types=1); -namespace JBZoo\PHPUnit; +namespace JBZoo\PHPUnit\Blueprint; use JBZoo\CsvBlueprint\Csv\CsvFile; +use JBZoo\PHPUnit\PHPUnit; + +use function JBZoo\PHPUnit\isSame; final class CsvReaderTest extends PHPUnit { - private const CSV_SIMPLE_HEADER = __DIR__ . '/fixtures/simple_header.csv'; - private const CSV_SIMPLE_NO_HEADER = __DIR__ . '/fixtures/simple_no_header.csv'; + private const CSV_SIMPLE_HEADER = PROJECT_TESTS . '/fixtures/simple_header.csv'; + private const CSV_SIMPLE_NO_HEADER = PROJECT_TESTS . '/fixtures/simple_no_header.csv'; - private const SCHEMA_SIMPLE_HEADER = __DIR__ . '/schemas/simple_header.yml'; - private const SCHEMA_SIMPLE_NO_HEADER = __DIR__ . '/schemas/simple_no_header.yml'; + private const SCHEMA_SIMPLE_HEADER = PROJECT_TESTS . '/schemas/simple_header.yml'; + private const SCHEMA_SIMPLE_NO_HEADER = PROJECT_TESTS . '/schemas/simple_no_header.yml'; public function testReadCsvFileWithoutHeader(): void { diff --git a/tests/RulesTest.php b/tests/Blueprint/RulesTest.php similarity index 81% rename from tests/RulesTest.php rename to tests/Blueprint/RulesTest.php index 7772cbd7..bff493f5 100644 --- a/tests/RulesTest.php +++ b/tests/Blueprint/RulesTest.php @@ -14,6 +14,8 @@ declare(strict_types=1); +namespace JBZoo\PHPUnit\Blueprint; + use JBZoo\CsvBlueprint\Validators\Rules\AllowValues; use JBZoo\CsvBlueprint\Validators\Rules\DateFormat; use JBZoo\CsvBlueprint\Validators\Rules\ExactValue; @@ -39,6 +41,7 @@ use JBZoo\CsvBlueprint\Validators\Rules\Precision; use JBZoo\CsvBlueprint\Validators\Rules\Regex; use JBZoo\CsvBlueprint\Validators\Rules\UnitFacing; +use JBZoo\CsvBlueprint\Validators\Rules\UsaMarketName; use JBZoo\PHPUnit\PHPUnit; use function JBZoo\PHPUnit\isSame; @@ -59,12 +62,12 @@ public function testAllowValues(): void isSame( 'Error "allow_values" at line 0, column "prop". ' . 'Value "" is not allowed. Allowed values: ["1", "2", "3"].', - $rule->validate(''), + (string)$rule->validate(''), ); isSame( 'Error "allow_values" at line 0, column "prop". ' . 'Value "invalid" is not allowed. Allowed values: ["1", "2", "3"].', - $rule->validate('invalid'), + (string)$rule->validate('invalid'), ); $rule = new AllowValues('prop', ['1', '2', '3', '']); @@ -81,12 +84,12 @@ public function testDateFormat(): void isSame( 'Error "date_format" at line 0, column "prop". ' . 'Date format of value "" is not valid. Expected format: "Y-m-d".', - $rule->validate(''), + (string)$rule->validate(''), ); isSame( 'Error "date_format" at line 0, column "prop". ' . 'Date format of value "2000-01-02 12:34:56" is not valid. Expected format: "Y-m-d".', - $rule->validate('2000-01-02 12:34:56'), + (string)$rule->validate('2000-01-02 12:34:56'), ); } @@ -96,17 +99,17 @@ public function testExactValue(): void isSame(null, $rule->validate('123')); isSame( 'Error "exact_value" at line 0, column "prop". Value "" is not strict equal to "123".', - $rule->validate(''), + (string)$rule->validate(''), ); isSame( 'Error "exact_value" at line 0, column "prop". Value "2000-01-02" is not strict equal to "123".', - $rule->validate('2000-01-02'), + (string)$rule->validate('2000-01-02'), ); } public function testIsBool(): void { - $rule = new IsBool('prop'); + $rule = new IsBool('prop', true); isSame(null, $rule->validate('true')); isSame(null, $rule->validate('false')); isSame(null, $rule->validate('TRUE')); @@ -115,17 +118,17 @@ public function testIsBool(): void isSame(null, $rule->validate('False')); isSame( 'Error "is_bool" at line 0, column "prop". Value "" is not allowed. Allowed values: ["true", "false"].', - $rule->validate(''), + (string)$rule->validate(''), ); isSame( 'Error "is_bool" at line 0, column "prop". Value "1" is not allowed. Allowed values: ["true", "false"].', - $rule->validate('1'), + (string)$rule->validate('1'), ); } public function testIsDomain(): void { - $rule = new IsDomain('prop'); + $rule = new IsDomain('prop', true); isSame(null, $rule->validate('example.com')); isSame(null, $rule->validate('sub.example.com')); isSame(null, $rule->validate('sub.sub.example.com')); @@ -134,147 +137,147 @@ public function testIsDomain(): void isSame(null, $rule->validate('sub-sub-example.qwerty')); isSame( 'Error "is_domain" at line 0, column "prop". Value "example" is not a valid domain.', - $rule->validate('example'), + (string)(string)$rule->validate('example'), ); isSame( 'Error "is_domain" at line 0, column "prop". Value "" is not a valid domain.', - $rule->validate(''), + (string)$rule->validate(''), ); } public function testIsEmail(): void { - $rule = new IsEmail('prop'); + $rule = new IsEmail('prop', true); isSame(null, $rule->validate('user@example.com')); isSame(null, $rule->validate('user@sub.example.com')); isSame( 'Error "is_email" at line 0, column "prop". Value "user:pass@example.com" is not a valid email.', - $rule->validate('user:pass@example.com'), + (string)(string)$rule->validate('user:pass@example.com'), ); } public function testIsFloat(): void { - $rule = new IsFloat('prop'); + $rule = new IsFloat('prop', true); isSame(null, $rule->validate('1')); isSame(null, $rule->validate('1.0')); isSame(null, $rule->validate('-1')); isSame(null, $rule->validate('-1.0')); isSame( 'Error "is_float" at line 0, column "prop". Value "1.000.000" is not a float number.', - $rule->validate('1.000.000'), + (string)$rule->validate('1.000.000'), ); isSame( 'Error "is_float" at line 0, column "prop". Value "" is not a float number.', - $rule->validate(''), + (string)$rule->validate(''), ); isSame( 'Error "is_float" at line 0, column "prop". Value " 1" is not a float number.', - $rule->validate(' 1'), + (string)$rule->validate(' 1'), ); } public function testIsInt(): void { - $rule = new IsInt('prop'); + $rule = new IsInt('prop', true); isSame(null, $rule->validate('1')); isSame(null, $rule->validate('0')); isSame(null, $rule->validate('-1')); isSame( 'Error "is_int" at line 0, column "prop". Value "1.000.000" is not an integer.', - $rule->validate('1.000.000'), + (string)$rule->validate('1.000.000'), ); isSame( 'Error "is_int" at line 0, column "prop". Value "1.1" is not an integer.', - $rule->validate('1.1'), + (string)(string)$rule->validate('1.1'), ); isSame( 'Error "is_int" at line 0, column "prop". Value "1.0" is not an integer.', - $rule->validate('1.0'), + (string)$rule->validate('1.0'), ); isSame( 'Error "is_int" at line 0, column "prop". Value "" is not an integer.', - $rule->validate(''), + (string)$rule->validate(''), ); isSame( 'Error "is_int" at line 0, column "prop". Value " 1" is not an integer.', - $rule->validate(' 1'), + (string)(string)$rule->validate(' 1'), ); isSame( 'Error "is_int" at line 0, column "prop". Value "1 " is not an integer.', - $rule->validate('1 '), + (string)(string)$rule->validate('1 '), ); } public function testIsIp(): void { - $rule = new IsIp('prop'); + $rule = new IsIp('prop', true); isSame(null, $rule->validate('127.0.0.1')); isSame(null, $rule->validate('0.0.0.0')); isSame( 'Error "is_ip" at line 0, column "prop". Value "1.2.3" is not a valid IP.', - $rule->validate('1.2.3'), + (string)$rule->validate('1.2.3'), ); } public function testIsLatitude(): void { - $rule = new IsLatitude('prop'); + $rule = new IsLatitude('prop', true); isSame(null, $rule->validate('0')); isSame(null, $rule->validate('90')); isSame(null, $rule->validate('-90')); isSame( 'Error "is_latitude" at line 0, column "prop". Value "123" is not a valid latitude (-90 <= x <= 90).', - $rule->validate('123'), + (string)$rule->validate('123'), ); isSame( 'Error "is_latitude" at line 0, column "prop". Value "90.1" is not a valid latitude (-90 <= x <= 90).', - $rule->validate('90.1'), + (string)$rule->validate('90.1'), ); isSame( 'Error "is_latitude" at line 0, column "prop". Value "90.1.1.1.1" is not a float number.', - $rule->validate('90.1.1.1.1'), + (string)$rule->validate('90.1.1.1.1'), ); } public function testIsLongitude(): void { - $rule = new IsLongitude('prop'); + $rule = new IsLongitude('prop', true); isSame(null, $rule->validate('0')); isSame(null, $rule->validate('180')); isSame(null, $rule->validate('-180')); isSame( 'Error "is_longitude" at line 0, column "prop". Value "1230" is not a valid longitude (-180 <= x <= 180).', - $rule->validate('1230'), + (string)$rule->validate('1230'), ); isSame( 'Error "is_longitude" at line 0, column "prop". ' . 'Value "180.0001" is not a valid longitude (-180 <= x <= 180).', - $rule->validate('180.0001'), + (string)$rule->validate('180.0001'), ); isSame( 'Error "is_longitude" at line 0, column "prop". Value "-180.1" is not a valid longitude (-180 <= x <= 180).', - $rule->validate('-180.1'), + (string)$rule->validate('-180.1'), ); isSame( 'Error "is_longitude" at line 0, column "prop". Value "1.0.0.0" is not a float number.', - $rule->validate('1.0.0.0'), + (string)$rule->validate('1.0.0.0'), ); } public function testIsUrl(): void { - $rule = new IsUrl('prop'); + $rule = new IsUrl('prop', true); isSame(null, $rule->validate('http://example.com')); isSame(null, $rule->validate('http://example.com/home-page')); isSame(null, $rule->validate('ftp://user:pass@example.com/home-page?param=value&v=asd#anchor')); isSame( 'Error "is_url" at line 0, column "prop". Value "123" is not a valid URL.', - $rule->validate('123'), + (string)$rule->validate('123'), ); isSame( 'Error "is_url" at line 0, column "prop". Value "//example.com" is not a valid URL.', - $rule->validate('//example.com'), + (string)$rule->validate('//example.com'), ); } @@ -285,11 +288,11 @@ public function testMin(): void isSame(null, $rule->validate('11')); isSame( 'Error "min" at line 0, column "prop". Value "9" is less than "10".', - $rule->validate('9'), + (string)$rule->validate('9'), ); isSame( 'Error "min" at line 0, column "prop". Value "example.com" is not a float number.', - $rule->validate('example.com'), + (string)$rule->validate('example.com'), ); $rule = new Min('prop', 10.1); @@ -297,11 +300,11 @@ public function testMin(): void isSame(null, $rule->validate('11')); isSame( 'Error "min" at line 0, column "prop". Value "9" is less than "10.1".', - $rule->validate('9'), + (string)$rule->validate('9'), ); isSame( 'Error "min" at line 0, column "prop". Value "example.com" is not a float number.', - $rule->validate('example.com'), + (string)$rule->validate('example.com'), ); } @@ -312,11 +315,11 @@ public function testMax(): void isSame(null, $rule->validate('10')); isSame( 'Error "max" at line 0, column "prop". Value "123" is greater than "10".', - $rule->validate('123'), + (string)$rule->validate('123'), ); isSame( 'Error "max" at line 0, column "prop". Value "example.com" is not a float number.', - $rule->validate('example.com'), + (string)$rule->validate('example.com'), ); $rule = new Max('prop', 10.1); @@ -325,7 +328,7 @@ public function testMax(): void isSame(null, $rule->validate('10.1')); isSame( 'Error "max" at line 0, column "prop". Value "10.2" is greater than "10.1".', - $rule->validate('10.2'), + (string)$rule->validate('10.2'), ); } @@ -336,7 +339,7 @@ public function testMinDate(): void isSame( 'Error "min_date" at line 0, column "prop". ' . 'Value "2000-01-09" is less than the minimum date "2000-01-10T00:00:00.000+00:00".', - $rule->validate('2000-01-09'), + (string)$rule->validate('2000-01-09'), ); $rule = new MinDate('prop', '2000-01-10 00:00:00 +01:00'); @@ -344,7 +347,7 @@ public function testMinDate(): void isSame( 'Error "min_date" at line 0, column "prop". ' . 'Value "2000-01-09 23:59:59 Europe/Berlin" is less than the minimum date "2000-01-10T00:00:00.000+01:00".', - $rule->validate('2000-01-09 23:59:59 Europe/Berlin'), + (string)$rule->validate('2000-01-09 23:59:59 Europe/Berlin'), ); $rule = new MinDate('prop', '-1000 years'); @@ -358,7 +361,7 @@ public function testMaxDate(): void isSame( 'Error "max_date" at line 0, column "prop". ' . 'Value "2000-01-11" is more than the maximum date "2000-01-10T00:00:00.000+00:00".', - $rule->validate('2000-01-11'), + (string)$rule->validate('2000-01-11'), ); $rule = new MaxDate('prop', '2000-01-10 00:00:00'); @@ -366,7 +369,7 @@ public function testMaxDate(): void isSame( 'Error "max_date" at line 0, column "prop". ' . 'Value "2000-01-10 00:00:01" is more than the maximum date "2000-01-10T00:00:00.000+00:00".', - $rule->validate('2000-01-10 00:00:01'), + (string)$rule->validate('2000-01-10 00:00:01'), ); $rule = new MaxDate('prop', '+1 day'); @@ -381,15 +384,15 @@ public function testMinLength(): void isSame(null, $rule->validate(' 1 ')); isSame( 'Error "min_length" at line 0, column "prop". Value "1234" (legth: 4) is too short. Min length is 5.', - $rule->validate('1234'), + (string)$rule->validate('1234'), ); isSame( 'Error "min_length" at line 0, column "prop". Value "123 " (legth: 4) is too short. Min length is 5.', - $rule->validate('123 '), + (string)$rule->validate('123 '), ); isSame( 'Error "min_length" at line 0, column "prop". Value "" (legth: 0) is too short. Min length is 5.', - $rule->validate(''), + (string)$rule->validate(''), ); } @@ -403,17 +406,17 @@ public function testMaxLength(): void isSame(null, $rule->validate(' 1 ')); isSame( 'Error "max_length" at line 0, column "prop". Value "123456" (legth: 6) is too long. Max length is 5.', - $rule->validate('123456'), + (string)$rule->validate('123456'), ); isSame( 'Error "max_length" at line 0, column "prop". Value "12345 " (legth: 6) is too long. Max length is 5.', - $rule->validate('12345 '), + (string)$rule->validate('12345 '), ); } public function testNotEmpty(): void { - $rule = new NotEmpty('prop'); + $rule = new NotEmpty('prop', true); isSame(null, $rule->validate('0')); isSame(null, $rule->validate('false')); isSame(null, $rule->validate('1')); @@ -421,17 +424,17 @@ public function testNotEmpty(): void isSame(null, $rule->validate(' ')); isSame( 'Error "not_empty" at line 0, column "prop". Value is empty.', - $rule->validate(''), + (string)$rule->validate(''), ); isSame( 'Error "not_empty" at line 0, column "prop". Value is empty.', - $rule->validate(null), + (string)$rule->validate(null), ); } public function testOnlyCapitalize(): void { - $rule = new OnlyCapitalize('prop'); + $rule = new OnlyCapitalize('prop', true); isSame(null, $rule->validate('0')); isSame(null, $rule->validate('False')); isSame(null, $rule->validate('Qwe Rty')); @@ -439,17 +442,17 @@ public function testOnlyCapitalize(): void isSame(null, $rule->validate(' ')); isSame( 'Error "only_capitalize" at line 0, column "prop". Value "qwerty" should be in capitalize.', - $rule->validate('qwerty'), + (string)$rule->validate('qwerty'), ); isSame( 'Error "only_capitalize" at line 0, column "prop". Value "qwe Rty" should be in capitalize.', - $rule->validate('qwe Rty'), + (string)$rule->validate('qwe Rty'), ); } public function testOnlyLowercase(): void { - $rule = new OnlyLowercase('prop'); + $rule = new OnlyLowercase('prop', true); isSame(null, $rule->validate('0')); isSame(null, $rule->validate('false')); isSame(null, $rule->validate('qwe rty')); @@ -457,28 +460,28 @@ public function testOnlyLowercase(): void isSame(null, $rule->validate(' ')); isSame( 'Error "only_lowercase" at line 0, column "prop". Value "Qwerty" should be in lowercase.', - $rule->validate('Qwerty'), + (string)$rule->validate('Qwerty'), ); isSame( 'Error "only_lowercase" at line 0, column "prop". Value "qwe Rty" should be in lowercase.', - $rule->validate('qwe Rty'), + (string)$rule->validate('qwe Rty'), ); } public function testOnlyUppercase(): void { - $rule = new OnlyUppercase('prop'); + $rule = new OnlyUppercase('prop', true); isSame(null, $rule->validate('0')); isSame(null, $rule->validate('FALSE')); isSame(null, $rule->validate('QWE RTY')); isSame(null, $rule->validate(' ')); isSame( 'Error "only_uppercase" at line 0, column "prop". Value "Qwerty" is not uppercase.', - $rule->validate('Qwerty'), + (string)$rule->validate('Qwerty'), ); isSame( 'Error "only_uppercase" at line 0, column "prop". Value "qwe Rty" is not uppercase.', - $rule->validate('qwe Rty'), + (string)$rule->validate('qwe Rty'), ); } @@ -491,12 +494,12 @@ public function testPrecision(): void isSame( 'Error "precision" at line 0, column "prop". ' . 'Value "1.1" has a precision of 1 but should have a precision of 0.', - $rule->validate('1.1'), + (string)$rule->validate('1.1'), ); isSame( 'Error "precision" at line 0, column "prop". ' . 'Value "1.0" has a precision of 1 but should have a precision of 0.', - $rule->validate('1.0'), + (string)$rule->validate('1.0'), ); $rule = new Precision('prop', 1); @@ -506,12 +509,12 @@ public function testPrecision(): void isSame( 'Error "precision" at line 0, column "prop". ' . 'Value "1" has a precision of 0 but should have a precision of 1.', - $rule->validate('1'), + (string)$rule->validate('1'), ); isSame( 'Error "precision" at line 0, column "prop". ' . 'Value "1.01" has a precision of 2 but should have a precision of 1.', - $rule->validate('1.01'), + (string)$rule->validate('1.01'), ); $rule = new Precision('prop', 2); @@ -521,12 +524,12 @@ public function testPrecision(): void isSame( 'Error "precision" at line 0, column "prop". ' . 'Value "2.0" has a precision of 1 but should have a precision of 2.', - $rule->validate('2.0'), + (string)$rule->validate('2.0'), ); isSame( 'Error "precision" at line 0, column "prop". ' . 'Value "1.000" has a precision of 3 but should have a precision of 2.', - $rule->validate('1.000'), + (string)$rule->validate('1.000'), ); } @@ -538,7 +541,7 @@ public function testRegex(): void isSame(null, $rule->validate('a')); isSame( 'Error "regex" at line 0, column "prop". Value "1bc" does not match the pattern "/^a/".', - $rule->validate('1bc'), + (string)$rule->validate('1bc'), ); $rule = new Regex('prop', '^a'); @@ -547,13 +550,13 @@ public function testRegex(): void isSame(null, $rule->validate('a')); isSame( 'Error "regex" at line 0, column "prop". Value "1bc" does not match the pattern "/^a/u".', - $rule->validate('1bc'), + (string)$rule->validate('1bc'), ); } public function testUnitFacing(): void { - $rule = new UnitFacing('prop'); + $rule = new UnitFacing('prop', true); isSame(null, $rule->validate('N')); isSame(null, $rule->validate('S')); isSame(null, $rule->validate('E')); @@ -566,7 +569,20 @@ public function testUnitFacing(): void isSame( 'Error "unit_facing" at line 0, column "prop". Value "qwe" is not allowed. ' . 'Allowed values: ["N", "S", "E", "W", "NE", "SE", "NW", "SW", "none", ""].', - $rule->validate('qwe'), + (string)$rule->validate('qwe'), + ); + } + + public function testUsaMarketName(): void + { + $rule = new UsaMarketName('prop', true); + isSame(null, $rule->validate('New York, NY')); + isSame(null, $rule->validate('City, ST')); + isSame( + 'Error "usa_market_name" at line 0, column "prop". ' . + 'Invalid market name format for value ", ST". ' . + 'Market name must have format "New York, NY".', + (string)$rule->validate(', ST'), ); } } diff --git a/tests/SchemaTest.php b/tests/Blueprint/SchemaTest.php similarity index 93% rename from tests/SchemaTest.php rename to tests/Blueprint/SchemaTest.php index fb6bf51d..10d46fe4 100644 --- a/tests/SchemaTest.php +++ b/tests/Blueprint/SchemaTest.php @@ -14,14 +14,23 @@ declare(strict_types=1); -namespace JBZoo\PHPUnit; +namespace JBZoo\PHPUnit\Blueprint; use JBZoo\CsvBlueprint\Schema; +use JBZoo\PHPUnit\PHPUnit; + +use function JBZoo\PHPUnit\isEmpty; +use function JBZoo\PHPUnit\isFalse; +use function JBZoo\PHPUnit\isNotEmpty; +use function JBZoo\PHPUnit\isNotNull; +use function JBZoo\PHPUnit\isNull; +use function JBZoo\PHPUnit\isSame; +use function JBZoo\PHPUnit\isTrue; final class SchemaTest extends PHPUnit { - private const SCHEMA_EXAMPLE_EMPTY = __DIR__ . '/schemas/example_empty.yml'; - private const SCHEMA_EXAMPLE_FULL = __DIR__ . '/schemas/example_full.yml'; + private const SCHEMA_EXAMPLE_EMPTY = PROJECT_TESTS . '/schemas/example_empty.yml'; + private const SCHEMA_EXAMPLE_FULL = PROJECT_TESTS . '/schemas/example_full.yml'; public function testFilename(): void { diff --git a/tests/UtilsTest.php b/tests/Blueprint/UtilsTest.php similarity index 93% rename from tests/UtilsTest.php rename to tests/Blueprint/UtilsTest.php index f4e31e45..c10fdee6 100644 --- a/tests/UtilsTest.php +++ b/tests/Blueprint/UtilsTest.php @@ -14,9 +14,12 @@ declare(strict_types=1); -namespace JBZoo\PHPUnit; +namespace JBZoo\PHPUnit\Blueprint; use JBZoo\CsvBlueprint\Utils; +use JBZoo\PHPUnit\PHPUnit; + +use function JBZoo\PHPUnit\isSame; final class UtilsTest extends PHPUnit { diff --git a/tests/ValidatorTest.php b/tests/Blueprint/ValidatorTest.php similarity index 86% rename from tests/ValidatorTest.php rename to tests/Blueprint/ValidatorTest.php index 30e384ff..1d3f2c00 100644 --- a/tests/ValidatorTest.php +++ b/tests/Blueprint/ValidatorTest.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace JBZoo\PHPUnit; +namespace JBZoo\PHPUnit\Blueprint; use JBZoo\CsvBlueprint\Csv\CsvFile; use JBZoo\CsvBlueprint\Validators\Rules\IsDomain; @@ -23,15 +23,18 @@ use JBZoo\CsvBlueprint\Validators\Rules\IsLatitude; use JBZoo\CsvBlueprint\Validators\Rules\IsLongitude; use JBZoo\CsvBlueprint\Validators\Rules\IsUrl; +use JBZoo\PHPUnit\PHPUnit; + +use function JBZoo\PHPUnit\isSame; final class ValidatorTest extends PHPUnit { - private const CSV_SIMPLE_HEADER = __DIR__ . '/fixtures/simple_header.csv'; - private const CSV_SIMPLE_NO_HEADER = __DIR__ . '/fixtures/simple_no_header.csv'; - private const CSV_COMPLEX = __DIR__ . '/fixtures/complex_header.csv'; + private const CSV_SIMPLE_HEADER = PROJECT_TESTS . '/fixtures/simple_header.csv'; + private const CSV_SIMPLE_NO_HEADER = PROJECT_TESTS . '/fixtures/simple_no_header.csv'; + private const CSV_COMPLEX = PROJECT_TESTS . '/fixtures/complex_header.csv'; - private const SCHEMA_SIMPLE_HEADER = __DIR__ . '/schemas/simple_header.yml'; - private const SCHEMA_SIMPLE_NO_HEADER = __DIR__ . '/schemas/simple_no_header.yml'; + private const SCHEMA_SIMPLE_HEADER = PROJECT_TESTS . '/schemas/simple_header.yml'; + private const SCHEMA_SIMPLE_NO_HEADER = PROJECT_TESTS . '/schemas/simple_no_header.yml'; protected function setUp(): void { @@ -65,13 +68,16 @@ public function testNotEmptyMessage(): void isSame([], $csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('integer', 'not_empty', true)); - isSame('Error "not_empty" at line 18, column "integer (0)". Value is empty.', $csv->validate()[0]); + isSame( + 'Error "not_empty" at line 18, column "integer (0)". Value is empty.', + (string)$csv->validate()[0], + ); } public function testNoName(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule(null, 'not_empty', true)); - isSame('Property "name" is not defined for column id=0 in schema: _custom_array_', $csv->validate()[0]); + isSame('Property "name" is not defined for column id=0 in schema: _custom_array_', (string)$csv->validate()[0]); } public function testMin(): void @@ -80,7 +86,7 @@ public function testMin(): void isSame([], $csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'min', 10)); - isSame('Error "min" at line 1, column "seq (0)". Value "1" is less than "10".', $csv->validate()[0]); + isSame('Error "min" at line 1, column "seq (0)". Value "1" is less than "10".', (string)$csv->validate()[0]); } public function testMax(): void @@ -91,7 +97,7 @@ public function testMax(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'max', 10)); isSame( 'Error "max" at line 11, column "seq (0)". Value "11" is greater than "10".', - $csv->validate()[0], + (string)$csv->validate()[0], ); } @@ -106,19 +112,19 @@ public function testRegex(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'regex', '[a-z]')); isSame( 'Error "regex" at line 1, column "seq (0)". Value "1" does not match the pattern "/[a-z]/u".', - $csv->validate()[0], + (string)$csv->validate()[0], ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'regex', '/[a-z]/')); isSame( 'Error "regex" at line 1, column "seq (0)". Value "1" does not match the pattern "/[a-z]/".', - $csv->validate()[0], + (string)$csv->validate()[0], ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'regex', '/[a-z]/i')); isSame( 'Error "regex" at line 1, column "seq (0)". Value "1" does not match the pattern "/[a-z]/i".', - $csv->validate()[0], + (string)$csv->validate()[0], ); } @@ -130,7 +136,7 @@ public function testMinLength(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'min_length', 1000)); isSame( 'Error "min_length" at line 1, column "seq (0)". Value "1" (legth: 1) is too short. Min length is 1000.', - $csv->validate()[0], + (string)$csv->validate()[0], ); } @@ -142,7 +148,7 @@ public function testMaxLength(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'max_length', 1)); isSame( 'Error "max_length" at line 10, column "seq (0)". Value "10" (legth: 2) is too long. Max length is 1.', - $csv->validate()[0], + (string)$csv->validate()[0], ); } @@ -154,7 +160,7 @@ public function testOnlyTrimed(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('sentence', 'only_trimed', true)); isSame( 'Error "only_trimed" at line 13, column "sentence (0)". Value " Urecam" is not trimmed.', - $csv->validate()[0], + (string)$csv->validate()[0], ); } @@ -166,7 +172,7 @@ public function testOnlyUppercase(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'only_uppercase', true)); isSame( 'Error "only_uppercase" at line 1, column "bool (0)". Value "true" is not uppercase.', - $csv->validate()[0], + (string)$csv->validate()[0], ); } @@ -178,7 +184,7 @@ public function testOnlyLowercase(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'only_lowercase', true)); isSame( 'Error "only_lowercase" at line 7, column "bool (0)". Value "False" should be in lowercase.', - $csv->validate()[0], + (string)$csv->validate()[0], ); } @@ -190,7 +196,7 @@ public function testOnlyCapitalize(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'only_capitalize', true)); isSame( 'Error "only_capitalize" at line 1, column "bool (0)". Value "true" should be in capitalize.', - $csv->validate()[0], + (string)$csv->validate()[0], ); } @@ -203,14 +209,14 @@ public function testPrecision(): void isSame( 'Error "precision" at line 1, column "seq (0)". ' . 'Value "1" has a precision of 0 but should have a precision of 1.', - $csv->validate()[0], + (string)$csv->validate()[0], ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('float', 'precision', 3)); isSame( 'Error "precision" at line 2, column "float (0)". ' . 'Value "506847750940.2624" has a precision of 4 but should have a precision of 3.', - $csv->validate()[0], + (string)$csv->validate()[0], ); } @@ -223,14 +229,14 @@ public function testMinDate(): void isSame( 'Error "min_date" at line 1, column "date (0)". ' . 'Value "2042/11/18" is less than the minimum date "2120-01-01T00:00:00.000+00:00".', - $csv->validate()[0], + (string)$csv->validate()[0], ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'min_date', '2042/11/17')); isSame( 'Error "min_date" at line 4, column "date (0)". ' . 'Value "2032/09/09" is less than the minimum date "2042-11-17T00:00:00.000+00:00".', - $csv->validate()[0], + (string)$csv->validate()[0], ); } @@ -243,14 +249,14 @@ public function testMaxDate(): void isSame( 'Error "max_date" at line 22, column "date (0)". ' . 'Value "2120/02/01" is more than the maximum date "2120-01-01T00:00:00.000+00:00".', - $csv->validate()[0], + (string)$csv->validate()[0], ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'max_date', '2042/11/17')); isSame( 'Error "max_date" at line 1, column "date (0)". ' . 'Value "2042/11/18" is more than the maximum date "2042-11-17T00:00:00.000+00:00".', - $csv->validate()[0], + (string)$csv->validate()[0], ); } @@ -263,7 +269,7 @@ public function testDateFormat(): void isSame( 'Error "date_format" at line 1, column "date (0)". ' . 'Date format of value "2042/11/18" is not valid. Expected format: "Y/m/d H:i:s".', - $csv->validate()[0], + (string)$csv->validate()[0], ); } @@ -283,7 +289,7 @@ public function testAllowValues(): void isSame( 'Error "allow_values" at line 7, column "bool (0)". ' . 'Value "False" is not allowed. Allowed values: ["true", "false"].', - $csv->validate()[0], + (string)$csv->validate()[0], ); } @@ -295,13 +301,13 @@ public function testExactValue(): void $csv = new CsvFile(self::CSV_SIMPLE_HEADER, $this->getRule('exact', 'exact_value', '2')); isSame( 'Error "exact_value" at line 1, column "exact (0)". Value "1" is not strict equal to "2".', - $csv->validate()[0], + (string)$csv->validate()[0], ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'exact_value', 'true')); isSame( 'Error "exact_value" at line 3, column "bool (0)". Value "false" is not strict equal to "true".', - $csv->validate()[0], + (string)$csv->validate()[0], ); } @@ -313,7 +319,7 @@ public function testIsInt(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'is_int', true)); isSame( 'Error "is_int" at line 1, column "bool (0)". Value "true" is not an integer.', - $csv->validate()[0], + (string)$csv->validate()[0], ); } @@ -325,7 +331,7 @@ public function testIsFloat(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'is_float', true)); isSame( 'Error "is_float" at line 1, column "bool (0)". Value "true" is not a float number.', - $csv->validate()[0], + (string)$csv->validate()[0], ); } @@ -337,7 +343,7 @@ public function testIsBool(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('yn', 'is_bool', true)); isSame( 'Error "is_bool" at line 1, column "yn (0)". Value "n" is not allowed. Allowed values: ["true", "false"].', - $csv->validate()[0], + (string)$csv->validate()[0], ); } @@ -349,7 +355,7 @@ public function testIsEmail(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('yn', 'is_email', true)); isSame( 'Error "is_email" at line 1, column "yn (0)". Value "N" is not a valid email.', - $csv->validate()[0], + (string)$csv->validate()[0], ); } @@ -359,7 +365,7 @@ public function testIsEmailPure(): void isSame(null, $rule->validate('user@example.com')); isSame( 'Error "is_email" at line 0, column "prop". Value "example.com" is not a valid email.', - $rule->validate('example.com'), + (string)$rule->validate('example.com'), ); } @@ -371,7 +377,7 @@ public function testIsDomain(): void isSame(null, $rule->validate('sub.sub.example.com')); isSame( 'Error "is_domain" at line 0, column "prop". Value "123" is not a valid domain.', - $rule->validate('123'), + (string)$rule->validate('123'), ); } @@ -381,7 +387,7 @@ public function testIsIp(): void isSame(null, $rule->validate('127.0.0.1')); isSame( 'Error "is_ip" at line 0, column "prop". Value "123" is not a valid IP.', - $rule->validate('123'), + (string)$rule->validate('123'), ); } @@ -393,7 +399,7 @@ public function testIsUrl(): void isSame(null, $rule->validate('ftp://example.com/home-page?param=value')); isSame( 'Error "is_url" at line 0, column "prop". Value "123" is not a valid URL.', - $rule->validate('123'), + (string)$rule->validate('123'), ); } @@ -405,11 +411,11 @@ public function testIsLatitude(): void isSame(null, $rule->validate('-90')); isSame( 'Error "is_latitude" at line 0, column "prop". Value "123" is not a valid latitude (-90 <= x <= 90).', - $rule->validate('123'), + (string)$rule->validate('123'), ); isSame( 'Error "is_latitude" at line 0, column "prop". Value "1.0.0.0" is not a float number.', - $rule->validate('1.0.0.0'), + (string)$rule->validate('1.0.0.0'), ); } @@ -421,11 +427,11 @@ public function testIsLongitude(): void isSame(null, $rule->validate('-180')); isSame( 'Error "is_longitude" at line 0, column "prop". Value "1230" is not a valid longitude (-180 <= x <= 180).', - $rule->validate('1230'), + (string)$rule->validate('1230'), ); isSame( 'Error "is_longitude" at line 0, column "prop". Value "1.0.0.0" is not a float number.', - $rule->validate('1.0.0.0'), + (string)$rule->validate('1.0.0.0'), ); } From a40d4cf7c768404387c574c2f786c9f66de8e53e Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Sun, 10 Mar 2024 19:14:47 +0400 Subject: [PATCH 04/25] Refactor CSV structure definition and validation rules - Updated CSV structure definition in full.yml - Refactored error message concatenation in Error.php - Renamed UnitFacing validator to CardinalDirection - Updated ValidateFile command description --- schemas-examples/full.yml | 53 ++++++++++++++++++ src/Commands/ValidateFile.php | 2 +- src/Csv/ParseConfig.php | 3 +- src/Validators/Error.php | 3 +- .../{UnitFacing.php => CardinalDirection.php} | 15 +---- tests/Blueprint/RulesTest.php | 4 +- tests/schemas/ready.yml | 55 ------------------- 7 files changed, 62 insertions(+), 73 deletions(-) create mode 100644 schemas-examples/full.yml rename src/Validators/Rules/{UnitFacing.php => CardinalDirection.php} (74%) delete mode 100644 tests/schemas/ready.yml diff --git a/schemas-examples/full.yml b/schemas-examples/full.yml new file mode 100644 index 00000000..3914853c --- /dev/null +++ b/schemas-examples/full.yml @@ -0,0 +1,53 @@ +# +# JBZoo Toolbox - Csv-Blueprint. +# +# This file is part of the JBZoo Toolbox project. +# For the full copyright and license information, please view the LICENSE +# file that was distributed with this source code. +# +# @license MIT +# @copyright Copyright (C) JBZoo.com, All rights reserved. +# @see https://github.com/JBZoo/Csv-Blueprint +# + +csv_structure: + header: true # If the first row is a header. If true, name of each column is required + delimiter: , # Delimiter character in CSV file + quote_char: \ # Quote character in CSV file + enclosure: "\"" # Enclosure for each field in CSV file + encoding: utf-8 # Only utf-8, utf-16, utf-32 (Experimental) + bom: false # If the file has a BOM (Byte Order Mark) at the beginning (Experimental) + +columns: + - name: "csv header name" + description: > + Lorem ipsum dolor sit amet, consectetur adipiscing elit + sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam + rule: + allow_values: [ "y", "n", "" ] # Strict set of values that are allowed + date_format: Y-m-d # See: https://www.php.net/manual/en/datetime.format.php + exact_value: "Some string" # Case-sensitive. Exact value for string in the column + is_bool: true # true|false, Case-insensitive + is_domain: true # Only domain name. Example: "example.com" + is_email: true # Only email format. Example: "user@example.com" + is_float: true # Check format only. Can be negative and positive. Dot as decimal separator + is_int: true # Check format only. Can be negative and positive. Witout any separators + is_ip: true # Only IPv4. Example: "127.0.0.1" + is_latitude: true # Can be integer or float. Example: 50.123456 + is_longitude: true # Can be integer or float. Example: -89.123456 + is_url: true # Only URL format. Example: "https://example.com/page?query=string#anchor" + min: 10 # Can be integer or float, negative and positive + max: 100 # Can be integer or float, negative and positive + min_length: 1 # Integer only. Min length of the string with spaces + max_length: 10 # Integer only. Max length of the string with spaces + min_date: 2000-01-02 # See examples https://www.php.net/manual/en/function.strtotime.php + max_date: now # See examples https://www.php.net/manual/en/function.strtotime.php + not_empty: true # Value is not empty string. Ignore spaces. + only_capitalize: true # String is only capitalized. Example: "Hello World" + only_lowercase: true # String is only lowercase. Example: "hello world" + only_uppercase: true # String is only capitalized. Example: "HELLO WORLD" + only_trimed: true # Only trimed strings. Example: "Hello World" (not " Hello World ") + precision: 2 # Strict(!) number of digits after the decimal point + regex: "/^[\d]{2}$/" # Any valid regex pattern. See https://www.php.net/manual/en/reference.pcre.pattern.syntax.php + cardinal_direction: true # Valid cardinal direction. Examples: "N", "S", "NE", "SE", "none", "" + usa_market_name: true # Check if the value is a valid USA market name. Example: "New York, NY" diff --git a/src/Commands/ValidateFile.php b/src/Commands/ValidateFile.php index b992c96a..d9cf52a7 100644 --- a/src/Commands/ValidateFile.php +++ b/src/Commands/ValidateFile.php @@ -27,7 +27,7 @@ protected function configure(): void { $this ->setName('validate:file') - ->setDescription('Validate CSV file(s) by rules from yml file(s)') + ->setDescription('Validate CSV file by rules from yml file(s)') ->addArgument( 'csv-file-or-dir', InputArgument::REQUIRED, diff --git a/src/Csv/ParseConfig.php b/src/Csv/ParseConfig.php index 37a6a2a0..bf3840e7 100644 --- a/src/Csv/ParseConfig.php +++ b/src/Csv/ParseConfig.php @@ -85,7 +85,8 @@ public function getEnclosure(): string public function getEncoding(): string { - $encoding = \strtolower(\trim($this->structure->getString('encoding', self::FALLBACK_VALUES['encoding']))); + $encoding = \strtolower(\trim($this->structure->getString('encoding', self::FALLBACK_VALUES['encoding']))); + $availableOptions = [ // TODO: add flexible handler for this self::ENCODING_UTF8, self::ENCODING_UTF16, diff --git a/src/Validators/Error.php b/src/Validators/Error.php index 51c314ce..650cdb11 100644 --- a/src/Validators/Error.php +++ b/src/Validators/Error.php @@ -28,7 +28,8 @@ public function __construct( public function __toString(): string { - return "Error \"{$this->getRuleCode()}\" at line {$this->getLine()}, column \"{$this->getColumnName()}\". {$this->getMessage()}."; + return "Error \"{$this->getRuleCode()}\" at line {$this->getLine()}, " . + "column \"{$this->getColumnName()}\". {$this->getMessage()}."; } public function getRuleCode(): string diff --git a/src/Validators/Rules/UnitFacing.php b/src/Validators/Rules/CardinalDirection.php similarity index 74% rename from src/Validators/Rules/UnitFacing.php rename to src/Validators/Rules/CardinalDirection.php index caf7f7eb..f1cf9be9 100644 --- a/src/Validators/Rules/UnitFacing.php +++ b/src/Validators/Rules/CardinalDirection.php @@ -16,7 +16,7 @@ namespace JBZoo\CsvBlueprint\Validators\Rules; -class UnitFacing extends AllowValues +class CardinalDirection extends AllowValues { public function validateRule(?string $cellValue): ?string { @@ -29,17 +29,6 @@ public function validateRule(?string $cellValue): ?string public function getOptionAsArray(): array { - return [ - 'N', - 'S', - 'E', - 'W', - 'NE', - 'SE', - 'NW', - 'SW', - 'none', - '', - ]; + return ['N', 'S', 'E', 'W', 'NE', 'SE', 'NW', 'SW', 'none', '']; } } diff --git a/tests/Blueprint/RulesTest.php b/tests/Blueprint/RulesTest.php index bff493f5..da28a09c 100644 --- a/tests/Blueprint/RulesTest.php +++ b/tests/Blueprint/RulesTest.php @@ -17,6 +17,7 @@ namespace JBZoo\PHPUnit\Blueprint; use JBZoo\CsvBlueprint\Validators\Rules\AllowValues; +use JBZoo\CsvBlueprint\Validators\Rules\CardinalDirection; use JBZoo\CsvBlueprint\Validators\Rules\DateFormat; use JBZoo\CsvBlueprint\Validators\Rules\ExactValue; use JBZoo\CsvBlueprint\Validators\Rules\IsBool; @@ -40,7 +41,6 @@ use JBZoo\CsvBlueprint\Validators\Rules\OnlyUppercase; use JBZoo\CsvBlueprint\Validators\Rules\Precision; use JBZoo\CsvBlueprint\Validators\Rules\Regex; -use JBZoo\CsvBlueprint\Validators\Rules\UnitFacing; use JBZoo\CsvBlueprint\Validators\Rules\UsaMarketName; use JBZoo\PHPUnit\PHPUnit; @@ -556,7 +556,7 @@ public function testRegex(): void public function testUnitFacing(): void { - $rule = new UnitFacing('prop', true); + $rule = new CardinalDirection('prop', true); isSame(null, $rule->validate('N')); isSame(null, $rule->validate('S')); isSame(null, $rule->validate('E')); diff --git a/tests/schemas/ready.yml b/tests/schemas/ready.yml deleted file mode 100644 index b47b15a1..00000000 --- a/tests/schemas/ready.yml +++ /dev/null @@ -1,55 +0,0 @@ -# -# JBZoo Toolbox - Csv-Blueprint. -# -# This file is part of the JBZoo Toolbox project. -# For the full copyright and license information, please view the LICENSE -# file that was distributed with this source code. -# -# @license MIT -# @copyright Copyright (C) JBZoo.com, All rights reserved. -# @see https://github.com/JBZoo/Csv-Blueprint -# - -#finename_pattern: ^example\.csv$ - -#includes: # Alias is always required -# - /path/schema_1.yml as alias_1 # Full path to another schema. - -csv_structure: - # inherit: alias_1 - bom: false - delimiter: , - quote_char: \ - enclosure: "\"" - encoding: utf-8 - header: true - # strict_column_order: true - # other_columns_possible: true - -columns: - - name: Header Name - description: Some description - rule: - not_empty: true - regex: ^[a-zA-Z0-9]+$ - min: 1 - max: 255 - precision: 2 - min_length: 1 - max_length: 10 - only_trimed: true - only_uppercase: false - only_lowercase: false - only_capitalize: false - min_date: 2023-03-09 - max_date: 2024-03-09 - date_format: Y-m-d - allow_values: [ value1, value2, value3 ] - exact_value: "Qwerty" - is_bool: true - is_domain: true - is_email: true - is_float: true - is_int: true - is_ip: true - is_url: true From 14a7c6b49b52c1f31655eb08c357590e485362f8 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Sun, 10 Mar 2024 19:19:39 +0400 Subject: [PATCH 05/25] Refactor IsUuid4 class name and add test for IsUuid4 validator. --- src/Validators/Rules/IsUuid4.php | 35 ++++++++++++++++++++++++++++++++ tests/Blueprint/RulesTest.php | 14 ++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/Validators/Rules/IsUuid4.php diff --git a/src/Validators/Rules/IsUuid4.php b/src/Validators/Rules/IsUuid4.php new file mode 100644 index 00000000..0d6c48e8 --- /dev/null +++ b/src/Validators/Rules/IsUuid4.php @@ -0,0 +1,35 @@ +getOptionAsBool()) { + return null; + } + + $uuid4 = '/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89ABab][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/'; + + if (!\preg_match($uuid4, $cellValue)) { + return 'Value is not a valid UUID v4'; + } + + return null; + } +} diff --git a/tests/Blueprint/RulesTest.php b/tests/Blueprint/RulesTest.php index da28a09c..7395115d 100644 --- a/tests/Blueprint/RulesTest.php +++ b/tests/Blueprint/RulesTest.php @@ -29,6 +29,7 @@ use JBZoo\CsvBlueprint\Validators\Rules\IsLatitude; use JBZoo\CsvBlueprint\Validators\Rules\IsLongitude; use JBZoo\CsvBlueprint\Validators\Rules\IsUrl; +use JBZoo\CsvBlueprint\Validators\Rules\IsUuid4; use JBZoo\CsvBlueprint\Validators\Rules\Max; use JBZoo\CsvBlueprint\Validators\Rules\MaxDate; use JBZoo\CsvBlueprint\Validators\Rules\MaxLength; @@ -43,6 +44,7 @@ use JBZoo\CsvBlueprint\Validators\Rules\Regex; use JBZoo\CsvBlueprint\Validators\Rules\UsaMarketName; use JBZoo\PHPUnit\PHPUnit; +use JBZoo\Utils\Str; use function JBZoo\PHPUnit\isSame; @@ -567,7 +569,7 @@ public function testUnitFacing(): void isSame(null, $rule->validate('SW')); isSame(null, $rule->validate('none')); isSame( - 'Error "unit_facing" at line 0, column "prop". Value "qwe" is not allowed. ' . + 'Error "cardinal_direction" at line 0, column "prop". Value "qwe" is not allowed. ' . 'Allowed values: ["N", "S", "E", "W", "NE", "SE", "NW", "SW", "none", ""].', (string)$rule->validate('qwe'), ); @@ -585,4 +587,14 @@ public function testUsaMarketName(): void (string)$rule->validate(', ST'), ); } + + public function testIsUuid4(): void + { + $rule = new IsUuid4('prop', true); + isSame(null, $rule->validate(Str::uuid())); + isSame( + 'Error "is_uuid4" at line 0, column "prop". Value is not a valid UUID v4.', + (string)$rule->validate('123'), + ); + } } From 08c85968b3e0c88a297a1fce65d5f26e6b4e994f Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Sun, 10 Mar 2024 20:11:37 +0400 Subject: [PATCH 06/25] Add CSV schema examples to README.md and update dependencies in composer.json - Add full CSV schema examples in YAML, JSON, and PHP formats - Update jbzoo/toolbox-dev to version 7.1 and add jbzoo/markdown to composer.json --- README.md | 57 ++++++++++++-- composer.json | 3 +- schemas-examples/full.json | 46 +++++++++++ schemas-examples/full.php | 64 +++++++++++++++ schemas-examples/full.yml | 21 ++--- src/Csv/ParseConfig.php | 10 +-- tests/Blueprint/MiscTest.php | 143 ++++++++++++++++++++++++++++++++++ tests/Blueprint/UtilsTest.php | 48 ------------ 8 files changed, 323 insertions(+), 69 deletions(-) create mode 100644 schemas-examples/full.json create mode 100644 schemas-examples/full.php create mode 100644 tests/Blueprint/MiscTest.php delete mode 100644 tests/Blueprint/UtilsTest.php diff --git a/README.md b/README.md index 48be9939..09f464da 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,61 @@ composer require jbzoo/csv-blueprint ### Usage -```php -use JBZoo\CsvBlueprint\Csv-Blueprint; -// Just use it! -$object = new Csv-Blueprint(); -$object->doSomeStreetMagic(':)'); +### Schema file examples + +
+ Click to see YAML format + + ```yml +# It's a full example of the CSV schema file in YAML format. + +csv_structure: # Here are default values. You can skip this section if you don't need to override the default values + header: true # If the first row is a header. If true, name of each column is required + delimiter: , # Delimiter character in CSV file + quote_char: \ # Quote character in CSV file + enclosure: "\"" # Enclosure for each field in CSV file + encoding: utf-8 # Only utf-8, utf-16, utf-32 (Experimental) + bom: false # If the file has a BOM (Byte Order Mark) at the beginning (Experimental) + +columns: + - name: "csv header name" + description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + rules: + allow_values: [ y, n, "" ] # Strict set of values that are allowed + date_format: Y-m-d # See: https://www.php.net/manual/en/datetime.format.php + exact_value: Some string # Case-sensitive. Exact value for string in the column + is_bool: true # true|false, Case-insensitive + is_domain: true # Only domain name. Example: "example.com" + is_email: true # Only email format. Example: "user@example.com" + is_float: true # Check format only. Can be negative and positive. Dot as decimal separator + is_int: true # Check format only. Can be negative and positive. Without any separators + is_ip: true # Only IPv4. Example: "127.0.0.1" + is_latitude: true # Can be integer or float. Example: 50.123456 + is_longitude: true # Can be integer or float. Example: -89.123456 + is_url: true # Only URL format. Example: "https://example.com/page?query=string#anchor" + is_uuid4: true # Only UUID4 format. Example: "550e8400-e29b-41d4-a716-446655440000" + min: 10 # Can be integer or float, negative and positive + max: 100 # Can be integer or float, negative and positive + min_length: 1 # Integer only. Min length of the string with spaces + max_length: 10 # Integer only. Max length of the string with spaces + min_date: "2000-01-02" # See examples https://www.php.net/manual/en/function.strtotime.php + max_date: now # See examples https://www.php.net/manual/en/function.strtotime.php + not_empty: true # Value is not empty string. Ignore spaces. + only_capitalize: true # String is only capitalized. Example: "Hello World" + only_lowercase: true # String is only lowercase. Example: "hello world" + only_uppercase: true # String is only capitalized. Example: "HELLO WORLD" + only_trimed: true # Only trimed strings. Example: "Hello World" (not " Hello World ") + precision: 2 # Strict(!) number of digits after the decimal point + regex: /^[\d]{2}$/ # Any valid regex pattern. See https://www.php.net/manual/en/reference.pcre.pattern.syntax.php + cardinal_direction: true # Valid cardinal direction. Examples: "N", "S", "NE", "SE", "none", "" + usa_market_name: true # Check if the value is a valid USA market name. Example: "New York, NY" + ``` +
+ + ## Unit tests and check code style ```sh diff --git a/composer.json b/composer.json index 3d98946e..f944a76a 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,8 @@ "require-dev" : { "roave/security-advisories" : "dev-latest", - "jbzoo/toolbox-dev" : "^7.1" + "jbzoo/toolbox-dev" : "^7.1", + "jbzoo/markdown": "^7.0" }, "autoload" : { diff --git a/schemas-examples/full.json b/schemas-examples/full.json new file mode 100644 index 00000000..d34bd493 --- /dev/null +++ b/schemas-examples/full.json @@ -0,0 +1,46 @@ +{ + "csv_structure" : { + "header" : true, + "delimiter" : ",", + "quote_char" : "\\", + "enclosure" : "\"", + "encoding" : "utf-8", + "bom" : false + }, + "columns" : [ + { + "name" : "csv header name", + "description" : "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + "rules" : { + "allow_values" : ["y", "n", ""], + "date_format" : "Y-m-d", + "exact_value" : "Some string", + "is_bool" : true, + "is_domain" : true, + "is_email" : true, + "is_float" : true, + "is_int" : true, + "is_ip" : true, + "is_latitude" : true, + "is_longitude" : true, + "is_url" : true, + "is_uuid4" : true, + "min" : 10, + "max" : 100, + "min_length" : 1, + "max_length" : 10, + "min_date" : '2000-01-02', + "max_date" : "now", + "not_empty" : true, + "only_capitalize" : true, + "only_lowercase" : true, + "only_uppercase" : true, + "only_trimed" : true, + "precision" : 2, + "regex" : "\/^[\\d]{2}$\/", + "cardinal_direction" : true, + "usa_market_name" : true + } + } + ] +} diff --git a/schemas-examples/full.php b/schemas-examples/full.php new file mode 100644 index 00000000..48e01988 --- /dev/null +++ b/schemas-examples/full.php @@ -0,0 +1,64 @@ + [ + 'header' => true, + 'delimiter' => ',', + 'quote_char' => '\\', + 'enclosure' => '"', + 'encoding' => 'utf-8', + 'bom' => false, + ], + 'columns' => [ + [ + 'name' => 'csv header name', + 'description' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'rules' => [ + 'allow_values' => ['y', 'n', ''], + 'date_format' => 'Y-m-d', + 'exact_value' => 'Some string', + 'is_bool' => true, + 'is_domain' => true, + 'is_email' => true, + 'is_float' => true, + 'is_int' => true, + 'is_ip' => true, + 'is_latitude' => true, + 'is_longitude' => true, + 'is_url' => true, + 'is_uuid4' => true, + 'min' => 10, + 'max' => 100, + 'min_length' => 1, + 'max_length' => 10, + 'min_date' => '2000-01-02', + 'max_date' => 'now', + 'not_empty' => true, + 'only_capitalize' => true, + 'only_lowercase' => true, + 'only_uppercase' => true, + 'only_trimed' => true, + 'precision' => 2, + 'regex' => '/^[\\d]{2}$/', + 'cardinal_direction' => true, + 'usa_market_name' => true, + ], + ], + ], +]; diff --git a/schemas-examples/full.yml b/schemas-examples/full.yml index 3914853c..e1a54e88 100644 --- a/schemas-examples/full.yml +++ b/schemas-examples/full.yml @@ -10,7 +10,9 @@ # @see https://github.com/JBZoo/Csv-Blueprint # -csv_structure: +# It's a full example of the CSV schema file in YAML format. + +csv_structure: # Here are default values. You can skip this section if you don't need to override the default values header: true # If the first row is a header. If true, name of each column is required delimiter: , # Delimiter character in CSV file quote_char: \ # Quote character in CSV file @@ -20,27 +22,26 @@ csv_structure: columns: - name: "csv header name" - description: > - Lorem ipsum dolor sit amet, consectetur adipiscing elit - sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam - rule: - allow_values: [ "y", "n", "" ] # Strict set of values that are allowed + description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + rules: + allow_values: [ y, n, "" ] # Strict set of values that are allowed date_format: Y-m-d # See: https://www.php.net/manual/en/datetime.format.php - exact_value: "Some string" # Case-sensitive. Exact value for string in the column + exact_value: Some string # Case-sensitive. Exact value for string in the column is_bool: true # true|false, Case-insensitive is_domain: true # Only domain name. Example: "example.com" is_email: true # Only email format. Example: "user@example.com" is_float: true # Check format only. Can be negative and positive. Dot as decimal separator - is_int: true # Check format only. Can be negative and positive. Witout any separators + is_int: true # Check format only. Can be negative and positive. Without any separators is_ip: true # Only IPv4. Example: "127.0.0.1" is_latitude: true # Can be integer or float. Example: 50.123456 is_longitude: true # Can be integer or float. Example: -89.123456 is_url: true # Only URL format. Example: "https://example.com/page?query=string#anchor" + is_uuid4: true # Only UUID4 format. Example: "550e8400-e29b-41d4-a716-446655440000" min: 10 # Can be integer or float, negative and positive max: 100 # Can be integer or float, negative and positive min_length: 1 # Integer only. Min length of the string with spaces max_length: 10 # Integer only. Max length of the string with spaces - min_date: 2000-01-02 # See examples https://www.php.net/manual/en/function.strtotime.php + min_date: "2000-01-02" # See examples https://www.php.net/manual/en/function.strtotime.php max_date: now # See examples https://www.php.net/manual/en/function.strtotime.php not_empty: true # Value is not empty string. Ignore spaces. only_capitalize: true # String is only capitalized. Example: "Hello World" @@ -48,6 +49,6 @@ columns: only_uppercase: true # String is only capitalized. Example: "HELLO WORLD" only_trimed: true # Only trimed strings. Example: "Hello World" (not " Hello World ") precision: 2 # Strict(!) number of digits after the decimal point - regex: "/^[\d]{2}$/" # Any valid regex pattern. See https://www.php.net/manual/en/reference.pcre.pattern.syntax.php + regex: /^[\d]{2}$/ # Any valid regex pattern. See https://www.php.net/manual/en/reference.pcre.pattern.syntax.php cardinal_direction: true # Valid cardinal direction. Examples: "N", "S", "NE", "SE", "none", "" usa_market_name: true # Check if the value is a valid USA market name. Example: "New York, NY" diff --git a/src/Csv/ParseConfig.php b/src/Csv/ParseConfig.php index bf3840e7..f71a5d09 100644 --- a/src/Csv/ParseConfig.php +++ b/src/Csv/ParseConfig.php @@ -120,17 +120,17 @@ public function getArrayCopy(): array { return [ // System rules - 'inherit' => $this->getInherit(), + // 'inherit' => $this->getInherit(), // TODO Implement me // Reading rules - 'bom' => $this->isBom(), + 'header' => $this->isHeader(), 'delimiter' => $this->getDelimiter(), 'quote_char' => $this->getQuoteChar(), 'enclosure' => $this->getEnclosure(), 'encoding' => $this->getEncoding(), - 'header' => $this->isHeader(), + 'bom' => $this->isBom(), // Global validation rules - 'strict_column_order' => $this->isStrictColumnOrder(), - 'other_columns_possible' => $this->isOtherColumnsPossible(), + // 'strict_column_order' => $this->isStrictColumnOrder(), // TODO Implement me + // 'other_columns_possible' => $this->isOtherColumnsPossible(), // TODO Implement me ]; } } diff --git a/tests/Blueprint/MiscTest.php b/tests/Blueprint/MiscTest.php new file mode 100644 index 00000000..b8d699ee --- /dev/null +++ b/tests/Blueprint/MiscTest.php @@ -0,0 +1,143 @@ +findArray('columns.0.rules'); + $rulesInConfig = \array_keys($rulesInConfig); + \sort($rulesInConfig); + + $finder = (new Finder()) + ->files() + ->in(PROJECT_ROOT . '/src/Validators/Rules') + ->ignoreDotFiles(false) + ->ignoreVCS(true) + ->name('/\\.php$/'); + + foreach ($finder as $file) { + $ruleName = Utils::camelToKebabCase($file->getFilenameWithoutExtension()); + $excludeRules = [ + 'abstarct_rule', + 'exception', + 'rule_exception', + ]; + + if (\in_array($ruleName, $excludeRules, true)) { + continue; + } + + $rulesInCode[] = $ruleName; + } + \sort($rulesInCode); + + isSame($rulesInCode, $rulesInConfig); + } + + public function testCsvStrutureDefaultValues(): void + { + $defaultsInDoc = yml(PROJECT_ROOT . '/schemas-examples/full.yml')->findArray('csv_structure'); + + $schema = new Schema([]); + $schema->getCsvStructure()->getArrayCopy(); + + isSame($defaultsInDoc, $schema->getCsvStructure()->getArrayCopy()); + } + + public function testCheckYmlSchemaExampleInReadme(): void + { + $this->testCheckExampleInReadme(PROJECT_ROOT . '/schemas-examples/full.yml', 'yml', 'YAML format', 12); + } + + public function testCheckPhpSchemaExampleInReadme(): void + { + $this->testCheckExampleInReadme(PROJECT_ROOT . '/schemas-examples/full.php', 'php', 'PHP Array as file', 14); + } + + public function testCheckJsonSchemaExampleInReadme(): void + { + $this->testCheckExampleInReadme(PROJECT_ROOT . '/schemas-examples/full.json', 'json', 'JSON Format', 0); + } + + public function testCompareExamplesWithOrig(): void + { + $basepath = PROJECT_ROOT . '/schemas-examples/full'; + + $origYml = yml("{$basepath}.yml")->getArrayCopy(); + + isSame($origYml, phpArray("{$basepath}.php")->getArrayCopy(), 'PHP config is invalid'); + isSame($origYml, json("{$basepath}.json")->getArrayCopy(), 'JSON config is invalid'); + } + + private function testCheckExampleInReadme( + string $filepath, + string $type, + string $title, + int $skipFirstLines = 0 + ): void { + $filepath = \implode( + "\n", + \array_slice(\explode("\n", \file_get_contents($filepath)), $skipFirstLines), + ); + + if ($type === 'php') { + $filepath = \implode("\n", ["```php", ' Date: Sun, 10 Mar 2024 20:19:06 +0400 Subject: [PATCH 07/25] Add spoiler functionality to README.md Add spoiler functionality to README.md in order to show different file formats. This includes adding the PHP array as file, JSON format, and PHP code as spoilers. Also refactor the MiscTest.php to use a private helper method for generating spoiler. --- .gitignore | 1 - README.md | 119 +- composer.lock | 8124 ++++++++++++++++++++++++++++++++ schemas-examples/full.json | 2 +- tests/Blueprint/MiscTest.php | 18 +- tests/Blueprint/SchemaTest.php | 66 +- 6 files changed, 8292 insertions(+), 38 deletions(-) create mode 100644 composer.lock diff --git a/.gitignore b/.gitignore index 9fbada9a..b0f75c93 100644 --- a/.gitignore +++ b/.gitignore @@ -15,5 +15,4 @@ build vendor phpunit.xml -composer.lock *.cache diff --git a/README.md b/README.md index 09f464da..01266209 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ composer require jbzoo/csv-blueprint
Click to see YAML format - ```yml +```yml # It's a full example of the CSV schema file in YAML format. csv_structure: # Here are default values. You can skip this section if you don't need to override the default values @@ -70,6 +70,123 @@ columns:
+
+ Click to see PHP Array as file + +```php + [ + 'header' => true, + 'delimiter' => ',', + 'quote_char' => '\\', + 'enclosure' => '"', + 'encoding' => 'utf-8', + 'bom' => false, + ], + 'columns' => [ + [ + 'name' => 'csv header name', + 'description' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'rules' => [ + 'allow_values' => ['y', 'n', ''], + 'date_format' => 'Y-m-d', + 'exact_value' => 'Some string', + 'is_bool' => true, + 'is_domain' => true, + 'is_email' => true, + 'is_float' => true, + 'is_int' => true, + 'is_ip' => true, + 'is_latitude' => true, + 'is_longitude' => true, + 'is_url' => true, + 'is_uuid4' => true, + 'min' => 10, + 'max' => 100, + 'min_length' => 1, + 'max_length' => 10, + 'min_date' => '2000-01-02', + 'max_date' => 'now', + 'not_empty' => true, + 'only_capitalize' => true, + 'only_lowercase' => true, + 'only_uppercase' => true, + 'only_trimed' => true, + 'precision' => 2, + 'regex' => '/^[\\d]{2}$/', + 'cardinal_direction' => true, + 'usa_market_name' => true, + ], + ], + ], +]; + +``` + +
+ + +
+ Click to see JSON Format + +```json +{ + "csv_structure" : { + "header" : true, + "delimiter" : ",", + "quote_char" : "\\", + "enclosure" : "\"", + "encoding" : "utf-8", + "bom" : false + }, + "columns" : [ + { + "name" : "csv header name", + "description" : "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + "rules" : { + "allow_values" : ["y", "n", ""], + "date_format" : "Y-m-d", + "exact_value" : "Some string", + "is_bool" : true, + "is_domain" : true, + "is_email" : true, + "is_float" : true, + "is_int" : true, + "is_ip" : true, + "is_latitude" : true, + "is_longitude" : true, + "is_url" : true, + "is_uuid4" : true, + "min" : 10, + "max" : 100, + "min_length" : 1, + "max_length" : 10, + "min_date" : "2000-01-02", + "max_date" : "now", + "not_empty" : true, + "only_capitalize" : true, + "only_lowercase" : true, + "only_uppercase" : true, + "only_trimed" : true, + "precision" : 2, + "regex" : "\/^[\\d]{2}$\/", + "cardinal_direction" : true, + "usa_market_name" : true + } + } + ] +} + +``` + +
+ + ## Unit tests and check code style ```sh diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000..1e70afbf --- /dev/null +++ b/composer.lock @@ -0,0 +1,8124 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "34ca1de55f9be82342e8922b281d6923", + "packages": [ + { + "name": "bluepsyduck/symfony-process-manager", + "version": "1.3.3", + "source": { + "type": "git", + "url": "https://github.com/BluePsyduck/symfony-process-manager.git", + "reference": "2988e74f063722a5c2868882a7ba41a63c83b34b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/BluePsyduck/symfony-process-manager/zipball/2988e74f063722a5c2868882a7ba41a63c83b34b", + "reference": "2988e74f063722a5c2868882a7ba41a63c83b34b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "symfony/process": "^3.0 || ^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "bluepsyduck/test-helper": "^1.0", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^8.0 || ^9.0", + "rregeer/phpunit-coverage-check": "^0.3", + "squizlabs/php_codesniffer": "^3.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "BluePsyduck\\SymfonyProcessManager\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-3.0-or-later" + ], + "authors": [ + { + "name": "BluePsyduck", + "email": "bluepsyduck@gmx.com" + } + ], + "description": "A process manager for Symfony processes, able to run them in parallel.", + "homepage": "https://github.com/BluePsyduck/symfony-process-manager", + "keywords": [ + "manager", + "parallel", + "process", + "symfony" + ], + "support": { + "issues": "https://github.com/BluePsyduck/symfony-process-manager/issues", + "source": "https://github.com/BluePsyduck/symfony-process-manager/tree/1.3.3" + }, + "time": "2021-12-03T21:30:28+00:00" + }, + { + "name": "fakerphp/faker", + "version": "v1.23.1", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b", + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franรงois Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" + }, + "time": "2024-01-02T13:46:09+00:00" + }, + { + "name": "jbzoo/cli", + "version": "7.1.8", + "source": { + "type": "git", + "url": "https://github.com/JBZoo/Cli.git", + "reference": "7577c4d88d9724103269696a4c7726ec68211279" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JBZoo/Cli/zipball/7577c4d88d9724103269696a4c7726ec68211279", + "reference": "7577c4d88d9724103269696a4c7726ec68211279", + "shasum": "" + }, + "require": { + "bluepsyduck/symfony-process-manager": ">=1.3.3", + "jbzoo/event": "^7.0", + "jbzoo/utils": "^7.1", + "monolog/monolog": "^3.4", + "php": "^8.1", + "symfony/console": ">=6.4", + "symfony/lock": ">=6.4", + "symfony/process": ">=6.4" + }, + "require-dev": { + "jbzoo/toolbox-dev": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "JBZoo\\Cli\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Denis Smetannikov", + "email": "admin@jbzoo.com", + "role": "lead" + } + ], + "description": "The framework helps create CLI tools and provides new tools for symfony/console, symfony/process.", + "keywords": [ + "ELK", + "cli", + "command-line", + "console", + "console-application", + "cron", + "crontab", + "elastic", + "elk-stack", + "jbzoo", + "logstash", + "process", + "symfony", + "symfony-console", + "symfony-process", + "terminal" + ], + "support": { + "issues": "https://github.com/JBZoo/Cli/issues", + "source": "https://github.com/JBZoo/Cli/tree/7.1.8" + }, + "time": "2024-01-28T13:57:00+00:00" + }, + { + "name": "jbzoo/data", + "version": "7.1.1", + "source": { + "type": "git", + "url": "https://github.com/JBZoo/Data.git", + "reference": "8666e8cdc912c3fd6176aa0ca5ab29bd1a7d0ab2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JBZoo/Data/zipball/8666e8cdc912c3fd6176aa0ca5ab29bd1a7d0ab2", + "reference": "8666e8cdc912c3fd6176aa0ca5ab29bd1a7d0ab2", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^8.1" + }, + "require-dev": { + "jbzoo/toolbox-dev": "^7.1", + "jbzoo/utils": "^7.1.1", + "symfony/polyfill-ctype": ">=1.28.0", + "symfony/polyfill-mbstring": ">=1.28.0", + "symfony/polyfill-php73": ">=1.28.0", + "symfony/polyfill-php80": ">=1.28.0", + "symfony/polyfill-php81": ">=1.28.0", + "symfony/yaml": ">=6.4" + }, + "suggest": { + "jbzoo/utils": ">=7.1", + "symfony/yaml": ">=6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "JBZoo\\Data\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Denis Smetannikov", + "email": "admin@jbzoo.com", + "role": "lead" + } + ], + "description": "An extended version of the ArrayObject object for working with system settings or just for working with data arrays", + "keywords": [ + "array", + "arrayobject", + "config", + "data", + "ini", + "json", + "params", + "yml" + ], + "support": { + "issues": "https://github.com/JBZoo/Data/issues", + "source": "https://github.com/JBZoo/Data/tree/7.1.1" + }, + "time": "2024-01-28T08:47:08+00:00" + }, + { + "name": "jbzoo/event", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/JBZoo/Event.git", + "reference": "09c87f83acc79252cc7f01b173c4c6eb399e62a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JBZoo/Event/zipball/09c87f83acc79252cc7f01b173c4c6eb399e62a1", + "reference": "09c87f83acc79252cc7f01b173c4c6eb399e62a1", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "jbzoo/data": "^7.1", + "jbzoo/toolbox-dev": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "JBZoo\\Event\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Denis Smetannikov", + "email": "admin@jbzoo.com", + "role": "lead" + }, + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "https://sabre.io/event/" + } + ], + "description": "Library for event-based development", + "keywords": [ + "HOOK", + "emit", + "emitter", + "event-manager", + "eventmanager", + "events", + "jbzoo", + "listener", + "observer", + "signal" + ], + "support": { + "issues": "https://github.com/JBZoo/Event/issues", + "source": "https://github.com/JBZoo/Event/tree/7.0.1" + }, + "time": "2024-01-28T08:57:37+00:00" + }, + { + "name": "jbzoo/utils", + "version": "7.1.2", + "source": { + "type": "git", + "url": "https://github.com/JBZoo/Utils.git", + "reference": "55ddbe0558f2e4a8f69b4469d9ead97f2de5e13f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JBZoo/Utils/zipball/55ddbe0558f2e4a8f69b4469d9ead97f2de5e13f", + "reference": "55ddbe0558f2e4a8f69b4469d9ead97f2de5e13f", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-filter": "*", + "ext-gd": "*", + "ext-posix": "*", + "php": "^8.1" + }, + "require-dev": { + "jbzoo/data": "^7.1", + "jbzoo/toolbox-dev": "^7.1", + "symfony/process": ">=6.4" + }, + "suggest": { + "ext-intl": "*", + "ext-mbstring": "Provides multibyte specific string functions", + "jbzoo/data": ">=4.0", + "symfony/polyfill-mbstring": "For UTF-8 if ext-mbstring disabled", + "symfony/process": "For Cli::exec() method only" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "files": [ + "src/defines.php", + "src/aliases.php" + ], + "psr-4": { + "JBZoo\\Utils\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Denis Smetannikov", + "email": "admin@jbzoo.com", + "role": "lead" + }, + { + "name": "Brandon Wamboldt", + "email": "brandon.wamboldt@gmail.com" + }, + { + "name": "Luรญs Nรณbrega", + "email": "luis.barros.nobrega@gmail.com" + } + ], + "description": "Collection of PHP functions, mini classes and snippets for everyday developer's routine life.", + "keywords": [ + "array", + "cli", + "collection", + "command line", + "dates", + "email", + "env", + "environment", + "filesystem", + "filter", + "helper", + "helpers", + "http", + "image", + "mbstring", + "misc", + "serialize", + "slugify", + "string", + "timer", + "url", + "utility", + "utils" + ], + "support": { + "issues": "https://github.com/JBZoo/Utils/issues", + "source": "https://github.com/JBZoo/Utils/tree/7.1.2" + }, + "time": "2024-01-28T08:37:18+00:00" + }, + { + "name": "league/csv", + "version": "9.15.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/csv.git", + "reference": "fa7e2441c0bc9b2360f4314fd6c954f7ff40d435" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/fa7e2441c0bc9b2360f4314fd6c954f7ff40d435", + "reference": "fa7e2441c0bc9b2360f4314fd6c954f7ff40d435", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": "^8.1.2" + }, + "require-dev": { + "doctrine/collections": "^2.1.4", + "ext-dom": "*", + "ext-xdebug": "*", + "friendsofphp/php-cs-fixer": "^v3.22.0", + "phpbench/phpbench": "^1.2.15", + "phpstan/phpstan": "^1.10.57", + "phpstan/phpstan-deprecation-rules": "^1.1.4", + "phpstan/phpstan-phpunit": "^1.3.15", + "phpstan/phpstan-strict-rules": "^1.5.2", + "phpunit/phpunit": "^10.5.9", + "symfony/var-dumper": "^6.4.2" + }, + "suggest": { + "ext-dom": "Required to use the XMLConverter and the HTMLConverter classes", + "ext-iconv": "Needed to ease transcoding CSV using iconv stream filters" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "League\\Csv\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://github.com/nyamsprod/", + "role": "Developer" + } + ], + "description": "CSV data manipulation made easy in PHP", + "homepage": "https://csv.thephpleague.com", + "keywords": [ + "convert", + "csv", + "export", + "filter", + "import", + "read", + "transform", + "write" + ], + "support": { + "docs": "https://csv.thephpleague.com", + "issues": "https://github.com/thephpleague/csv/issues", + "rss": "https://github.com/thephpleague/csv/releases.atom", + "source": "https://github.com/thephpleague/csv" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2024-02-20T20:00:00+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.5.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "c915e2634718dbc8a4a15c61b0e62e7a44e14448" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c915e2634718dbc8a4a15c61b0e62e7a44e14448", + "reference": "c915e2634718dbc8a4a15c61b0e62e7a44e14448", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-strict-rules": "^1.4", + "phpunit/phpunit": "^10.1", + "predis/predis": "^1.1 || ^2", + "ruflin/elastica": "^7", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.5.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2023-10-27T15:32:31+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/log", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/2.0.0" + }, + "time": "2021-07-14T16:41:46+00:00" + }, + { + "name": "symfony/console", + "version": "v6.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "0d9e4eb5ad413075624378f474c4167ea202de78" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/0d9e4eb5ad413075624378f474c4167ea202de78", + "reference": "0d9e4eb5ad413075624378f474c4167ea202de78", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.4.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-02-22T20:27:10+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/lock", + "version": "v6.4.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/lock.git", + "reference": "1cabf3cc775b1aa6008ebd471fa773444af4e956" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/lock/zipball/1cabf3cc775b1aa6008ebd471fa773444af4e956", + "reference": "1cabf3cc775b1aa6008ebd471fa773444af4e956", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/dbal": "<2.13", + "symfony/cache": "<6.2" + }, + "require-dev": { + "doctrine/dbal": "^2.13|^3|^4", + "predis/predis": "^1.1|^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Lock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jรฉrรฉmy Derussรฉ", + "email": "jeremy@derusse.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Creates and manages locks, a mechanism to provide exclusive access to a shared resource", + "homepage": "https://symfony.com", + "keywords": [ + "cas", + "flock", + "locking", + "mutex", + "redlock", + "semaphore" + ], + "support": { + "source": "https://github.com/symfony/lock/tree/v6.4.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-23T14:51:35+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/process", + "version": "v6.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "710e27879e9be3395de2b98da3f52a946039f297" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/710e27879e9be3395de2b98da3f52a946039f297", + "reference": "710e27879e9be3395de2b98da3f52a946039f297", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v6.4.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-02-20T12:31:00+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.4.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/fe07cbc8d837f60caf7018068e350cc5163681a0", + "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.4.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-12-26T14:02:43+00:00" + }, + { + "name": "symfony/string", + "version": "v6.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "4e465a95bdc32f49cf4c7f07f751b843bbd6dcd9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/4e465a95bdc32f49cf4c7f07f751b843bbd6dcd9", + "reference": "4e465a95bdc32f49cf4c7f07f751b843bbd6dcd9", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/intl": "^6.2|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v6.4.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-02-01T13:16:41+00:00" + } + ], + "packages-dev": [ + { + "name": "amphp/amp", + "version": "v2.6.2", + "source": { + "type": "git", + "url": "https://github.com/amphp/amp.git", + "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/amp/zipball/9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", + "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^7 | ^8 | ^9", + "psalm/phar": "^3.11@dev", + "react/promise": "^2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "files": [ + "lib/functions.php", + "lib/Internal/functions.php" + ], + "psr-4": { + "Amp\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "https://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v2.6.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2022-02-20T17:52:18+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v1.8.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", + "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", + "shasum": "" + }, + "require": { + "amphp/amp": "^2", + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.4", + "friendsofphp/php-cs-fixer": "^2.3", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6 || ^7 || ^8", + "psalm/phar": "^3.11.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "lib/functions.php" + ], + "psr-4": { + "Amp\\ByteStream\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "http://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2021-03-30T17:13:30+00:00" + }, + { + "name": "composer/pcre", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/4775f35b2d70865807c89d32c8e7385b86eb0ace", + "reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.1.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-03-07T15:38:35+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2023-08-31T09:50:34+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "ced299686f41dce890debac69273b47ffe98a40c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", + "reference": "ced299686f41dce890debac69273b47ffe98a40c", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-02-25T21:32:43+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpstan/phpstan": "1.4.10 || 1.10.15", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "0.18.4", + "psr/log": "^1 || ^2 || ^3", + "vimeo/psalm": "4.30.0 || 5.12.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.3" + }, + "time": "2024-01-30T19:34:25+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:23:10+00:00" + }, + { + "name": "felixfbecker/advanced-json-rpc", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "shasum": "" + }, + "require": { + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "php": "^7.1 || ^8.0", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "A more advanced JSONRPC implementation", + "support": { + "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", + "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" + }, + "time": "2021-06-11T22:34:44+00:00" + }, + { + "name": "felixfbecker/language-server-protocol", + "version": "v1.5.2", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server-protocol.git", + "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", + "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "*", + "squizlabs/php_codesniffer": "^3.1", + "vimeo/psalm": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "LanguageServerProtocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "PHP classes for the Language Server Protocol", + "keywords": [ + "language", + "microsoft", + "php", + "server" + ], + "support": { + "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" + }, + "time": "2022-03-02T22:36:06+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Thรฉo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.1.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2024-02-07T09:43:46+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.51.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "127fa74f010da99053e3f5b62672615b72dd6efd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/127fa74f010da99053e3f5b62672615b72dd6efd", + "reference": "127fa74f010da99053e3f5b62672615b72dd6efd", + "shasum": "" + }, + "require": { + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.3", + "ext-filter": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^5.4 || ^6.0 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", + "symfony/polyfill-mbstring": "^1.28", + "symfony/polyfill-php80": "^1.28", + "symfony/polyfill-php81": "^1.28", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3 || ^2.0", + "justinrainbow/json-schema": "^5.2", + "keradus/cli-executor": "^2.1", + "mikey179/vfsstream": "^1.6.11", + "php-coveralls/php-coveralls": "^2.7", + "php-cs-fixer/accessible-object": "^1.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.4", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.4", + "phpunit/phpunit": "^9.6 || ^10.5.5 || ^11.0.2", + "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiล„ski", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.51.0" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2024-02-28T19:50:06+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.1", + "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Mรกrk Sรกgi-Kazรกr", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:35:24+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:19:20+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.6.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Mรกrk Sรกgi-Kazรกr", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Mรกrk Sรกgi-Kazรกr", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.6.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:05:35+00:00" + }, + { + "name": "jbzoo/codestyle", + "version": "7.1.0", + "source": { + "type": "git", + "url": "https://github.com/JBZoo/Codestyle.git", + "reference": "50c3261d9d566d1e1f94b34b8c2a2ef64e035b0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JBZoo/Codestyle/zipball/50c3261d9d566d1e1f94b34b8c2a2ef64e035b0c", + "reference": "50c3261d9d566d1e1f94b34b8c2a2ef64e035b0c", + "shasum": "" + }, + "require": { + "friendsofphp/php-cs-fixer": ">=3.48.0", + "jbzoo/data": "^7.1", + "kubawerlos/php-cs-fixer-custom-fixers": ">=3.19.2", + "nikic/php-parser": ">=4.18.0", + "pdepend/pdepend": ">=2.16.2", + "phan/phan": ">=5.4.3", + "php": "^8.1", + "phpmd/phpmd": ">=2.15.0", + "phpmetrics/phpmetrics": ">=2.8.2", + "phpstan/phpstan": ">=1.10.57", + "phpstan/phpstan-strict-rules": ">=1.5.2", + "povils/phpmnd": ">=3.4.0", + "squizlabs/php_codesniffer": ">=3.8.1", + "symfony/console": ">=6.4", + "symfony/finder": ">=6.4", + "symfony/yaml": ">=6.4", + "vimeo/psalm": ">=5.20.0" + }, + "require-dev": { + "jbzoo/phpunit": "^7.0", + "jbzoo/utils": "^7.1.1", + "symfony/var-dumper": ">=6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "files": [ + "src/compatibility.php" + ], + "psr-4": { + "JBZoo\\Codestyle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Denis Smetannikov", + "email": "admin@jbzoo.com", + "role": "lead" + } + ], + "description": "Collection of QA tools and JBZoo code standards", + "keywords": [ + "Codestyle", + "PHPStan", + "coding-standard", + "jbzoo", + "phan", + "php", + "php-cs-fixer", + "phpcompatibility", + "phpcs", + "phpmd", + "phpmetrics", + "phpunit", + "psalm", + "qa", + "tests" + ], + "support": { + "issues": "https://github.com/JBZoo/Codestyle/issues", + "source": "https://github.com/JBZoo/Codestyle/tree/7.1.0" + }, + "time": "2024-01-27T21:57:08+00:00" + }, + { + "name": "jbzoo/jbdump", + "version": "1.5.6", + "source": { + "type": "git", + "url": "https://github.com/JBZoo/JBDump.git", + "reference": "044e1f9f648d2467d9161732f1195a44d452ca50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JBZoo/JBDump/zipball/044e1f9f648d2467d9161732f1195a44d452ca50", + "reference": "044e1f9f648d2467d9161732f1195a44d452ca50", + "shasum": "" + }, + "require": { + "php": ">=5.3.10" + }, + "replace": { + "smetdenis/jbdump": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "SmetDenis", + "email": "admin@jbzoo.com" + }, + { + "name": "Kaloyan K. Tsvetkov", + "email": "kaloyan@kaloyan.info" + }, + { + "name": "Jeremy Dorn", + "email": "jeremy@jeremydorn.com" + }, + { + "name": "Florin Patan", + "email": "florinpatan@gmail.com" + } + ], + "description": "Script for debug and dump PHP variables and other stuff. This tool is a nice replacement for print_r() and var_dump() functions.", + "keywords": [ + "debug", + "debugging", + "dump", + "dumper", + "jbdump", + "krumo", + "php", + "pretty", + "print", + "print_r", + "var_dump", + "vars" + ], + "support": { + "issues": "https://github.com/JBZoo/JBDump/issues", + "source": "https://github.com/JBZoo/JBDump/tree/1.5.6" + }, + "time": "2021-03-31T09:21:47+00:00" + }, + { + "name": "jbzoo/markdown", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/JBZoo/Markdown.git", + "reference": "02e9d756ed91d33c63a7794db1279af56e4da5e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JBZoo/Markdown/zipball/02e9d756ed91d33c63a7794db1279af56e4da5e9", + "reference": "02e9d756ed91d33c63a7794db1279af56e4da5e9", + "shasum": "" + }, + "require": { + "jbzoo/utils": "^7.1", + "php": "^8.1" + }, + "require-dev": { + "jbzoo/toolbox-dev": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "JBZoo\\Markdown\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Denis Smetannikov", + "email": "admin@jbzoo.com", + "role": "lead" + } + ], + "description": "Tools to render markdown text from PHP code", + "keywords": [ + "Rendering", + "jbzoo", + "markdown", + "readme", + "text" + ], + "support": { + "issues": "https://github.com/JBZoo/Markdown/issues", + "source": "https://github.com/JBZoo/Markdown/tree/7.0.1" + }, + "time": "2024-01-28T12:43:57+00:00" + }, + { + "name": "jbzoo/phpunit", + "version": "7.1.0", + "source": { + "type": "git", + "url": "https://github.com/JBZoo/PHPUnit.git", + "reference": "4a70dd033b90a08498cbd7147a1c3150198f51f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JBZoo/PHPUnit/zipball/4a70dd033b90a08498cbd7147a1c3150198f51f8", + "reference": "4a70dd033b90a08498cbd7147a1c3150198f51f8", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-mbstring": "*", + "jbzoo/markdown": "^7.0", + "php": "^8.1", + "phpunit/phpunit": "^9.6.16", + "ulrichsg/getopt-php": ">=4.0.3" + }, + "require-dev": { + "guzzlehttp/guzzle": ">=7.8.1", + "jbzoo/codestyle": "^7.1", + "jbzoo/data": "^7.1", + "jbzoo/http-client": "^7.0", + "jbzoo/toolbox-dev": "^7.0", + "jbzoo/utils": "^7.1", + "symfony/process": ">=6.4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions/defines.php", + "src/functions/aliases.php", + "src/functions/tools.php" + ], + "psr-4": { + "JBZoo\\PHPUnit\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Denis Smetannikov", + "email": "admin@jbzoo.com", + "role": "lead" + } + ], + "description": "PHPUnit toolbox with short assert aliases and useful functions around testing", + "keywords": [ + "aliases", + "assert", + "assertion", + "debug", + "jbzoo", + "phpunit", + "short-syntax", + "testing" + ], + "support": { + "issues": "https://github.com/JBZoo/PHPUnit/issues", + "source": "https://github.com/JBZoo/PHPUnit/tree/7.1.0" + }, + "time": "2024-01-27T22:11:15+00:00" + }, + { + "name": "jbzoo/toolbox-dev", + "version": "7.1.0", + "source": { + "type": "git", + "url": "https://github.com/JBZoo/Toolbox-Dev.git", + "reference": "1048efcb712eb2392e19e1b30201d191a97d66ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JBZoo/Toolbox-Dev/zipball/1048efcb712eb2392e19e1b30201d191a97d66ca", + "reference": "1048efcb712eb2392e19e1b30201d191a97d66ca", + "shasum": "" + }, + "require": { + "fakerphp/faker": ">=1.23.0", + "jbzoo/codestyle": "^7.1", + "jbzoo/jbdump": ">=1.5.6|^7.0", + "jbzoo/markdown": "^7.0", + "jbzoo/phpunit": "^7.1", + "php": "^8.1", + "php-coveralls/php-coveralls": ">=2.7.0", + "symfony/var-dumper": ">=6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "files": [ + "src/var-dumper.php" + ], + "psr-4": { + "JBZoo\\ToolboxDev\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Denis Smetannikov", + "email": "admin@jbzoo.com", + "role": "lead" + } + ], + "description": "Developer toolbox only for JBZoo libs on github+travis", + "keywords": [ + "Toolbox", + "debug", + "dev", + "dev-kit", + "developer", + "devkit", + "jbzoo" + ], + "support": { + "issues": "https://github.com/JBZoo/Toolbox-Dev/issues", + "source": "https://github.com/JBZoo/Toolbox-Dev/tree/7.1.0" + }, + "time": "2024-01-27T22:19:13+00:00" + }, + { + "name": "kubawerlos/php-cs-fixer-custom-fixers", + "version": "v3.21.0", + "source": { + "type": "git", + "url": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers.git", + "reference": "3d1bb75be0df6c6fba4487c75b9e425a2c1d27c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kubawerlos/php-cs-fixer-custom-fixers/zipball/3d1bb75be0df6c6fba4487c75b9e425a2c1d27c9", + "reference": "3d1bb75be0df6c6fba4487c75b9e425a2c1d27c9", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-tokenizer": "*", + "friendsofphp/php-cs-fixer": "^3.50", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6.4 || ^10.0.14" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpCsFixerCustomFixers\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kuba Werล‚os", + "email": "werlos@gmail.com" + } + ], + "description": "A set of custom fixers for PHP CS Fixer", + "support": { + "issues": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/issues", + "source": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/tree/v3.21.0" + }, + "time": "2024-02-24T08:53:34+00:00" + }, + { + "name": "microsoft/tolerant-php-parser", + "version": "v0.1.2", + "source": { + "type": "git", + "url": "https://github.com/microsoft/tolerant-php-parser.git", + "reference": "3eccfd273323aaf69513e2f1c888393f5947804b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/microsoft/tolerant-php-parser/zipball/3eccfd273323aaf69513e2f1c888393f5947804b", + "reference": "3eccfd273323aaf69513e2f1c888393f5947804b", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Microsoft\\PhpParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Lourens", + "email": "roblou@microsoft.com" + } + ], + "description": "Tolerant PHP-to-AST parser designed for IDE usage scenarios", + "support": { + "issues": "https://github.com/microsoft/tolerant-php-parser/issues", + "source": "https://github.com/microsoft/tolerant-php-parser/tree/v0.1.2" + }, + "time": "2022-10-05T17:30:19+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2023-03-08T13:26:56+00:00" + }, + { + "name": "netresearch/jsonmapper", + "version": "v4.4.1", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/132c75c7dd83e45353ebb9c6c9f591952995bbf0", + "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/v4.4.1" + }, + "time": "2024-01-31T06:18:54+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.18.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" + }, + "time": "2023-12-10T21:03:43+00:00" + }, + { + "name": "pdepend/pdepend", + "version": "2.16.2", + "source": { + "type": "git", + "url": "https://github.com/pdepend/pdepend.git", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "shasum": "" + }, + "require": { + "php": ">=5.3.7", + "symfony/config": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/dependency-injection": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/filesystem": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/polyfill-mbstring": "^1.19" + }, + "require-dev": { + "easy-doc/easy-doc": "0.0.0|^1.2.3", + "gregwar/rst": "^1.0", + "squizlabs/php_codesniffer": "^2.0.0" + }, + "bin": [ + "src/bin/pdepend" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "PDepend\\": "src/main/php/PDepend" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Official version of pdepend to be handled with Composer", + "keywords": [ + "PHP Depend", + "PHP_Depend", + "dev", + "pdepend" + ], + "support": { + "issues": "https://github.com/pdepend/pdepend/issues", + "source": "https://github.com/pdepend/pdepend/tree/2.16.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/pdepend/pdepend", + "type": "tidelift" + } + ], + "time": "2023-12-17T18:09:59+00:00" + }, + { + "name": "phan/phan", + "version": "5.4.3", + "source": { + "type": "git", + "url": "https://github.com/phan/phan.git", + "reference": "86a7acd99c1239b8867b49feca2398851212e7fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phan/phan/zipball/86a7acd99c1239b8867b49feca2398851212e7fe", + "reference": "86a7acd99c1239b8867b49feca2398851212e7fe", + "shasum": "" + }, + "require": { + "composer/semver": "^1.4|^2.0|^3.0", + "composer/xdebug-handler": "^2.0|^3.0", + "ext-filter": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "felixfbecker/advanced-json-rpc": "^3.0.4", + "microsoft/tolerant-php-parser": "0.1.2", + "netresearch/jsonmapper": "^1.6.0|^2.0|^3.0|^4.0", + "php": "^7.2.0|^8.0.0", + "sabre/event": "^5.1.3", + "symfony/console": "^3.2|^4.0|^5.0|^6.0|^7.0", + "symfony/polyfill-mbstring": "^1.11.0", + "symfony/polyfill-php80": "^1.20.0", + "tysonandre/var_representation_polyfill": "^0.0.2|^0.1.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.0" + }, + "suggest": { + "ext-ast": "Needed for parsing ASTs (unless --use-fallback-parser is used). 1.0.1+ is needed, 1.0.16+ is recommended.", + "ext-iconv": "Either iconv or mbstring is needed to ensure issue messages are valid utf-8", + "ext-igbinary": "Improves performance of polyfill when ext-ast is unavailable", + "ext-mbstring": "Either iconv or mbstring is needed to ensure issue messages are valid utf-8", + "ext-tokenizer": "Needed for fallback/polyfill parser support and file/line-based suppressions.", + "ext-var_representation": "Suggested for converting values to strings in issue messages" + }, + "bin": [ + "phan", + "phan_client", + "tocheckstyle" + ], + "type": "project", + "autoload": { + "psr-4": { + "Phan\\": "src/Phan" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tyson Andre" + }, + { + "name": "Rasmus Lerdorf" + }, + { + "name": "Andrew S. Morrison" + } + ], + "description": "A static analyzer for PHP", + "keywords": [ + "analyzer", + "php", + "static", + "static analysis" + ], + "support": { + "issues": "https://github.com/phan/phan/issues", + "source": "https://github.com/phan/phan/tree/5.4.3" + }, + "time": "2023-12-26T17:57:35+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "php-coveralls/php-coveralls", + "version": "v2.7.0", + "source": { + "type": "git", + "url": "https://github.com/php-coveralls/php-coveralls.git", + "reference": "b36fa4394e519dafaddc04ae03976bc65a25ba15" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/b36fa4394e519dafaddc04ae03976bc65a25ba15", + "reference": "b36fa4394e519dafaddc04ae03976bc65a25ba15", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "php": "^7.0 || ^8.0", + "psr/log": "^1.0 || ^2.0", + "symfony/config": "^2.1 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/console": "^2.1 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/yaml": "^2.0.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0 || ^7.0 || >=8.0 <8.5.29 || >=9.0 <9.5.23", + "sanmai/phpunit-legacy-adapter": "^6.1 || ^8.0" + }, + "suggest": { + "symfony/http-kernel": "Allows Symfony integration" + }, + "bin": [ + "bin/php-coveralls" + ], + "type": "library", + "autoload": { + "psr-4": { + "PhpCoveralls\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kitamura Satoshi", + "email": "with.no.parachute@gmail.com", + "homepage": "https://www.facebook.com/satooshi.jp", + "role": "Original creator" + }, + { + "name": "Takashi Matsuo", + "email": "tmatsuo@google.com" + }, + { + "name": "Google Inc" + }, + { + "name": "Dariusz Ruminski", + "email": "dariusz.ruminski@gmail.com", + "homepage": "https://github.com/keradus" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-coveralls/php-coveralls/graphs/contributors" + } + ], + "description": "PHP client library for Coveralls API", + "homepage": "https://github.com/php-coveralls/php-coveralls", + "keywords": [ + "ci", + "coverage", + "github", + "test" + ], + "support": { + "issues": "https://github.com/php-coveralls/php-coveralls/issues", + "source": "https://github.com/php-coveralls/php-coveralls/tree/v2.7.0" + }, + "time": "2023-11-22T10:21:01+00:00" + }, + { + "name": "php-parallel-lint/php-console-color", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-parallel-lint/PHP-Console-Color.git", + "reference": "7adfefd530aa2d7570ba87100a99e2483a543b88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-parallel-lint/PHP-Console-Color/zipball/7adfefd530aa2d7570ba87100a99e2483a543b88", + "reference": "7adfefd530aa2d7570ba87100a99e2483a543b88", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "replace": { + "jakub-onderka/php-console-color": "*" + }, + "require-dev": { + "php-parallel-lint/php-code-style": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.0", + "php-parallel-lint/php-var-dump-check": "0.*", + "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHP_Parallel_Lint\\PhpConsoleColor\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "jakub.onderka@gmail.com" + } + ], + "description": "Simple library for creating colored console ouput.", + "support": { + "issues": "https://github.com/php-parallel-lint/PHP-Console-Color/issues", + "source": "https://github.com/php-parallel-lint/PHP-Console-Color/tree/v1.0.1" + }, + "time": "2021-12-25T06:49:29+00:00" + }, + { + "name": "php-parallel-lint/php-console-highlighter", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-parallel-lint/PHP-Console-Highlighter.git", + "reference": "5b4803384d3303cf8e84141039ef56c8a123138d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-parallel-lint/PHP-Console-Highlighter/zipball/5b4803384d3303cf8e84141039ef56c8a123138d", + "reference": "5b4803384d3303cf8e84141039ef56c8a123138d", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.2", + "php-parallel-lint/php-console-color": "^1.0.1" + }, + "replace": { + "jakub-onderka/php-console-highlighter": "*" + }, + "require-dev": { + "php-parallel-lint/php-code-style": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.0", + "php-parallel-lint/php-var-dump-check": "0.*", + "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHP_Parallel_Lint\\PhpConsoleHighlighter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "acci@acci.cz", + "homepage": "http://www.acci.cz/" + } + ], + "description": "Highlight PHP code in terminal", + "support": { + "issues": "https://github.com/php-parallel-lint/PHP-Console-Highlighter/issues", + "source": "https://github.com/php-parallel-lint/PHP-Console-Highlighter/tree/v1.0.0" + }, + "time": "2022-02-18T08:23:19+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + }, + "time": "2021-10-19T17:43:47+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "153ae662783729388a584b4361f2545e4d841e3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", + "reference": "153ae662783729388a584b4361f2545e4d841e3c", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.13" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" + }, + "time": "2024-02-23T11:10:43+00:00" + }, + { + "name": "phpmd/phpmd", + "version": "2.15.0", + "source": { + "type": "git", + "url": "https://github.com/phpmd/phpmd.git", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/74a1f56e33afad4128b886e334093e98e1b5e7c0", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0", + "shasum": "" + }, + "require": { + "composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0", + "ext-xml": "*", + "pdepend/pdepend": "^2.16.1", + "php": ">=5.3.9" + }, + "require-dev": { + "easy-doc/easy-doc": "0.0.0 || ^1.3.2", + "ext-json": "*", + "ext-simplexml": "*", + "gregwar/rst": "^1.0", + "mikey179/vfsstream": "^1.6.8", + "squizlabs/php_codesniffer": "^2.9.2 || ^3.7.2" + }, + "bin": [ + "src/bin/phpmd" + ], + "type": "library", + "autoload": { + "psr-0": { + "PHPMD\\": "src/main/php" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Manuel Pichler", + "email": "github@manuel-pichler.de", + "homepage": "https://github.com/manuelpichler", + "role": "Project Founder" + }, + { + "name": "Marc Wรผrth", + "email": "ravage@bluewin.ch", + "homepage": "https://github.com/ravage84", + "role": "Project Maintainer" + }, + { + "name": "Other contributors", + "homepage": "https://github.com/phpmd/phpmd/graphs/contributors", + "role": "Contributors" + } + ], + "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.", + "homepage": "https://phpmd.org/", + "keywords": [ + "dev", + "mess detection", + "mess detector", + "pdepend", + "phpmd", + "pmd" + ], + "support": { + "irc": "irc://irc.freenode.org/phpmd", + "issues": "https://github.com/phpmd/phpmd/issues", + "source": "https://github.com/phpmd/phpmd/tree/2.15.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/phpmd/phpmd", + "type": "tidelift" + } + ], + "time": "2023-12-11T08:22:20+00:00" + }, + { + "name": "phpmetrics/phpmetrics", + "version": "v2.8.2", + "source": { + "type": "git", + "url": "https://github.com/phpmetrics/PhpMetrics.git", + "reference": "4b77140a11452e63c7a9b98e0648320bf6710090" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmetrics/PhpMetrics/zipball/4b77140a11452e63c7a9b98e0648320bf6710090", + "reference": "4b77140a11452e63c7a9b98e0648320bf6710090", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "nikic/php-parser": "^3|^4", + "php": ">=5.5" + }, + "replace": { + "halleck45/php-metrics": "*", + "halleck45/phpmetrics": "*" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14", + "sebastian/comparator": ">=1.2.3", + "squizlabs/php_codesniffer": "^3.5", + "symfony/dom-crawler": "^3.0 || ^4.0 || ^5.0" + }, + "bin": [ + "bin/phpmetrics" + ], + "type": "library", + "autoload": { + "files": [ + "./src/functions.php" + ], + "psr-0": { + "Hal\\": "./src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jean-Franรงois Lรฉpine", + "email": "lepinejeanfrancois@yahoo.fr", + "homepage": "http://www.lepine.pro", + "role": "Copyright Holder" + } + ], + "description": "Static analyzer tool for PHP : Coupling, Cyclomatic complexity, Maintainability Index, Halstead's metrics... and more !", + "homepage": "http://www.phpmetrics.org", + "keywords": [ + "analysis", + "qa", + "quality", + "testing" + ], + "support": { + "issues": "https://github.com/PhpMetrics/PhpMetrics/issues", + "source": "https://github.com/phpmetrics/PhpMetrics/tree/v2.8.2" + }, + "time": "2023-03-08T15:03:36+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.26.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "231e3186624c03d7e7c890ec662b81e6b0405227" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/231e3186624c03d7e7c890ec662b81e6b0405227", + "reference": "231e3186624c03d7e7c890ec662b81e6b0405227", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.26.0" + }, + "time": "2024-02-23T16:05:55+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.10.60", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/95dcea7d6c628a3f2f56d091d8a0219485a86bbe", + "reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2024-03-07T13:30:19+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.5.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "7a50e9662ee9f3942e4aaaf3d603653f60282542" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/7a50e9662ee9f3942e4aaaf3d603653f60282542", + "reference": "7a50e9662ee9f3942e4aaaf3d603653f60282542", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.10.34" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.2" + }, + "time": "2023-10-30T14:35:06+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.31", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:37:42+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.17", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1a156980d78a6666721b7e8e8502fe210b587fcd", + "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.28", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.17" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2024-02-23T13:14:51+00:00" + }, + { + "name": "povils/phpmnd", + "version": "v3.4.1", + "source": { + "type": "git", + "url": "https://github.com/povils/phpmnd.git", + "reference": "1f3eb869e3a2c4fddbe0995fddd97b75caa2c203" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/povils/phpmnd/zipball/1f3eb869e3a2c4fddbe0995fddd97b75caa2c203", + "reference": "1f3eb869e3a2c4fddbe0995fddd97b75caa2c203", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": "^7.4 || ^8.0", + "php-parallel-lint/php-console-highlighter": "^1.0", + "phpunit/php-timer": "^2.0||^3.0||^4.0||^5.0||^6.0", + "symfony/console": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/finder": "^4.4 || ^5.0 || ^6.0 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6", + "squizlabs/php_codesniffer": "^2.8.1||^3.5" + }, + "bin": [ + "bin/phpmnd" + ], + "type": "application", + "autoload": { + "psr-4": { + "Povils\\PHPMND\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Povilas Susinskas", + "email": "povilassusinskas@gmail.com" + } + ], + "description": "A tool to detect Magic numbers in codebase", + "support": { + "issues": "https://github.com/povils/phpmnd/issues", + "source": "https://github.com/povils/phpmnd/tree/v3.4.1" + }, + "time": "2024-03-02T17:53:44+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "e616d01114759c4c489f93b099585439f795fe35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + }, + "time": "2023-04-10T20:10:41+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "roave/security-advisories", + "version": "dev-latest", + "source": { + "type": "git", + "url": "https://github.com/Roave/SecurityAdvisories.git", + "reference": "83b3589bb774f27084c7f358c13f465d94afa036" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/83b3589bb774f27084c7f358c13f465d94afa036", + "reference": "83b3589bb774f27084c7f358c13f465d94afa036", + "shasum": "" + }, + "conflict": { + "3f/pygmentize": "<1.2", + "admidio/admidio": "<4.2.13", + "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3", + "aheinze/cockpit": "<2.2", + "aimeos/aimeos-typo3": "<19.10.12|>=20,<20.10.5", + "airesvsg/acf-to-rest-api": "<=3.1", + "akaunting/akaunting": "<2.1.13", + "akeneo/pim-community-dev": "<5.0.119|>=6,<6.0.53", + "alextselegidis/easyappointments": "<1.5", + "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", + "amazing/media2click": ">=1,<1.3.3", + "amphp/artax": "<1.0.6|>=2,<2.0.6", + "amphp/http": "<1.0.1", + "amphp/http-client": ">=4,<4.4", + "anchorcms/anchor-cms": "<=0.12.7", + "andreapollastri/cipi": "<=3.1.15", + "andrewhaine/silverstripe-form-capture": ">=0.2,<=0.2.3|>=1,<1.0.2|>=2,<2.2.5", + "apache-solr-for-typo3/solr": "<2.8.3", + "apereo/phpcas": "<1.6", + "api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6|>=2.6,<2.7.10|>=3,<3.0.12|>=3.1,<3.1.3", + "appwrite/server-ce": "<=1.2.1", + "arc/web": "<3", + "area17/twill": "<1.2.5|>=2,<2.5.3", + "artesaos/seotools": "<0.17.2", + "asymmetricrypt/asymmetricrypt": "<9.9.99", + "athlon1600/php-proxy": "<=5.1", + "athlon1600/php-proxy-app": "<=3", + "austintoddj/canvas": "<=3.4.2", + "automad/automad": "<=1.10.9", + "awesome-support/awesome-support": "<=6.0.7", + "aws/aws-sdk-php": "<3.288.1", + "azuracast/azuracast": "<0.18.3", + "backdrop/backdrop": "<1.24.2", + "backpack/crud": "<3.4.9", + "bacula-web/bacula-web": "<8.0.0.0-RC2-dev", + "badaso/core": "<2.7", + "bagisto/bagisto": "<2.1", + "barrelstrength/sprout-base-email": "<1.2.7", + "barrelstrength/sprout-forms": "<3.9", + "barryvdh/laravel-translation-manager": "<0.6.2", + "barzahlen/barzahlen-php": "<2.0.1", + "baserproject/basercms": "<5.0.9", + "bassjobsen/bootstrap-3-typeahead": ">4.0.2", + "bigfork/silverstripe-form-capture": ">=3,<3.1.1", + "billz/raspap-webgui": "<2.9.5", + "bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3", + "bmarshall511/wordpress_zero_spam": "<5.2.13", + "bolt/bolt": "<3.7.2", + "bolt/core": "<=4.2", + "bottelet/flarepoint": "<2.2.1", + "bref/bref": "<2.1.13", + "brightlocal/phpwhois": "<=4.2.5", + "brotkrueml/codehighlight": "<2.7", + "brotkrueml/schema": "<1.13.1|>=2,<2.5.1", + "brotkrueml/typo3-matomo-integration": "<1.3.2", + "buddypress/buddypress": "<7.2.1", + "bugsnag/bugsnag-laravel": ">=2,<2.0.2", + "bytefury/crater": "<6.0.2", + "cachethq/cachet": "<2.5.1", + "cakephp/cakephp": "<3.10.3|>=4,<4.0.10|>=4.1,<4.1.4|>=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", + "cakephp/database": ">=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", + "cardgate/magento2": "<2.0.33", + "cardgate/woocommerce": "<=3.1.15", + "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", + "cartalyst/sentry": "<=2.1.6", + "catfan/medoo": "<1.7.5", + "cecil/cecil": "<7.47.1", + "centreon/centreon": "<22.10.0.0-beta1", + "cesnet/simplesamlphp-module-proxystatistics": "<3.1", + "chriskacerguis/codeigniter-restserver": "<=2.7.1", + "civicrm/civicrm-core": ">=4.2,<4.2.9|>=4.3,<4.3.3", + "ckeditor/ckeditor": "<4.24", + "cockpit-hq/cockpit": "<=2.6.3|==2.7", + "codeception/codeception": "<3.1.3|>=4,<4.1.22", + "codeigniter/framework": "<3.1.9", + "codeigniter4/framework": "<=4.4.2", + "codeigniter4/shield": "<1.0.0.0-beta8", + "codiad/codiad": "<=2.8.4", + "composer/composer": "<1.10.27|>=2,<2.2.23|>=2.3,<2.7", + "concrete5/concrete5": "<9.2.7", + "concrete5/core": "<8.5.8|>=9,<9.1", + "contao-components/mediaelement": ">=2.14.2,<2.21.1", + "contao/contao": ">=4,<4.4.56|>=4.5,<4.9.40|>=4.10,<4.11.7|>=4.13,<4.13.21|>=5.1,<5.1.4", + "contao/core": ">=2,<3.5.39", + "contao/core-bundle": ">=3,<3.5.35|>=4,<4.9.42|>=4.10,<4.13.28|>=5,<5.1.10", + "contao/listing-bundle": ">=4,<4.4.8", + "contao/managed-edition": "<=1.5", + "corveda/phpsandbox": "<1.3.5", + "cosenary/instagram": "<=2.3", + "craftcms/cms": "<4.6.2", + "croogo/croogo": "<4", + "cuyz/valinor": "<0.12", + "czproject/git-php": "<4.0.3", + "darylldoyle/safe-svg": "<1.9.10", + "datadog/dd-trace": ">=0.30,<0.30.2", + "datatables/datatables": "<1.10.10", + "david-garcia/phpwhois": "<=4.3.1", + "dbrisinajumi/d2files": "<1", + "dcat/laravel-admin": "<=2.1.3.0-beta", + "derhansen/fe_change_pwd": "<2.0.5|>=3,<3.0.3", + "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1|>=7,<7.4", + "desperado/xml-bundle": "<=0.1.7", + "directmailteam/direct-mail": "<6.0.3|>=7,<7.0.3|>=8,<9.5.2", + "doctrine/annotations": "<1.2.7", + "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2", + "doctrine/common": "<2.4.3|>=2.5,<2.5.1", + "doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2|>=3,<3.1.4", + "doctrine/doctrine-bundle": "<1.5.2", + "doctrine/doctrine-module": "<=0.7.1", + "doctrine/mongodb-odm": "<1.0.2", + "doctrine/mongodb-odm-bundle": "<3.0.1", + "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4", + "dolibarr/dolibarr": "<18.0.2", + "dompdf/dompdf": "<2.0.4", + "doublethreedigital/guest-entries": "<3.1.2", + "drupal/core": ">=6,<6.38|>=7,<7.96|>=8,<10.1.8|>=10.2,<10.2.2", + "drupal/drupal": ">=5,<5.11|>=6,<6.38|>=7,<7.80|>=8,<8.9.16|>=9,<9.1.12|>=9.2,<9.2.4", + "duncanmcclean/guest-entries": "<3.1.2", + "dweeves/magmi": "<=0.7.24", + "ec-cube/ec-cube": "<2.4.4", + "ecodev/newsletter": "<=4", + "ectouch/ectouch": "<=2.7.2", + "elefant/cms": "<2.0.7", + "elgg/elgg": "<3.3.24|>=4,<4.0.5", + "elijaa/phpmemcacheadmin": "<=1.3", + "encore/laravel-admin": "<=1.8.19", + "endroid/qr-code-bundle": "<3.4.2", + "enhavo/enhavo-app": "<=0.13.1", + "enshrined/svg-sanitize": "<0.15", + "erusev/parsedown": "<1.7.2", + "ether/logs": "<3.0.4", + "evolutioncms/evolution": "<=3.2.3", + "exceedone/exment": "<4.4.3|>=5,<5.0.3", + "exceedone/laravel-admin": "<2.2.3|==3", + "ezsystems/demobundle": ">=5.4,<5.4.6.1-dev", + "ezsystems/ez-support-tools": ">=2.2,<2.2.3", + "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1-dev", + "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1-dev|>=5.4,<5.4.11.1-dev|>=2017.12,<2017.12.0.1-dev", + "ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24", + "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.26", + "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1", + "ezsystems/ezplatform-graphql": ">=1.0.0.0-RC1-dev,<1.0.13|>=2.0.0.0-beta1,<2.3.12", + "ezsystems/ezplatform-kernel": "<1.2.5.1-dev|>=1.3,<1.3.34", + "ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8", + "ezsystems/ezplatform-richtext": ">=2.3,<2.3.7.1-dev", + "ezsystems/ezplatform-solr-search-engine": ">=1.7,<1.7.12|>=2,<2.0.2|>=3.3,<3.3.15", + "ezsystems/ezplatform-user": ">=1,<1.0.1", + "ezsystems/ezpublish-kernel": "<6.13.8.2-dev|>=7,<7.5.31", + "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.06,<=2019.03.5.1", + "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", + "ezsystems/repository-forms": ">=2.3,<2.3.2.1-dev|>=2.5,<2.5.15", + "ezyang/htmlpurifier": "<4.1.1", + "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2", + "facturascripts/facturascripts": "<=2022.08", + "feehi/cms": "<=2.1.1", + "feehi/feehicms": "<=2.1.1", + "fenom/fenom": "<=2.12.1", + "filegator/filegator": "<7.8", + "firebase/php-jwt": "<6", + "fixpunkt/fp-masterquiz": "<2.2.1|>=3,<3.5.2", + "fixpunkt/fp-newsletter": "<1.1.1|>=2,<2.1.2|>=2.2,<3.2.6", + "flarum/core": "<1.8.5", + "flarum/framework": "<1.8.5", + "flarum/mentions": "<1.6.3", + "flarum/sticky": ">=0.1.0.0-beta14,<=0.1.0.0-beta15", + "flarum/tags": "<=0.1.0.0-beta13", + "floriangaerber/magnesium": "<0.3.1", + "fluidtypo3/vhs": "<5.1.1", + "fof/byobu": ">=0.3.0.0-beta2,<1.1.7", + "fof/upload": "<1.2.3", + "foodcoopshop/foodcoopshop": ">=3.2,<3.6.1", + "fooman/tcpdf": "<6.2.22", + "forkcms/forkcms": "<5.11.1", + "fossar/tcpdf-parser": "<6.2.22", + "francoisjacquet/rosariosis": "<11", + "frappant/frp-form-answers": "<3.1.2|>=4,<4.0.2", + "friendsofsymfony/oauth2-php": "<1.3", + "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", + "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", + "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", + "friendsoftypo3/openid": ">=4.5,<4.5.31|>=4.7,<4.7.16|>=6,<6.0.11|>=6.1,<6.1.6", + "froala/wysiwyg-editor": "<3.2.7|>=4.0.1,<=4.1.1", + "froxlor/froxlor": "<=2.1.1", + "fuel/core": "<1.8.1", + "funadmin/funadmin": "<=3.2|>=3.3.2,<=3.3.3", + "gaoming13/wechat-php-sdk": "<=1.10.2", + "genix/cms": "<=1.1.11", + "getgrav/grav": "<1.7.44", + "getkirby/cms": "<4.1.1", + "getkirby/kirby": "<=2.5.12", + "getkirby/panel": "<2.5.14", + "getkirby/starterkit": "<=3.7.0.2", + "gilacms/gila": "<=1.15.4", + "gleez/cms": "<=1.2|==2", + "globalpayments/php-sdk": "<2", + "gogentooss/samlbase": "<1.2.7", + "google/protobuf": "<3.15", + "gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3", + "gree/jose": "<2.2.1", + "gregwar/rst": "<1.0.3", + "grumpydictator/firefly-iii": "<6.1.7", + "gugoan/economizzer": "<=0.9.0.0-beta1", + "guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5", + "guzzlehttp/psr7": "<1.9.1|>=2,<2.4.5", + "haffner/jh_captcha": "<=2.1.3|>=3,<=3.0.2", + "harvesthq/chosen": "<1.8.7", + "helloxz/imgurl": "<=2.31", + "hhxsv5/laravel-s": "<3.7.36", + "hillelcoren/invoice-ninja": "<5.3.35", + "himiklab/yii2-jqgrid-widget": "<1.0.8", + "hjue/justwriting": "<=1", + "hov/jobfair": "<1.0.13|>=2,<2.0.2", + "httpsoft/http-message": "<1.0.12", + "hyn/multi-tenant": ">=5.6,<5.7.2", + "ibexa/admin-ui": ">=4.2,<4.2.3", + "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4|>=4.2,<4.2.3|>=4.5,<4.5.4", + "ibexa/graphql": ">=2.5,<2.5.31|>=3.3,<3.3.28|>=4.2,<4.2.3", + "ibexa/post-install": "<=1.0.4", + "ibexa/solr": ">=4.5,<4.5.4", + "ibexa/user": ">=4,<4.4.3", + "icecoder/icecoder": "<=8.1", + "idno/known": "<=1.3.1", + "illuminate/auth": "<5.5.10", + "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<=4.1.99999|>=4.2,<=4.2.99999|>=5,<=5.0.99999|>=5.1,<=5.1.99999|>=5.2,<=5.2.99999|>=5.3,<=5.3.99999|>=5.4,<=5.4.99999|>=5.5,<=5.5.49|>=5.6,<=5.6.99999|>=5.7,<=5.7.99999|>=5.8,<=5.8.99999|>=6,<6.18.31|>=7,<7.22.4", + "illuminate/database": "<6.20.26|>=7,<7.30.5|>=8,<8.40", + "illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15", + "illuminate/view": "<6.20.42|>=7,<7.30.6|>=8,<8.75", + "impresscms/impresscms": "<=1.4.5", + "impresspages/impresspages": "<=1.0.12", + "in2code/femanager": "<5.5.3|>=6,<6.3.4|>=7,<7.2.3", + "in2code/ipandlanguageredirect": "<5.1.2", + "in2code/lux": "<17.6.1|>=18,<24.0.2", + "innologi/typo3-appointments": "<2.0.6", + "intelliants/subrion": "<4.2.2", + "islandora/islandora": ">=2,<2.4.1", + "ivankristianto/phpwhois": "<=4.3", + "jackalope/jackalope-doctrine-dbal": "<1.7.4", + "james-heinrich/getid3": "<1.9.21", + "james-heinrich/phpthumb": "<1.7.12", + "jasig/phpcas": "<1.3.3", + "jcbrand/converse.js": "<3.3.3", + "joomla/application": "<1.0.13", + "joomla/archive": "<1.1.12|>=2,<2.0.1", + "joomla/filesystem": "<1.6.2|>=2,<2.0.1", + "joomla/filter": "<1.4.4|>=2,<2.0.1", + "joomla/framework": "<1.5.7|>=2.5.4,<=3.8.12", + "joomla/input": ">=2,<2.0.2", + "joomla/joomla-cms": ">=2.5,<3.9.12", + "joomla/session": "<1.3.1", + "joyqi/hyper-down": "<=2.4.27", + "jsdecena/laracom": "<2.0.9", + "jsmitty12/phpwhois": "<5.1", + "juzaweb/cms": "<=3.4", + "kazist/phpwhois": "<=4.2.6", + "kelvinmo/simplexrd": "<3.1.1", + "kevinpapst/kimai2": "<1.16.7", + "khodakhah/nodcms": "<=3", + "kimai/kimai": "<2.1", + "kitodo/presentation": "<3.2.3|>=3.3,<3.3.4", + "klaviyo/magento2-extension": ">=1,<3", + "knplabs/knp-snappy": "<=1.4.2", + "kohana/core": "<3.3.3", + "krayin/laravel-crm": "<1.2.2", + "kreait/firebase-php": ">=3.2,<3.8.1", + "la-haute-societe/tcpdf": "<6.2.22", + "laminas/laminas-diactoros": "<2.18.1|==2.19|==2.20|==2.21|==2.22|==2.23|>=2.24,<2.24.2|>=2.25,<2.25.2", + "laminas/laminas-form": "<2.17.1|>=3,<3.0.2|>=3.1,<3.1.1", + "laminas/laminas-http": "<2.14.2", + "laravel/fortify": "<1.11.1", + "laravel/framework": "<6.20.44|>=7,<7.30.6|>=8,<8.75", + "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", + "latte/latte": "<2.10.8", + "lavalite/cms": "<=9", + "lcobucci/jwt": ">=3.4,<3.4.6|>=4,<4.0.4|>=4.1,<4.1.5", + "league/commonmark": "<0.18.3", + "league/flysystem": "<1.1.4|>=2,<2.1.1", + "league/oauth2-server": ">=8.3.2,<8.4.2|>=8.5,<8.5.3", + "lexik/jwt-authentication-bundle": "<2.10.7|>=2.11,<2.11.3", + "librenms/librenms": "<2017.08.18", + "liftkit/database": "<2.13.2", + "limesurvey/limesurvey": "<3.27.19", + "livehelperchat/livehelperchat": "<=3.91", + "livewire/livewire": ">2.2.4,<2.2.6", + "lms/routes": "<2.1.1", + "localizationteam/l10nmgr": "<7.4|>=8,<8.7|>=9,<9.2", + "luyadev/yii-helpers": "<1.2.1", + "magento/community-edition": "<2.4.3.0-patch3|>=2.4.4,<2.4.5", + "magento/core": "<=1.9.4.5", + "magento/magento1ce": "<1.9.4.3-dev", + "magento/magento1ee": ">=1,<1.14.4.3-dev", + "magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2.0-patch2", + "magneto/core": "<1.9.4.4-dev", + "maikuolan/phpmussel": ">=1,<1.6", + "mainwp/mainwp": "<=4.4.3.3", + "mantisbt/mantisbt": "<2.26.1", + "marcwillmann/turn": "<0.3.3", + "matyhtf/framework": "<3.0.6", + "mautic/core": "<4.3", + "mediawiki/core": "<1.36.2", + "mediawiki/matomo": "<2.4.3", + "mediawiki/semantic-media-wiki": "<4.0.2", + "melisplatform/melis-asset-manager": "<5.0.1", + "melisplatform/melis-cms": "<5.0.1", + "melisplatform/melis-front": "<5.0.1", + "mezzio/mezzio-swoole": "<3.7|>=4,<4.3", + "mgallegos/laravel-jqgrid": "<=1.3", + "microsoft/microsoft-graph": ">=1.16,<1.109.1|>=2,<2.0.1", + "microsoft/microsoft-graph-beta": "<2.0.1", + "microsoft/microsoft-graph-core": "<2.0.2", + "microweber/microweber": "<=2.0.4", + "miniorange/miniorange-saml": "<1.4.3", + "mittwald/typo3_forum": "<1.2.1", + "mobiledetect/mobiledetectlib": "<2.8.32", + "modx/revolution": "<=2.8.3.0-patch", + "mojo42/jirafeau": "<4.4", + "mongodb/mongodb": ">=1,<1.9.2", + "monolog/monolog": ">=1.8,<1.12", + "moodle/moodle": "<4.3.3", + "mos/cimage": "<0.7.19", + "movim/moxl": ">=0.8,<=0.10", + "mpdf/mpdf": "<=7.1.7", + "munkireport/comment": "<4.1", + "munkireport/managedinstalls": "<2.6", + "munkireport/munkireport": ">=2.5.3,<5.6.3", + "mustache/mustache": ">=2,<2.14.1", + "namshi/jose": "<2.2", + "neoan3-apps/template": "<1.1.1", + "neorazorx/facturascripts": "<2022.04", + "neos/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", + "neos/form": ">=1.2,<4.3.3|>=5,<5.0.9|>=5.1,<5.1.3", + "neos/media-browser": "<7.3.19|>=8,<8.0.16|>=8.1,<8.1.11|>=8.2,<8.2.11|>=8.3,<8.3.9", + "neos/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.9.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<5.3.10|>=7,<7.0.9|>=7.1,<7.1.7|>=7.2,<7.2.6|>=7.3,<7.3.4|>=8,<8.0.2", + "neos/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", + "netgen/tagsbundle": ">=3.4,<3.4.11|>=4,<4.0.15", + "nette/application": ">=2,<2.0.19|>=2.1,<2.1.13|>=2.2,<2.2.10|>=2.3,<2.3.14|>=2.4,<2.4.16|>=3,<3.0.6", + "nette/nette": ">=2,<2.0.19|>=2.1,<2.1.13", + "nilsteampassnet/teampass": "<3.0.10", + "nonfiction/nterchange": "<4.1.1", + "notrinos/notrinos-erp": "<=0.7", + "noumo/easyii": "<=0.9", + "nukeviet/nukeviet": "<4.5.02", + "nyholm/psr7": "<1.6.1", + "nystudio107/craft-seomatic": "<3.4.12", + "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1", + "october/backend": "<1.1.2", + "october/cms": "<1.0.469|==1.0.469|==1.0.471|==1.1.1", + "october/october": "<=3.4.4", + "october/rain": "<1.0.472|>=1.1,<1.1.2", + "october/system": "<1.0.476|>=1.1,<1.1.12|>=2,<2.2.34|>=3,<3.5.2", + "omeka/omeka-s": "<4.0.3", + "onelogin/php-saml": "<2.10.4", + "oneup/uploader-bundle": ">=1,<1.9.3|>=2,<2.1.5", + "open-web-analytics/open-web-analytics": "<1.7.4", + "opencart/opencart": "<=3.0.3.7|>=4,<4.0.2.3-dev", + "openid/php-openid": "<2.3", + "openmage/magento-lts": "<20.5", + "opensource-workshop/connect-cms": "<1.7.2|>=2,<2.3.2", + "orchid/platform": ">=9,<9.4.4|>=14.0.0.0-alpha4,<14.5", + "oro/calendar-bundle": ">=4.2,<=4.2.6|>=5,<=5.0.6|>=5.1,<5.1.1", + "oro/commerce": ">=4.1,<5.0.11|>=5.1,<5.1.1", + "oro/crm": ">=1.7,<1.7.4|>=3.1,<4.1.17|>=4.2,<4.2.7", + "oro/crm-call-bundle": ">=4.2,<=4.2.5|>=5,<5.0.4|>=5.1,<5.1.1", + "oro/customer-portal": ">=4.2,<=4.2.8|>=5,<5.0.11|>=5.1,<5.1.1", + "oro/platform": ">=1.7,<1.7.4|>=3.1,<3.1.29|>=4.1,<4.1.17|>=4.2,<=4.2.10|>=5,<5.0.8", + "oxid-esales/oxideshop-ce": "<4.5", + "packbackbooks/lti-1-3-php-library": "<5", + "padraic/humbug_get_contents": "<1.1.2", + "pagarme/pagarme-php": "<3", + "pagekit/pagekit": "<=1.0.18", + "paragonie/random_compat": "<2", + "passbolt/passbolt_api": "<2.11", + "paypal/merchant-sdk-php": "<3.12", + "pear/archive_tar": "<1.4.14", + "pear/auth": "<1.2.4", + "pear/crypt_gpg": "<1.6.7", + "pear/pear": "<=1.10.1", + "pegasus/google-for-jobs": "<1.5.1|>=2,<2.1.1", + "personnummer/personnummer": "<3.0.2", + "phanan/koel": "<5.1.4", + "phenx/php-svg-lib": "<0.5.2", + "php-mod/curl": "<2.3.2", + "phpbb/phpbb": "<3.2.10|>=3.3,<3.3.1", + "phpems/phpems": ">=6,<=6.1.3", + "phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7", + "phpmailer/phpmailer": "<6.5", + "phpmussel/phpmussel": ">=1,<1.6", + "phpmyadmin/phpmyadmin": "<5.2.1", + "phpmyfaq/phpmyfaq": "<3.2.5", + "phpoffice/phpexcel": "<1.8", + "phpoffice/phpspreadsheet": "<1.16", + "phpseclib/phpseclib": "<2.0.47|>=3,<3.0.36", + "phpservermon/phpservermon": "<3.6", + "phpsysinfo/phpsysinfo": "<3.4.3", + "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3", + "phpwhois/phpwhois": "<=4.2.5", + "phpxmlrpc/extras": "<0.6.1", + "phpxmlrpc/phpxmlrpc": "<4.9.2", + "pi/pi": "<=2.5", + "pimcore/admin-ui-classic-bundle": "<1.3.4", + "pimcore/customer-management-framework-bundle": "<4.0.6", + "pimcore/data-hub": "<1.2.4", + "pimcore/demo": "<10.3", + "pimcore/ecommerce-framework-bundle": "<1.0.10", + "pimcore/perspective-editor": "<1.5.1", + "pimcore/pimcore": "<11.1.1", + "pixelfed/pixelfed": "<0.11.11", + "plotly/plotly.js": "<2.25.2", + "pocketmine/bedrock-protocol": "<8.0.2", + "pocketmine/pocketmine-mp": "<5.11.2", + "pocketmine/raklib": ">=0.14,<0.14.6|>=0.15,<0.15.1", + "pressbooks/pressbooks": "<5.18", + "prestashop/autoupgrade": ">=4,<4.10.1", + "prestashop/blockreassurance": "<=5.1.3", + "prestashop/blockwishlist": ">=2,<2.1.1", + "prestashop/contactform": ">=1.0.1,<4.3", + "prestashop/gamification": "<2.3.2", + "prestashop/prestashop": "<8.1.4", + "prestashop/productcomments": "<5.0.2", + "prestashop/ps_emailsubscription": "<2.6.1", + "prestashop/ps_facetedsearch": "<3.4.1", + "prestashop/ps_linklist": "<3.1", + "privatebin/privatebin": "<1.4", + "processwire/processwire": "<=3.0.210", + "propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7", + "propel/propel1": ">=1,<=1.7.1", + "pterodactyl/panel": "<1.7", + "ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2", + "ptrofimov/beanstalk_console": "<1.7.14", + "pubnub/pubnub": "<6.1", + "pusher/pusher-php-server": "<2.2.1", + "pwweb/laravel-core": "<=0.3.6.0-beta", + "pyrocms/pyrocms": "<=3.9.1", + "rainlab/blog-plugin": "<1.4.1", + "rainlab/debugbar-plugin": "<3.1", + "rainlab/user-plugin": "<=1.4.5", + "rankmath/seo-by-rank-math": "<=1.0.95", + "rap2hpoutre/laravel-log-viewer": "<0.13", + "react/http": ">=0.7,<1.9", + "really-simple-plugins/complianz-gdpr": "<6.4.2", + "redaxo/source": "<=5.15.1", + "remdex/livehelperchat": "<3.99", + "reportico-web/reportico": "<=7.1.21", + "rhukster/dom-sanitizer": "<1.0.7", + "rmccue/requests": ">=1.6,<1.8", + "robrichards/xmlseclibs": ">=1,<3.0.4", + "roots/soil": "<4.1", + "rudloff/alltube": "<3.0.3", + "s-cart/core": "<6.9", + "s-cart/s-cart": "<6.9", + "sabberworm/php-css-parser": ">=1,<1.0.1|>=2,<2.0.1|>=3,<3.0.1|>=4,<4.0.1|>=5,<5.0.9|>=5.1,<5.1.3|>=5.2,<5.2.1|>=6,<6.0.2|>=7,<7.0.4|>=8,<8.0.1|>=8.1,<8.1.1|>=8.2,<8.2.1|>=8.3,<8.3.1", + "sabre/dav": ">=1.6,<1.7.11|>=1.8,<1.8.9", + "scheb/two-factor-bundle": "<3.26|>=4,<4.11", + "sensiolabs/connect": "<4.2.3", + "serluck/phpwhois": "<=4.2.6", + "sfroemken/url_redirect": "<=1.2.1", + "sheng/yiicms": "<=1.2", + "shopware/core": "<=6.5.7.3", + "shopware/platform": "<=6.5.7.3|>=6.5.8,<6.5.8.7-dev", + "shopware/production": "<=6.3.5.2", + "shopware/shopware": "<=5.7.17", + "shopware/storefront": "<=6.4.8.1|>=6.5.8,<6.5.8.7-dev", + "shopxo/shopxo": "<2.2.6", + "showdoc/showdoc": "<2.10.4", + "silverstripe-australia/advancedreports": ">=1,<=2", + "silverstripe/admin": "<1.13.19|>=2,<2.1.8", + "silverstripe/assets": ">=1,<1.11.1", + "silverstripe/cms": "<4.11.3", + "silverstripe/comments": ">=1.3,<1.9.99|>=2,<2.9.99|>=3,<3.1.1", + "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", + "silverstripe/framework": "<4.13.39|>=5,<5.1.11", + "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.8.2|>=4,<4.3.7|>=5,<5.1.3", + "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1", + "silverstripe/recipe-cms": ">=4.5,<4.5.3", + "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", + "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4", + "silverstripe/silverstripe-omnipay": "<2.5.2|>=3,<3.0.2|>=3.1,<3.1.4|>=3.2,<3.2.1", + "silverstripe/subsites": ">=2,<2.6.1", + "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1", + "silverstripe/userforms": "<3", + "silverstripe/versioned-admin": ">=1,<1.11.1", + "simple-updates/phpwhois": "<=1", + "simplesamlphp/saml2": "<1.10.6|>=2,<2.3.8|>=3,<3.1.4|==5.0.0.0-alpha12", + "simplesamlphp/simplesamlphp": "<1.18.6", + "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", + "simplesamlphp/simplesamlphp-module-openid": "<1", + "simplesamlphp/simplesamlphp-module-openidprovider": "<0.9", + "simplesamlphp/xml-security": "==1.6.11", + "simplito/elliptic-php": "<1.0.6", + "sitegeist/fluid-components": "<3.5", + "sjbr/sr-freecap": "<2.4.6|>=2.5,<2.5.3", + "slim/psr7": "<1.4.1|>=1.5,<1.5.1|>=1.6,<1.6.1", + "slim/slim": "<2.6", + "slub/slub-events": "<3.0.3", + "smarty/smarty": "<3.1.48|>=4,<4.3.1", + "snipe/snipe-it": "<=6.2.2", + "socalnick/scn-social-auth": "<1.15.2", + "socialiteproviders/steam": "<1.1", + "spatie/browsershot": "<3.57.4", + "spipu/html2pdf": "<5.2.8", + "spoon/library": "<1.4.1", + "spoonity/tcpdf": "<6.2.22", + "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", + "ssddanbrown/bookstack": "<22.02.3", + "statamic/cms": "<4.46", + "stormpath/sdk": "<9.9.99", + "studio-42/elfinder": "<2.1.62", + "subhh/libconnect": "<7.0.8|>=8,<8.1", + "sukohi/surpass": "<1", + "sulu/sulu": "<1.6.44|>=2,<2.4.17|>=2.5,<2.5.13", + "sumocoders/framework-user-bundle": "<1.4", + "superbig/craft-audit": "<3.0.2", + "swag/paypal": "<5.4.4", + "swiftmailer/swiftmailer": ">=4,<5.4.5", + "swiftyedit/swiftyedit": "<1.2", + "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", + "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", + "sylius/grid-bundle": "<1.10.1", + "sylius/paypal-plugin": ">=1,<1.2.4|>=1.3,<1.3.1", + "sylius/resource-bundle": ">=1,<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4", + "sylius/sylius": "<1.9.10|>=1.10,<1.10.11|>=1.11,<1.11.2", + "symbiote/silverstripe-multivaluefield": ">=3,<3.0.99", + "symbiote/silverstripe-queuedjobs": ">=3,<3.0.2|>=3.1,<3.1.4|>=4,<4.0.7|>=4.1,<4.1.2|>=4.2,<4.2.4|>=4.3,<4.3.3|>=4.4,<4.4.3|>=4.5,<4.5.1|>=4.6,<4.6.4", + "symbiote/silverstripe-seed": "<6.0.3", + "symbiote/silverstripe-versionedfiles": "<=2.0.3", + "symfont/process": ">=0", + "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", + "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4", + "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", + "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<5.3.15|>=5.4.3,<5.4.4|>=6.0.3,<6.0.4", + "symfony/http-foundation": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7", + "symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", + "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1", + "symfony/mime": ">=4.3,<4.3.8", + "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/polyfill": ">=1,<1.10", + "symfony/polyfill-php55": ">=1,<1.10", + "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/routing": ">=2,<2.0.19", + "symfony/security": ">=2,<2.7.51|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.8", + "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", + "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.9", + "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", + "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8", + "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.3.2|>=5.4,<5.4.31|>=6,<6.3.8", + "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", + "symfony/symfony": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", + "symfony/translation": ">=2,<2.0.17", + "symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", + "symfony/ux-autocomplete": "<2.11.2", + "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", + "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", + "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", + "symfony/webhook": ">=6.3,<6.3.8", + "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7|>=2.2.0.0-beta1,<2.2.0.0-beta2", + "symphonycms/symphony-2": "<2.6.4", + "t3/dce": "<0.11.5|>=2.2,<2.6.2", + "t3g/svg-sanitizer": "<1.0.3", + "t3s/content-consent": "<1.0.3|>=2,<2.0.2", + "tastyigniter/tastyigniter": "<3.3", + "tcg/voyager": "<=1.4", + "tecnickcom/tcpdf": "<6.2.22", + "terminal42/contao-tablelookupwizard": "<3.3.5", + "thelia/backoffice-default-template": ">=2.1,<2.1.2", + "thelia/thelia": ">=2.1,<2.1.3", + "theonedemon/phpwhois": "<=4.2.5", + "thinkcmf/thinkcmf": "<=5.1.7", + "thorsten/phpmyfaq": "<3.2.2", + "tikiwiki/tiki-manager": "<=17.1", + "tinymce/tinymce": "<5.10.9|>=6,<6.7.3", + "tinymighty/wiki-seo": "<1.2.2", + "titon/framework": "<9.9.99", + "tobiasbg/tablepress": "<=2.0.0.0-RC1", + "topthink/framework": "<6.0.14", + "topthink/think": "<=6.1.1", + "topthink/thinkphp": "<=3.2.3", + "torrentpier/torrentpier": "<=2.4.1", + "tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2", + "tribalsystems/zenario": "<=9.4.59197", + "truckersmp/phpwhois": "<=4.3.1", + "ttskch/pagination-service-provider": "<1", + "twig/twig": "<1.44.7|>=2,<2.15.3|>=3,<3.4.3", + "typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", + "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", + "typo3/cms-core": "<=8.7.56|>=9,<=9.5.45|>=10,<=10.4.42|>=11,<=11.5.34|>=12,<=12.4.10|==13", + "typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1", + "typo3/cms-fluid": "<4.3.4|>=4.4,<4.4.1", + "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", + "typo3/cms-frontend": "<4.3.9|>=4.4,<4.4.5", + "typo3/cms-install": "<4.1.14|>=4.2,<4.2.16|>=4.3,<4.3.9|>=4.4,<4.4.5|>=12.2,<12.4.8", + "typo3/cms-rte-ckeditor": ">=9.5,<9.5.42|>=10,<10.4.39|>=11,<11.5.30", + "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", + "typo3/html-sanitizer": ">=1,<=1.5.2|>=2,<=2.1.3", + "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3", + "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", + "typo3/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", + "typo3fluid/fluid": ">=2,<2.0.8|>=2.1,<2.1.7|>=2.2,<2.2.4|>=2.3,<2.3.7|>=2.4,<2.4.4|>=2.5,<2.5.11|>=2.6,<2.6.10", + "ua-parser/uap-php": "<3.8", + "uasoft-indonesia/badaso": "<=2.9.7", + "unisharp/laravel-filemanager": "<2.6.4", + "userfrosting/userfrosting": ">=0.3.1,<4.6.3", + "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2", + "uvdesk/community-skeleton": "<=1.1.1", + "vanilla/safecurl": "<0.9.2", + "verot/class.upload.php": "<=2.1.6", + "vova07/yii2-fileapi-widget": "<0.1.9", + "vrana/adminer": "<4.8.1", + "waldhacker/hcaptcha": "<2.1.2", + "wallabag/tcpdf": "<6.2.22", + "wallabag/wallabag": "<2.6.7", + "wanglelecc/laracms": "<=1.0.3", + "web-auth/webauthn-framework": ">=3.3,<3.3.4", + "webbuilders-group/silverstripe-kapost-bridge": "<0.4", + "webcoast/deferred-image-processing": "<1.0.2", + "webklex/laravel-imap": "<5.3", + "webklex/php-imap": "<5.3", + "webpa/webpa": "<3.1.2", + "wikibase/wikibase": "<=1.39.3", + "wikimedia/parsoid": "<0.12.2", + "willdurand/js-translation-bundle": "<2.1.1", + "winter/wn-backend-module": "<1.2.4", + "winter/wn-system-module": "<1.2.4", + "wintercms/winter": "<1.2.3", + "woocommerce/woocommerce": "<6.6", + "wp-cli/wp-cli": ">=0.12,<2.5", + "wp-graphql/wp-graphql": "<=1.14.5", + "wpanel/wpanel4-cms": "<=4.3.1", + "wpcloud/wp-stateless": "<3.2", + "wwbn/avideo": "<=12.4", + "xataface/xataface": "<3", + "xpressengine/xpressengine": "<3.0.15", + "yeswiki/yeswiki": "<4.1", + "yetiforce/yetiforce-crm": "<=6.4", + "yidashi/yii2cmf": "<=2", + "yii2mod/yii2-cms": "<1.9.2", + "yiisoft/yii": "<1.1.29", + "yiisoft/yii2": "<2.0.38", + "yiisoft/yii2-authclient": "<2.2.15", + "yiisoft/yii2-bootstrap": "<2.0.4", + "yiisoft/yii2-dev": "<2.0.43", + "yiisoft/yii2-elasticsearch": "<2.0.5", + "yiisoft/yii2-gii": "<=2.2.4", + "yiisoft/yii2-jui": "<2.0.4", + "yiisoft/yii2-redis": "<2.0.8", + "yikesinc/yikes-inc-easy-mailchimp-extender": "<6.8.6", + "yoast-seo-for-typo3/yoast_seo": "<7.2.3", + "yourls/yourls": "<=1.8.2", + "yuan1994/tpadmin": "<=1.3.12", + "zencart/zencart": "<=1.5.7.0-beta", + "zendesk/zendesk_api_client_php": "<2.2.11", + "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3", + "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", + "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", + "zendframework/zend-db": "<2.2.10|>=2.3,<2.3.5", + "zendframework/zend-developer-tools": ">=1.2.2,<1.2.3", + "zendframework/zend-diactoros": "<1.8.4", + "zendframework/zend-feed": "<2.10.3", + "zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-http": "<2.8.1", + "zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6", + "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3", + "zendframework/zend-mail": ">=2,<2.4.11|>=2.5,<2.7.2", + "zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-session": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.9|>=2.3,<2.3.4", + "zendframework/zend-validator": ">=2.3,<2.3.6", + "zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6", + "zendframework/zendframework": "<=3", + "zendframework/zendframework1": "<1.12.20", + "zendframework/zendopenid": "<2.0.2", + "zendframework/zendrest": "<2.0.2", + "zendframework/zendservice-amazon": "<2.0.3", + "zendframework/zendservice-api": "<1", + "zendframework/zendservice-audioscrobbler": "<2.0.2", + "zendframework/zendservice-nirvanix": "<2.0.2", + "zendframework/zendservice-slideshare": "<2.0.2", + "zendframework/zendservice-technorati": "<2.0.2", + "zendframework/zendservice-windowsazure": "<2.0.2", + "zendframework/zendxml": ">=1,<1.0.1", + "zenstruck/collection": "<0.2.1", + "zetacomponents/mail": "<1.8.2", + "zf-commons/zfc-user": "<1.2.2", + "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3", + "zfr/zfr-oauth2-server-module": "<0.1.2", + "zoujingli/thinkadmin": "<=6.1.53" + }, + "default-branch": true, + "type": "metapackage", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "role": "maintainer" + }, + { + "name": "Ilya Tribusean", + "email": "slash3b@gmail.com", + "role": "maintainer" + } + ], + "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", + "keywords": [ + "dev" + ], + "support": { + "issues": "https://github.com/Roave/SecurityAdvisories/issues", + "source": "https://github.com/Roave/SecurityAdvisories/tree/latest" + }, + "funding": [ + { + "url": "https://github.com/Ocramius", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/roave/security-advisories", + "type": "tidelift" + } + ], + "time": "2024-03-08T12:05:25+00:00" + }, + { + "name": "sabre/event", + "version": "5.1.4", + "source": { + "type": "git", + "url": "https://github.com/sabre-io/event.git", + "reference": "d7da22897125d34d7eddf7977758191c06a74497" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabre-io/event/zipball/d7da22897125d34d7eddf7977758191c06a74497", + "reference": "d7da22897125d34d7eddf7977758191c06a74497", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.17.1", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.0" + }, + "type": "library", + "autoload": { + "files": [ + "lib/coroutine.php", + "lib/Loop/functions.php", + "lib/Promise/functions.php" + ], + "psr-4": { + "Sabre\\Event\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "description": "sabre/event is a library for lightweight event-based programming", + "homepage": "http://sabre.io/event/", + "keywords": [ + "EventEmitter", + "async", + "coroutine", + "eventloop", + "events", + "hooks", + "plugin", + "promise", + "reactor", + "signal" + ], + "support": { + "forum": "https://groups.google.com/group/sabredav-discuss", + "issues": "https://github.com/sabre-io/event/issues", + "source": "https://github.com/fruux/sabre-event" + }, + "time": "2021-11-04T06:51:17+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:27:43+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:41:17+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:19:30+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:30:58+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:33:00+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:35:11+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:20:34+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:07:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "spatie/array-to-xml", + "version": "3.2.3", + "source": { + "type": "git", + "url": "https://github.com/spatie/array-to-xml.git", + "reference": "c95fd4db94ec199f798d4b5b4a81757bd20d88ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/c95fd4db94ec199f798d4b5b4a81757bd20d88ab", + "reference": "c95fd4db94ec199f798d4b5b4a81757bd20d88ab", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": "^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.2", + "pestphp/pest": "^1.21", + "spatie/pest-plugin-snapshots": "^1.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\ArrayToXml\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://freek.dev", + "role": "Developer" + } + ], + "description": "Convert an array to xml", + "homepage": "https://github.com/spatie/array-to-xml", + "keywords": [ + "array", + "convert", + "xml" + ], + "support": { + "source": "https://github.com/spatie/array-to-xml/tree/3.2.3" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2024-02-07T10:39:02+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.9.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/d63cee4890a8afaf86a22e51ad4d97c91dd4579b", + "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-02-16T15:06:51+00:00" + }, + { + "name": "symfony/config", + "version": "v6.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "6ea4affc27f2086c9d16b92ab5429ce1e3c38047" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/6ea4affc27f2086c9d16b92ab5429ce1e3c38047", + "reference": "6ea4affc27f2086c9d16b92ab5429ce1e3c38047", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "require-dev": { + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v6.4.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-02-26T07:52:26+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v6.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "6236e5e843cb763e9d0f74245678b994afea5363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/6236e5e843cb763e9d0f74245678b994afea5363", + "reference": "6236e5e843cb763e9d0f74245678b994afea5363", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.2.10|^7.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.1", + "symfony/finder": "<5.4", + "symfony/proxy-manager-bridge": "<6.3", + "symfony/yaml": "<5.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^6.1|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-02-22T20:27:10+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v6.4.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "ae9d3a6f3003a6caf56acd7466d8d52378d44fef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ae9d3a6f3003a6caf56acd7466d8d52378d44fef", + "reference": "ae9d3a6f3003a6caf56acd7466d8d52378d44fef", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-23T14:51:35+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v6.4.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "7f3b1755eb49297a0827a7575d5d2b2fd11cc9fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/7f3b1755eb49297a0827a7575d5d2b2fd11cc9fb", + "reference": "7f3b1755eb49297a0827a7575d5d2b2fd11cc9fb", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v6.4.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-23T14:51:35+00:00" + }, + { + "name": "symfony/finder", + "version": "v6.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "11d736e97f116ac375a81f96e662911a34cd50ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/11d736e97f116ac375a81f96e662911a34cd50ce", + "reference": "11d736e97f116ac375a81f96e662911a34cd50ce", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v6.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-10-31T17:30:12+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v6.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "22301f0e7fdeaacc14318928612dee79be99860e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/22301f0e7fdeaacc14318928612dee79be99860e", + "reference": "22301f0e7fdeaacc14318928612dee79be99860e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v6.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-08T10:16:24+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/c565ad1e63f30e7477fc40738343c62b40bc672d", + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v6.4.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "416596166641f1f728b0a64f5b9dd07cceb410c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/416596166641f1f728b0a64f5b9dd07cceb410c1", + "reference": "416596166641f1f728b0a64f5b9dd07cceb410c1", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v6.4.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-23T14:35:58+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "b439823f04c98b84d4366c79507e9da6230944b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b439823f04c98b84d4366c79507e9da6230944b1", + "reference": "b439823f04c98b84d4366c79507e9da6230944b1", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^6.3|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", + "twig/twig": "^2.13|^3.0.4" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.4.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-02-15T11:23:52+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v6.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "0bd342e24aef49fc82a21bd4eedd3e665d177e5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0bd342e24aef49fc82a21bd4eedd3e665d177e5b", + "reference": "0bd342e24aef49fc82a21bd4eedd3e665d177e5b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v6.4.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-02-26T08:37:45+00:00" + }, + { + "name": "symfony/yaml", + "version": "v6.4.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "d75715985f0f94f978e3a8fa42533e10db921b90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/d75715985f0f94f978e3a8fa42533e10db921b90", + "reference": "d75715985f0f94f978e3a8fa42533e10db921b90", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0|^7.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v6.4.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-23T14:51:35+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + }, + { + "name": "tysonandre/var_representation_polyfill", + "version": "0.1.3", + "source": { + "type": "git", + "url": "https://github.com/TysonAndre/var_representation_polyfill.git", + "reference": "e9116c2c352bb0835ca428b442dde7767c11ad32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TysonAndre/var_representation_polyfill/zipball/e9116c2c352bb0835ca428b442dde7767c11ad32", + "reference": "e9116c2c352bb0835ca428b442dde7767c11ad32", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.2.0|^8.0.0" + }, + "provide": { + "ext-var_representation": "*" + }, + "require-dev": { + "phan/phan": "^5.4.1", + "phpunit/phpunit": "^8.5.0" + }, + "suggest": { + "ext-var_representation": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.1.3-dev" + } + }, + "autoload": { + "files": [ + "src/var_representation.php" + ], + "psr-4": { + "VarRepresentation\\": "src/VarRepresentation" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tyson Andre" + } + ], + "description": "Polyfill for var_representation: convert a variable to a string in a way that fixes the shortcomings of var_export", + "keywords": [ + "var_export", + "var_representation" + ], + "support": { + "issues": "https://github.com/TysonAndre/var_representation_polyfill/issues", + "source": "https://github.com/TysonAndre/var_representation_polyfill/tree/0.1.3" + }, + "time": "2022-08-31T12:59:22+00:00" + }, + { + "name": "ulrichsg/getopt-php", + "version": "v4.0.3", + "source": { + "type": "git", + "url": "https://github.com/getopt-php/getopt-php.git", + "reference": "91c31c9bc0ff9bbe22b0b02c63c7f5ddb8d2a8d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getopt-php/getopt-php/zipball/91c31c9bc0ff9bbe22b0b02c63c7f5ddb8d2a8d5", + "reference": "91c31c9bc0ff9bbe22b0b02c63c7f5ddb8d2a8d5", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "*", + "squizlabs/php_codesniffer": "^3.5.8", + "tflori/phpunit-printer": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "GetOpt\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ulrich Schmidt-Goertz", + "email": "ulrich@schmidt-goertz.de" + }, + { + "name": "Thomas Flori", + "email": "thflori@gmail.com" + } + ], + "description": "Command line arguments parser for PHP 7.1 and above", + "homepage": "http://getopt-php.github.io/getopt-php", + "support": { + "issues": "https://github.com/getopt-php/getopt-php/issues", + "source": "https://github.com/getopt-php/getopt-php/tree/v4.0.3" + }, + "time": "2022-12-13T20:37:45+00:00" + }, + { + "name": "vimeo/psalm", + "version": "5.22.2", + "source": { + "type": "git", + "url": "https://github.com/vimeo/psalm.git", + "reference": "d768d914152dbbf3486c36398802f74e80cfde48" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/d768d914152dbbf3486c36398802f74e80cfde48", + "reference": "d768d914152dbbf3486c36398802f74e80cfde48", + "shasum": "" + }, + "require": { + "amphp/amp": "^2.4.2", + "amphp/byte-stream": "^1.5", + "composer-runtime-api": "^2", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^2.0 || ^3.0", + "dnoegel/php-xdg-base-dir": "^0.1.1", + "ext-ctype": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "felixfbecker/advanced-json-rpc": "^3.1", + "felixfbecker/language-server-protocol": "^1.5.2", + "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "nikic/php-parser": "^4.16", + "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0", + "spatie/array-to-xml": "^2.17.0 || ^3.0", + "symfony/console": "^4.1.6 || ^5.0 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0" + }, + "conflict": { + "nikic/php-parser": "4.17.0" + }, + "provide": { + "psalm/psalm": "self.version" + }, + "require-dev": { + "amphp/phpunit-util": "^2.0", + "bamarni/composer-bin-plugin": "^1.4", + "brianium/paratest": "^6.9", + "ext-curl": "*", + "mockery/mockery": "^1.5", + "nunomaduro/mock-final-classes": "^1.1", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpdoc-parser": "^1.6", + "phpunit/phpunit": "^9.6", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.18", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.6", + "symfony/process": "^4.4 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "ext-curl": "In order to send data to shepherd", + "ext-igbinary": "^2.0.5 is required, used to serialize caching data" + }, + "bin": [ + "psalm", + "psalm-language-server", + "psalm-plugin", + "psalm-refactor", + "psalter" + ], + "type": "project", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev", + "dev-4.x": "4.x-dev", + "dev-3.x": "3.x-dev", + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psalm\\": "src/Psalm/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Brown" + } + ], + "description": "A static analysis tool for finding errors in PHP applications", + "keywords": [ + "code", + "inspection", + "php", + "static analysis" + ], + "support": { + "docs": "https://psalm.dev/docs", + "issues": "https://github.com/vimeo/psalm/issues", + "source": "https://github.com/vimeo/psalm" + }, + "time": "2024-02-22T23:39:07+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": { + "roave/security-advisories": 20 + }, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": "^8.1", + "ext-mbstring": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/schemas-examples/full.json b/schemas-examples/full.json index d34bd493..d447da37 100644 --- a/schemas-examples/full.json +++ b/schemas-examples/full.json @@ -29,7 +29,7 @@ "max" : 100, "min_length" : 1, "max_length" : 10, - "min_date" : '2000-01-02', + "min_date" : "2000-01-02", "max_date" : "now", "not_empty" : true, "only_capitalize" : true, diff --git a/tests/Blueprint/MiscTest.php b/tests/Blueprint/MiscTest.php index b8d699ee..1239ff4a 100644 --- a/tests/Blueprint/MiscTest.php +++ b/tests/Blueprint/MiscTest.php @@ -132,12 +132,26 @@ private function testCheckExampleInReadme( ); if ($type === 'php') { - $filepath = \implode("\n", ["```php", 'getSpoiler("Click to see {$title}", $tmpl); isFileContains($tmpl, PROJECT_ROOT . '/README.md'); } + + private function getSpoiler(string $title, string $body): string + { + return \implode("\n", [ + '
', + " {$title}", + '', + "{$body}", + '', + '
', + '', + ]); + } } diff --git a/tests/Blueprint/SchemaTest.php b/tests/Blueprint/SchemaTest.php index 10d46fe4..7fad60c5 100644 --- a/tests/Blueprint/SchemaTest.php +++ b/tests/Blueprint/SchemaTest.php @@ -54,28 +54,28 @@ public function testScvStruture(): void { $schemaEmpty = new Schema(self::SCHEMA_EXAMPLE_EMPTY); isSame([ - 'inherit' => null, - 'bom' => false, - 'delimiter' => ',', - 'quote_char' => '\\', - 'enclosure' => '"', - 'encoding' => 'utf-8', - 'header' => true, - 'strict_column_order' => false, - 'other_columns_possible' => false, + // 'inherit' => null, + 'header' => true, + 'delimiter' => ',', + 'quote_char' => '\\', + 'enclosure' => '"', + 'encoding' => 'utf-8', + 'bom' => false, + // 'strict_column_order' => false, + // 'other_columns_possible' => false, ], $schemaEmpty->getCsvStructure()->getArrayCopy()); $schemaFull = new Schema(self::SCHEMA_EXAMPLE_FULL); isSame([ - 'inherit' => 'alias_1', - 'bom' => false, - 'delimiter' => ',', - 'quote_char' => '\\', - 'enclosure' => '"', - 'encoding' => 'utf-8', - 'header' => true, - 'strict_column_order' => true, - 'other_columns_possible' => true, + // 'inherit' => 'alias_1', + 'header' => true, + 'delimiter' => ',', + 'quote_char' => '\\', + 'enclosure' => '"', + 'encoding' => 'utf-8', + 'bom' => false, + // 'strict_column_order' => true, + // 'other_columns_possible' => true, ], $schemaFull->getCsvStructure()->getArrayCopy()); } @@ -151,7 +151,7 @@ public function testGetUndefinedColumnByName(): void public function testGetColumnMinimal(): void { $schemaFull = new Schema(self::SCHEMA_EXAMPLE_FULL); - $column = $schemaFull->getColumn(0); + $column = $schemaFull->getColumn(0); isSame('', $column->getName()); isSame('', $column->getDescription()); @@ -170,7 +170,7 @@ public function testGetColumnMinimal(): void public function testGetColumnProps(): void { $schemaFull = new Schema(self::SCHEMA_EXAMPLE_FULL); - $column = $schemaFull->getColumn(1); + $column = $schemaFull->getColumn(1); isSame('General available options', $column->getName()); isSame('Some description', $column->getDescription()); @@ -189,7 +189,7 @@ public function testGetColumnProps(): void public function testGetColumnRules(): void { $schemaFull = new Schema(self::SCHEMA_EXAMPLE_FULL); - $column = $schemaFull->getColumn('Some String'); + $column = $schemaFull->getColumn('Some String'); isSame([ 'min_length' => 1, @@ -204,23 +204,23 @@ public function testGetColumnRules(): void public function testGetColumnAggregateRules(): void { $schemaFull = new Schema(self::SCHEMA_EXAMPLE_FULL); - $column = $schemaFull->getColumn(1); + $column = $schemaFull->getColumn(1); isSame([ - 'unique' => false, - 'sorted' => 'asc', - 'sorted_flag' => 'SORT_NATURAL', - 'count_min' => 1, - 'count_max' => 10, - 'count_empty_min' => 1, - 'count_empty_max' => 10, - 'count_filled_min' => 1, - 'count_filled_max' => 10, - 'custom_1' => [ + 'unique' => false, + 'sorted' => 'asc', + 'sorted_flag' => 'SORT_NATURAL', + 'count_min' => 1, + 'count_max' => 10, + 'count_empty_min' => 1, + 'count_empty_max' => 10, + 'count_filled_min' => 1, + 'count_filled_max' => 10, + 'custom_1' => [ 'class' => 'My\\Aggregate\\Rules1', 'args' => ['value'], ], - 'custom_2' => [ + 'custom_2' => [ 'class' => 'My\\Aggregate\\Rules2', 'args' => ['value1', 'value2'], ], From c3382b86d8aac0c55ae934a557374841177eaf5b Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Sun, 10 Mar 2024 20:21:14 +0400 Subject: [PATCH 08/25] Update readme: Add more descriptive titles for examples and file formats. Fix testCheckYmlSchemaExampleInReadme method to match updated title. Fix messages in MiscTest. --- README.md | 6 +++--- tests/Blueprint/MiscTest.php | 9 +++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 01266209..d61c88a7 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ composer require jbzoo/csv-blueprint ### Schema file examples
- Click to see YAML format + Click to see: YAML format (with comment) ```yml # It's a full example of the CSV schema file in YAML format. @@ -71,7 +71,7 @@ columns:
- Click to see PHP Array as file + Click to see: PHP Array as file ```php - Click to see JSON Format + Click to see: JSON Format ```json { diff --git a/tests/Blueprint/MiscTest.php b/tests/Blueprint/MiscTest.php index 1239ff4a..9be17231 100644 --- a/tests/Blueprint/MiscTest.php +++ b/tests/Blueprint/MiscTest.php @@ -97,7 +97,12 @@ public function testCsvStrutureDefaultValues(): void public function testCheckYmlSchemaExampleInReadme(): void { - $this->testCheckExampleInReadme(PROJECT_ROOT . '/schemas-examples/full.yml', 'yml', 'YAML format', 12); + $this->testCheckExampleInReadme( + PROJECT_ROOT . '/schemas-examples/full.yml', + 'yml', + 'YAML format (with comment)', + 12 + ); } public function testCheckPhpSchemaExampleInReadme(): void @@ -137,7 +142,7 @@ private function testCheckExampleInReadme( $tmpl = \implode("\n", ["```{$type}", $filepath, '```']); } - $tmpl = $this->getSpoiler("Click to see {$title}", $tmpl); + $tmpl = $this->getSpoiler("Click to see: {$title}", $tmpl); isFileContains($tmpl, PROJECT_ROOT . '/README.md'); } From 8dcaed9c9ebcd9e34263d4a4c089b5488bc42761 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Sun, 10 Mar 2024 20:45:51 +0400 Subject: [PATCH 09/25] Refactor CSV schema column rules' YAML and JSON representations --- README.md | 223 +++++++++++++++++---------------- schemas-examples/full.json | 42 +++---- schemas-examples/full.php | 44 ++++--- schemas-examples/full.yml | 60 +++++---- tests/Blueprint/MiscTest.php | 13 +- tests/Blueprint/SchemaTest.php | 30 ++--- 6 files changed, 219 insertions(+), 193 deletions(-) diff --git a/README.md b/README.md index d61c88a7..106935c2 100644 --- a/README.md +++ b/README.md @@ -33,104 +33,56 @@ csv_structure: # Here are default values. You can skip this section if you don't bom: false # If the file has a BOM (Byte Order Mark) at the beginning (Experimental) columns: - - name: "csv header name" - description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + - name: "csv_header_name" # Any custom name of the column in the CSV file (first row). Required if "csv_structure.header" is true. + description: "Lorem ipsum" # Optional. Description of the column. Not used in the validation process. rules: - allow_values: [ y, n, "" ] # Strict set of values that are allowed - date_format: Y-m-d # See: https://www.php.net/manual/en/datetime.format.php + # You can use the rules in any combination. Or not use any of them. + # They are grouped below simply for ease of navigation and reading. + # If you see the value for the rule is "true" - that's just an enable flag. + # In other cases, these are rule parameters. + + # General rules + not_empty: true # Value is not empty string. Ignore spaces. exact_value: Some string # Case-sensitive. Exact value for string in the column - is_bool: true # true|false, Case-insensitive - is_domain: true # Only domain name. Example: "example.com" - is_email: true # Only email format. Example: "user@example.com" - is_float: true # Check format only. Can be negative and positive. Dot as decimal separator - is_int: true # Check format only. Can be negative and positive. Without any separators - is_ip: true # Only IPv4. Example: "127.0.0.1" - is_latitude: true # Can be integer or float. Example: 50.123456 - is_longitude: true # Can be integer or float. Example: -89.123456 - is_url: true # Only URL format. Example: "https://example.com/page?query=string#anchor" - is_uuid4: true # Only UUID4 format. Example: "550e8400-e29b-41d4-a716-446655440000" - min: 10 # Can be integer or float, negative and positive - max: 100 # Can be integer or float, negative and positive + allow_values: [ y, n, "" ] # Strict set of values that are allowed. Case-sensitive. + + # Strings only + regex: /^[\d]{2}$/ # Any valid regex pattern. See https://www.php.net/manual/en/reference.pcre.pattern.syntax.php min_length: 1 # Integer only. Min length of the string with spaces max_length: 10 # Integer only. Max length of the string with spaces - min_date: "2000-01-02" # See examples https://www.php.net/manual/en/function.strtotime.php - max_date: now # See examples https://www.php.net/manual/en/function.strtotime.php - not_empty: true # Value is not empty string. Ignore spaces. - only_capitalize: true # String is only capitalized. Example: "Hello World" - only_lowercase: true # String is only lowercase. Example: "hello world" - only_uppercase: true # String is only capitalized. Example: "HELLO WORLD" only_trimed: true # Only trimed strings. Example: "Hello World" (not " Hello World ") - precision: 2 # Strict(!) number of digits after the decimal point - regex: /^[\d]{2}$/ # Any valid regex pattern. See https://www.php.net/manual/en/reference.pcre.pattern.syntax.php - cardinal_direction: true # Valid cardinal direction. Examples: "N", "S", "NE", "SE", "none", "" - usa_market_name: true # Check if the value is a valid USA market name. Example: "New York, NY" - -``` - -
- - -
- Click to see: PHP Array as file + only_lowercase: true # String is only lower-case. Example: "hello world" + only_uppercase: true # String is only upper-case. Example: "HELLO WORLD" + only_capitalize: true # String is only capitalized. Example: "Hello World" -```php - [ - 'header' => true, - 'delimiter' => ',', - 'quote_char' => '\\', - 'enclosure' => '"', - 'encoding' => 'utf-8', - 'bom' => false, - ], - 'columns' => [ - [ - 'name' => 'csv header name', - 'description' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', - 'rules' => [ - 'allow_values' => ['y', 'n', ''], - 'date_format' => 'Y-m-d', - 'exact_value' => 'Some string', - 'is_bool' => true, - 'is_domain' => true, - 'is_email' => true, - 'is_float' => true, - 'is_int' => true, - 'is_ip' => true, - 'is_latitude' => true, - 'is_longitude' => true, - 'is_url' => true, - 'is_uuid4' => true, - 'min' => 10, - 'max' => 100, - 'min_length' => 1, - 'max_length' => 10, - 'min_date' => '2000-01-02', - 'max_date' => 'now', - 'not_empty' => true, - 'only_capitalize' => true, - 'only_lowercase' => true, - 'only_uppercase' => true, - 'only_trimed' => true, - 'precision' => 2, - 'regex' => '/^[\\d]{2}$/', - 'cardinal_direction' => true, - 'usa_market_name' => true, - ], - ], - ], -]; + # Specific formats + is_bool: true # Allow only boolean values "true" and "false", case-insensitive + is_int: true # Check format only. Can be negative and positive. Without any separators + is_float: true # Check format only. Can be negative and positive. Dot as decimal separator + is_ip: true # Only IPv4. Example: "127.0.0.1" + is_url: true # Only URL format. Example: "https://example.com/page?query=string#anchor" + is_email: true # Only email format. Example: "user@example.com" + is_domain: true # Only domain name. Example: "example.com" + is_uuid4: true # Only UUID4 format. Example: "550e8400-e29b-41d4-a716-446655440000" + is_latitude: true # Can be integer or float. Example: 50.123456 + is_longitude: true # Can be integer or float. Example: -89.123456 + cardinal_direction: true # Valid cardinal direction. Examples: "N", "S", "NE", "SE", "none", "" + usa_market_name: true # Check if the value is a valid USA market name. Example: "New York, NY" ```
-
Click to see: JSON Format @@ -146,35 +98,35 @@ return [ }, "columns" : [ { - "name" : "csv header name", - "description" : "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + "name" : "csv_header_name", + "description" : "Lorem ipsum", "rules" : { + "not_empty" : true, + "exact_value" : "Some string", "allow_values" : ["y", "n", ""], + "regex" : "\/^[\\d]{2}$\/", + "min_length" : 1, + "max_length" : 10, + "only_trimed" : true, + "only_lowercase" : true, + "only_uppercase" : true, + "only_capitalize" : true, + "min" : 10, + "max" : 100.5, + "precision" : 2, "date_format" : "Y-m-d", - "exact_value" : "Some string", + "min_date" : "2000-01-02", + "max_date" : "+1 day", "is_bool" : true, - "is_domain" : true, - "is_email" : true, - "is_float" : true, "is_int" : true, + "is_float" : true, "is_ip" : true, - "is_latitude" : true, - "is_longitude" : true, "is_url" : true, + "is_email" : true, + "is_domain" : true, "is_uuid4" : true, - "min" : 10, - "max" : 100, - "min_length" : 1, - "max_length" : 10, - "min_date" : "2000-01-02", - "max_date" : "now", - "not_empty" : true, - "only_capitalize" : true, - "only_lowercase" : true, - "only_uppercase" : true, - "only_trimed" : true, - "precision" : 2, - "regex" : "\/^[\\d]{2}$\/", + "is_latitude" : true, + "is_longitude" : true, "cardinal_direction" : true, "usa_market_name" : true } @@ -187,6 +139,65 @@ return [
+
+ Click to see: PHP Array as file + +```php + [ + 'header' => true, + 'delimiter' => ',', + 'quote_char' => '\\', + 'enclosure' => '"', + 'encoding' => 'utf-8', + 'bom' => false, + ], + 'columns' => [ + [ + 'name' => 'csv_header_name', + 'description' => 'Lorem ipsum', + 'rules' => + [ + 'not_empty' => true, + 'exact_value' => 'Some string', + 'allow_values' => ['y', 'n', ''], + 'regex' => '/^[\\d]{2}$/', + 'min_length' => 1, + 'max_length' => 10, + 'only_trimed' => true, + 'only_lowercase' => true, + 'only_uppercase' => true, + 'only_capitalize' => true, + 'min' => 10, + 'max' => 100.5, + 'precision' => 2, + 'date_format' => 'Y-m-d', + 'min_date' => '2000-01-02', + 'max_date' => '+1 day', + 'is_bool' => true, + 'is_int' => true, + 'is_float' => true, + 'is_ip' => true, + 'is_url' => true, + 'is_email' => true, + 'is_domain' => true, + 'is_uuid4' => true, + 'is_latitude' => true, + 'is_longitude' => true, + 'cardinal_direction' => true, + 'usa_market_name' => true, + ], + ], + ], +]; + +``` + +
+ ## Unit tests and check code style ```sh diff --git a/schemas-examples/full.json b/schemas-examples/full.json index d447da37..0029a65e 100644 --- a/schemas-examples/full.json +++ b/schemas-examples/full.json @@ -9,35 +9,35 @@ }, "columns" : [ { - "name" : "csv header name", - "description" : "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + "name" : "csv_header_name", + "description" : "Lorem ipsum", "rules" : { + "not_empty" : true, + "exact_value" : "Some string", "allow_values" : ["y", "n", ""], + "regex" : "\/^[\\d]{2}$\/", + "min_length" : 1, + "max_length" : 10, + "only_trimed" : true, + "only_lowercase" : true, + "only_uppercase" : true, + "only_capitalize" : true, + "min" : 10, + "max" : 100.5, + "precision" : 2, "date_format" : "Y-m-d", - "exact_value" : "Some string", + "min_date" : "2000-01-02", + "max_date" : "+1 day", "is_bool" : true, - "is_domain" : true, - "is_email" : true, - "is_float" : true, "is_int" : true, + "is_float" : true, "is_ip" : true, - "is_latitude" : true, - "is_longitude" : true, "is_url" : true, + "is_email" : true, + "is_domain" : true, "is_uuid4" : true, - "min" : 10, - "max" : 100, - "min_length" : 1, - "max_length" : 10, - "min_date" : "2000-01-02", - "max_date" : "now", - "not_empty" : true, - "only_capitalize" : true, - "only_lowercase" : true, - "only_uppercase" : true, - "only_trimed" : true, - "precision" : 2, - "regex" : "\/^[\\d]{2}$\/", + "is_latitude" : true, + "is_longitude" : true, "cardinal_direction" : true, "usa_market_name" : true } diff --git a/schemas-examples/full.php b/schemas-examples/full.php index 48e01988..50029fed 100644 --- a/schemas-examples/full.php +++ b/schemas-examples/full.php @@ -14,8 +14,6 @@ declare(strict_types=1); -# It's a full example of the CSV schema file in PHP format. - return [ 'csv_structure' => [ 'header' => true, @@ -27,35 +25,35 @@ ], 'columns' => [ [ - 'name' => 'csv header name', - 'description' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'name' => 'csv_header_name', + 'description' => 'Lorem ipsum', 'rules' => [ + 'not_empty' => true, + 'exact_value' => 'Some string', 'allow_values' => ['y', 'n', ''], + 'regex' => '/^[\\d]{2}$/', + 'min_length' => 1, + 'max_length' => 10, + 'only_trimed' => true, + 'only_lowercase' => true, + 'only_uppercase' => true, + 'only_capitalize' => true, + 'min' => 10, + 'max' => 100.5, + 'precision' => 2, 'date_format' => 'Y-m-d', - 'exact_value' => 'Some string', + 'min_date' => '2000-01-02', + 'max_date' => '+1 day', 'is_bool' => true, - 'is_domain' => true, - 'is_email' => true, - 'is_float' => true, 'is_int' => true, + 'is_float' => true, 'is_ip' => true, - 'is_latitude' => true, - 'is_longitude' => true, 'is_url' => true, + 'is_email' => true, + 'is_domain' => true, 'is_uuid4' => true, - 'min' => 10, - 'max' => 100, - 'min_length' => 1, - 'max_length' => 10, - 'min_date' => '2000-01-02', - 'max_date' => 'now', - 'not_empty' => true, - 'only_capitalize' => true, - 'only_lowercase' => true, - 'only_uppercase' => true, - 'only_trimed' => true, - 'precision' => 2, - 'regex' => '/^[\\d]{2}$/', + 'is_latitude' => true, + 'is_longitude' => true, 'cardinal_direction' => true, 'usa_market_name' => true, ], diff --git a/schemas-examples/full.yml b/schemas-examples/full.yml index e1a54e88..5c1bd5e2 100644 --- a/schemas-examples/full.yml +++ b/schemas-examples/full.yml @@ -21,34 +21,48 @@ csv_structure: # Here are default values. You can skip this section if you don't bom: false # If the file has a BOM (Byte Order Mark) at the beginning (Experimental) columns: - - name: "csv header name" - description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + - name: "csv_header_name" # Any custom name of the column in the CSV file (first row). Required if "csv_structure.header" is true. + description: "Lorem ipsum" # Optional. Description of the column. Not used in the validation process. rules: - allow_values: [ y, n, "" ] # Strict set of values that are allowed - date_format: Y-m-d # See: https://www.php.net/manual/en/datetime.format.php + # You can use the rules in any combination. Or not use any of them. + # They are grouped below simply for ease of navigation and reading. + # If you see the value for the rule is "true" - that's just an enable flag. + # In other cases, these are rule parameters. + + # General rules + not_empty: true # Value is not empty string. Ignore spaces. exact_value: Some string # Case-sensitive. Exact value for string in the column - is_bool: true # true|false, Case-insensitive - is_domain: true # Only domain name. Example: "example.com" - is_email: true # Only email format. Example: "user@example.com" - is_float: true # Check format only. Can be negative and positive. Dot as decimal separator - is_int: true # Check format only. Can be negative and positive. Without any separators - is_ip: true # Only IPv4. Example: "127.0.0.1" - is_latitude: true # Can be integer or float. Example: 50.123456 - is_longitude: true # Can be integer or float. Example: -89.123456 - is_url: true # Only URL format. Example: "https://example.com/page?query=string#anchor" - is_uuid4: true # Only UUID4 format. Example: "550e8400-e29b-41d4-a716-446655440000" - min: 10 # Can be integer or float, negative and positive - max: 100 # Can be integer or float, negative and positive + allow_values: [ y, n, "" ] # Strict set of values that are allowed. Case-sensitive. + + # Strings only + regex: /^[\d]{2}$/ # Any valid regex pattern. See https://www.php.net/manual/en/reference.pcre.pattern.syntax.php min_length: 1 # Integer only. Min length of the string with spaces max_length: 10 # Integer only. Max length of the string with spaces - min_date: "2000-01-02" # See examples https://www.php.net/manual/en/function.strtotime.php - max_date: now # See examples https://www.php.net/manual/en/function.strtotime.php - not_empty: true # Value is not empty string. Ignore spaces. - only_capitalize: true # String is only capitalized. Example: "Hello World" - only_lowercase: true # String is only lowercase. Example: "hello world" - only_uppercase: true # String is only capitalized. Example: "HELLO WORLD" only_trimed: true # Only trimed strings. Example: "Hello World" (not " Hello World ") + only_lowercase: true # String is only lower-case. Example: "hello world" + only_uppercase: true # String is only upper-case. Example: "HELLO WORLD" + only_capitalize: true # String is only capitalized. Example: "Hello World" + + # Deciaml and integer numbers + min: 10 # Can be integer or float, negative and positive + max: 100.50 # Can be integer or float, negative and positive precision: 2 # Strict(!) number of digits after the decimal point - regex: /^[\d]{2}$/ # Any valid regex pattern. See https://www.php.net/manual/en/reference.pcre.pattern.syntax.php + + # Dates + date_format: Y-m-d # See: https://www.php.net/manual/en/datetime.format.php + min_date: "2000-01-02" # See examples https://www.php.net/manual/en/function.strtotime.php + max_date: "+1 day" # See examples https://www.php.net/manual/en/function.strtotime.php + + # Specific formats + is_bool: true # Allow only boolean values "true" and "false", case-insensitive + is_int: true # Check format only. Can be negative and positive. Without any separators + is_float: true # Check format only. Can be negative and positive. Dot as decimal separator + is_ip: true # Only IPv4. Example: "127.0.0.1" + is_url: true # Only URL format. Example: "https://example.com/page?query=string#anchor" + is_email: true # Only email format. Example: "user@example.com" + is_domain: true # Only domain name. Example: "example.com" + is_uuid4: true # Only UUID4 format. Example: "550e8400-e29b-41d4-a716-446655440000" + is_latitude: true # Can be integer or float. Example: 50.123456 + is_longitude: true # Can be integer or float. Example: -89.123456 cardinal_direction: true # Valid cardinal direction. Examples: "N", "S", "NE", "SE", "none", "" usa_market_name: true # Check if the value is a valid USA market name. Example: "New York, NY" diff --git a/tests/Blueprint/MiscTest.php b/tests/Blueprint/MiscTest.php index 9be17231..dd646243 100644 --- a/tests/Blueprint/MiscTest.php +++ b/tests/Blueprint/MiscTest.php @@ -18,7 +18,6 @@ use JBZoo\CsvBlueprint\Schema; use JBZoo\CsvBlueprint\Utils; -use JBZoo\Markdown\Markdown; use JBZoo\PHPUnit\PHPUnit; use Symfony\Component\Finder\Finder; @@ -67,7 +66,7 @@ public function testFullListOfRules(): void ->name('/\\.php$/'); foreach ($finder as $file) { - $ruleName = Utils::camelToKebabCase($file->getFilenameWithoutExtension()); + $ruleName = Utils::camelToKebabCase($file->getFilenameWithoutExtension()); $excludeRules = [ 'abstarct_rule', 'exception', @@ -101,7 +100,7 @@ public function testCheckYmlSchemaExampleInReadme(): void PROJECT_ROOT . '/schemas-examples/full.yml', 'yml', 'YAML format (with comment)', - 12 + 12, ); } @@ -121,6 +120,10 @@ public function testCompareExamplesWithOrig(): void $origYml = yml("{$basepath}.yml")->getArrayCopy(); + // To update examples ONLY!!! + // file_put_contents("{$basepath}.php", (string)phpArray($origYml)); + // file_put_contents("{$basepath}.json", (string)json($origYml)); + isSame($origYml, phpArray("{$basepath}.php")->getArrayCopy(), 'PHP config is invalid'); isSame($origYml, json("{$basepath}.json")->getArrayCopy(), 'JSON config is invalid'); } @@ -129,7 +132,7 @@ private function testCheckExampleInReadme( string $filepath, string $type, string $title, - int $skipFirstLines = 0 + int $skipFirstLines = 0, ): void { $filepath = \implode( "\n", @@ -137,7 +140,7 @@ private function testCheckExampleInReadme( ); if ($type === 'php') { - $tmpl = \implode("\n", ["```php", 'getColumn(0); + $column = $schemaFull->getColumn(0); isSame('', $column->getName()); isSame('', $column->getDescription()); @@ -170,7 +170,7 @@ public function testGetColumnMinimal(): void public function testGetColumnProps(): void { $schemaFull = new Schema(self::SCHEMA_EXAMPLE_FULL); - $column = $schemaFull->getColumn(1); + $column = $schemaFull->getColumn(1); isSame('General available options', $column->getName()); isSame('Some description', $column->getDescription()); @@ -189,7 +189,7 @@ public function testGetColumnProps(): void public function testGetColumnRules(): void { $schemaFull = new Schema(self::SCHEMA_EXAMPLE_FULL); - $column = $schemaFull->getColumn('Some String'); + $column = $schemaFull->getColumn('Some String'); isSame([ 'min_length' => 1, @@ -204,23 +204,23 @@ public function testGetColumnRules(): void public function testGetColumnAggregateRules(): void { $schemaFull = new Schema(self::SCHEMA_EXAMPLE_FULL); - $column = $schemaFull->getColumn(1); + $column = $schemaFull->getColumn(1); isSame([ - 'unique' => false, - 'sorted' => 'asc', - 'sorted_flag' => 'SORT_NATURAL', - 'count_min' => 1, - 'count_max' => 10, - 'count_empty_min' => 1, - 'count_empty_max' => 10, - 'count_filled_min' => 1, - 'count_filled_max' => 10, - 'custom_1' => [ + 'unique' => false, + 'sorted' => 'asc', + 'sorted_flag' => 'SORT_NATURAL', + 'count_min' => 1, + 'count_max' => 10, + 'count_empty_min' => 1, + 'count_empty_max' => 10, + 'count_filled_min' => 1, + 'count_filled_max' => 10, + 'custom_1' => [ 'class' => 'My\\Aggregate\\Rules1', 'args' => ['value'], ], - 'custom_2' => [ + 'custom_2' => [ 'class' => 'My\\Aggregate\\Rules2', 'args' => ['value1', 'value2'], ], From a7fc230d36781309783dfbd6fb526a58b25e484b Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Sun, 10 Mar 2024 21:19:03 +0400 Subject: [PATCH 10/25] Refactor CSV validation methods Refactored CSV validation methods to use ErrorSuite for better error handling and reporting. Updated validation logic for specific rules like min, max, regex, and format to provide more descriptive error messages. Updated the CsvFile's validate method to return an ErrorSuite. --- README.md | 63 +++++------ src/Csv/Column.php | 3 +- src/Csv/CsvFile.php | 60 +++++----- src/Validators/Error.php | 2 +- src/Validators/ErrorSuite.php | 92 +++++++++++++++ src/Validators/Ruleset.php | 6 +- src/Validators/Validator.php | 2 +- tests/Blueprint/ValidatorTest.php | 181 +++++++++--------------------- 8 files changed, 217 insertions(+), 192 deletions(-) create mode 100644 src/Validators/ErrorSuite.php diff --git a/README.md b/README.md index 106935c2..b2923dad 100644 --- a/README.md +++ b/README.md @@ -155,41 +155,40 @@ return [ 'encoding' => 'utf-8', 'bom' => false, ], - 'columns' => [ + 'columns' => [ [ 'name' => 'csv_header_name', 'description' => 'Lorem ipsum', - 'rules' => - [ - 'not_empty' => true, - 'exact_value' => 'Some string', - 'allow_values' => ['y', 'n', ''], - 'regex' => '/^[\\d]{2}$/', - 'min_length' => 1, - 'max_length' => 10, - 'only_trimed' => true, - 'only_lowercase' => true, - 'only_uppercase' => true, - 'only_capitalize' => true, - 'min' => 10, - 'max' => 100.5, - 'precision' => 2, - 'date_format' => 'Y-m-d', - 'min_date' => '2000-01-02', - 'max_date' => '+1 day', - 'is_bool' => true, - 'is_int' => true, - 'is_float' => true, - 'is_ip' => true, - 'is_url' => true, - 'is_email' => true, - 'is_domain' => true, - 'is_uuid4' => true, - 'is_latitude' => true, - 'is_longitude' => true, - 'cardinal_direction' => true, - 'usa_market_name' => true, - ], + 'rules' => [ + 'not_empty' => true, + 'exact_value' => 'Some string', + 'allow_values' => ['y', 'n', ''], + 'regex' => '/^[\\d]{2}$/', + 'min_length' => 1, + 'max_length' => 10, + 'only_trimed' => true, + 'only_lowercase' => true, + 'only_uppercase' => true, + 'only_capitalize' => true, + 'min' => 10, + 'max' => 100.5, + 'precision' => 2, + 'date_format' => 'Y-m-d', + 'min_date' => '2000-01-02', + 'max_date' => '+1 day', + 'is_bool' => true, + 'is_int' => true, + 'is_float' => true, + 'is_ip' => true, + 'is_url' => true, + 'is_email' => true, + 'is_domain' => true, + 'is_uuid4' => true, + 'is_latitude' => true, + 'is_longitude' => true, + 'cardinal_direction' => true, + 'usa_market_name' => true, + ], ], ], ]; diff --git a/src/Csv/Column.php b/src/Csv/Column.php index 9030801f..23c248a2 100644 --- a/src/Csv/Column.php +++ b/src/Csv/Column.php @@ -17,6 +17,7 @@ namespace JBZoo\CsvBlueprint\Csv; use JBZoo\CsvBlueprint\Utils; +use JBZoo\CsvBlueprint\Validators\ErrorSuite; use JBZoo\CsvBlueprint\Validators\Validator; use JBZoo\Data\Data; @@ -104,7 +105,7 @@ public function getInherit(): string return $this->column->getString('inherit', self::FALLBACK_VALUES['inherit']); } - public function validate(string $cellValue, int $line): array + public function validate(string $cellValue, int $line): ErrorSuite { return (new Validator($this))->validate($cellValue, $line); } diff --git a/src/Csv/CsvFile.php b/src/Csv/CsvFile.php index 95269ad1..b9b609bd 100644 --- a/src/Csv/CsvFile.php +++ b/src/Csv/CsvFile.php @@ -17,6 +17,8 @@ namespace JBZoo\CsvBlueprint\Csv; use JBZoo\CsvBlueprint\Schema; +use JBZoo\CsvBlueprint\Validators\Error; +use JBZoo\CsvBlueprint\Validators\ErrorSuite; use League\Csv\ByteSequence; use League\Csv\Reader as LeagueReader; use League\Csv\Statement; @@ -35,9 +37,9 @@ public function __construct(string $csvFilename, null|array|string $csvSchemaFil } $this->csvFilename = \realpath($csvFilename); - $this->schema = new Schema($csvSchemaFilenameOrArray); - $this->structure = $this->schema->getCsvStructure(); - $this->reader = $this->prepareReader(); + $this->schema = new Schema($csvSchemaFilenameOrArray); + $this->structure = $this->schema->getCsvStructure(); + $this->reader = $this->prepareReader(); } public function getCsvFilename(): string @@ -72,11 +74,15 @@ public function getRecordsChunk(int $offset = 0, int $limit = -1): \League\Csv\R return Statement::create(null, $offset, $limit)->process($this->reader, $this->getHeader()); } - public function validate(bool $quickStop = false): array + public function validate(bool $quickStop = false): ErrorSuite { - return $this->validateHeader() + - $this->validateEachCell($quickStop) + - $this->validateAggregateRules($quickStop); + $errors = new ErrorSuite(); + + $errors->addErrorSuit($this->validateHeader()) + ->addErrorSuit($this->validateEachCell($quickStop)) + ->addErrorSuit($this->validateAggregateRules($quickStop)); + + return $errors; } private function prepareReader(): LeagueReader @@ -105,27 +111,32 @@ private function prepareReader(): LeagueReader return $reader; } - private function validateHeader(): array + private function validateHeader(): ErrorSuite { + $errors = new ErrorSuite(); + if (!$this->getCsvStructure()->isHeader()) { - return []; + return $errors; } - $errorAcc = []; - foreach ($this->schema->getColumns() as $column) { if ($column->getName() === '') { - $errorAcc[] = "Property \"name\" is not defined for column id={$column->getId()} " . - "in schema: {$this->schema->getFilename()}"; + $error = new Error( + 'csv_structure.header', + "Property \"name\" is not defined in schema: {$this->schema->getFilename()}", + $column->getHumanName(), + ); + + $errors->addError($error); } } - return $errorAcc; + return $errors; } - private function validateEachCell(bool $quickStop = false): array + private function validateEachCell(bool $quickStop = false): ErrorSuite { - $errorAcc = []; + $errors = new ErrorSuite(); foreach ($this->getRecords() as $line => $record) { $columns = $this->schema->getColumnsMappedByHeader($this->getHeader()); @@ -135,25 +146,18 @@ private function validateEachCell(bool $quickStop = false): array continue; } - $errorAcc = $this->appendErrors($errorAcc, $column->validate($record[$column->getKey()], $line)); - if ($quickStop && \count($errorAcc) > 0) { + $errors->addErrorSuit($column->validate($record[$column->getKey()], $line)); + if ($quickStop && $errors->count() > 0) { return $errorAcc; } } } - return $errorAcc; + return $errors; } - private function validateAggregateRules(bool $quickStop = false): array + private function validateAggregateRules(bool $quickStop = false): ErrorSuite { - return []; - } - - private function appendErrors(array $errorAcc, array $newErrors): array - { - $errorAcc += \array_filter($newErrors); - - return $errorAcc; + return new ErrorSuite(); } } diff --git a/src/Validators/Error.php b/src/Validators/Error.php index 650cdb11..f36a8fe2 100644 --- a/src/Validators/Error.php +++ b/src/Validators/Error.php @@ -21,7 +21,7 @@ final class Error public function __construct( private string $ruleCode, private string $message, - private string $columnName, + private string $columnName = '', private int $line = 0, ) { } diff --git a/src/Validators/ErrorSuite.php b/src/Validators/ErrorSuite.php new file mode 100644 index 00000000..d27e37fa --- /dev/null +++ b/src/Validators/ErrorSuite.php @@ -0,0 +1,92 @@ +render(self::MODE_PLAIN_TEXT); + } + + public function render(string $mode = self::MODE_PLAIN_TEXT): string + { + if ($this->count() === 0) { + return ''; + } + + if ($mode === self::MODE_PLAIN_TEXT) { + return $this->renderPlainText(); + } + + throw new Exception('Unknown error render mode: ' . $mode); + } + + /** + * @return Error[] + */ + public function getErrors(): array + { + return $this->errors; + } + + public function addError(null|Error $error): self + { + if ($error === null) { + return $this; + } + + $this->errors[] = $error; + return $this; + } + + public function addErrorSuit(null|self $errorSuite): self + { + if ($errorSuite === null) { + return $this; + } + + $this->errors = \array_merge($this->getErrors(), $errorSuite->getErrors()); + return $this; + } + + private function renderPlainText(): string + { + $result = []; + + foreach ($this->errors as $error) { + $result[] = (string)$error; + } + + return \implode("\n", $result); + } + + public function count(): int + { + return \count($this->errors); + } + + public function get(int $index): ?Error + { + return $this->errors[$index] ?? null; + } +} diff --git a/src/Validators/Ruleset.php b/src/Validators/Ruleset.php index acaed831..be2745d2 100644 --- a/src/Validators/Ruleset.php +++ b/src/Validators/Ruleset.php @@ -48,12 +48,12 @@ public function createRule(string $ruleName, null|array|bool|float|int|string $o throw new Exception("Rule \"{$ruleName}\" not found. Expected class: {$classname}"); } - public function validate(?string $cellValue, int $line): array + public function validate(?string $cellValue, int $line): ErrorSuite { - $errors = []; + $errors = new ErrorSuite(); foreach ($this->rules as $rule) { - $errors[] = $rule->validate($cellValue, $line); + $errors->addError($rule->validate($cellValue, $line)); } return $errors; diff --git a/src/Validators/Validator.php b/src/Validators/Validator.php index 4ff3adbb..486f4f5f 100644 --- a/src/Validators/Validator.php +++ b/src/Validators/Validator.php @@ -31,7 +31,7 @@ public function __construct(Column $column) /** * @return Error[] */ - public function validate(?string $cellValue, int $line): array + public function validate(?string $cellValue, int $line): ErrorSuite { return $this->ruleset->validate($cellValue, $line); } diff --git a/tests/Blueprint/ValidatorTest.php b/tests/Blueprint/ValidatorTest.php index 1d3f2c00..fd2ef3b4 100644 --- a/tests/Blueprint/ValidatorTest.php +++ b/tests/Blueprint/ValidatorTest.php @@ -53,223 +53,228 @@ public function testUndefinedRule(): void public function testValidWithHeader(): void { $csv = new CsvFile(self::CSV_SIMPLE_HEADER, self::SCHEMA_SIMPLE_HEADER); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); } public function testValidWithoutHeader(): void { $csv = new CsvFile(self::CSV_SIMPLE_NO_HEADER, self::SCHEMA_SIMPLE_NO_HEADER); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); } public function testNotEmptyMessage(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'not_empty', true)); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('integer', 'not_empty', true)); isSame( 'Error "not_empty" at line 18, column "integer (0)". Value is empty.', - (string)$csv->validate()[0], + (string)$csv->validate(), ); } public function testNoName(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule(null, 'not_empty', true)); - isSame('Property "name" is not defined for column id=0 in schema: _custom_array_', (string)$csv->validate()[0]); + isSame( + 'Error "csv_structure.header" at line 0, column "(0)". ' . + 'Property "name" is not defined in schema: _custom_array_.', + (string)$csv->validate() + ); } public function testMin(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'min', -10)); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'min', 10)); - isSame('Error "min" at line 1, column "seq (0)". Value "1" is less than "10".', (string)$csv->validate()[0]); + isSame('Error "min" at line 1, column "seq (0)". Value "1" is less than "10".', + (string)$csv->validate()->get(0)); } public function testMax(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'max', 10000)); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'max', 10)); isSame( 'Error "max" at line 11, column "seq (0)". Value "11" is greater than "10".', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); } public function testRegex(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'regex', '.*')); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'regex', '^[a-zA-Z0-9]+$')); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'regex', '[a-z]')); isSame( 'Error "regex" at line 1, column "seq (0)". Value "1" does not match the pattern "/[a-z]/u".', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'regex', '/[a-z]/')); isSame( 'Error "regex" at line 1, column "seq (0)". Value "1" does not match the pattern "/[a-z]/".', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'regex', '/[a-z]/i')); isSame( 'Error "regex" at line 1, column "seq (0)". Value "1" does not match the pattern "/[a-z]/i".', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); } public function testMinLength(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'min_length', 1)); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'min_length', 1000)); isSame( 'Error "min_length" at line 1, column "seq (0)". Value "1" (legth: 1) is too short. Min length is 1000.', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); } public function testMaxLength(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'max_length', 10)); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'max_length', 1)); isSame( 'Error "max_length" at line 10, column "seq (0)". Value "10" (legth: 2) is too long. Max length is 1.', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); } public function testOnlyTrimed(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'only_trimed', true)); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('sentence', 'only_trimed', true)); isSame( 'Error "only_trimed" at line 13, column "sentence (0)". Value " Urecam" is not trimmed.', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); } public function testOnlyUppercase(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'only_uppercase', true)); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'only_uppercase', true)); isSame( 'Error "only_uppercase" at line 1, column "bool (0)". Value "true" is not uppercase.', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); } public function testOnlyLowercase(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'only_lowercase', true)); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'only_lowercase', true)); isSame( 'Error "only_lowercase" at line 7, column "bool (0)". Value "False" should be in lowercase.', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); } public function testOnlyCapitalize(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'only_capitalize', true)); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'only_capitalize', true)); isSame( 'Error "only_capitalize" at line 1, column "bool (0)". Value "true" should be in capitalize.', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); } public function testPrecision(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'precision', 0)); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'precision', 1)); isSame( 'Error "precision" at line 1, column "seq (0)". ' . 'Value "1" has a precision of 0 but should have a precision of 1.', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('float', 'precision', 3)); isSame( 'Error "precision" at line 2, column "float (0)". ' . 'Value "506847750940.2624" has a precision of 4 but should have a precision of 3.', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); } public function testMinDate(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'min_date', '2000-01-01')); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'min_date', '2120-01-01')); isSame( 'Error "min_date" at line 1, column "date (0)". ' . 'Value "2042/11/18" is less than the minimum date "2120-01-01T00:00:00.000+00:00".', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'min_date', '2042/11/17')); isSame( 'Error "min_date" at line 4, column "date (0)". ' . 'Value "2032/09/09" is less than the minimum date "2042-11-17T00:00:00.000+00:00".', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); } public function testMaxDate(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'max_date', '2200-01-01')); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'max_date', '2120-01-01')); isSame( 'Error "max_date" at line 22, column "date (0)". ' . 'Value "2120/02/01" is more than the maximum date "2120-01-01T00:00:00.000+00:00".', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'max_date', '2042/11/17')); isSame( 'Error "max_date" at line 1, column "date (0)". ' . 'Value "2042/11/18" is more than the maximum date "2042-11-17T00:00:00.000+00:00".', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); } public function testDateFormat(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'date_format', 'Y/m/d')); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'date_format', 'Y/m/d H:i:s')); isSame( 'Error "date_format" at line 1, column "date (0)". ' . 'Date format of value "2042/11/18" is not valid. Expected format: "Y/m/d H:i:s".', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); } @@ -283,155 +288,79 @@ public function testAllowValues(): void ['true', 'false', 'False', 'True'], ), ); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'allow_values', ['true', 'false'])); isSame( 'Error "allow_values" at line 7, column "bool (0)". ' . 'Value "False" is not allowed. Allowed values: ["true", "false"].', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); } public function testExactValue(): void { $csv = new CsvFile(self::CSV_SIMPLE_HEADER, $this->getRule('exact', 'exact_value', '1')); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_SIMPLE_HEADER, $this->getRule('exact', 'exact_value', '2')); isSame( 'Error "exact_value" at line 1, column "exact (0)". Value "1" is not strict equal to "2".', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'exact_value', 'true')); isSame( 'Error "exact_value" at line 3, column "bool (0)". Value "false" is not strict equal to "true".', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); } public function testIsInt(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'is_int', true)); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'is_int', true)); isSame( 'Error "is_int" at line 1, column "bool (0)". Value "true" is not an integer.', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); } public function testIsFloat(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'is_float', true)); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'is_float', true)); isSame( 'Error "is_float" at line 1, column "bool (0)". Value "true" is not a float number.', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); } public function testIsBool(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'is_bool', true)); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('yn', 'is_bool', true)); isSame( 'Error "is_bool" at line 1, column "yn (0)". Value "n" is not allowed. Allowed values: ["true", "false"].', - (string)$csv->validate()[0], + (string)$csv->validate()->get(0), ); } public function testIsEmail(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('email', 'is_email', true)); - isSame([], $csv->validate()); + isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('yn', 'is_email', true)); isSame( 'Error "is_email" at line 1, column "yn (0)". Value "N" is not a valid email.', - (string)$csv->validate()[0], - ); - } - - public function testIsEmailPure(): void - { - $rule = new IsEmail('prop', true); - isSame(null, $rule->validate('user@example.com')); - isSame( - 'Error "is_email" at line 0, column "prop". Value "example.com" is not a valid email.', - (string)$rule->validate('example.com'), - ); - } - - public function testIsDomain(): void - { - $rule = new IsDomain('prop', true); - isSame(null, $rule->validate('example.com')); - isSame(null, $rule->validate('sub.example.com')); - isSame(null, $rule->validate('sub.sub.example.com')); - isSame( - 'Error "is_domain" at line 0, column "prop". Value "123" is not a valid domain.', - (string)$rule->validate('123'), - ); - } - - public function testIsIp(): void - { - $rule = new IsIp('prop', true); - isSame(null, $rule->validate('127.0.0.1')); - isSame( - 'Error "is_ip" at line 0, column "prop". Value "123" is not a valid IP.', - (string)$rule->validate('123'), - ); - } - - public function testIsUrl(): void - { - $rule = new IsUrl('prop', true); - isSame(null, $rule->validate('http://example.com')); - isSame(null, $rule->validate('http://example.com/home-page')); - isSame(null, $rule->validate('ftp://example.com/home-page?param=value')); - isSame( - 'Error "is_url" at line 0, column "prop". Value "123" is not a valid URL.', - (string)$rule->validate('123'), - ); - } - - public function testIsLatitude(): void - { - $rule = new IsLatitude('prop', true); - isSame(null, $rule->validate('0')); - isSame(null, $rule->validate('90')); - isSame(null, $rule->validate('-90')); - isSame( - 'Error "is_latitude" at line 0, column "prop". Value "123" is not a valid latitude (-90 <= x <= 90).', - (string)$rule->validate('123'), - ); - isSame( - 'Error "is_latitude" at line 0, column "prop". Value "1.0.0.0" is not a float number.', - (string)$rule->validate('1.0.0.0'), - ); - } - - public function testIsLongitude(): void - { - $rule = new IsLongitude('prop', true); - isSame(null, $rule->validate('0')); - isSame(null, $rule->validate('180')); - isSame(null, $rule->validate('-180')); - isSame( - 'Error "is_longitude" at line 0, column "prop". Value "1230" is not a valid longitude (-180 <= x <= 180).', - (string)$rule->validate('1230'), - ); - isSame( - 'Error "is_longitude" at line 0, column "prop". Value "1.0.0.0" is not a float number.', - (string)$rule->validate('1.0.0.0'), + (string)$csv->validate()->get(0), ); } From ae75c7420460d80b7299f89bea8bb7e0d09e9c27 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Sun, 10 Mar 2024 21:19:19 +0400 Subject: [PATCH 11/25] Refactor CsvFile constructor and add additional methods to ErrorSuite - Refactor CsvFile constructor to improve readability by separating assignment statements for schema, structure, and reader. - Add new methods to ErrorSuite for counting errors and retrieving error by index. --- src/Csv/CsvFile.php | 6 +++--- src/Validators/ErrorSuite.php | 26 ++++++++++++++------------ tests/Blueprint/ValidatorTest.php | 14 +++++--------- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/Csv/CsvFile.php b/src/Csv/CsvFile.php index b9b609bd..f5615f2b 100644 --- a/src/Csv/CsvFile.php +++ b/src/Csv/CsvFile.php @@ -37,9 +37,9 @@ public function __construct(string $csvFilename, null|array|string $csvSchemaFil } $this->csvFilename = \realpath($csvFilename); - $this->schema = new Schema($csvSchemaFilenameOrArray); - $this->structure = $this->schema->getCsvStructure(); - $this->reader = $this->prepareReader(); + $this->schema = new Schema($csvSchemaFilenameOrArray); + $this->structure = $this->schema->getCsvStructure(); + $this->reader = $this->prepareReader(); } public function getCsvFilename(): string diff --git a/src/Validators/ErrorSuite.php b/src/Validators/ErrorSuite.php index d27e37fa..c8291e28 100644 --- a/src/Validators/ErrorSuite.php +++ b/src/Validators/ErrorSuite.php @@ -49,26 +49,38 @@ public function getErrors(): array return $this->errors; } - public function addError(null|Error $error): self + public function addError(?Error $error): self { if ($error === null) { return $this; } $this->errors[] = $error; + return $this; } - public function addErrorSuit(null|self $errorSuite): self + public function addErrorSuit(?self $errorSuite): self { if ($errorSuite === null) { return $this; } $this->errors = \array_merge($this->getErrors(), $errorSuite->getErrors()); + return $this; } + public function count(): int + { + return \count($this->errors); + } + + public function get(int $index): ?Error + { + return $this->errors[$index] ?? null; + } + private function renderPlainText(): string { $result = []; @@ -79,14 +91,4 @@ private function renderPlainText(): string return \implode("\n", $result); } - - public function count(): int - { - return \count($this->errors); - } - - public function get(int $index): ?Error - { - return $this->errors[$index] ?? null; - } } diff --git a/tests/Blueprint/ValidatorTest.php b/tests/Blueprint/ValidatorTest.php index fd2ef3b4..d0e99769 100644 --- a/tests/Blueprint/ValidatorTest.php +++ b/tests/Blueprint/ValidatorTest.php @@ -17,12 +17,6 @@ namespace JBZoo\PHPUnit\Blueprint; use JBZoo\CsvBlueprint\Csv\CsvFile; -use JBZoo\CsvBlueprint\Validators\Rules\IsDomain; -use JBZoo\CsvBlueprint\Validators\Rules\IsEmail; -use JBZoo\CsvBlueprint\Validators\Rules\IsIp; -use JBZoo\CsvBlueprint\Validators\Rules\IsLatitude; -use JBZoo\CsvBlueprint\Validators\Rules\IsLongitude; -use JBZoo\CsvBlueprint\Validators\Rules\IsUrl; use JBZoo\PHPUnit\PHPUnit; use function JBZoo\PHPUnit\isSame; @@ -80,7 +74,7 @@ public function testNoName(): void isSame( 'Error "csv_structure.header" at line 0, column "(0)". ' . 'Property "name" is not defined in schema: _custom_array_.', - (string)$csv->validate() + (string)$csv->validate(), ); } @@ -90,8 +84,10 @@ public function testMin(): void isSame('', (string)$csv->validate()); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'min', 10)); - isSame('Error "min" at line 1, column "seq (0)". Value "1" is less than "10".', - (string)$csv->validate()->get(0)); + isSame( + 'Error "min" at line 1, column "seq (0)". Value "1" is less than "10".', + (string)$csv->validate()->get(0), + ); } public function testMax(): void From bfcbcfa2d8ec544ebce8b28180667e5f9600d89c Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Sun, 10 Mar 2024 21:20:33 +0400 Subject: [PATCH 12/25] Add support for rendering errors as a plain list. --- src/Validators/ErrorSuite.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Validators/ErrorSuite.php b/src/Validators/ErrorSuite.php index c8291e28..e39024aa 100644 --- a/src/Validators/ErrorSuite.php +++ b/src/Validators/ErrorSuite.php @@ -19,6 +19,7 @@ final class ErrorSuite { public const MODE_PLAIN_TEXT = 'plain'; + public const MODE_PLAIN_LIST = 'list'; /** @var Error[] */ private array $errors = []; @@ -38,6 +39,10 @@ public function render(string $mode = self::MODE_PLAIN_TEXT): string return $this->renderPlainText(); } + if ($mode === self::MODE_PLAIN_LIST) { + return $this->renderList(); + } + throw new Exception('Unknown error render mode: ' . $mode); } @@ -91,4 +96,15 @@ private function renderPlainText(): string return \implode("\n", $result); } + + private function renderList(): string + { + $result = []; + + foreach ($this->errors as $error) { + $result[] = (string)$error; + } + + return ' * ' . \implode("\n * ", $result) . "\n"; + } } From e646c1b9c9ef01e352bc8481f915100e46ce567c Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Sun, 10 Mar 2024 21:21:41 +0400 Subject: [PATCH 13/25] Fix typo in file paths in MiscTest.php tests Fix typo in the file paths for schema examples in MiscTest.php tests to point to the correct file location. --- {schemas-examples => schema-examples}/full.json | 0 {schemas-examples => schema-examples}/full.php | 0 {schemas-examples => schema-examples}/full.yml | 0 tests/Blueprint/MiscTest.php | 12 ++++++------ 4 files changed, 6 insertions(+), 6 deletions(-) rename {schemas-examples => schema-examples}/full.json (100%) rename {schemas-examples => schema-examples}/full.php (100%) rename {schemas-examples => schema-examples}/full.yml (100%) diff --git a/schemas-examples/full.json b/schema-examples/full.json similarity index 100% rename from schemas-examples/full.json rename to schema-examples/full.json diff --git a/schemas-examples/full.php b/schema-examples/full.php similarity index 100% rename from schemas-examples/full.php rename to schema-examples/full.php diff --git a/schemas-examples/full.yml b/schema-examples/full.yml similarity index 100% rename from schemas-examples/full.yml rename to schema-examples/full.yml diff --git a/tests/Blueprint/MiscTest.php b/tests/Blueprint/MiscTest.php index dd646243..91b2d20a 100644 --- a/tests/Blueprint/MiscTest.php +++ b/tests/Blueprint/MiscTest.php @@ -54,7 +54,7 @@ public function testPrepareRegex(): void public function testFullListOfRules(): void { - $rulesInConfig = yml(PROJECT_ROOT . '/schemas-examples/full.yml')->findArray('columns.0.rules'); + $rulesInConfig = yml(PROJECT_ROOT . '/schema-examples/full.yml')->findArray('columns.0.rules'); $rulesInConfig = \array_keys($rulesInConfig); \sort($rulesInConfig); @@ -86,7 +86,7 @@ public function testFullListOfRules(): void public function testCsvStrutureDefaultValues(): void { - $defaultsInDoc = yml(PROJECT_ROOT . '/schemas-examples/full.yml')->findArray('csv_structure'); + $defaultsInDoc = yml(PROJECT_ROOT . '/schema-examples/full.yml')->findArray('csv_structure'); $schema = new Schema([]); $schema->getCsvStructure()->getArrayCopy(); @@ -97,7 +97,7 @@ public function testCsvStrutureDefaultValues(): void public function testCheckYmlSchemaExampleInReadme(): void { $this->testCheckExampleInReadme( - PROJECT_ROOT . '/schemas-examples/full.yml', + PROJECT_ROOT . '/schema-examples/full.yml', 'yml', 'YAML format (with comment)', 12, @@ -106,17 +106,17 @@ public function testCheckYmlSchemaExampleInReadme(): void public function testCheckPhpSchemaExampleInReadme(): void { - $this->testCheckExampleInReadme(PROJECT_ROOT . '/schemas-examples/full.php', 'php', 'PHP Array as file', 14); + $this->testCheckExampleInReadme(PROJECT_ROOT . '/schema-examples/full.php', 'php', 'PHP Array as file', 14); } public function testCheckJsonSchemaExampleInReadme(): void { - $this->testCheckExampleInReadme(PROJECT_ROOT . '/schemas-examples/full.json', 'json', 'JSON Format', 0); + $this->testCheckExampleInReadme(PROJECT_ROOT . '/schema-examples/full.json', 'json', 'JSON Format', 0); } public function testCompareExamplesWithOrig(): void { - $basepath = PROJECT_ROOT . '/schemas-examples/full'; + $basepath = PROJECT_ROOT . '/schema-examples/full'; $origYml = yml("{$basepath}.yml")->getArrayCopy(); From 8daa1fe2248e6e4408b0380dd30eddae0de3cc20 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Sun, 10 Mar 2024 22:57:25 +0400 Subject: [PATCH 14/25] Validate CSV file by applying schema rules. If errors are found, display error messages with details. If no errors are found, display "Looks good!" message. --- src/Commands/ValidateCsv.php | 92 ++++++++++++++++++++ src/Commands/ValidateFile.php | 52 ------------ src/Csv/CsvFile.php | 5 +- src/Validators/Error.php | 2 +- src/Validators/ErrorSuite.php | 14 ++-- tests/Blueprint/RulesTest.php | 134 +++++++++++++++--------------- tests/Blueprint/ValidatorTest.php | 56 ++++++------- tests/fixtures/demo.csv | 11 +++ tests/schemas/demo_invalid.yml | 42 ++++++++++ tests/schemas/demo_valid.yml | 40 +++++++++ 10 files changed, 293 insertions(+), 155 deletions(-) create mode 100644 src/Commands/ValidateCsv.php delete mode 100644 src/Commands/ValidateFile.php create mode 100644 tests/fixtures/demo.csv create mode 100644 tests/schemas/demo_invalid.yml create mode 100644 tests/schemas/demo_valid.yml diff --git a/src/Commands/ValidateCsv.php b/src/Commands/ValidateCsv.php new file mode 100644 index 00000000..7db44667 --- /dev/null +++ b/src/Commands/ValidateCsv.php @@ -0,0 +1,92 @@ +setName('validate:csv') + ->setDescription('Validate CSV file by rule') + ->addOption( + 'csv', + 'c', + InputOption::VALUE_REQUIRED, + 'CSV filepath to validate. If not set or empty, then the STDIN is used.', + ) + ->addOption( + 'schema', + 's', + InputOption::VALUE_REQUIRED, + 'Schema rule filepath', + ); + + parent::configure(); + } + + protected function executeAction(): int + { + $csvFilename = $this->getCsvFilepath(); + $schemaFilename = $this->getSchemaFilepath(); + + $csvFile = new CsvFile($csvFilename, $schemaFilename); + $errorSuite = $csvFile->validate(); + if ($errorSuite->count() > 0) { + $this->_($errorSuite->render(ErrorSuite::RENDER_TEXT), OutLvl::ERROR); + + throw new Exception('CSV file is not valid! Found ' . $errorSuite->count() . ' errors.'); + } + + $this->_('Looks good!'); + + return self::SUCCESS; + } + + private function getCsvFilepath(): string + { + $csvFilename = $this->getOptString('csv'); + + if (\file_exists($csvFilename) === false) { + throw new Exception("CSV file not found: {$csvFilename}"); + } + + $this->_('CSV : ' . \str_replace(PATH_ROOT, '.', \realpath($csvFilename))); + + return $csvFilename; + } + + private function getSchemaFilepath(): string + { + $schemaFilename = $this->getOptString('schema'); + + if (\file_exists($schemaFilename) === false) { + throw new Exception("Schema file not found: {$schemaFilename}"); + } + + $this->_('Schema : ' . \str_replace(PATH_ROOT, '.', \realpath($schemaFilename))); + + return $schemaFilename; + } +} diff --git a/src/Commands/ValidateFile.php b/src/Commands/ValidateFile.php deleted file mode 100644 index d9cf52a7..00000000 --- a/src/Commands/ValidateFile.php +++ /dev/null @@ -1,52 +0,0 @@ -setName('validate:file') - ->setDescription('Validate CSV file by rules from yml file(s)') - ->addArgument( - 'csv-file-or-dir', - InputArgument::REQUIRED, - 'Path to CSV file or directory with CSV files', - ) - ->addArgument( - 'rule-file-or-dir', - InputArgument::REQUIRED, - 'Path to rule file (yml) or directory with rule files', - ); - - parent::configure(); - } - - protected function executeAction(): int - { - $yml = '/Users/smetdenis/Work/projects/jbzoo-csv-validator/tests/rules/example.yml'; - dump(yml($yml)); - - return self::SUCCESS; - } -} diff --git a/src/Csv/CsvFile.php b/src/Csv/CsvFile.php index f5615f2b..97165b59 100644 --- a/src/Csv/CsvFile.php +++ b/src/Csv/CsvFile.php @@ -123,8 +123,9 @@ private function validateHeader(): ErrorSuite if ($column->getName() === '') { $error = new Error( 'csv_structure.header', - "Property \"name\" is not defined in schema: {$this->schema->getFilename()}", + "Property \"name\" is not defined in schema: \"{$this->schema->getFilename()}\"", $column->getHumanName(), + 1, ); $errors->addError($error); @@ -146,7 +147,7 @@ private function validateEachCell(bool $quickStop = false): ErrorSuite continue; } - $errors->addErrorSuit($column->validate($record[$column->getKey()], $line)); + $errors->addErrorSuit($column->validate($record[$column->getKey()], $line + 1)); if ($quickStop && $errors->count() > 0) { return $errorAcc; } diff --git a/src/Validators/Error.php b/src/Validators/Error.php index f36a8fe2..e87c7fce 100644 --- a/src/Validators/Error.php +++ b/src/Validators/Error.php @@ -28,7 +28,7 @@ public function __construct( public function __toString(): string { - return "Error \"{$this->getRuleCode()}\" at line {$this->getLine()}, " . + return "\"{$this->getRuleCode()}\" at line {$this->getLine()}, " . "column \"{$this->getColumnName()}\". {$this->getMessage()}."; } diff --git a/src/Validators/ErrorSuite.php b/src/Validators/ErrorSuite.php index e39024aa..115f2714 100644 --- a/src/Validators/ErrorSuite.php +++ b/src/Validators/ErrorSuite.php @@ -18,28 +18,28 @@ final class ErrorSuite { - public const MODE_PLAIN_TEXT = 'plain'; - public const MODE_PLAIN_LIST = 'list'; + public const RENDER_TEXT = 'plain'; + public const RENDER_LIST = 'list'; /** @var Error[] */ private array $errors = []; public function __toString(): string { - return $this->render(self::MODE_PLAIN_TEXT); + return $this->render(self::RENDER_TEXT); } - public function render(string $mode = self::MODE_PLAIN_TEXT): string + public function render(string $mode = self::RENDER_TEXT): string { if ($this->count() === 0) { return ''; } - if ($mode === self::MODE_PLAIN_TEXT) { + if ($mode === self::RENDER_TEXT) { return $this->renderPlainText(); } - if ($mode === self::MODE_PLAIN_LIST) { + if ($mode === self::RENDER_LIST) { return $this->renderList(); } @@ -105,6 +105,6 @@ private function renderList(): string $result[] = (string)$error; } - return ' * ' . \implode("\n * ", $result) . "\n"; + return ' * ' . \implode("\n * ", $result); } } diff --git a/tests/Blueprint/RulesTest.php b/tests/Blueprint/RulesTest.php index 7395115d..56f89de0 100644 --- a/tests/Blueprint/RulesTest.php +++ b/tests/Blueprint/RulesTest.php @@ -62,12 +62,12 @@ public function testAllowValues(): void isSame(null, $rule->validate('2')); isSame(null, $rule->validate('3')); isSame( - 'Error "allow_values" at line 0, column "prop". ' . + '"allow_values" at line 0, column "prop". ' . 'Value "" is not allowed. Allowed values: ["1", "2", "3"].', (string)$rule->validate(''), ); isSame( - 'Error "allow_values" at line 0, column "prop". ' . + '"allow_values" at line 0, column "prop". ' . 'Value "invalid" is not allowed. Allowed values: ["1", "2", "3"].', (string)$rule->validate('invalid'), ); @@ -84,12 +84,12 @@ public function testDateFormat(): void $rule = new DateFormat('prop', 'Y-m-d'); isSame(null, $rule->validate('2000-12-31')); isSame( - 'Error "date_format" at line 0, column "prop". ' . + '"date_format" at line 0, column "prop". ' . 'Date format of value "" is not valid. Expected format: "Y-m-d".', (string)$rule->validate(''), ); isSame( - 'Error "date_format" at line 0, column "prop". ' . + '"date_format" at line 0, column "prop". ' . 'Date format of value "2000-01-02 12:34:56" is not valid. Expected format: "Y-m-d".', (string)$rule->validate('2000-01-02 12:34:56'), ); @@ -100,11 +100,11 @@ public function testExactValue(): void $rule = new ExactValue('prop', '123'); isSame(null, $rule->validate('123')); isSame( - 'Error "exact_value" at line 0, column "prop". Value "" is not strict equal to "123".', + '"exact_value" at line 0, column "prop". Value "" is not strict equal to "123".', (string)$rule->validate(''), ); isSame( - 'Error "exact_value" at line 0, column "prop". Value "2000-01-02" is not strict equal to "123".', + '"exact_value" at line 0, column "prop". Value "2000-01-02" is not strict equal to "123".', (string)$rule->validate('2000-01-02'), ); } @@ -119,11 +119,11 @@ public function testIsBool(): void isSame(null, $rule->validate('True')); isSame(null, $rule->validate('False')); isSame( - 'Error "is_bool" at line 0, column "prop". Value "" is not allowed. Allowed values: ["true", "false"].', + '"is_bool" at line 0, column "prop". Value "" is not allowed. Allowed values: ["true", "false"].', (string)$rule->validate(''), ); isSame( - 'Error "is_bool" at line 0, column "prop". Value "1" is not allowed. Allowed values: ["true", "false"].', + '"is_bool" at line 0, column "prop". Value "1" is not allowed. Allowed values: ["true", "false"].', (string)$rule->validate('1'), ); } @@ -138,11 +138,11 @@ public function testIsDomain(): void isSame(null, $rule->validate('sub-sub-example.com')); isSame(null, $rule->validate('sub-sub-example.qwerty')); isSame( - 'Error "is_domain" at line 0, column "prop". Value "example" is not a valid domain.', + '"is_domain" at line 0, column "prop". Value "example" is not a valid domain.', (string)(string)$rule->validate('example'), ); isSame( - 'Error "is_domain" at line 0, column "prop". Value "" is not a valid domain.', + '"is_domain" at line 0, column "prop". Value "" is not a valid domain.', (string)$rule->validate(''), ); } @@ -153,7 +153,7 @@ public function testIsEmail(): void isSame(null, $rule->validate('user@example.com')); isSame(null, $rule->validate('user@sub.example.com')); isSame( - 'Error "is_email" at line 0, column "prop". Value "user:pass@example.com" is not a valid email.', + '"is_email" at line 0, column "prop". Value "user:pass@example.com" is not a valid email.', (string)(string)$rule->validate('user:pass@example.com'), ); } @@ -162,19 +162,21 @@ public function testIsFloat(): void { $rule = new IsFloat('prop', true); isSame(null, $rule->validate('1')); + isSame(null, $rule->validate('01')); isSame(null, $rule->validate('1.0')); + isSame(null, $rule->validate('01.0')); isSame(null, $rule->validate('-1')); isSame(null, $rule->validate('-1.0')); isSame( - 'Error "is_float" at line 0, column "prop". Value "1.000.000" is not a float number.', + '"is_float" at line 0, column "prop". Value "1.000.000" is not a float number.', (string)$rule->validate('1.000.000'), ); isSame( - 'Error "is_float" at line 0, column "prop". Value "" is not a float number.', + '"is_float" at line 0, column "prop". Value "" is not a float number.', (string)$rule->validate(''), ); isSame( - 'Error "is_float" at line 0, column "prop". Value " 1" is not a float number.', + '"is_float" at line 0, column "prop". Value " 1" is not a float number.', (string)$rule->validate(' 1'), ); } @@ -183,30 +185,32 @@ public function testIsInt(): void { $rule = new IsInt('prop', true); isSame(null, $rule->validate('1')); + isSame(null, $rule->validate('01')); isSame(null, $rule->validate('0')); + isSame(null, $rule->validate('00')); isSame(null, $rule->validate('-1')); isSame( - 'Error "is_int" at line 0, column "prop". Value "1.000.000" is not an integer.', + '"is_int" at line 0, column "prop". Value "1.000.000" is not an integer.', (string)$rule->validate('1.000.000'), ); isSame( - 'Error "is_int" at line 0, column "prop". Value "1.1" is not an integer.', + '"is_int" at line 0, column "prop". Value "1.1" is not an integer.', (string)(string)$rule->validate('1.1'), ); isSame( - 'Error "is_int" at line 0, column "prop". Value "1.0" is not an integer.', + '"is_int" at line 0, column "prop". Value "1.0" is not an integer.', (string)$rule->validate('1.0'), ); isSame( - 'Error "is_int" at line 0, column "prop". Value "" is not an integer.', + '"is_int" at line 0, column "prop". Value "" is not an integer.', (string)$rule->validate(''), ); isSame( - 'Error "is_int" at line 0, column "prop". Value " 1" is not an integer.', + '"is_int" at line 0, column "prop". Value " 1" is not an integer.', (string)(string)$rule->validate(' 1'), ); isSame( - 'Error "is_int" at line 0, column "prop". Value "1 " is not an integer.', + '"is_int" at line 0, column "prop". Value "1 " is not an integer.', (string)(string)$rule->validate('1 '), ); } @@ -217,7 +221,7 @@ public function testIsIp(): void isSame(null, $rule->validate('127.0.0.1')); isSame(null, $rule->validate('0.0.0.0')); isSame( - 'Error "is_ip" at line 0, column "prop". Value "1.2.3" is not a valid IP.', + '"is_ip" at line 0, column "prop". Value "1.2.3" is not a valid IP.', (string)$rule->validate('1.2.3'), ); } @@ -229,15 +233,15 @@ public function testIsLatitude(): void isSame(null, $rule->validate('90')); isSame(null, $rule->validate('-90')); isSame( - 'Error "is_latitude" at line 0, column "prop". Value "123" is not a valid latitude (-90 <= x <= 90).', + '"is_latitude" at line 0, column "prop". Value "123" is not a valid latitude (-90 <= x <= 90).', (string)$rule->validate('123'), ); isSame( - 'Error "is_latitude" at line 0, column "prop". Value "90.1" is not a valid latitude (-90 <= x <= 90).', + '"is_latitude" at line 0, column "prop". Value "90.1" is not a valid latitude (-90 <= x <= 90).', (string)$rule->validate('90.1'), ); isSame( - 'Error "is_latitude" at line 0, column "prop". Value "90.1.1.1.1" is not a float number.', + '"is_latitude" at line 0, column "prop". Value "90.1.1.1.1" is not a float number.', (string)$rule->validate('90.1.1.1.1'), ); } @@ -249,20 +253,20 @@ public function testIsLongitude(): void isSame(null, $rule->validate('180')); isSame(null, $rule->validate('-180')); isSame( - 'Error "is_longitude" at line 0, column "prop". Value "1230" is not a valid longitude (-180 <= x <= 180).', + '"is_longitude" at line 0, column "prop". Value "1230" is not a valid longitude (-180 <= x <= 180).', (string)$rule->validate('1230'), ); isSame( - 'Error "is_longitude" at line 0, column "prop". ' . + '"is_longitude" at line 0, column "prop". ' . 'Value "180.0001" is not a valid longitude (-180 <= x <= 180).', (string)$rule->validate('180.0001'), ); isSame( - 'Error "is_longitude" at line 0, column "prop". Value "-180.1" is not a valid longitude (-180 <= x <= 180).', + '"is_longitude" at line 0, column "prop". Value "-180.1" is not a valid longitude (-180 <= x <= 180).', (string)$rule->validate('-180.1'), ); isSame( - 'Error "is_longitude" at line 0, column "prop". Value "1.0.0.0" is not a float number.', + '"is_longitude" at line 0, column "prop". Value "1.0.0.0" is not a float number.', (string)$rule->validate('1.0.0.0'), ); } @@ -274,11 +278,11 @@ public function testIsUrl(): void isSame(null, $rule->validate('http://example.com/home-page')); isSame(null, $rule->validate('ftp://user:pass@example.com/home-page?param=value&v=asd#anchor')); isSame( - 'Error "is_url" at line 0, column "prop". Value "123" is not a valid URL.', + '"is_url" at line 0, column "prop". Value "123" is not a valid URL.', (string)$rule->validate('123'), ); isSame( - 'Error "is_url" at line 0, column "prop". Value "//example.com" is not a valid URL.', + '"is_url" at line 0, column "prop". Value "//example.com" is not a valid URL.', (string)$rule->validate('//example.com'), ); } @@ -289,11 +293,11 @@ public function testMin(): void isSame(null, $rule->validate('10')); isSame(null, $rule->validate('11')); isSame( - 'Error "min" at line 0, column "prop". Value "9" is less than "10".', + '"min" at line 0, column "prop". Value "9" is less than "10".', (string)$rule->validate('9'), ); isSame( - 'Error "min" at line 0, column "prop". Value "example.com" is not a float number.', + '"min" at line 0, column "prop". Value "example.com" is not a float number.', (string)$rule->validate('example.com'), ); @@ -301,11 +305,11 @@ public function testMin(): void isSame(null, $rule->validate('10.1')); isSame(null, $rule->validate('11')); isSame( - 'Error "min" at line 0, column "prop". Value "9" is less than "10.1".', + '"min" at line 0, column "prop". Value "9" is less than "10.1".', (string)$rule->validate('9'), ); isSame( - 'Error "min" at line 0, column "prop". Value "example.com" is not a float number.', + '"min" at line 0, column "prop". Value "example.com" is not a float number.', (string)$rule->validate('example.com'), ); } @@ -316,11 +320,11 @@ public function testMax(): void isSame(null, $rule->validate('9')); isSame(null, $rule->validate('10')); isSame( - 'Error "max" at line 0, column "prop". Value "123" is greater than "10".', + '"max" at line 0, column "prop". Value "123" is greater than "10".', (string)$rule->validate('123'), ); isSame( - 'Error "max" at line 0, column "prop". Value "example.com" is not a float number.', + '"max" at line 0, column "prop". Value "example.com" is not a float number.', (string)$rule->validate('example.com'), ); @@ -329,7 +333,7 @@ public function testMax(): void isSame(null, $rule->validate('10.0')); isSame(null, $rule->validate('10.1')); isSame( - 'Error "max" at line 0, column "prop". Value "10.2" is greater than "10.1".', + '"max" at line 0, column "prop". Value "10.2" is greater than "10.1".', (string)$rule->validate('10.2'), ); } @@ -339,7 +343,7 @@ public function testMinDate(): void $rule = new MinDate('prop', '2000-01-10'); isSame(null, $rule->validate('2000-01-10')); isSame( - 'Error "min_date" at line 0, column "prop". ' . + '"min_date" at line 0, column "prop". ' . 'Value "2000-01-09" is less than the minimum date "2000-01-10T00:00:00.000+00:00".', (string)$rule->validate('2000-01-09'), ); @@ -347,7 +351,7 @@ public function testMinDate(): void $rule = new MinDate('prop', '2000-01-10 00:00:00 +01:00'); isSame(null, $rule->validate('2000-01-10 00:00:00 +01:00')); isSame( - 'Error "min_date" at line 0, column "prop". ' . + '"min_date" at line 0, column "prop". ' . 'Value "2000-01-09 23:59:59 Europe/Berlin" is less than the minimum date "2000-01-10T00:00:00.000+01:00".', (string)$rule->validate('2000-01-09 23:59:59 Europe/Berlin'), ); @@ -361,7 +365,7 @@ public function testMaxDate(): void $rule = new MaxDate('prop', '2000-01-10'); isSame(null, $rule->validate('2000-01-09')); isSame( - 'Error "max_date" at line 0, column "prop". ' . + '"max_date" at line 0, column "prop". ' . 'Value "2000-01-11" is more than the maximum date "2000-01-10T00:00:00.000+00:00".', (string)$rule->validate('2000-01-11'), ); @@ -369,7 +373,7 @@ public function testMaxDate(): void $rule = new MaxDate('prop', '2000-01-10 00:00:00'); isSame(null, $rule->validate('2000-01-10 00:00:00')); isSame( - 'Error "max_date" at line 0, column "prop". ' . + '"max_date" at line 0, column "prop". ' . 'Value "2000-01-10 00:00:01" is more than the maximum date "2000-01-10T00:00:00.000+00:00".', (string)$rule->validate('2000-01-10 00:00:01'), ); @@ -385,15 +389,15 @@ public function testMinLength(): void isSame(null, $rule->validate(' ')); isSame(null, $rule->validate(' 1 ')); isSame( - 'Error "min_length" at line 0, column "prop". Value "1234" (legth: 4) is too short. Min length is 5.', + '"min_length" at line 0, column "prop". Value "1234" (legth: 4) is too short. Min length is 5.', (string)$rule->validate('1234'), ); isSame( - 'Error "min_length" at line 0, column "prop". Value "123 " (legth: 4) is too short. Min length is 5.', + '"min_length" at line 0, column "prop". Value "123 " (legth: 4) is too short. Min length is 5.', (string)$rule->validate('123 '), ); isSame( - 'Error "min_length" at line 0, column "prop". Value "" (legth: 0) is too short. Min length is 5.', + '"min_length" at line 0, column "prop". Value "" (legth: 0) is too short. Min length is 5.', (string)$rule->validate(''), ); } @@ -407,11 +411,11 @@ public function testMaxLength(): void isSame(null, $rule->validate(' ')); isSame(null, $rule->validate(' 1 ')); isSame( - 'Error "max_length" at line 0, column "prop". Value "123456" (legth: 6) is too long. Max length is 5.', + '"max_length" at line 0, column "prop". Value "123456" (legth: 6) is too long. Max length is 5.', (string)$rule->validate('123456'), ); isSame( - 'Error "max_length" at line 0, column "prop". Value "12345 " (legth: 6) is too long. Max length is 5.', + '"max_length" at line 0, column "prop". Value "12345 " (legth: 6) is too long. Max length is 5.', (string)$rule->validate('12345 '), ); } @@ -425,11 +429,11 @@ public function testNotEmpty(): void isSame(null, $rule->validate(' 0')); isSame(null, $rule->validate(' ')); isSame( - 'Error "not_empty" at line 0, column "prop". Value is empty.', + '"not_empty" at line 0, column "prop". Value is empty.', (string)$rule->validate(''), ); isSame( - 'Error "not_empty" at line 0, column "prop". Value is empty.', + '"not_empty" at line 0, column "prop". Value is empty.', (string)$rule->validate(null), ); } @@ -443,11 +447,11 @@ public function testOnlyCapitalize(): void isSame(null, $rule->validate(' Qwe Rty')); isSame(null, $rule->validate(' ')); isSame( - 'Error "only_capitalize" at line 0, column "prop". Value "qwerty" should be in capitalize.', + '"only_capitalize" at line 0, column "prop". Value "qwerty" should be in capitalize.', (string)$rule->validate('qwerty'), ); isSame( - 'Error "only_capitalize" at line 0, column "prop". Value "qwe Rty" should be in capitalize.', + '"only_capitalize" at line 0, column "prop". Value "qwe Rty" should be in capitalize.', (string)$rule->validate('qwe Rty'), ); } @@ -461,11 +465,11 @@ public function testOnlyLowercase(): void isSame(null, $rule->validate(' qwe rty')); isSame(null, $rule->validate(' ')); isSame( - 'Error "only_lowercase" at line 0, column "prop". Value "Qwerty" should be in lowercase.', + '"only_lowercase" at line 0, column "prop". Value "Qwerty" should be in lowercase.', (string)$rule->validate('Qwerty'), ); isSame( - 'Error "only_lowercase" at line 0, column "prop". Value "qwe Rty" should be in lowercase.', + '"only_lowercase" at line 0, column "prop". Value "qwe Rty" should be in lowercase.', (string)$rule->validate('qwe Rty'), ); } @@ -478,11 +482,11 @@ public function testOnlyUppercase(): void isSame(null, $rule->validate('QWE RTY')); isSame(null, $rule->validate(' ')); isSame( - 'Error "only_uppercase" at line 0, column "prop". Value "Qwerty" is not uppercase.', + '"only_uppercase" at line 0, column "prop". Value "Qwerty" is not uppercase.', (string)$rule->validate('Qwerty'), ); isSame( - 'Error "only_uppercase" at line 0, column "prop". Value "qwe Rty" is not uppercase.', + '"only_uppercase" at line 0, column "prop". Value "qwe Rty" is not uppercase.', (string)$rule->validate('qwe Rty'), ); } @@ -494,12 +498,12 @@ public function testPrecision(): void isSame(null, $rule->validate('10')); isSame(null, $rule->validate('-10')); isSame( - 'Error "precision" at line 0, column "prop". ' . + '"precision" at line 0, column "prop". ' . 'Value "1.1" has a precision of 1 but should have a precision of 0.', (string)$rule->validate('1.1'), ); isSame( - 'Error "precision" at line 0, column "prop". ' . + '"precision" at line 0, column "prop". ' . 'Value "1.0" has a precision of 1 but should have a precision of 0.', (string)$rule->validate('1.0'), ); @@ -509,12 +513,12 @@ public function testPrecision(): void isSame(null, $rule->validate('10.0')); isSame(null, $rule->validate('-10.0')); isSame( - 'Error "precision" at line 0, column "prop". ' . + '"precision" at line 0, column "prop". ' . 'Value "1" has a precision of 0 but should have a precision of 1.', (string)$rule->validate('1'), ); isSame( - 'Error "precision" at line 0, column "prop". ' . + '"precision" at line 0, column "prop". ' . 'Value "1.01" has a precision of 2 but should have a precision of 1.', (string)$rule->validate('1.01'), ); @@ -524,12 +528,12 @@ public function testPrecision(): void isSame(null, $rule->validate('10.00')); isSame(null, $rule->validate('-10.00')); isSame( - 'Error "precision" at line 0, column "prop". ' . + '"precision" at line 0, column "prop". ' . 'Value "2.0" has a precision of 1 but should have a precision of 2.', (string)$rule->validate('2.0'), ); isSame( - 'Error "precision" at line 0, column "prop". ' . + '"precision" at line 0, column "prop". ' . 'Value "1.000" has a precision of 3 but should have a precision of 2.', (string)$rule->validate('1.000'), ); @@ -542,7 +546,7 @@ public function testRegex(): void isSame(null, $rule->validate('aaa')); isSame(null, $rule->validate('a')); isSame( - 'Error "regex" at line 0, column "prop". Value "1bc" does not match the pattern "/^a/".', + '"regex" at line 0, column "prop". Value "1bc" does not match the pattern "/^a/".', (string)$rule->validate('1bc'), ); @@ -551,7 +555,7 @@ public function testRegex(): void isSame(null, $rule->validate('aaa')); isSame(null, $rule->validate('a')); isSame( - 'Error "regex" at line 0, column "prop". Value "1bc" does not match the pattern "/^a/u".', + '"regex" at line 0, column "prop". Value "1bc" does not match the pattern "/^a/u".', (string)$rule->validate('1bc'), ); } @@ -569,7 +573,7 @@ public function testUnitFacing(): void isSame(null, $rule->validate('SW')); isSame(null, $rule->validate('none')); isSame( - 'Error "cardinal_direction" at line 0, column "prop". Value "qwe" is not allowed. ' . + '"cardinal_direction" at line 0, column "prop". Value "qwe" is not allowed. ' . 'Allowed values: ["N", "S", "E", "W", "NE", "SE", "NW", "SW", "none", ""].', (string)$rule->validate('qwe'), ); @@ -581,7 +585,7 @@ public function testUsaMarketName(): void isSame(null, $rule->validate('New York, NY')); isSame(null, $rule->validate('City, ST')); isSame( - 'Error "usa_market_name" at line 0, column "prop". ' . + '"usa_market_name" at line 0, column "prop". ' . 'Invalid market name format for value ", ST". ' . 'Market name must have format "New York, NY".', (string)$rule->validate(', ST'), @@ -593,7 +597,7 @@ public function testIsUuid4(): void $rule = new IsUuid4('prop', true); isSame(null, $rule->validate(Str::uuid())); isSame( - 'Error "is_uuid4" at line 0, column "prop". Value is not a valid UUID v4.', + '"is_uuid4" at line 0, column "prop". Value is not a valid UUID v4.', (string)$rule->validate('123'), ); } diff --git a/tests/Blueprint/ValidatorTest.php b/tests/Blueprint/ValidatorTest.php index d0e99769..70101ef9 100644 --- a/tests/Blueprint/ValidatorTest.php +++ b/tests/Blueprint/ValidatorTest.php @@ -63,7 +63,7 @@ public function testNotEmptyMessage(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('integer', 'not_empty', true)); isSame( - 'Error "not_empty" at line 18, column "integer (0)". Value is empty.', + '"not_empty" at line 19, column "integer (0)". Value is empty.', (string)$csv->validate(), ); } @@ -72,8 +72,8 @@ public function testNoName(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule(null, 'not_empty', true)); isSame( - 'Error "csv_structure.header" at line 0, column "(0)". ' . - 'Property "name" is not defined in schema: _custom_array_.', + '"csv_structure.header" at line 1, column "(0)". ' . + 'Property "name" is not defined in schema: "_custom_array_".', (string)$csv->validate(), ); } @@ -85,7 +85,7 @@ public function testMin(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'min', 10)); isSame( - 'Error "min" at line 1, column "seq (0)". Value "1" is less than "10".', + '"min" at line 2, column "seq (0)". Value "1" is less than "10".', (string)$csv->validate()->get(0), ); } @@ -97,7 +97,7 @@ public function testMax(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'max', 10)); isSame( - 'Error "max" at line 11, column "seq (0)". Value "11" is greater than "10".', + '"max" at line 12, column "seq (0)". Value "11" is greater than "10".', (string)$csv->validate()->get(0), ); } @@ -112,19 +112,19 @@ public function testRegex(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'regex', '[a-z]')); isSame( - 'Error "regex" at line 1, column "seq (0)". Value "1" does not match the pattern "/[a-z]/u".', + '"regex" at line 2, column "seq (0)". Value "1" does not match the pattern "/[a-z]/u".', (string)$csv->validate()->get(0), ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'regex', '/[a-z]/')); isSame( - 'Error "regex" at line 1, column "seq (0)". Value "1" does not match the pattern "/[a-z]/".', + '"regex" at line 2, column "seq (0)". Value "1" does not match the pattern "/[a-z]/".', (string)$csv->validate()->get(0), ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'regex', '/[a-z]/i')); isSame( - 'Error "regex" at line 1, column "seq (0)". Value "1" does not match the pattern "/[a-z]/i".', + '"regex" at line 2, column "seq (0)". Value "1" does not match the pattern "/[a-z]/i".', (string)$csv->validate()->get(0), ); } @@ -136,7 +136,7 @@ public function testMinLength(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'min_length', 1000)); isSame( - 'Error "min_length" at line 1, column "seq (0)". Value "1" (legth: 1) is too short. Min length is 1000.', + '"min_length" at line 2, column "seq (0)". Value "1" (legth: 1) is too short. Min length is 1000.', (string)$csv->validate()->get(0), ); } @@ -148,7 +148,7 @@ public function testMaxLength(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'max_length', 1)); isSame( - 'Error "max_length" at line 10, column "seq (0)". Value "10" (legth: 2) is too long. Max length is 1.', + '"max_length" at line 11, column "seq (0)". Value "10" (legth: 2) is too long. Max length is 1.', (string)$csv->validate()->get(0), ); } @@ -160,7 +160,7 @@ public function testOnlyTrimed(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('sentence', 'only_trimed', true)); isSame( - 'Error "only_trimed" at line 13, column "sentence (0)". Value " Urecam" is not trimmed.', + '"only_trimed" at line 14, column "sentence (0)". Value " Urecam" is not trimmed.', (string)$csv->validate()->get(0), ); } @@ -172,7 +172,7 @@ public function testOnlyUppercase(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'only_uppercase', true)); isSame( - 'Error "only_uppercase" at line 1, column "bool (0)". Value "true" is not uppercase.', + '"only_uppercase" at line 2, column "bool (0)". Value "true" is not uppercase.', (string)$csv->validate()->get(0), ); } @@ -184,7 +184,7 @@ public function testOnlyLowercase(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'only_lowercase', true)); isSame( - 'Error "only_lowercase" at line 7, column "bool (0)". Value "False" should be in lowercase.', + '"only_lowercase" at line 8, column "bool (0)". Value "False" should be in lowercase.', (string)$csv->validate()->get(0), ); } @@ -196,7 +196,7 @@ public function testOnlyCapitalize(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'only_capitalize', true)); isSame( - 'Error "only_capitalize" at line 1, column "bool (0)". Value "true" should be in capitalize.', + '"only_capitalize" at line 2, column "bool (0)". Value "true" should be in capitalize.', (string)$csv->validate()->get(0), ); } @@ -208,14 +208,14 @@ public function testPrecision(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'precision', 1)); isSame( - 'Error "precision" at line 1, column "seq (0)". ' . + '"precision" at line 2, column "seq (0)". ' . 'Value "1" has a precision of 0 but should have a precision of 1.', (string)$csv->validate()->get(0), ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('float', 'precision', 3)); isSame( - 'Error "precision" at line 2, column "float (0)". ' . + '"precision" at line 3, column "float (0)". ' . 'Value "506847750940.2624" has a precision of 4 but should have a precision of 3.', (string)$csv->validate()->get(0), ); @@ -228,14 +228,14 @@ public function testMinDate(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'min_date', '2120-01-01')); isSame( - 'Error "min_date" at line 1, column "date (0)". ' . + '"min_date" at line 2, column "date (0)". ' . 'Value "2042/11/18" is less than the minimum date "2120-01-01T00:00:00.000+00:00".', (string)$csv->validate()->get(0), ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'min_date', '2042/11/17')); isSame( - 'Error "min_date" at line 4, column "date (0)". ' . + '"min_date" at line 5, column "date (0)". ' . 'Value "2032/09/09" is less than the minimum date "2042-11-17T00:00:00.000+00:00".', (string)$csv->validate()->get(0), ); @@ -248,14 +248,14 @@ public function testMaxDate(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'max_date', '2120-01-01')); isSame( - 'Error "max_date" at line 22, column "date (0)". ' . + '"max_date" at line 23, column "date (0)". ' . 'Value "2120/02/01" is more than the maximum date "2120-01-01T00:00:00.000+00:00".', (string)$csv->validate()->get(0), ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'max_date', '2042/11/17')); isSame( - 'Error "max_date" at line 1, column "date (0)". ' . + '"max_date" at line 2, column "date (0)". ' . 'Value "2042/11/18" is more than the maximum date "2042-11-17T00:00:00.000+00:00".', (string)$csv->validate()->get(0), ); @@ -268,7 +268,7 @@ public function testDateFormat(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'date_format', 'Y/m/d H:i:s')); isSame( - 'Error "date_format" at line 1, column "date (0)". ' . + '"date_format" at line 2, column "date (0)". ' . 'Date format of value "2042/11/18" is not valid. Expected format: "Y/m/d H:i:s".', (string)$csv->validate()->get(0), ); @@ -288,7 +288,7 @@ public function testAllowValues(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'allow_values', ['true', 'false'])); isSame( - 'Error "allow_values" at line 7, column "bool (0)". ' . + '"allow_values" at line 8, column "bool (0)". ' . 'Value "False" is not allowed. Allowed values: ["true", "false"].', (string)$csv->validate()->get(0), ); @@ -301,13 +301,13 @@ public function testExactValue(): void $csv = new CsvFile(self::CSV_SIMPLE_HEADER, $this->getRule('exact', 'exact_value', '2')); isSame( - 'Error "exact_value" at line 1, column "exact (0)". Value "1" is not strict equal to "2".', + '"exact_value" at line 2, column "exact (0)". Value "1" is not strict equal to "2".', (string)$csv->validate()->get(0), ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'exact_value', 'true')); isSame( - 'Error "exact_value" at line 3, column "bool (0)". Value "false" is not strict equal to "true".', + '"exact_value" at line 4, column "bool (0)". Value "false" is not strict equal to "true".', (string)$csv->validate()->get(0), ); } @@ -319,7 +319,7 @@ public function testIsInt(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'is_int', true)); isSame( - 'Error "is_int" at line 1, column "bool (0)". Value "true" is not an integer.', + '"is_int" at line 2, column "bool (0)". Value "true" is not an integer.', (string)$csv->validate()->get(0), ); } @@ -331,7 +331,7 @@ public function testIsFloat(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'is_float', true)); isSame( - 'Error "is_float" at line 1, column "bool (0)". Value "true" is not a float number.', + '"is_float" at line 2, column "bool (0)". Value "true" is not a float number.', (string)$csv->validate()->get(0), ); } @@ -343,7 +343,7 @@ public function testIsBool(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('yn', 'is_bool', true)); isSame( - 'Error "is_bool" at line 1, column "yn (0)". Value "n" is not allowed. Allowed values: ["true", "false"].', + '"is_bool" at line 2, column "yn (0)". Value "n" is not allowed. Allowed values: ["true", "false"].', (string)$csv->validate()->get(0), ); } @@ -355,7 +355,7 @@ public function testIsEmail(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('yn', 'is_email', true)); isSame( - 'Error "is_email" at line 1, column "yn (0)". Value "N" is not a valid email.', + '"is_email" at line 2, column "yn (0)". Value "N" is not a valid email.', (string)$csv->validate()->get(0), ); } diff --git a/tests/fixtures/demo.csv b/tests/fixtures/demo.csv new file mode 100644 index 00000000..451ae8a8 --- /dev/null +++ b/tests/fixtures/demo.csv @@ -0,0 +1,11 @@ +Name,City,Float,Birthday,Favorite color +Clyde,Rivsikgo,4825.1856,2000-01-01,red +Elsie,Vonavka,03.6544,2000-12-01,green +Derek,Sarefunaw,-177.9088,2000-01-31,green +Dylan,Wufolu,74605.944,1998-02-28,blue +Carl,Gorriju,0.8431,1955-05-14,red +Landon,Mojebol,123.64,1989-05-15,red +Olive,Pebiogu,0,1955-05-14,green +Willie,Sowaah,0.001,2010-07-20,red +Derrick,Rakufag,42,1990-09-10,green +Lois,Mofninle,-19366059127.6032,1988-08-24,blue diff --git a/tests/schemas/demo_invalid.yml b/tests/schemas/demo_invalid.yml new file mode 100644 index 00000000..6a39d179 --- /dev/null +++ b/tests/schemas/demo_invalid.yml @@ -0,0 +1,42 @@ +# +# JBZoo Toolbox - Csv-Blueprint. +# +# This file is part of the JBZoo Toolbox project. +# For the full copyright and license information, please view the LICENSE +# file that was distributed with this source code. +# +# @license MIT +# @copyright Copyright (C) JBZoo.com, All rights reserved. +# @see https://github.com/JBZoo/Csv-Blueprint +# + +columns: + - name: Name + rules: + not_empty: true + min_length: 5 + max_length: 7 + + - name: + rules: + not_empty: true + only_capitalize: true + + - name: Float + rules: + not_empty: true + is_float: true + min: -19366059128 + max: 74605 + + - name: Birthday + rules: + not_empty: true + date_format: Y-m-d + min_date: "1955-05-15" + max_date: "2009-01-01" + + - name: Favorite color + rules: + not_empty: true + allow_values: [ red, green, Blue ] diff --git a/tests/schemas/demo_valid.yml b/tests/schemas/demo_valid.yml new file mode 100644 index 00000000..a90fea5d --- /dev/null +++ b/tests/schemas/demo_valid.yml @@ -0,0 +1,40 @@ +# +# JBZoo Toolbox - Csv-Blueprint. +# +# This file is part of the JBZoo Toolbox project. +# For the full copyright and license information, please view the LICENSE +# file that was distributed with this source code. +# +# @license MIT +# @copyright Copyright (C) JBZoo.com, All rights reserved. +# @see https://github.com/JBZoo/Csv-Blueprint +# + +columns: + - name: Name + rules: + not_empty: true + min_length: 4 + max_length: 7 + + - name: City + rules: + not_empty: true + only_capitalize: true + + - name: Float + rules: + not_empty: true + is_float: true + min: -19366059128 + max: 74606 + + - name: Birthday + rules: + not_empty: true + date_format: Y-m-d + + - name: Favorite color + rules: + not_empty: true + allow_values: [ red, green, blue ] From 94179dc1b58b619d2d647686048d5aa474d7123a Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Mon, 11 Mar 2024 00:36:30 +0400 Subject: [PATCH 15/25] Refactor workflows to focus on demos Renamed workflow from "CI" to "Demo" to emphasize its focus on demonstrating features and use cases. Removed outdated or unused configuration options from the workflow file. Cleaned up commands and added a new command to run demo for GitHub. Updated README, composer.json, and other related files to reflect the changes being made to the workflow. --- .github/workflows/demo.yml | 50 +++++++ Makefile | 26 ++++ README.md | 10 +- composer.json | 7 +- composer.lock | 204 ++++++++++++++++++++--------- src/Commands/ValidateCsv.php | 48 ++++++- src/Csv/Column.php | 10 +- src/Csv/CsvFile.php | 6 +- src/Schema.php | 2 +- src/Validators/ErrorSuite.php | 84 ++++++++++-- tests/Blueprint/MiscTest.php | 2 +- tests/Blueprint/ValidatorTest.php | 2 +- tests/schemas/example_full.yml | 4 +- tests/schemas/simple_no_header.yml | 2 +- 14 files changed, 358 insertions(+), 99 deletions(-) create mode 100644 .github/workflows/demo.yml diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml new file mode 100644 index 00000000..03748da2 --- /dev/null +++ b/.github/workflows/demo.yml @@ -0,0 +1,50 @@ +# +# JBZoo Toolbox - Csv-Blueprint. +# +# This file is part of the JBZoo Toolbox project. +# For the full copyright and license information, please view the LICENSE +# file that was distributed with this source code. +# +# @license MIT +# @copyright Copyright (C) JBZoo.com, All rights reserved. +# @see https://github.com/JBZoo/Csv-Blueprint +# + +name: Demo + +on: + pull_request: + branches: + - "*" + push: + branches: + - 'master' + +env: + COLUMNS: 120 + TERM_PROGRAM: Hyper + +jobs: + pure-php: + name: Pure PHP + runs-on: ubuntu-latest + strategy: + matrix: + php-version: [ 8.1, 8.2, 8.3 ] + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + tools: composer + + - name: Build the Project + run: make update --no-print-directory + + - name: ๐Ÿงช PHPUnit Tests + run: make demo-github diff --git a/Makefile b/Makefile index 0cbdad5b..00893b46 100644 --- a/Makefile +++ b/Makefile @@ -25,3 +25,29 @@ update: ##@Project Install/Update all 3rd party dependencies test-all: ##@Project Run all project tests at once @make test @make codestyle + + +demo-valid: ##@Project Run demo valid CSV + $(call title,"Demo - Valid CSV") + @${PHP_BIN} ./csv-blueprint validate:csv \ + --csv=./tests/fixtures/demo.csv \ + --schema=./tests/schemas/demo_valid.yml + + +demo-invalid: ##@Project Run demo invalid CSV + $(call title,"Demo - Invalid CSV") + @${PHP_BIN} ./csv-blueprint validate:csv \ + --csv=./tests/fixtures/demo.csv \ + --schema=./tests/schemas/demo_invalid.yml \ + --output=github + +demo-gihub: ##@Project Run demo invalid CSV + @${PHP_BIN} ./csv-blueprint validate:csv \ + --csv=./tests/fixtures/demo.csv \ + --schema=./tests/schemas/demo_invalid.yml \ + --output=github + + +demo: ##@Project Run all demo commands + @make demo-valid + @make demo-invalid diff --git a/README.md b/README.md index b2923dad..2a7cbfe1 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ composer require jbzoo/csv-blueprint ```yml # It's a full example of the CSV schema file in YAML format. -csv_structure: # Here are default values. You can skip this section if you don't need to override the default values +csv: # Here are default values. You can skip this section if you don't need to override the default values header: true # If the first row is a header. If true, name of each column is required delimiter: , # Delimiter character in CSV file quote_char: \ # Quote character in CSV file @@ -33,7 +33,7 @@ csv_structure: # Here are default values. You can skip this section if you don't bom: false # If the file has a BOM (Byte Order Mark) at the beginning (Experimental) columns: - - name: "csv_header_name" # Any custom name of the column in the CSV file (first row). Required if "csv_structure.header" is true. + - name: "csv_header_name" # Any custom name of the column in the CSV file (first row). Required if "csv.header" is true. description: "Lorem ipsum" # Optional. Description of the column. Not used in the validation process. rules: # You can use the rules in any combination. Or not use any of them. @@ -88,7 +88,7 @@ columns: ```json { - "csv_structure" : { + "csv" : { "header" : true, "delimiter" : ",", "quote_char" : "\\", @@ -96,7 +96,7 @@ columns: "encoding" : "utf-8", "bom" : false }, - "columns" : [ + "columns" : [ { "name" : "csv_header_name", "description" : "Lorem ipsum", @@ -147,7 +147,7 @@ columns: declare(strict_types=1); return [ - 'csv_structure' => [ + 'csv' => [ 'header' => true, 'delimiter' => ',', 'quote_char' => '\\', diff --git a/composer.json b/composer.json index f944a76a..b78cd5e6 100644 --- a/composer.json +++ b/composer.json @@ -28,17 +28,18 @@ "require" : { "php" : "^8.1", "ext-mbstring" : "*", + "jbzoo/data" : "^7.1", "jbzoo/cli" : "^7.1", + "jbzoo/utils" : "^7.1", "league/csv" : "^9.15", "fakerphp/faker" : "^1.23", - "jbzoo/utils" : "^7.1" + "jbzoo/ci-report-converter": "^7.2" }, "require-dev" : { "roave/security-advisories" : "dev-latest", - "jbzoo/toolbox-dev" : "^7.1", - "jbzoo/markdown": "^7.0" + "jbzoo/toolbox-dev" : "^7.1" }, "autoload" : { diff --git a/composer.lock b/composer.lock index 1e70afbf..e87cb2a4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "34ca1de55f9be82342e8922b281d6923", + "content-hash": "661fc4e2593d0d9598e69e2de68aca8f", "packages": [ { "name": "bluepsyduck/symfony-process-manager", @@ -126,6 +126,92 @@ }, "time": "2024-01-02T13:46:09+00:00" }, + { + "name": "jbzoo/ci-report-converter", + "version": "7.2.1", + "source": { + "type": "git", + "url": "https://github.com/JBZoo/CI-Report-Converter.git", + "reference": "b411b01d673f2d70cf8edf8693352c6ff831bd8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JBZoo/CI-Report-Converter/zipball/b411b01d673f2d70cf8edf8693352c6ff831bd8c", + "reference": "b411b01d673f2d70cf8edf8693352c6ff831bd8c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-hash": "*", + "ext-simplexml": "*", + "jbzoo/cli": "^7.1.8", + "jbzoo/data": "^7.1", + "jbzoo/markdown": "^7.0", + "jbzoo/utils": "^7.1", + "php": "^8.1", + "symfony/console": ">=6.4" + }, + "require-dev": { + "jbzoo/mermaid-php": "^7.2", + "jbzoo/toolbox-dev": "^7.1", + "roave/security-advisories": "dev-master" + }, + "bin": [ + "ci-report-converter" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "JBZoo\\CIReportConverter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Denis Smetannikov", + "email": "admin@jbzoo.com", + "role": "lead" + } + ], + "description": "The tool converts different error reporting standards for deep integration with popular CI systems (TeamCity, IntelliJ IDEA, GitHub Actions, etc)", + "keywords": [ + "Codestyle", + "Github Actions", + "IntelliJ IDEA", + "PHPStan", + "actions", + "checkstyle", + "ci", + "continuous integration", + "github", + "inspections", + "junit", + "phan", + "phpcs", + "phploc", + "phpmd", + "phpmnd", + "phpstorm", + "pmd", + "psalm", + "teamcity", + "teamcity-inspections", + "tests" + ], + "support": { + "issues": "https://github.com/JBZoo/CI-Report-Converter/issues", + "source": "https://github.com/JBZoo/CI-Report-Converter/tree/7.2.1" + }, + "time": "2024-01-28T14:03:00+00:00" + }, { "name": "jbzoo/cli", "version": "7.1.8", @@ -344,6 +430,63 @@ }, "time": "2024-01-28T08:57:37+00:00" }, + { + "name": "jbzoo/markdown", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/JBZoo/Markdown.git", + "reference": "02e9d756ed91d33c63a7794db1279af56e4da5e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JBZoo/Markdown/zipball/02e9d756ed91d33c63a7794db1279af56e4da5e9", + "reference": "02e9d756ed91d33c63a7794db1279af56e4da5e9", + "shasum": "" + }, + "require": { + "jbzoo/utils": "^7.1", + "php": "^8.1" + }, + "require-dev": { + "jbzoo/toolbox-dev": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "JBZoo\\Markdown\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Denis Smetannikov", + "email": "admin@jbzoo.com", + "role": "lead" + } + ], + "description": "Tools to render markdown text from PHP code", + "keywords": [ + "Rendering", + "jbzoo", + "markdown", + "readme", + "text" + ], + "support": { + "issues": "https://github.com/JBZoo/Markdown/issues", + "source": "https://github.com/JBZoo/Markdown/tree/7.0.1" + }, + "time": "2024-01-28T12:43:57+00:00" + }, { "name": "jbzoo/utils", "version": "7.1.2", @@ -2803,63 +2946,6 @@ }, "time": "2021-03-31T09:21:47+00:00" }, - { - "name": "jbzoo/markdown", - "version": "7.0.1", - "source": { - "type": "git", - "url": "https://github.com/JBZoo/Markdown.git", - "reference": "02e9d756ed91d33c63a7794db1279af56e4da5e9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/JBZoo/Markdown/zipball/02e9d756ed91d33c63a7794db1279af56e4da5e9", - "reference": "02e9d756ed91d33c63a7794db1279af56e4da5e9", - "shasum": "" - }, - "require": { - "jbzoo/utils": "^7.1", - "php": "^8.1" - }, - "require-dev": { - "jbzoo/toolbox-dev": "^7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "7.x-dev" - } - }, - "autoload": { - "psr-4": { - "JBZoo\\Markdown\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Denis Smetannikov", - "email": "admin@jbzoo.com", - "role": "lead" - } - ], - "description": "Tools to render markdown text from PHP code", - "keywords": [ - "Rendering", - "jbzoo", - "markdown", - "readme", - "text" - ], - "support": { - "issues": "https://github.com/JBZoo/Markdown/issues", - "source": "https://github.com/JBZoo/Markdown/tree/7.0.1" - }, - "time": "2024-01-28T12:43:57+00:00" - }, { "name": "jbzoo/phpunit", "version": "7.1.0", @@ -5362,7 +5448,7 @@ "react/http": ">=0.7,<1.9", "really-simple-plugins/complianz-gdpr": "<6.4.2", "redaxo/source": "<=5.15.1", - "remdex/livehelperchat": "<3.99", + "remdex/livehelperchat": "<4.29", "reportico-web/reportico": "<=7.1.21", "rhukster/dom-sanitizer": "<1.0.7", "rmccue/requests": ">=1.6,<1.8", diff --git a/src/Commands/ValidateCsv.php b/src/Commands/ValidateCsv.php index 7db44667..5e13da01 100644 --- a/src/Commands/ValidateCsv.php +++ b/src/Commands/ValidateCsv.php @@ -41,6 +41,14 @@ protected function configure(): void 's', InputOption::VALUE_REQUIRED, 'Schema rule filepath', + ) + ->addOption( + 'output', + 'o', + InputOption::VALUE_REQUIRED, + 'Report output format. Available options: ' . + \implode(', ', ErrorSuite::getAvaiableRenderFormats()) . '', + ErrorSuite::RENDER_TABLE, ); parent::configure(); @@ -54,12 +62,25 @@ protected function executeAction(): int $csvFile = new CsvFile($csvFilename, $schemaFilename); $errorSuite = $csvFile->validate(); if ($errorSuite->count() > 0) { - $this->_($errorSuite->render(ErrorSuite::RENDER_TEXT), OutLvl::ERROR); + $this->_( + $errorSuite->render($this->getOptString('output')), + $this->isTextMode() ? OutLvl::E : OutLvl::DEFAULT, + ); - throw new Exception('CSV file is not valid! Found ' . $errorSuite->count() . ' errors.'); + if ($this->isTextMode()) { + $this->_( + 'CSV file is not valid! ' . + 'Found ' . $errorSuite->count() . ' errors.', + OutLvl::ERROR, + ); + } + + return self::FAILURE; } - $this->_('Looks good!'); + if ($this->isTextMode()) { + $this->_('Looks good!'); + } return self::SUCCESS; } @@ -72,7 +93,9 @@ private function getCsvFilepath(): string throw new Exception("CSV file not found: {$csvFilename}"); } - $this->_('CSV : ' . \str_replace(PATH_ROOT, '.', \realpath($csvFilename))); + if ($this->isTextMode()) { + $this->_('CSV : ' . \str_replace(PATH_ROOT, '.', \realpath($csvFilename))); + } return $csvFilename; } @@ -85,8 +108,23 @@ private function getSchemaFilepath(): string throw new Exception("Schema file not found: {$schemaFilename}"); } - $this->_('Schema : ' . \str_replace(PATH_ROOT, '.', \realpath($schemaFilename))); + if ($this->isTextMode()) { + $this->_('Schema : ' . \str_replace(PATH_ROOT, '.', \realpath($schemaFilename))); + } return $schemaFilename; } + + private function isTextMode(): bool + { + return $this->getOutputMode() === ErrorSuite::RENDER_TEXT + || $this->getOutputMode() === ErrorSuite::RENDER_GITHUB + || $this->getOutputMode() === ErrorSuite::RENDER_TEAMCITY + || $this->getOutputMode() === ErrorSuite::RENDER_TABLE; + } + + private function getOutputMode(): string + { + return $this->getOptString('output', ErrorSuite::RENDER_TABLE, ErrorSuite::getAvaiableRenderFormats()); + } } diff --git a/src/Csv/Column.php b/src/Csv/Column.php index 23c248a2..28bc4546 100644 --- a/src/Csv/Column.php +++ b/src/Csv/Column.php @@ -35,10 +35,10 @@ final class Column 'aggregate_rules' => [], ]; - private int $id; - private Data $column; - private array $rules; - private array $aggregateRules; + private int $id; + private Data $column; + private array $rules; + private array $aggregateRules; public function __construct(int $id, array $config) { @@ -65,7 +65,7 @@ public function getDescription(): string public function getHumanName(): string { - return \trim($this->getName() . ' (' . $this->getId() . ')'); + return $this->getId() . ':' . \trim($this->getName()); } public function getKey(): string diff --git a/src/Csv/CsvFile.php b/src/Csv/CsvFile.php index 97165b59..744adac4 100644 --- a/src/Csv/CsvFile.php +++ b/src/Csv/CsvFile.php @@ -44,7 +44,7 @@ public function __construct(string $csvFilename, null|array|string $csvSchemaFil public function getCsvFilename(): string { - return $this->csvFilename; + return \str_replace(PROJECT_ROOT, '.', $this->csvFilename); } public function getCsvStructure(): ParseConfig @@ -76,7 +76,7 @@ public function getRecordsChunk(int $offset = 0, int $limit = -1): \League\Csv\R public function validate(bool $quickStop = false): ErrorSuite { - $errors = new ErrorSuite(); + $errors = new ErrorSuite($this->getCsvFilename()); $errors->addErrorSuit($this->validateHeader()) ->addErrorSuit($this->validateEachCell($quickStop)) @@ -122,7 +122,7 @@ private function validateHeader(): ErrorSuite foreach ($this->schema->getColumns() as $column) { if ($column->getName() === '') { $error = new Error( - 'csv_structure.header', + 'csv.header', "Property \"name\" is not defined in schema: \"{$this->schema->getFilename()}\"", $column->getHumanName(), 1, diff --git a/src/Schema.php b/src/Schema.php index c12e7285..2d2f5b35 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -62,7 +62,7 @@ public function getFilename(): string public function getCsvStructure(): ParseConfig { - return new ParseConfig($this->data->getArray('csv_structure')); + return new ParseConfig($this->data->getArray('csv')); } /** diff --git a/src/Validators/ErrorSuite.php b/src/Validators/ErrorSuite.php index 115f2714..f97c6119 100644 --- a/src/Validators/ErrorSuite.php +++ b/src/Validators/ErrorSuite.php @@ -16,14 +16,30 @@ namespace JBZoo\CsvBlueprint\Validators; +use JBZoo\CIReportConverter\Converters\GithubCliConverter; +use JBZoo\CIReportConverter\Converters\GitLabJsonConverter; +use JBZoo\CIReportConverter\Converters\JUnitConverter; +use JBZoo\CIReportConverter\Converters\TeamCityTestsConverter; +use JBZoo\CIReportConverter\Formats\Source\SourceSuite; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Output\BufferedOutput; + final class ErrorSuite { - public const RENDER_TEXT = 'plain'; - public const RENDER_LIST = 'list'; + public const RENDER_TEXT = 'text'; + public const RENDER_TABLE = 'table'; + public const RENDER_TEAMCITY = 'teamcity'; + public const RENDER_GITLAB = 'gitlab'; + public const RENDER_GITHUB = 'github'; + public const RENDER_JUNIT = 'junit'; /** @var Error[] */ private array $errors = []; + public function __construct(private ?string $csvFilename = null) + { + } + public function __toString(): string { return $this->render(self::RENDER_TEXT); @@ -35,15 +51,20 @@ public function render(string $mode = self::RENDER_TEXT): string return ''; } - if ($mode === self::RENDER_TEXT) { - return $this->renderPlainText(); - } - - if ($mode === self::RENDER_LIST) { - return $this->renderList(); + $map = [ + self::RENDER_TEXT => fn () => $this->renderPlainText(), + self::RENDER_TABLE => fn () => $this->renderTable(), + self::RENDER_GITHUB => fn () => (new GithubCliConverter())->fromInternal($this->prepareSourceSuite()), + self::RENDER_GITLAB => fn () => (new GitLabJsonConverter())->fromInternal($this->prepareSourceSuite()), + self::RENDER_TEAMCITY => fn () => (new TeamCityTestsConverter())->fromInternal($this->prepareSourceSuite()), + self::RENDER_JUNIT => fn () => (new JUnitConverter())->fromInternal($this->prepareSourceSuite()), + ]; + + if (isset($map[$mode])) { + return $map[$mode](); } - throw new Exception('Unknown error render mode: ' . $mode); + throw new Exception("Unknown error render mode: {$mode}"); } /** @@ -86,6 +107,18 @@ public function get(int $index): ?Error return $this->errors[$index] ?? null; } + public static function getAvaiableRenderFormats(): array + { + return [ + self::RENDER_TEXT, + self::RENDER_TABLE, + self::RENDER_GITHUB, + self::RENDER_GITLAB, + self::RENDER_TEAMCITY, + self::RENDER_JUNIT, + ]; + } + private function renderPlainText(): string { $result = []; @@ -97,14 +130,39 @@ private function renderPlainText(): string return \implode("\n", $result); } - private function renderList(): string + private function renderTable(): string { - $result = []; + $buffer = new BufferedOutput(); + $table = (new Table($buffer)) + ->setHeaderTitle($this->csvFilename) + ->setFooterTitle($this->csvFilename) + ->setHeaders(['Line', 'id:Column', 'Rule', 'Message']) + ->setColumnMaxWidth(0, 10) + ->setColumnMaxWidth(1, 20) + ->setColumnMaxWidth(2, 20) + ->setColumnMaxWidth(3, 60); foreach ($this->errors as $error) { - $result[] = (string)$error; + $table->addRow([$error->getLine(), $error->getColumnName(), $error->getRuleCode(), $error->getMessage()]); + } + + $table->render(); + + return $buffer->fetch(); + } + + private function prepareSourceSuite(): SourceSuite + { + $suite = new SourceSuite($this->csvFilename); + + foreach ($this->errors as $error) { + $caseName = $error->getRuleCode() . ' at column ' . $error->getColumnName(); + $case = $suite->addTestCase($caseName); + $case->line = $error->getLine(); + $case->file = $this->csvFilename; + $case->errOut = $error->getMessage(); } - return ' * ' . \implode("\n * ", $result); + return $suite; } } diff --git a/tests/Blueprint/MiscTest.php b/tests/Blueprint/MiscTest.php index 91b2d20a..08702b3d 100644 --- a/tests/Blueprint/MiscTest.php +++ b/tests/Blueprint/MiscTest.php @@ -86,7 +86,7 @@ public function testFullListOfRules(): void public function testCsvStrutureDefaultValues(): void { - $defaultsInDoc = yml(PROJECT_ROOT . '/schema-examples/full.yml')->findArray('csv_structure'); + $defaultsInDoc = yml(PROJECT_ROOT . '/schema-examples/full.yml')->findArray('csv'); $schema = new Schema([]); $schema->getCsvStructure()->getArrayCopy(); diff --git a/tests/Blueprint/ValidatorTest.php b/tests/Blueprint/ValidatorTest.php index 70101ef9..ef039f6a 100644 --- a/tests/Blueprint/ValidatorTest.php +++ b/tests/Blueprint/ValidatorTest.php @@ -72,7 +72,7 @@ public function testNoName(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule(null, 'not_empty', true)); isSame( - '"csv_structure.header" at line 1, column "(0)". ' . + '"csv.header" at line 1, column "(0)". ' . 'Property "name" is not defined in schema: "_custom_array_".', (string)$csv->validate(), ); diff --git a/tests/schemas/example_full.yml b/tests/schemas/example_full.yml index 9313d861..51fdb55f 100644 --- a/tests/schemas/example_full.yml +++ b/tests/schemas/example_full.yml @@ -27,7 +27,7 @@ includes: # Alias is always required - ../path/schema_3.yml as alias_3 # Relative path based on the current schema path. Go up one level. -csv_structure: # How to parse file before validation +csv: # How to parse file before validation inherit: alias_1 # Inherited from another schema. Options above will overwrite inherited options. bom: false # true - file starts with BOM, false - no BOM delimiter: , # delimiter char to separate cells @@ -43,7 +43,7 @@ csv_structure: # How to parse file before validation columns: - invalid_option: true # Just to test super minimal configuration - - name: General available options # Can be optional if csv_structure\header: false. If set, then header must contain this value + - name: General available options # Can be optional if csv\header: false. If set, then header must contain this value description: Some description # Any custom description type: some_type # Can be optional. At your own risk! If empty, then use Validator\Base required: true # If true, then column must be present in the file diff --git a/tests/schemas/simple_no_header.yml b/tests/schemas/simple_no_header.yml index 1bf25be3..ebcb82ac 100644 --- a/tests/schemas/simple_no_header.yml +++ b/tests/schemas/simple_no_header.yml @@ -10,7 +10,7 @@ # @see https://github.com/JBZoo/Csv-Blueprint # -csv_structure: +csv: header: false columns: From f0a9c13ad85f742876a90e1ac3f0573f449a607e Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Mon, 11 Mar 2024 00:38:49 +0400 Subject: [PATCH 16/25] Fix typo in Makefile command and update PHP version to 8.3 in workflow demo.yml --- .github/workflows/demo.yml | 7 ++----- Makefile | 5 +++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index 03748da2..cca6f530 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -28,9 +28,6 @@ jobs: pure-php: name: Pure PHP runs-on: ubuntu-latest - strategy: - matrix: - php-version: [ 8.1, 8.2, 8.3 ] steps: - name: Checkout code uses: actions/checkout@v3 @@ -40,11 +37,11 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ matrix.php-version }} + php-version: 8.3 tools: composer - name: Build the Project run: make update --no-print-directory - - name: ๐Ÿงช PHPUnit Tests + - name: Validate CSV run: make demo-github diff --git a/Makefile b/Makefile index 00893b46..9c17551d 100644 --- a/Makefile +++ b/Makefile @@ -41,11 +41,12 @@ demo-invalid: ##@Project Run demo invalid CSV --schema=./tests/schemas/demo_invalid.yml \ --output=github -demo-gihub: ##@Project Run demo invalid CSV +demo-github: ##@Project Run demo invalid CSV @${PHP_BIN} ./csv-blueprint validate:csv \ --csv=./tests/fixtures/demo.csv \ --schema=./tests/schemas/demo_invalid.yml \ - --output=github + --output=github\ + --mute-errors demo: ##@Project Run all demo commands From 35001b883ed935c24e175b17c1a5b82058b3ea2e Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Mon, 11 Mar 2024 00:44:42 +0400 Subject: [PATCH 17/25] Refactor workflow file for better readability and maintenance --- .github/workflows/demo.yml | 25 +++++++++++++++++++++++-- Makefile | 4 ++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index cca6f530..aca09af3 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -43,5 +43,26 @@ jobs: - name: Build the Project run: make update --no-print-directory - - name: Validate CSV - run: make demo-github + - name: Validate CSV (text) + run: OUTPUT=text make demo-github + continue-on-error: true + + - name: Validate CSV (table) + run: OUTPUT=table make demo-github + continue-on-error: true + + - name: Validate CSV (github) + run: OUTPUT=github make demo-github + continue-on-error: true + + - name: Validate CSV (gitlab) + run: OUTPUT=gitlab make demo-github + continue-on-error: true + + - name: Validate CSV (teamcity) + run: OUTPUT=teamcity make demo-github + continue-on-error: true + + - name: Validate CSV (junit) + run: OUTPUT=junit make demo-github + continue-on-error: true diff --git a/Makefile b/Makefile index 9c17551d..deb7e46f 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ ifneq (, $(wildcard ./vendor/jbzoo/codestyle/src/init.Makefile)) include ./vendor/jbzoo/codestyle/src/init.Makefile endif +OUTPUT ?= table update: ##@Project Install/Update all 3rd party dependencies $(call title,"Install/Update all 3rd party dependencies") @@ -45,8 +46,7 @@ demo-github: ##@Project Run demo invalid CSV @${PHP_BIN} ./csv-blueprint validate:csv \ --csv=./tests/fixtures/demo.csv \ --schema=./tests/schemas/demo_invalid.yml \ - --output=github\ - --mute-errors + --output=$(OUTPUT) demo: ##@Project Run all demo commands From c53f7bfb5a9c417b6b37d7273035f10d4d2d22c9 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Mon, 11 Mar 2024 00:46:58 +0400 Subject: [PATCH 18/25] Refactor Makefile to add support for ANSI output in demo-github task --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index deb7e46f..4f8decbd 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,8 @@ demo-github: ##@Project Run demo invalid CSV @${PHP_BIN} ./csv-blueprint validate:csv \ --csv=./tests/fixtures/demo.csv \ --schema=./tests/schemas/demo_invalid.yml \ - --output=$(OUTPUT) + --output=$(OUTPUT) \ + --ansi demo: ##@Project Run all demo commands From c57d003a025023bccdfc8c0abd32d9472135a8a7 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Mon, 11 Mar 2024 00:57:33 +0400 Subject: [PATCH 19/25] Refactor makefile build command for consistent output - Changed the make update command to remove --no-print-directory option for standard output consistency. --- .github/workflows/demo.yml | 10 +++--- Makefile | 4 +-- README.md | 6 ++-- schema-examples/full.json | 4 +-- schema-examples/full.php | 2 +- schema-examples/full.yml | 2 +- src/Commands/ValidateCsv.php | 5 ++- tests/Blueprint/CsvReaderTest.php | 4 +-- tests/Blueprint/ValidatorTest.php | 54 +++++++++++++++---------------- 9 files changed, 46 insertions(+), 45 deletions(-) diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index aca09af3..5c2e204a 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -41,14 +41,14 @@ jobs: tools: composer - name: Build the Project - run: make update --no-print-directory + run: make update - - name: Validate CSV (text) - run: OUTPUT=text make demo-github + - name: Validate CSV (default is table) + run: make demo-github continue-on-error: true - - name: Validate CSV (table) - run: OUTPUT=table make demo-github + - name: Validate CSV (text) + run: OUTPUT=text make demo-github continue-on-error: true - name: Validate CSV (github) diff --git a/Makefile b/Makefile index 4f8decbd..6629f209 100644 --- a/Makefile +++ b/Makefile @@ -39,8 +39,8 @@ demo-invalid: ##@Project Run demo invalid CSV $(call title,"Demo - Invalid CSV") @${PHP_BIN} ./csv-blueprint validate:csv \ --csv=./tests/fixtures/demo.csv \ - --schema=./tests/schemas/demo_invalid.yml \ - --output=github + --schema=./tests/schemas/demo_invalid.yml + demo-github: ##@Project Run demo invalid CSV @${PHP_BIN} ./csv-blueprint validate:csv \ diff --git a/README.md b/README.md index 2a7cbfe1..2eab9728 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ csv: # Here are default values. You can skip this section if you don't need to o bom: false # If the file has a BOM (Byte Order Mark) at the beginning (Experimental) columns: - - name: "csv_header_name" # Any custom name of the column in the CSV file (first row). Required if "csv.header" is true. + - name: "csv_header_name" # Any custom name of the column in the CSV file (first row). Required if "csv_structure.header" is true. description: "Lorem ipsum" # Optional. Description of the column. Not used in the validation process. rules: # You can use the rules in any combination. Or not use any of them. @@ -83,6 +83,7 @@ columns:
+
Click to see: JSON Format @@ -139,6 +140,7 @@ columns:
+
Click to see: PHP Array as file @@ -147,7 +149,7 @@ columns: declare(strict_types=1); return [ - 'csv' => [ + 'csv' => [ 'header' => true, 'delimiter' => ',', 'quote_char' => '\\', diff --git a/schema-examples/full.json b/schema-examples/full.json index 0029a65e..51bf4158 100644 --- a/schema-examples/full.json +++ b/schema-examples/full.json @@ -1,5 +1,5 @@ { - "csv_structure" : { + "csv" : { "header" : true, "delimiter" : ",", "quote_char" : "\\", @@ -7,7 +7,7 @@ "encoding" : "utf-8", "bom" : false }, - "columns" : [ + "columns" : [ { "name" : "csv_header_name", "description" : "Lorem ipsum", diff --git a/schema-examples/full.php b/schema-examples/full.php index 50029fed..b5bae14a 100644 --- a/schema-examples/full.php +++ b/schema-examples/full.php @@ -15,7 +15,7 @@ declare(strict_types=1); return [ - 'csv_structure' => [ + 'csv' => [ 'header' => true, 'delimiter' => ',', 'quote_char' => '\\', diff --git a/schema-examples/full.yml b/schema-examples/full.yml index 5c1bd5e2..b49102eb 100644 --- a/schema-examples/full.yml +++ b/schema-examples/full.yml @@ -12,7 +12,7 @@ # It's a full example of the CSV schema file in YAML format. -csv_structure: # Here are default values. You can skip this section if you don't need to override the default values +csv: # Here are default values. You can skip this section if you don't need to override the default values header: true # If the first row is a header. If true, name of each column is required delimiter: , # Delimiter character in CSV file quote_char: \ # Quote character in CSV file diff --git a/src/Commands/ValidateCsv.php b/src/Commands/ValidateCsv.php index 5e13da01..35ff06c4 100644 --- a/src/Commands/ValidateCsv.php +++ b/src/Commands/ValidateCsv.php @@ -69,9 +69,8 @@ protected function executeAction(): int if ($this->isTextMode()) { $this->_( - 'CSV file is not valid! ' . - 'Found ' . $errorSuite->count() . ' errors.', - OutLvl::ERROR, + 'CSV file is not valid! Found ' . $errorSuite->count() . ' errors.', + OutLvl::E, ); } diff --git a/tests/Blueprint/CsvReaderTest.php b/tests/Blueprint/CsvReaderTest.php index c80ef60f..6ec33902 100644 --- a/tests/Blueprint/CsvReaderTest.php +++ b/tests/Blueprint/CsvReaderTest.php @@ -32,7 +32,7 @@ final class CsvReaderTest extends PHPUnit public function testReadCsvFileWithoutHeader(): void { $csv = new CsvFile(self::CSV_SIMPLE_NO_HEADER, self::SCHEMA_SIMPLE_NO_HEADER); - isSame(self::CSV_SIMPLE_NO_HEADER, $csv->getCsvFilename()); + isSame('./tests/fixtures/simple_no_header.csv', $csv->getCsvFilename()); isSame([], $csv->getHeader()); @@ -50,7 +50,7 @@ public function testReadCsvFileWithoutHeader(): void public function testReadCsvFileWithHeader(): void { $csv = new CsvFile(self::CSV_SIMPLE_HEADER, self::SCHEMA_SIMPLE_HEADER); - isSame(self::CSV_SIMPLE_HEADER, $csv->getCsvFilename()); + isSame('./tests/fixtures/simple_header.csv', $csv->getCsvFilename()); isSame(['seq', 'bool', 'exact'], $csv->getHeader()); diff --git a/tests/Blueprint/ValidatorTest.php b/tests/Blueprint/ValidatorTest.php index ef039f6a..74fecdbe 100644 --- a/tests/Blueprint/ValidatorTest.php +++ b/tests/Blueprint/ValidatorTest.php @@ -63,7 +63,7 @@ public function testNotEmptyMessage(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('integer', 'not_empty', true)); isSame( - '"not_empty" at line 19, column "integer (0)". Value is empty.', + '"not_empty" at line 19, column "0:integer". Value is empty.', (string)$csv->validate(), ); } @@ -72,7 +72,7 @@ public function testNoName(): void { $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule(null, 'not_empty', true)); isSame( - '"csv.header" at line 1, column "(0)". ' . + '"csv.header" at line 1, column "0:". ' . 'Property "name" is not defined in schema: "_custom_array_".', (string)$csv->validate(), ); @@ -85,7 +85,7 @@ public function testMin(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'min', 10)); isSame( - '"min" at line 2, column "seq (0)". Value "1" is less than "10".', + '"min" at line 2, column "0:seq". Value "1" is less than "10".', (string)$csv->validate()->get(0), ); } @@ -97,7 +97,7 @@ public function testMax(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'max', 10)); isSame( - '"max" at line 12, column "seq (0)". Value "11" is greater than "10".', + '"max" at line 12, column "0:seq". Value "11" is greater than "10".', (string)$csv->validate()->get(0), ); } @@ -112,19 +112,19 @@ public function testRegex(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'regex', '[a-z]')); isSame( - '"regex" at line 2, column "seq (0)". Value "1" does not match the pattern "/[a-z]/u".', + '"regex" at line 2, column "0:seq". Value "1" does not match the pattern "/[a-z]/u".', (string)$csv->validate()->get(0), ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'regex', '/[a-z]/')); isSame( - '"regex" at line 2, column "seq (0)". Value "1" does not match the pattern "/[a-z]/".', + '"regex" at line 2, column "0:seq". Value "1" does not match the pattern "/[a-z]/".', (string)$csv->validate()->get(0), ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'regex', '/[a-z]/i')); isSame( - '"regex" at line 2, column "seq (0)". Value "1" does not match the pattern "/[a-z]/i".', + '"regex" at line 2, column "0:seq". Value "1" does not match the pattern "/[a-z]/i".', (string)$csv->validate()->get(0), ); } @@ -136,7 +136,7 @@ public function testMinLength(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'min_length', 1000)); isSame( - '"min_length" at line 2, column "seq (0)". Value "1" (legth: 1) is too short. Min length is 1000.', + '"min_length" at line 2, column "0:seq". Value "1" (legth: 1) is too short. Min length is 1000.', (string)$csv->validate()->get(0), ); } @@ -148,7 +148,7 @@ public function testMaxLength(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'max_length', 1)); isSame( - '"max_length" at line 11, column "seq (0)". Value "10" (legth: 2) is too long. Max length is 1.', + '"max_length" at line 11, column "0:seq". Value "10" (legth: 2) is too long. Max length is 1.', (string)$csv->validate()->get(0), ); } @@ -160,7 +160,7 @@ public function testOnlyTrimed(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('sentence', 'only_trimed', true)); isSame( - '"only_trimed" at line 14, column "sentence (0)". Value " Urecam" is not trimmed.', + '"only_trimed" at line 14, column "0:sentence". Value " Urecam" is not trimmed.', (string)$csv->validate()->get(0), ); } @@ -172,7 +172,7 @@ public function testOnlyUppercase(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'only_uppercase', true)); isSame( - '"only_uppercase" at line 2, column "bool (0)". Value "true" is not uppercase.', + '"only_uppercase" at line 2, column "0:bool". Value "true" is not uppercase.', (string)$csv->validate()->get(0), ); } @@ -184,7 +184,7 @@ public function testOnlyLowercase(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'only_lowercase', true)); isSame( - '"only_lowercase" at line 8, column "bool (0)". Value "False" should be in lowercase.', + '"only_lowercase" at line 8, column "0:bool". Value "False" should be in lowercase.', (string)$csv->validate()->get(0), ); } @@ -196,7 +196,7 @@ public function testOnlyCapitalize(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'only_capitalize', true)); isSame( - '"only_capitalize" at line 2, column "bool (0)". Value "true" should be in capitalize.', + '"only_capitalize" at line 2, column "0:bool". Value "true" should be in capitalize.', (string)$csv->validate()->get(0), ); } @@ -208,14 +208,14 @@ public function testPrecision(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'precision', 1)); isSame( - '"precision" at line 2, column "seq (0)". ' . + '"precision" at line 2, column "0:seq". ' . 'Value "1" has a precision of 0 but should have a precision of 1.', (string)$csv->validate()->get(0), ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('float', 'precision', 3)); isSame( - '"precision" at line 3, column "float (0)". ' . + '"precision" at line 3, column "0:float". ' . 'Value "506847750940.2624" has a precision of 4 but should have a precision of 3.', (string)$csv->validate()->get(0), ); @@ -228,14 +228,14 @@ public function testMinDate(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'min_date', '2120-01-01')); isSame( - '"min_date" at line 2, column "date (0)". ' . + '"min_date" at line 2, column "0:date". ' . 'Value "2042/11/18" is less than the minimum date "2120-01-01T00:00:00.000+00:00".', (string)$csv->validate()->get(0), ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'min_date', '2042/11/17')); isSame( - '"min_date" at line 5, column "date (0)". ' . + '"min_date" at line 5, column "0:date". ' . 'Value "2032/09/09" is less than the minimum date "2042-11-17T00:00:00.000+00:00".', (string)$csv->validate()->get(0), ); @@ -248,14 +248,14 @@ public function testMaxDate(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'max_date', '2120-01-01')); isSame( - '"max_date" at line 23, column "date (0)". ' . + '"max_date" at line 23, column "0:date". ' . 'Value "2120/02/01" is more than the maximum date "2120-01-01T00:00:00.000+00:00".', (string)$csv->validate()->get(0), ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'max_date', '2042/11/17')); isSame( - '"max_date" at line 2, column "date (0)". ' . + '"max_date" at line 2, column "0:date". ' . 'Value "2042/11/18" is more than the maximum date "2042-11-17T00:00:00.000+00:00".', (string)$csv->validate()->get(0), ); @@ -268,7 +268,7 @@ public function testDateFormat(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('date', 'date_format', 'Y/m/d H:i:s')); isSame( - '"date_format" at line 2, column "date (0)". ' . + '"date_format" at line 2, column "0:date". ' . 'Date format of value "2042/11/18" is not valid. Expected format: "Y/m/d H:i:s".', (string)$csv->validate()->get(0), ); @@ -288,7 +288,7 @@ public function testAllowValues(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'allow_values', ['true', 'false'])); isSame( - '"allow_values" at line 8, column "bool (0)". ' . + '"allow_values" at line 8, column "0:bool". ' . 'Value "False" is not allowed. Allowed values: ["true", "false"].', (string)$csv->validate()->get(0), ); @@ -301,13 +301,13 @@ public function testExactValue(): void $csv = new CsvFile(self::CSV_SIMPLE_HEADER, $this->getRule('exact', 'exact_value', '2')); isSame( - '"exact_value" at line 2, column "exact (0)". Value "1" is not strict equal to "2".', + '"exact_value" at line 2, column "0:exact". Value "1" is not strict equal to "2".', (string)$csv->validate()->get(0), ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'exact_value', 'true')); isSame( - '"exact_value" at line 4, column "bool (0)". Value "false" is not strict equal to "true".', + '"exact_value" at line 4, column "0:bool". Value "false" is not strict equal to "true".', (string)$csv->validate()->get(0), ); } @@ -319,7 +319,7 @@ public function testIsInt(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'is_int', true)); isSame( - '"is_int" at line 2, column "bool (0)". Value "true" is not an integer.', + '"is_int" at line 2, column "0:bool". Value "true" is not an integer.', (string)$csv->validate()->get(0), ); } @@ -331,7 +331,7 @@ public function testIsFloat(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('bool', 'is_float', true)); isSame( - '"is_float" at line 2, column "bool (0)". Value "true" is not a float number.', + '"is_float" at line 2, column "0:bool". Value "true" is not a float number.', (string)$csv->validate()->get(0), ); } @@ -343,7 +343,7 @@ public function testIsBool(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('yn', 'is_bool', true)); isSame( - '"is_bool" at line 2, column "yn (0)". Value "n" is not allowed. Allowed values: ["true", "false"].', + '"is_bool" at line 2, column "0:yn". Value "n" is not allowed. Allowed values: ["true", "false"].', (string)$csv->validate()->get(0), ); } @@ -355,7 +355,7 @@ public function testIsEmail(): void $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('yn', 'is_email', true)); isSame( - '"is_email" at line 2, column "yn (0)". Value "N" is not a valid email.', + '"is_email" at line 2, column "0:yn". Value "N" is not a valid email.', (string)$csv->validate()->get(0), ); } From 56edbc93ccf4f204c98d644979b1e53e327bb63b Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Mon, 11 Mar 2024 01:01:03 +0400 Subject: [PATCH 20/25] Update PHP array format in README and test case for PHP format in Blueprint. --- README.md | 4 ++-- tests/Blueprint/MiscTest.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2eab9728..20a8749a 100644 --- a/README.md +++ b/README.md @@ -142,14 +142,14 @@ columns:
- Click to see: PHP Array as file + Click to see: PHP Format ```php [ + 'csv' => [ 'header' => true, 'delimiter' => ',', 'quote_char' => '\\', diff --git a/tests/Blueprint/MiscTest.php b/tests/Blueprint/MiscTest.php index 08702b3d..09a91b88 100644 --- a/tests/Blueprint/MiscTest.php +++ b/tests/Blueprint/MiscTest.php @@ -106,7 +106,7 @@ public function testCheckYmlSchemaExampleInReadme(): void public function testCheckPhpSchemaExampleInReadme(): void { - $this->testCheckExampleInReadme(PROJECT_ROOT . '/schema-examples/full.php', 'php', 'PHP Array as file', 14); + $this->testCheckExampleInReadme(PROJECT_ROOT . '/schema-examples/full.php', 'php', 'PHP Format', 14); } public function testCheckJsonSchemaExampleInReadme(): void From ea82d7d2065ab7217bd48d25072f5af3dd53ceb9 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Mon, 11 Mar 2024 01:32:31 +0400 Subject: [PATCH 21/25] PHPStan fixed! --- src/Commands/CreateCsv.php | 7 +---- src/Commands/CreateSchema.php | 7 +---- src/Commands/ValidateCsv.php | 4 +-- src/Commands/ValidateDir.php | 7 +---- src/Csv/Column.php | 8 ++++-- src/Csv/CsvFile.php | 37 +++++++++++++------------- src/Csv/ParseConfig.php | 2 +- src/Schema.php | 14 ++++++---- src/Utils.php | 2 +- src/Validators/Rules/AbstarctRule.php | 13 ++++++--- src/Validators/Rules/DateFormat.php | 6 ++--- src/Validators/Rules/IsDomain.php | 2 +- src/Validators/Rules/IsEmail.php | 2 +- src/Validators/Rules/IsFloat.php | 2 +- src/Validators/Rules/IsInt.php | 2 +- src/Validators/Rules/IsIp.php | 2 +- src/Validators/Rules/IsLatitude.php | 2 +- src/Validators/Rules/IsLongitude.php | 2 +- src/Validators/Rules/IsUrl.php | 2 +- src/Validators/Rules/IsUuid4.php | 2 +- src/Validators/Rules/Max.php | 2 +- src/Validators/Rules/MaxDate.php | 2 +- src/Validators/Rules/MaxLength.php | 5 ++-- src/Validators/Rules/Min.php | 2 +- src/Validators/Rules/MinDate.php | 2 +- src/Validators/Rules/MinLength.php | 3 ++- src/Validators/Rules/OnlyTrimed.php | 2 +- src/Validators/Rules/Regex.php | 2 +- src/Validators/Rules/UsaMarketName.php | 2 +- src/Validators/Ruleset.php | 7 ++--- src/Validators/Validator.php | 4 --- 31 files changed, 76 insertions(+), 82 deletions(-) diff --git a/src/Commands/CreateCsv.php b/src/Commands/CreateCsv.php index a0591666..7eac8231 100644 --- a/src/Commands/CreateCsv.php +++ b/src/Commands/CreateCsv.php @@ -20,8 +20,6 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; -use function JBZoo\Data\yml; - final class CreateCsv extends CliCommand { protected function configure(): void @@ -53,9 +51,6 @@ protected function configure(): void protected function executeAction(): int { - $yml = '/Users/smetdenis/Work/projects/jbzoo-csv-validator/tests/rules/example.yml'; - dump(yml($yml)); - - return self::SUCCESS; + throw new \RuntimeException('Not implemented yet'); } } diff --git a/src/Commands/CreateSchema.php b/src/Commands/CreateSchema.php index 183703a2..4b886d28 100644 --- a/src/Commands/CreateSchema.php +++ b/src/Commands/CreateSchema.php @@ -20,8 +20,6 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; -use function JBZoo\Data\yml; - final class CreateSchema extends CliCommand { protected function configure(): void @@ -53,9 +51,6 @@ protected function configure(): void protected function executeAction(): int { - $yml = '/Users/smetdenis/Work/projects/jbzoo-csv-validator/tests/rules/example.yml'; - dump(yml($yml)); - - return self::SUCCESS; + throw new \RuntimeException('Not implemented yet'); } } diff --git a/src/Commands/ValidateCsv.php b/src/Commands/ValidateCsv.php index 35ff06c4..d0b6c3e7 100644 --- a/src/Commands/ValidateCsv.php +++ b/src/Commands/ValidateCsv.php @@ -93,7 +93,7 @@ private function getCsvFilepath(): string } if ($this->isTextMode()) { - $this->_('CSV : ' . \str_replace(PATH_ROOT, '.', \realpath($csvFilename))); + $this->_('CSV : ' . \realpath($csvFilename)); } return $csvFilename; @@ -108,7 +108,7 @@ private function getSchemaFilepath(): string } if ($this->isTextMode()) { - $this->_('Schema : ' . \str_replace(PATH_ROOT, '.', \realpath($schemaFilename))); + $this->_('Schema : ' . \realpath($schemaFilename)); } return $schemaFilename; diff --git a/src/Commands/ValidateDir.php b/src/Commands/ValidateDir.php index fc10ec1d..4d4d6a7a 100644 --- a/src/Commands/ValidateDir.php +++ b/src/Commands/ValidateDir.php @@ -19,8 +19,6 @@ use JBZoo\Cli\CliCommand; use Symfony\Component\Console\Input\InputArgument; -use function JBZoo\Data\yml; - final class ValidateDir extends CliCommand { protected function configure(): void @@ -44,9 +42,6 @@ protected function configure(): void protected function executeAction(): int { - $yml = '/Users/smetdenis/Work/projects/jbzoo-csv-validator/tests/rules/example.yml'; - dump(yml($yml)); - - return self::SUCCESS; + throw new \RuntimeException('Not implemented yet'); } } diff --git a/src/Csv/Column.php b/src/Csv/Column.php index 28bc4546..9096305f 100644 --- a/src/Csv/Column.php +++ b/src/Csv/Column.php @@ -70,7 +70,11 @@ public function getHumanName(): string public function getKey(): string { - return $this->getName() ?: (string)$this->getId(); + if ($this->getName() !== '') { + return $this->getName(); + } + + return (string)$this->getId(); } public function getType(): string @@ -114,7 +118,7 @@ private function prepareRuleSet(string $schemaKey): array { $rules = []; - $ruleSetConfig = $this->column->getSelf($schemaKey, self::FALLBACK_VALUES[$schemaKey]); + $ruleSetConfig = $this->column->getSelf($schemaKey, []); foreach ($ruleSetConfig as $ruleName => $ruleValue) { if (\str_starts_with($ruleName, 'custom_')) { diff --git a/src/Csv/CsvFile.php b/src/Csv/CsvFile.php index 744adac4..10571795 100644 --- a/src/Csv/CsvFile.php +++ b/src/Csv/CsvFile.php @@ -19,7 +19,6 @@ use JBZoo\CsvBlueprint\Schema; use JBZoo\CsvBlueprint\Validators\Error; use JBZoo\CsvBlueprint\Validators\ErrorSuite; -use League\Csv\ByteSequence; use League\Csv\Reader as LeagueReader; use League\Csv\Statement; @@ -32,11 +31,11 @@ final class CsvFile public function __construct(string $csvFilename, null|array|string $csvSchemaFilenameOrArray = null) { - if (\realpath($csvFilename) && \file_exists($csvFilename) === false) { + if (\realpath($csvFilename) !== false && \file_exists($csvFilename) === false) { throw new \InvalidArgumentException('File not found: ' . $csvFilename); } - $this->csvFilename = \realpath($csvFilename); + $this->csvFilename = $csvFilename; $this->schema = new Schema($csvSchemaFilenameOrArray); $this->structure = $this->schema->getCsvStructure(); $this->reader = $this->prepareReader(); @@ -44,7 +43,7 @@ public function __construct(string $csvFilename, null|array|string $csvSchemaFil public function getCsvFilename(): string { - return \str_replace(PROJECT_ROOT, '.', $this->csvFilename); + return \str_replace(PROJECT_ROOT, '.', (string)\realpath($this->csvFilename)); } public function getCsvStructure(): ParseConfig @@ -64,15 +63,18 @@ public function getHeader(): array return []; } - public function getRecords(): \League\Csv\MapIterator + /** + * @return iterable|\League\Csv\MapIterator + */ + public function getRecords(): iterable { return $this->reader->getRecords($this->getHeader()); } - public function getRecordsChunk(int $offset = 0, int $limit = -1): \League\Csv\ResultSet - { - return Statement::create(null, $offset, $limit)->process($this->reader, $this->getHeader()); - } + // public function getRecordsChunk(int $offset = 0, int $limit = -1): \League\Csv\ResultSet + // { + // return Statement::create(null, $offset, $limit)->process($this->reader, $this->getHeader()); + // } public function validate(bool $quickStop = false): ErrorSuite { @@ -95,15 +97,6 @@ private function prepareReader(): LeagueReader if ($this->structure->isBom()) { $reader->includeInputBOM(); - - $encoding = $this->structure->getEncoding(); - if ($encoding === CsvStructure::ENCODING_UTF8) { - $reader->setOutputBOM(ByteSequence::BOM_UTF8); - } elseif ($encoding === CsvStructure::ENCODING_UTF16) { - $reader->setOutputBOM(ByteSequence::BOM_UTF16_LE); - } elseif ($encoding === CsvStructure::ENCODING_UTF32) { - $reader->setOutputBOM(ByteSequence::BOM_UTF32_LE); - } } else { $reader->skipInputBOM(); } @@ -149,7 +142,7 @@ private function validateEachCell(bool $quickStop = false): ErrorSuite $errors->addErrorSuit($column->validate($record[$column->getKey()], $line + 1)); if ($quickStop && $errors->count() > 0) { - return $errorAcc; + return $errors; } } } @@ -159,6 +152,12 @@ private function validateEachCell(bool $quickStop = false): ErrorSuite private function validateAggregateRules(bool $quickStop = false): ErrorSuite { + $errors = new ErrorSuite(); + + if ($quickStop && $errors->count() > 0) { + return $errors; + } + return new ErrorSuite(); } } diff --git a/src/Csv/ParseConfig.php b/src/Csv/ParseConfig.php index f71a5d09..0a19fd86 100644 --- a/src/Csv/ParseConfig.php +++ b/src/Csv/ParseConfig.php @@ -94,7 +94,7 @@ public function getEncoding(): string ]; $result = \in_array($encoding, $availableOptions, true) ? $encoding : null; - if ($result) { + if ($result !== null) { return $result; } diff --git a/src/Schema.php b/src/Schema.php index 2d2f5b35..d4e12c9d 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -38,7 +38,11 @@ public function __construct(null|array|string $csvSchemaFilenameOrArray = null) if (\is_array($csvSchemaFilenameOrArray)) { $this->filename = '_custom_array_'; $this->data = new Data($csvSchemaFilenameOrArray); - } elseif (\file_exists($csvSchemaFilenameOrArray)) { + } elseif ( + \is_string($csvSchemaFilenameOrArray) + && $csvSchemaFilenameOrArray !== '' + && \file_exists($csvSchemaFilenameOrArray) + ) { $this->filename = $csvSchemaFilenameOrArray; $fileExtension = \pathinfo($csvSchemaFilenameOrArray, \PATHINFO_EXTENSION); if ($fileExtension === 'yml' || $fileExtension === 'yaml') { @@ -55,7 +59,7 @@ public function __construct(null|array|string $csvSchemaFilenameOrArray = null) $this->columns = $this->prepareColumns(); } - public function getFilename(): string + public function getFilename(): ?string { return $this->filename; } @@ -74,7 +78,7 @@ public function getColumns(): array } /** - * @return Column[] + * @return Column[]|null[] */ public function getColumnsMappedByHeader(array $header): array { @@ -88,7 +92,7 @@ public function getColumnsMappedByHeader(array $header): array return $map; } - public function getColumn(int|string $columNameOrId) + public function getColumn(int|string $columNameOrId): ?Column { if (\is_int($columNameOrId)) { $column = \array_values($this->getColumns())[$columNameOrId] ?? null; @@ -96,7 +100,7 @@ public function getColumn(int|string $columNameOrId) $column = $this->getColumns()[$columNameOrId] ?? null; } - if (!$column) { + if ($column === null) { throw new Exception("Column \"{$columNameOrId}\" not found in schema \"{$this->filename}\""); } diff --git a/src/Utils.php b/src/Utils.php index 2b81c42f..dd26fed0 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -25,7 +25,7 @@ public static function kebabToCamelCase(string $input): string public static function camelToKebabCase(string $input): string { - return \strtolower(\preg_replace('/(?columnNameId = $columnNameId; $this->options = $options; @@ -42,7 +44,8 @@ public function __construct(?string $columnNameId, null|array|bool|float|int|str public function validate(?string $cellValue, int $line = 0): ?Error { - if ($error = $this->validateRule($cellValue)) { + $error = $this->validateRule($cellValue); + if ($error !== null) { return new Error($this->ruleCode, $error, $this->columnNameId, $line); } @@ -56,6 +59,10 @@ protected function getOptionAsBool(): bool protected function getOptionAsString(): string { + if (\is_array($this->options)) { + return (string)json($this->options); + } + return (string)$this->options; } @@ -74,7 +81,7 @@ protected function getOptionAsArray(): array return (array)$this->options; } - protected function getOptionAsData(): array + protected function getOptionAsData(): Data { return data($this->options); } diff --git a/src/Validators/Rules/DateFormat.php b/src/Validators/Rules/DateFormat.php index 4e1670e3..3e3e45ea 100644 --- a/src/Validators/Rules/DateFormat.php +++ b/src/Validators/Rules/DateFormat.php @@ -21,16 +21,16 @@ final class DateFormat extends AbstarctRule public function validateRule(?string $cellValue): ?string { $expectedDateFormat = $this->getOptionAsString(); - if (!$expectedDateFormat) { + if ($expectedDateFormat === '') { return 'Date format is not defined'; } - if (!$cellValue) { + if ($cellValue === null || $cellValue === '') { return 'Date format of value "" is not valid. Expected format: "' . $expectedDateFormat . '"'; } $date = \DateTimeImmutable::createFromFormat($expectedDateFormat, $cellValue); - if (!$date || $date->format($expectedDateFormat) !== $cellValue) { + if ($date === false || $date->format($expectedDateFormat) !== $cellValue) { return "Date format of value \"{$cellValue}\" is not valid. Expected format: \"{$expectedDateFormat}\""; } diff --git a/src/Validators/Rules/IsDomain.php b/src/Validators/Rules/IsDomain.php index 70bf6f92..0acb122b 100644 --- a/src/Validators/Rules/IsDomain.php +++ b/src/Validators/Rules/IsDomain.php @@ -26,7 +26,7 @@ public function validateRule(?string $cellValue): ?string $domainPattern = '/^(?!-)[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(\.[A-Za-z]{2,})$/'; - if (!\preg_match($domainPattern, $cellValue)) { + if (\preg_match($domainPattern, (string)$cellValue) === false) { return "Value \"{$cellValue}\" is not a valid domain"; } diff --git a/src/Validators/Rules/IsEmail.php b/src/Validators/Rules/IsEmail.php index 11c44eed..010f51ea 100644 --- a/src/Validators/Rules/IsEmail.php +++ b/src/Validators/Rules/IsEmail.php @@ -24,7 +24,7 @@ public function validateRule(?string $cellValue): ?string return null; } - if (!\filter_var($cellValue, \FILTER_VALIDATE_EMAIL)) { + if (\filter_var($cellValue, \FILTER_VALIDATE_EMAIL) === false) { return "Value \"{$cellValue}\" is not a valid email"; } diff --git a/src/Validators/Rules/IsFloat.php b/src/Validators/Rules/IsFloat.php index 7d05d901..a5d3c33a 100644 --- a/src/Validators/Rules/IsFloat.php +++ b/src/Validators/Rules/IsFloat.php @@ -24,7 +24,7 @@ public function validateRule(?string $cellValue): ?string return null; } - if (!\preg_match('/^-?\d+(\.\d+)?$/', $cellValue)) { + if (\preg_match('/^-?\d+(\.\d+)?$/', (string)$cellValue) === false) { return "Value \"{$cellValue}\" is not a float number"; } diff --git a/src/Validators/Rules/IsInt.php b/src/Validators/Rules/IsInt.php index e5919607..2fe16a97 100644 --- a/src/Validators/Rules/IsInt.php +++ b/src/Validators/Rules/IsInt.php @@ -24,7 +24,7 @@ public function validateRule(?string $cellValue): ?string return null; } - if (!\preg_match('/^-?\d+$/', $cellValue)) { + if (\preg_match('/^-?\d+$/', (string)$cellValue) === false) { return "Value \"{$cellValue}\" is not an integer"; } diff --git a/src/Validators/Rules/IsIp.php b/src/Validators/Rules/IsIp.php index 746acf7c..814c3bf1 100644 --- a/src/Validators/Rules/IsIp.php +++ b/src/Validators/Rules/IsIp.php @@ -24,7 +24,7 @@ public function validateRule(?string $cellValue): ?string return null; } - if (!\filter_var($cellValue, \FILTER_VALIDATE_IP)) { + if (\filter_var($cellValue, \FILTER_VALIDATE_IP) === false) { return "Value \"{$cellValue}\" is not a valid IP"; } diff --git a/src/Validators/Rules/IsLatitude.php b/src/Validators/Rules/IsLatitude.php index d2052890..2fb40715 100644 --- a/src/Validators/Rules/IsLatitude.php +++ b/src/Validators/Rules/IsLatitude.php @@ -25,7 +25,7 @@ public function validateRule(?string $cellValue): ?string } $result = parent::validateRule($cellValue); - if ($result) { + if ($result !== '') { return $result; } diff --git a/src/Validators/Rules/IsLongitude.php b/src/Validators/Rules/IsLongitude.php index a91ffd3a..aa785ca1 100644 --- a/src/Validators/Rules/IsLongitude.php +++ b/src/Validators/Rules/IsLongitude.php @@ -25,7 +25,7 @@ public function validateRule(?string $cellValue): ?string } $result = parent::validateRule($cellValue); - if ($result) { + if ($result !== '') { return $result; } diff --git a/src/Validators/Rules/IsUrl.php b/src/Validators/Rules/IsUrl.php index f9e2e30f..8bc1a242 100644 --- a/src/Validators/Rules/IsUrl.php +++ b/src/Validators/Rules/IsUrl.php @@ -24,7 +24,7 @@ public function validateRule(?string $cellValue): ?string return null; } - if (!\filter_var($cellValue, \FILTER_VALIDATE_URL)) { + if (\filter_var($cellValue, \FILTER_VALIDATE_URL) === false) { return "Value \"{$cellValue}\" is not a valid URL"; } diff --git a/src/Validators/Rules/IsUuid4.php b/src/Validators/Rules/IsUuid4.php index 0d6c48e8..fa755f4a 100644 --- a/src/Validators/Rules/IsUuid4.php +++ b/src/Validators/Rules/IsUuid4.php @@ -26,7 +26,7 @@ public function validateRule(?string $cellValue): ?string $uuid4 = '/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89ABab][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/'; - if (!\preg_match($uuid4, $cellValue)) { + if (\preg_match($uuid4, (string)$cellValue) === false) { return 'Value is not a valid UUID v4'; } diff --git a/src/Validators/Rules/Max.php b/src/Validators/Rules/Max.php index d056784c..65349ce5 100644 --- a/src/Validators/Rules/Max.php +++ b/src/Validators/Rules/Max.php @@ -21,7 +21,7 @@ final class Max extends IsFloat public function validateRule(?string $cellValue): ?string { $result = parent::validateRule($cellValue); - if ($result) { + if ($result !== '') { return $result; } diff --git a/src/Validators/Rules/MaxDate.php b/src/Validators/Rules/MaxDate.php index 5289aa20..addb827b 100644 --- a/src/Validators/Rules/MaxDate.php +++ b/src/Validators/Rules/MaxDate.php @@ -21,7 +21,7 @@ final class MaxDate extends AbstarctRule public function validateRule(?string $cellValue): ?string { $minDate = $this->getOptionAsDate(); - $cellDate = new \DateTimeImmutable($cellValue); + $cellDate = new \DateTimeImmutable((string)$cellValue); if ($cellDate > $minDate) { return "Value \"{$cellValue}\" is more than the maximum " . diff --git a/src/Validators/Rules/MaxLength.php b/src/Validators/Rules/MaxLength.php index 7b32ddc1..6cac2d8f 100644 --- a/src/Validators/Rules/MaxLength.php +++ b/src/Validators/Rules/MaxLength.php @@ -21,8 +21,9 @@ final class MaxLength extends AbstarctRule public function validateRule(?string $cellValue): ?string { $minLength = $this->getOptionAsInt(); - $length = \mb_strlen($cellValue); - if (\mb_strlen($cellValue) > $minLength) { + $length = \mb_strlen((string)$cellValue); + + if ($length > $minLength) { return "Value \"{$cellValue}\" (legth: {$length}) is too long. Max length is {$minLength}"; } diff --git a/src/Validators/Rules/Min.php b/src/Validators/Rules/Min.php index 937a3220..2285f964 100644 --- a/src/Validators/Rules/Min.php +++ b/src/Validators/Rules/Min.php @@ -21,7 +21,7 @@ final class Min extends IsFloat public function validateRule(?string $cellValue): ?string { $result = parent::validateRule($cellValue); - if ($result) { + if ($result !== '') { return $result; } diff --git a/src/Validators/Rules/MinDate.php b/src/Validators/Rules/MinDate.php index 813ad3b0..73bb3b39 100644 --- a/src/Validators/Rules/MinDate.php +++ b/src/Validators/Rules/MinDate.php @@ -21,7 +21,7 @@ final class MinDate extends AbstarctRule public function validateRule(?string $cellValue): ?string { $minDate = $this->getOptionAsDate(); - $cellDate = new \DateTimeImmutable($cellValue); + $cellDate = new \DateTimeImmutable((string)$cellValue); if ($cellDate < $minDate) { return "Value \"{$cellValue}\" is less than the minimum " . diff --git a/src/Validators/Rules/MinLength.php b/src/Validators/Rules/MinLength.php index 64892e9d..dab97808 100644 --- a/src/Validators/Rules/MinLength.php +++ b/src/Validators/Rules/MinLength.php @@ -21,7 +21,8 @@ final class MinLength extends AbstarctRule public function validateRule(?string $cellValue): ?string { $minLength = $this->getOptionAsInt(); - $length = \mb_strlen($cellValue); + $length = \mb_strlen((string)$cellValue); + if ($length < $minLength) { return "Value \"{$cellValue}\" (legth: {$length}) is too short. Min length is {$minLength}"; } diff --git a/src/Validators/Rules/OnlyTrimed.php b/src/Validators/Rules/OnlyTrimed.php index 125ea168..1da94d95 100644 --- a/src/Validators/Rules/OnlyTrimed.php +++ b/src/Validators/Rules/OnlyTrimed.php @@ -24,7 +24,7 @@ public function validateRule(?string $cellValue): ?string return null; } - if (\trim($cellValue) !== $cellValue) { + if (\trim((string)$cellValue) !== (string)$cellValue) { return "Value \"{$cellValue}\" is not trimmed"; } diff --git a/src/Validators/Rules/Regex.php b/src/Validators/Rules/Regex.php index 9245da3b..563025c5 100644 --- a/src/Validators/Rules/Regex.php +++ b/src/Validators/Rules/Regex.php @@ -23,7 +23,7 @@ final class Regex extends AbstarctRule public function validateRule(?string $cellValue): ?string { $regex = Utils::prepareRegex($this->getOptionAsString()); - if (!\preg_match($regex, $cellValue)) { + if (\preg_match((string)$regex, (string)$cellValue) === false) { return "Value \"{$cellValue}\" does not match the pattern \"{$regex}\""; } diff --git a/src/Validators/Rules/UsaMarketName.php b/src/Validators/Rules/UsaMarketName.php index 24f0abc3..206ec70b 100644 --- a/src/Validators/Rules/UsaMarketName.php +++ b/src/Validators/Rules/UsaMarketName.php @@ -24,7 +24,7 @@ public function validateRule(?string $cellValue): ?string return null; } - if (!\preg_match('/^[A-Za-z0-9\s-]+, [A-Z]{2}$/u', $cellValue)) { + if (\preg_match('/^[A-Za-z0-9\s-]+, [A-Z]{2}$/u', (string)$cellValue) === false) { return 'Invalid market name format for value "' . $cellValue . '". ' . 'Market name must have format "New York, NY"'; } diff --git a/src/Validators/Ruleset.php b/src/Validators/Ruleset.php index be2745d2..5d3a6b7e 100644 --- a/src/Validators/Ruleset.php +++ b/src/Validators/Ruleset.php @@ -36,16 +36,13 @@ public function __construct(array $rules, string $columnNameId) public function createRule(string $ruleName, null|array|bool|float|int|string $options = null): AbstarctRule { - if (\class_exists($ruleName)) { - return new $ruleName($this->columnNameId, $options); - } - $classname = __NAMESPACE__ . '\\Rules\\' . Utils::kebabToCamelCase($ruleName); if (\class_exists($classname)) { + // @phpstan-ignore-next-line return new $classname($this->columnNameId, $options); } - throw new Exception("Rule \"{$ruleName}\" not found. Expected class: {$classname}"); + throw new Exception("Rule \"{$ruleName}\" not found. Expected class: \"{$classname}\""); } public function validate(?string $cellValue, int $line): ErrorSuite diff --git a/src/Validators/Validator.php b/src/Validators/Validator.php index 486f4f5f..2d8c857c 100644 --- a/src/Validators/Validator.php +++ b/src/Validators/Validator.php @@ -20,7 +20,6 @@ final class Validator { - private Column $column; private Ruleset $ruleset; public function __construct(Column $column) @@ -28,9 +27,6 @@ public function __construct(Column $column) $this->ruleset = new Ruleset($column->getRules(), $column->getHumanName()); } - /** - * @return Error[] - */ public function validate(?string $cellValue, int $line): ErrorSuite { return $this->ruleset->validate($cellValue, $line); From b20e4ebfa5d6d23890ed59ac1eb3f67761b7ca18 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Mon, 11 Mar 2024 01:40:55 +0400 Subject: [PATCH 22/25] PHPStan fixed! --- src/Csv/CsvFile.php | 8 ++++---- src/Validators/Rules/IsDomain.php | 2 +- src/Validators/Rules/IsFloat.php | 2 +- src/Validators/Rules/IsInt.php | 2 +- src/Validators/Rules/IsLatitude.php | 2 +- src/Validators/Rules/IsLongitude.php | 2 +- src/Validators/Rules/IsUuid4.php | 2 +- src/Validators/Rules/Max.php | 2 +- src/Validators/Rules/Min.php | 2 +- src/Validators/Rules/Regex.php | 3 ++- src/Validators/Rules/UsaMarketName.php | 2 +- tests/Blueprint/ValidatorTest.php | 2 +- 12 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/Csv/CsvFile.php b/src/Csv/CsvFile.php index 10571795..473a485e 100644 --- a/src/Csv/CsvFile.php +++ b/src/Csv/CsvFile.php @@ -71,10 +71,10 @@ public function getRecords(): iterable return $this->reader->getRecords($this->getHeader()); } - // public function getRecordsChunk(int $offset = 0, int $limit = -1): \League\Csv\ResultSet - // { - // return Statement::create(null, $offset, $limit)->process($this->reader, $this->getHeader()); - // } + public function getRecordsChunk(int $offset = 0, int $limit = -1): \League\Csv\TabularDataReader + { + return Statement::create(null, $offset, $limit)->process($this->reader, $this->getHeader()); + } public function validate(bool $quickStop = false): ErrorSuite { diff --git a/src/Validators/Rules/IsDomain.php b/src/Validators/Rules/IsDomain.php index 0acb122b..88bea459 100644 --- a/src/Validators/Rules/IsDomain.php +++ b/src/Validators/Rules/IsDomain.php @@ -26,7 +26,7 @@ public function validateRule(?string $cellValue): ?string $domainPattern = '/^(?!-)[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(\.[A-Za-z]{2,})$/'; - if (\preg_match($domainPattern, (string)$cellValue) === false) { + if (\preg_match($domainPattern, (string)$cellValue) === 0) { return "Value \"{$cellValue}\" is not a valid domain"; } diff --git a/src/Validators/Rules/IsFloat.php b/src/Validators/Rules/IsFloat.php index a5d3c33a..74b61dea 100644 --- a/src/Validators/Rules/IsFloat.php +++ b/src/Validators/Rules/IsFloat.php @@ -24,7 +24,7 @@ public function validateRule(?string $cellValue): ?string return null; } - if (\preg_match('/^-?\d+(\.\d+)?$/', (string)$cellValue) === false) { + if (\preg_match('/^-?\d+(\.\d+)?$/', (string)$cellValue) === 0) { return "Value \"{$cellValue}\" is not a float number"; } diff --git a/src/Validators/Rules/IsInt.php b/src/Validators/Rules/IsInt.php index 2fe16a97..069fca8f 100644 --- a/src/Validators/Rules/IsInt.php +++ b/src/Validators/Rules/IsInt.php @@ -24,7 +24,7 @@ public function validateRule(?string $cellValue): ?string return null; } - if (\preg_match('/^-?\d+$/', (string)$cellValue) === false) { + if (\preg_match('/^-?\d+$/', (string)$cellValue) === 0) { return "Value \"{$cellValue}\" is not an integer"; } diff --git a/src/Validators/Rules/IsLatitude.php b/src/Validators/Rules/IsLatitude.php index 2fb40715..f3fc3db5 100644 --- a/src/Validators/Rules/IsLatitude.php +++ b/src/Validators/Rules/IsLatitude.php @@ -25,7 +25,7 @@ public function validateRule(?string $cellValue): ?string } $result = parent::validateRule($cellValue); - if ($result !== '') { + if ($result !== null) { return $result; } diff --git a/src/Validators/Rules/IsLongitude.php b/src/Validators/Rules/IsLongitude.php index aa785ca1..f9198c47 100644 --- a/src/Validators/Rules/IsLongitude.php +++ b/src/Validators/Rules/IsLongitude.php @@ -25,7 +25,7 @@ public function validateRule(?string $cellValue): ?string } $result = parent::validateRule($cellValue); - if ($result !== '') { + if ($result !== null) { return $result; } diff --git a/src/Validators/Rules/IsUuid4.php b/src/Validators/Rules/IsUuid4.php index fa755f4a..86a5b55b 100644 --- a/src/Validators/Rules/IsUuid4.php +++ b/src/Validators/Rules/IsUuid4.php @@ -26,7 +26,7 @@ public function validateRule(?string $cellValue): ?string $uuid4 = '/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89ABab][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/'; - if (\preg_match($uuid4, (string)$cellValue) === false) { + if (\preg_match($uuid4, (string)$cellValue) === 0) { return 'Value is not a valid UUID v4'; } diff --git a/src/Validators/Rules/Max.php b/src/Validators/Rules/Max.php index 65349ce5..9ce9b0a8 100644 --- a/src/Validators/Rules/Max.php +++ b/src/Validators/Rules/Max.php @@ -21,7 +21,7 @@ final class Max extends IsFloat public function validateRule(?string $cellValue): ?string { $result = parent::validateRule($cellValue); - if ($result !== '') { + if ($result !== null) { return $result; } diff --git a/src/Validators/Rules/Min.php b/src/Validators/Rules/Min.php index 2285f964..06ec2e21 100644 --- a/src/Validators/Rules/Min.php +++ b/src/Validators/Rules/Min.php @@ -21,7 +21,7 @@ final class Min extends IsFloat public function validateRule(?string $cellValue): ?string { $result = parent::validateRule($cellValue); - if ($result !== '') { + if ($result !== null) { return $result; } diff --git a/src/Validators/Rules/Regex.php b/src/Validators/Rules/Regex.php index 563025c5..057abde1 100644 --- a/src/Validators/Rules/Regex.php +++ b/src/Validators/Rules/Regex.php @@ -23,7 +23,8 @@ final class Regex extends AbstarctRule public function validateRule(?string $cellValue): ?string { $regex = Utils::prepareRegex($this->getOptionAsString()); - if (\preg_match((string)$regex, (string)$cellValue) === false) { + + if (\preg_match((string)$regex, (string)$cellValue) === 0) { return "Value \"{$cellValue}\" does not match the pattern \"{$regex}\""; } diff --git a/src/Validators/Rules/UsaMarketName.php b/src/Validators/Rules/UsaMarketName.php index 206ec70b..d570cfcc 100644 --- a/src/Validators/Rules/UsaMarketName.php +++ b/src/Validators/Rules/UsaMarketName.php @@ -24,7 +24,7 @@ public function validateRule(?string $cellValue): ?string return null; } - if (\preg_match('/^[A-Za-z0-9\s-]+, [A-Z]{2}$/u', (string)$cellValue) === false) { + if (\preg_match('/^[A-Za-z0-9\s-]+, [A-Z]{2}$/u', (string)$cellValue) === 0) { return 'Invalid market name format for value "' . $cellValue . '". ' . 'Market name must have format "New York, NY"'; } diff --git a/tests/Blueprint/ValidatorTest.php b/tests/Blueprint/ValidatorTest.php index 74fecdbe..8846c892 100644 --- a/tests/Blueprint/ValidatorTest.php +++ b/tests/Blueprint/ValidatorTest.php @@ -38,7 +38,7 @@ protected function setUp(): void public function testUndefinedRule(): void { $this->expectExceptionMessage( - 'Rule "undefined_rule" not found. Expected class: JBZoo\CsvBlueprint\Validators\Rules\UndefinedRule', + 'Rule "undefined_rule" not found. Expected class: "JBZoo\CsvBlueprint\Validators\Rules\UndefinedRule"', ); $csv = new CsvFile(self::CSV_COMPLEX, $this->getRule('seq', 'undefined_rule', true)); $csv->validate(); From 859231488a7c2dbe0fc1a4ce447bdbdf2d125898 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Mon, 11 Mar 2024 01:59:56 +0400 Subject: [PATCH 23/25] Phan fixed! --- src/Commands/CreateCsv.php | 3 +++ src/Commands/CreateSchema.php | 3 +++ src/Commands/ValidateCsv.php | 3 +++ src/Commands/ValidateDir.php | 3 +++ src/Csv/CsvFile.php | 5 +---- src/Schema.php | 5 +++++ src/Validators/ErrorSuite.php | 15 ++++++++------- src/Validators/Rules/Regex.php | 6 +++++- src/Validators/Rules/UsaMarketName.php | 2 +- src/Validators/Ruleset.php | 5 +++++ 10 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/Commands/CreateCsv.php b/src/Commands/CreateCsv.php index 7eac8231..0128051f 100644 --- a/src/Commands/CreateCsv.php +++ b/src/Commands/CreateCsv.php @@ -20,6 +20,9 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; +/** + * @psalm-suppress PropertyNotSetInConstructor + */ final class CreateCsv extends CliCommand { protected function configure(): void diff --git a/src/Commands/CreateSchema.php b/src/Commands/CreateSchema.php index 4b886d28..feadeafa 100644 --- a/src/Commands/CreateSchema.php +++ b/src/Commands/CreateSchema.php @@ -20,6 +20,9 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; +/** + * @psalm-suppress PropertyNotSetInConstructor + */ final class CreateSchema extends CliCommand { protected function configure(): void diff --git a/src/Commands/ValidateCsv.php b/src/Commands/ValidateCsv.php index d0b6c3e7..091396c3 100644 --- a/src/Commands/ValidateCsv.php +++ b/src/Commands/ValidateCsv.php @@ -23,6 +23,9 @@ use JBZoo\CsvBlueprint\Validators\ErrorSuite; use Symfony\Component\Console\Input\InputOption; +/** + * @psalm-suppress PropertyNotSetInConstructor + */ final class ValidateCsv extends CliCommand { protected function configure(): void diff --git a/src/Commands/ValidateDir.php b/src/Commands/ValidateDir.php index 4d4d6a7a..c3785aba 100644 --- a/src/Commands/ValidateDir.php +++ b/src/Commands/ValidateDir.php @@ -19,6 +19,9 @@ use JBZoo\Cli\CliCommand; use Symfony\Component\Console\Input\InputArgument; +/** + * @psalm-suppress PropertyNotSetInConstructor + */ final class ValidateDir extends CliCommand { protected function configure(): void diff --git a/src/Csv/CsvFile.php b/src/Csv/CsvFile.php index 473a485e..0c03d038 100644 --- a/src/Csv/CsvFile.php +++ b/src/Csv/CsvFile.php @@ -63,10 +63,7 @@ public function getHeader(): array return []; } - /** - * @return iterable|\League\Csv\MapIterator - */ - public function getRecords(): iterable + public function getRecords(): \Iterator { return $this->reader->getRecords($this->getHeader()); } diff --git a/src/Schema.php b/src/Schema.php index d4e12c9d..d3f7e084 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -44,7 +44,9 @@ public function __construct(null|array|string $csvSchemaFilenameOrArray = null) && \file_exists($csvSchemaFilenameOrArray) ) { $this->filename = $csvSchemaFilenameOrArray; + $this->data = new Data(); $fileExtension = \pathinfo($csvSchemaFilenameOrArray, \PATHINFO_EXTENSION); + if ($fileExtension === 'yml' || $fileExtension === 'yaml') { $this->data = yml($csvSchemaFilenameOrArray); } elseif ($fileExtension === 'json') { @@ -54,6 +56,9 @@ public function __construct(null|array|string $csvSchemaFilenameOrArray = null) } else { throw new \InvalidArgumentException("Unsupported file extension: {$fileExtension}"); } + } else { + $this->filename = null; + $this->data = new Data(); } $this->columns = $this->prepareColumns(); diff --git a/src/Validators/ErrorSuite.php b/src/Validators/ErrorSuite.php index f97c6119..35055d6b 100644 --- a/src/Validators/ErrorSuite.php +++ b/src/Validators/ErrorSuite.php @@ -51,13 +51,14 @@ public function render(string $mode = self::RENDER_TEXT): string return ''; } - $map = [ - self::RENDER_TEXT => fn () => $this->renderPlainText(), - self::RENDER_TABLE => fn () => $this->renderTable(), - self::RENDER_GITHUB => fn () => (new GithubCliConverter())->fromInternal($this->prepareSourceSuite()), - self::RENDER_GITLAB => fn () => (new GitLabJsonConverter())->fromInternal($this->prepareSourceSuite()), - self::RENDER_TEAMCITY => fn () => (new TeamCityTestsConverter())->fromInternal($this->prepareSourceSuite()), - self::RENDER_JUNIT => fn () => (new JUnitConverter())->fromInternal($this->prepareSourceSuite()), + $sourceSuite = $this->prepareSourceSuite(); + $map = [ + self::RENDER_TEXT => fn (): string => $this->renderPlainText(), + self::RENDER_TABLE => fn (): string => $this->renderTable(), + self::RENDER_GITHUB => static fn (): string => (new GithubCliConverter())->fromInternal($sourceSuite), + self::RENDER_GITLAB => static fn (): string => (new GitLabJsonConverter())->fromInternal($sourceSuite), + self::RENDER_TEAMCITY => static fn (): string => (new TeamCityTestsConverter())->fromInternal($sourceSuite), + self::RENDER_JUNIT => static fn (): string => (new JUnitConverter())->fromInternal($sourceSuite), ]; if (isset($map[$mode])) { diff --git a/src/Validators/Rules/Regex.php b/src/Validators/Rules/Regex.php index 057abde1..27b707ea 100644 --- a/src/Validators/Rules/Regex.php +++ b/src/Validators/Rules/Regex.php @@ -24,7 +24,11 @@ public function validateRule(?string $cellValue): ?string { $regex = Utils::prepareRegex($this->getOptionAsString()); - if (\preg_match((string)$regex, (string)$cellValue) === 0) { + if ($regex === null || $regex === '') { + return 'Regex pattern is not defined'; + } + + if (\preg_match($regex, (string)$cellValue) === 0) { return "Value \"{$cellValue}\" does not match the pattern \"{$regex}\""; } diff --git a/src/Validators/Rules/UsaMarketName.php b/src/Validators/Rules/UsaMarketName.php index d570cfcc..365bf4c5 100644 --- a/src/Validators/Rules/UsaMarketName.php +++ b/src/Validators/Rules/UsaMarketName.php @@ -25,7 +25,7 @@ public function validateRule(?string $cellValue): ?string } if (\preg_match('/^[A-Za-z0-9\s-]+, [A-Z]{2}$/u', (string)$cellValue) === 0) { - return 'Invalid market name format for value "' . $cellValue . '". ' . + return "Invalid market name format for value \"{$cellValue}\". " . 'Market name must have format "New York, NY"'; } diff --git a/src/Validators/Ruleset.php b/src/Validators/Ruleset.php index 5d3a6b7e..4f1565ce 100644 --- a/src/Validators/Ruleset.php +++ b/src/Validators/Ruleset.php @@ -28,12 +28,17 @@ final class Ruleset public function __construct(array $rules, string $columnNameId) { $this->columnNameId = $columnNameId; + $this->rules = []; foreach ($rules as $ruleName => $options) { $this->rules[] = $this->createRule($ruleName, $options); } } + /** + * @psalm-suppress MoreSpecificReturnType + * @psalm-suppress LessSpecificReturnStatement + */ public function createRule(string $ruleName, null|array|bool|float|int|string $options = null): AbstarctRule { $classname = __NAMESPACE__ . '\\Rules\\' . Utils::kebabToCamelCase($ruleName); From 36fa39ecf47e0d26a52743791332179a4d65c145 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Mon, 11 Mar 2024 02:25:18 +0400 Subject: [PATCH 24/25] Codestyle! --- .phan.php | 11 +++++++++++ composer.json | 18 +++++++++--------- composer.lock | 2 +- src/Csv/Column.php | 4 ++-- src/Csv/CsvFile.php | 8 ++++---- src/Schema.php | 6 +++--- src/Validators/Rules/MaxDate.php | 2 +- src/Validators/Rules/MinDate.php | 2 +- src/Validators/Rules/Precision.php | 11 ++++++----- src/Validators/Ruleset.php | 2 +- tests/Blueprint/CsvReaderTest.php | 4 ++-- 11 files changed, 41 insertions(+), 29 deletions(-) diff --git a/.phan.php b/.phan.php index 64820b43..8780395e 100644 --- a/.phan.php +++ b/.phan.php @@ -16,8 +16,19 @@ $default = include __DIR__ . '/vendor/jbzoo/codestyle/src/phan.php'; +// Remove SimplifyExpressionPlugin from plugin list +$default['plugins'] = \array_diff($default['plugins'], ['SimplifyExpressionPlugin']); + return \array_merge($default, [ 'directory_list' => [ 'src', + + 'vendor/jbzoo/data/src', + 'vendor/jbzoo/cli/src', + 'vendor/jbzoo/utils/src', + 'vendor/jbzoo/ci-report-converter/src', + 'vendor/league/csv/src', + 'vendor/fakerphp/faker/src', + 'vendor/symfony/console', ], ]); diff --git a/composer.json b/composer.json index b78cd5e6..ef26dcbe 100644 --- a/composer.json +++ b/composer.json @@ -26,15 +26,15 @@ "prefer-stable" : true, "require" : { - "php" : "^8.1", - "ext-mbstring" : "*", - - "jbzoo/data" : "^7.1", - "jbzoo/cli" : "^7.1", - "jbzoo/utils" : "^7.1", - "league/csv" : "^9.15", - "fakerphp/faker" : "^1.23", - "jbzoo/ci-report-converter": "^7.2" + "php" : "^8.1", + "ext-mbstring" : "*", + + "jbzoo/data" : "^7.1", + "jbzoo/cli" : "^7.1", + "jbzoo/utils" : "^7.1", + "jbzoo/ci-report-converter" : "^7.2", + "league/csv" : "^9.15", + "fakerphp/faker" : "^1.23" }, "require-dev" : { diff --git a/composer.lock b/composer.lock index e87cb2a4..b3c61e26 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "661fc4e2593d0d9598e69e2de68aca8f", + "content-hash": "aa2d64e2d78d191e4bc83a4ad90051b6", "packages": [ { "name": "bluepsyduck/symfony-process-manager", diff --git a/src/Csv/Column.php b/src/Csv/Column.php index 9096305f..1cc88b66 100644 --- a/src/Csv/Column.php +++ b/src/Csv/Column.php @@ -118,10 +118,10 @@ private function prepareRuleSet(string $schemaKey): array { $rules = []; - $ruleSetConfig = $this->column->getSelf($schemaKey, []); + $ruleSetConfig = $this->column->getSelf($schemaKey, [])->getArrayCopy(); foreach ($ruleSetConfig as $ruleName => $ruleValue) { - if (\str_starts_with($ruleName, 'custom_')) { + if (\str_starts_with((string)$ruleName, 'custom_')) { $rules[$ruleName] = \array_merge(['class' => '', 'args' => []], $ruleValue); } else { $rules[$ruleName] = $ruleValue; diff --git a/src/Csv/CsvFile.php b/src/Csv/CsvFile.php index 0c03d038..d39e35ac 100644 --- a/src/Csv/CsvFile.php +++ b/src/Csv/CsvFile.php @@ -43,7 +43,7 @@ public function __construct(string $csvFilename, null|array|string $csvSchemaFil public function getCsvFilename(): string { - return \str_replace(PROJECT_ROOT, '.', (string)\realpath($this->csvFilename)); + return \pathinfo((string)\realpath($this->csvFilename), \PATHINFO_BASENAME); } public function getCsvStructure(): ParseConfig @@ -79,7 +79,7 @@ public function validate(bool $quickStop = false): ErrorSuite $errors->addErrorSuit($this->validateHeader()) ->addErrorSuit($this->validateEachCell($quickStop)) - ->addErrorSuit($this->validateAggregateRules($quickStop)); + ->addErrorSuit(self::validateAggregateRules($quickStop)); return $errors; } @@ -137,7 +137,7 @@ private function validateEachCell(bool $quickStop = false): ErrorSuite continue; } - $errors->addErrorSuit($column->validate($record[$column->getKey()], $line + 1)); + $errors->addErrorSuit($column->validate($record[$column->getKey()], (int)$line + 1)); if ($quickStop && $errors->count() > 0) { return $errors; } @@ -147,7 +147,7 @@ private function validateEachCell(bool $quickStop = false): ErrorSuite return $errors; } - private function validateAggregateRules(bool $quickStop = false): ErrorSuite + private static function validateAggregateRules(bool $quickStop = false): ErrorSuite { $errors = new ErrorSuite(); diff --git a/src/Schema.php b/src/Schema.php index d3f7e084..acbe0b84 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -84,14 +84,14 @@ public function getColumns(): array /** * @return Column[]|null[] + * @phan-suppress PhanPartialTypeMismatchReturn */ public function getColumnsMappedByHeader(array $header): array { $map = []; foreach ($header as $headerName) { - $column = $this->columns[$headerName] ?? null; - $map[$headerName] = $column; + $map[$headerName] = $this->columns[$headerName] ?? null; } return $map; @@ -141,7 +141,7 @@ private function prepareColumns(): array $result = []; foreach ($this->data->getArray('columns') as $columnId => $columnPreferences) { - $column = new Column($columnId, $columnPreferences); + $column = new Column((int)$columnId, $columnPreferences); $result[$column->getKey()] = $column; } diff --git a/src/Validators/Rules/MaxDate.php b/src/Validators/Rules/MaxDate.php index addb827b..434ceeb2 100644 --- a/src/Validators/Rules/MaxDate.php +++ b/src/Validators/Rules/MaxDate.php @@ -23,7 +23,7 @@ public function validateRule(?string $cellValue): ?string $minDate = $this->getOptionAsDate(); $cellDate = new \DateTimeImmutable((string)$cellValue); - if ($cellDate > $minDate) { + if ($cellDate->getTimestamp() > $minDate->getTimestamp()) { return "Value \"{$cellValue}\" is more than the maximum " . "date \"{$minDate->format(\DATE_RFC3339_EXTENDED)}\""; } diff --git a/src/Validators/Rules/MinDate.php b/src/Validators/Rules/MinDate.php index 73bb3b39..225a4c43 100644 --- a/src/Validators/Rules/MinDate.php +++ b/src/Validators/Rules/MinDate.php @@ -23,7 +23,7 @@ public function validateRule(?string $cellValue): ?string $minDate = $this->getOptionAsDate(); $cellDate = new \DateTimeImmutable((string)$cellValue); - if ($cellDate < $minDate) { + if ($cellDate->getTimestamp() < $minDate->getTimestamp()) { return "Value \"{$cellValue}\" is less than the minimum " . "date \"{$minDate->format(\DATE_RFC3339_EXTENDED)}\""; } diff --git a/src/Validators/Rules/Precision.php b/src/Validators/Rules/Precision.php index 6fd59e70..e1bfe56c 100644 --- a/src/Validators/Rules/Precision.php +++ b/src/Validators/Rules/Precision.php @@ -20,16 +20,17 @@ final class Precision extends AbstarctRule { public function validateRule(?string $cellValue): ?string { - if ($this->getOptionAsInt() !== $this->getFloatPrecision($cellValue)) { - return "Value \"{$cellValue}\" has a precision of " . $this->getFloatPrecision( - $cellValue, - ) . ' but should have a precision of ' . $this->getOptionAsInt(); + $valuePrecision = self::getFloatPrecision($cellValue); + + if ($this->getOptionAsInt() !== $valuePrecision) { + return "Value \"{$cellValue}\" has a precision of {$valuePrecision} " . + "but should have a precision of {$this->getOptionAsInt()}"; } return null; } - private function getFloatPrecision(?string $cellValue): int + private static function getFloatPrecision(?string $cellValue): int { $floatAsString = (string)$cellValue; $dotPosition = \strpos($floatAsString, '.'); diff --git a/src/Validators/Ruleset.php b/src/Validators/Ruleset.php index 4f1565ce..42efff96 100644 --- a/src/Validators/Ruleset.php +++ b/src/Validators/Ruleset.php @@ -31,7 +31,7 @@ public function __construct(array $rules, string $columnNameId) $this->rules = []; foreach ($rules as $ruleName => $options) { - $this->rules[] = $this->createRule($ruleName, $options); + $this->rules[] = $this->createRule((string)$ruleName, $options); } } diff --git a/tests/Blueprint/CsvReaderTest.php b/tests/Blueprint/CsvReaderTest.php index 6ec33902..4291b685 100644 --- a/tests/Blueprint/CsvReaderTest.php +++ b/tests/Blueprint/CsvReaderTest.php @@ -32,7 +32,7 @@ final class CsvReaderTest extends PHPUnit public function testReadCsvFileWithoutHeader(): void { $csv = new CsvFile(self::CSV_SIMPLE_NO_HEADER, self::SCHEMA_SIMPLE_NO_HEADER); - isSame('./tests/fixtures/simple_no_header.csv', $csv->getCsvFilename()); + isSame('simple_no_header.csv', $csv->getCsvFilename()); isSame([], $csv->getHeader()); @@ -50,7 +50,7 @@ public function testReadCsvFileWithoutHeader(): void public function testReadCsvFileWithHeader(): void { $csv = new CsvFile(self::CSV_SIMPLE_HEADER, self::SCHEMA_SIMPLE_HEADER); - isSame('./tests/fixtures/simple_header.csv', $csv->getCsvFilename()); + isSame('simple_header.csv', $csv->getCsvFilename()); isSame(['seq', 'bool', 'exact'], $csv->getHeader()); From 7e41368a1e1b19a96add1790aa89d82aff5228f0 Mon Sep 17 00:00:00 2001 From: Denis Smet Date: Mon, 11 Mar 2024 02:30:35 +0400 Subject: [PATCH 25/25] Codestyle! --- src/Validators/Rules/IsLatitude.php | 7 +++++-- src/Validators/Rules/IsLongitude.php | 9 ++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Validators/Rules/IsLatitude.php b/src/Validators/Rules/IsLatitude.php index f3fc3db5..5085519e 100644 --- a/src/Validators/Rules/IsLatitude.php +++ b/src/Validators/Rules/IsLatitude.php @@ -18,6 +18,9 @@ final class IsLatitude extends IsFloat { + private float $min = -90.0; + private float $max = 90.0; + public function validateRule(?string $cellValue): ?string { if (!$this->getOptionAsBool()) { @@ -30,8 +33,8 @@ public function validateRule(?string $cellValue): ?string } $latitude = (float)$cellValue; - if ($latitude < -90.0 || $latitude > 90.0) { - return "Value \"{$cellValue}\" is not a valid latitude (-90 <= x <= 90)"; + if ($latitude < $this->min || $latitude > $this->max) { + return "Value \"{$cellValue}\" is not a valid latitude ({$this->min} <= x <= {$this->max})"; } return null; diff --git a/src/Validators/Rules/IsLongitude.php b/src/Validators/Rules/IsLongitude.php index f9198c47..97b06c28 100644 --- a/src/Validators/Rules/IsLongitude.php +++ b/src/Validators/Rules/IsLongitude.php @@ -18,6 +18,9 @@ final class IsLongitude extends IsFloat { + private float $min = -180.0; + private float $max = 180.0; + public function validateRule(?string $cellValue): ?string { if (!$this->getOptionAsBool()) { @@ -29,9 +32,9 @@ public function validateRule(?string $cellValue): ?string return $result; } - $latitude = (float)$cellValue; - if ($latitude < -180.0 || $latitude > 180.0) { - return "Value \"{$cellValue}\" is not a valid longitude (-180 <= x <= 180)"; + $longitude = (float)$cellValue; + if ($longitude < $this->min || $longitude > $this->max) { + return "Value \"{$cellValue}\" is not a valid longitude ({$this->min} <= x <= {$this->max})"; } return null;