diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 47c3096..cbe6308 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -5,6 +5,9 @@ on: [push]
jobs:
composer:
runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ php: [ 8.1, 8.2 ]
steps:
- uses: actions/checkout@v3
@@ -18,7 +21,7 @@ jobs:
- name: Composer install
uses: php-actions/composer@v6
with:
- php_version: 8.1
+ php_version: ${{ matrix.php }}
- name: Archive build
run: mkdir /tmp/github-actions/ && tar -cvf /tmp/github-actions/build.tar ./
@@ -31,7 +34,10 @@ jobs:
phpunit:
runs-on: ubuntu-latest
- needs: [composer]
+ needs: [ composer ]
+ strategy:
+ matrix:
+ php: [ 8.1, 8.2 ]
outputs:
coverage: ${{ steps.store-coverage.outputs.coverage_text }}
@@ -50,7 +56,7 @@ jobs:
env:
XDEBUG_MODE: cover
with:
- php_version: 8.1
+ php_version: ${{ matrix.php }}
php_extensions: xdebug
configuration: test/phpunit/phpunit.xml
bootstrap: vendor/autoload.php
@@ -83,7 +89,10 @@ jobs:
phpstan:
runs-on: ubuntu-latest
- needs: [composer]
+ needs: [ composer ]
+ strategy:
+ matrix:
+ php: [ 8.1, 8.2 ]
steps:
- uses: actions/download-artifact@v3
@@ -97,8 +106,56 @@ jobs:
- name: PHP Static Analysis
uses: php-actions/phpstan@v3
with:
+ php_version: ${{ matrix.php }}
path: src/
+ phpmd:
+ runs-on: ubuntu-latest
+ needs: [ composer ]
+ strategy:
+ matrix:
+ php: [ 8.1, 8.2 ]
+
+ steps:
+ - uses: actions/download-artifact@v3
+ with:
+ name: build-artifact
+ path: /tmp/github-actions
+
+ - name: Extract build archive
+ run: tar -xvf /tmp/github-actions/build.tar ./
+
+ - name: PHP Mess Detector
+ uses: php-actions/phpmd@v1
+ with:
+ php_version: ${{ matrix.php }}
+ path: src/
+ output: text
+ ruleset: phpmd.xml
+
+ phpcs:
+ runs-on: ubuntu-latest
+ needs: [ composer ]
+ strategy:
+ matrix:
+ php: [ 8.1, 8.2 ]
+
+ steps:
+ - uses: actions/download-artifact@v3
+ with:
+ name: build-artifact
+ path: /tmp/github-actions
+
+ - name: Extract build archive
+ run: tar -xvf /tmp/github-actions/build.tar ./
+
+ - name: PHP Code Sniffer
+ uses: php-actions/phpcs@v1
+ with:
+ php_version: ${{ matrix.php }}
+ path: src/
+ standard: phpcs.xml
+
remove_old_artifacts:
runs-on: ubuntu-latest
diff --git a/README.md b/README.md
index 0b6142f..e598af3 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ This repository performs W3C form validation for projects that have a [server-si
-
+
diff --git a/composer.json b/composer.json
index f2cfb32..bd16d36 100644
--- a/composer.json
+++ b/composer.json
@@ -15,8 +15,10 @@
},
"require-dev": {
- "phpunit/phpunit": "^10",
- "phpstan/phpstan": "^1.7.2"
+ "phpunit/phpunit": "^10.0",
+ "phpstan/phpstan": "^1.10",
+ "phpmd/phpmd": "^2.13",
+ "squizlabs/php_codesniffer": "^3.7"
},
"autoload": {
diff --git a/composer.lock b/composer.lock
index 81aea35..6b8c8a4 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": "33f917d4399beea20a2f8ca021a2b11e",
+ "content-hash": "be033f6c0674633bc8684f9c34f589d7",
"packages": [
{
"name": "phpgt/cssxpath",
@@ -62,16 +62,16 @@
},
{
"name": "phpgt/dom",
- "version": "v4.1.1",
+ "version": "v4.1.2",
"source": {
"type": "git",
"url": "https://github.com/PhpGt/Dom.git",
- "reference": "19ad48319273f725bcbfb9b2784cdf1deb1cf27f"
+ "reference": "3e1afe7b6d38e9968d400900a62cbe54753973a1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PhpGt/Dom/zipball/19ad48319273f725bcbfb9b2784cdf1deb1cf27f",
- "reference": "19ad48319273f725bcbfb9b2784cdf1deb1cf27f",
+ "url": "https://api.github.com/repos/PhpGt/Dom/zipball/3e1afe7b6d38e9968d400900a62cbe54753973a1",
+ "reference": "3e1afe7b6d38e9968d400900a62cbe54753973a1",
"shasum": ""
},
"require": {
@@ -79,13 +79,15 @@
"ext-libxml": "*",
"ext-mbstring": "*",
"php": ">=8.1",
- "phpgt/cssxpath": "^v1.1",
- "phpgt/propfunc": "^v1.0",
- "psr/http-message": "^v1.0"
+ "phpgt/cssxpath": "^1.1",
+ "phpgt/propfunc": "^1.0",
+ "psr/http-message": "^1.0"
},
"require-dev": {
- "phpstan/phpstan": "v1.8",
- "phpunit/phpunit": "v9.5"
+ "phpmd/phpmd": "^2.13",
+ "phpstan/phpstan": "^1.9",
+ "phpunit/phpunit": "^10.0",
+ "squizlabs/php_codesniffer": "^3.7"
},
"type": "library",
"autoload": {
@@ -148,7 +150,7 @@
"description": "The modern DOM API for PHP projects.",
"support": {
"issues": "https://github.com/PhpGt/Dom/issues",
- "source": "https://github.com/PhpGt/Dom/tree/v4.1.1"
+ "source": "https://github.com/PhpGt/Dom/tree/v4.1.2"
},
"funding": [
{
@@ -156,7 +158,7 @@
"type": "github"
}
],
- "time": "2022-09-25T10:57:14+00:00"
+ "time": "2023-03-02T18:40:07+00:00"
},
{
"name": "phpgt/propfunc",
@@ -265,6 +267,143 @@
}
],
"packages-dev": [
+ {
+ "name": "composer/pcre",
+ "version": "3.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/pcre.git",
+ "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/pcre/zipball/4bff79ddd77851fe3cdd11616ed3f92841ba5bd2",
+ "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2",
+ "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.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": "2022-11-17T09:50:14+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": "myclabs/deep-copy",
"version": "1.11.0",
@@ -380,6 +519,63 @@
},
"time": "2023-01-16T22:05:37+00:00"
},
+ {
+ "name": "pdepend/pdepend",
+ "version": "2.13.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/pdepend/pdepend.git",
+ "reference": "31be7cd4f305f3f7b52af99c1cb13fc938d1cfad"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/pdepend/pdepend/zipball/31be7cd4f305f3f7b52af99c1cb13fc938d1cfad",
+ "reference": "31be7cd4f305f3f7b52af99c1cb13fc938d1cfad",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.7",
+ "symfony/config": "^2.3.0|^3|^4|^5|^6.0",
+ "symfony/dependency-injection": "^2.3.0|^3|^4|^5|^6.0",
+ "symfony/filesystem": "^2.3.0|^3|^4|^5|^6.0"
+ },
+ "require-dev": {
+ "easy-doc/easy-doc": "0.0.0|^1.2.3",
+ "gregwar/rst": "^1.0",
+ "phpunit/phpunit": "^4.8.36|^5.7.27",
+ "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",
+ "support": {
+ "issues": "https://github.com/pdepend/pdepend/issues",
+ "source": "https://github.com/pdepend/pdepend/tree/2.13.0"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/pdepend/pdepend",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-02-28T20:56:15+00:00"
+ },
{
"name": "phar-io/manifest",
"version": "2.0.3",
@@ -491,18 +687,101 @@
},
"time": "2022-02-21T01:04:05+00:00"
},
+ {
+ "name": "phpmd/phpmd",
+ "version": "2.13.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpmd/phpmd.git",
+ "reference": "dad0228156856b3ad959992f9748514fa943f3e3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpmd/phpmd/zipball/dad0228156856b3ad959992f9748514fa943f3e3",
+ "reference": "dad0228156856b3ad959992f9748514fa943f3e3",
+ "shasum": ""
+ },
+ "require": {
+ "composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0",
+ "ext-xml": "*",
+ "pdepend/pdepend": "^2.12.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",
+ "phpunit/phpunit": "^4.8.36 || ^5.7.27",
+ "squizlabs/php_codesniffer": "^2.0"
+ },
+ "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": [
+ "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.13.0"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpmd/phpmd",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-09-10T08:44:15+00:00"
+ },
{
"name": "phpstan/phpstan",
- "version": "1.9.17",
+ "version": "1.10.3",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
- "reference": "204e459e7822f2c586463029f5ecec31bb45a1f2"
+ "reference": "5419375b5891add97dc74be71e6c1c34baaddf64"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/204e459e7822f2c586463029f5ecec31bb45a1f2",
- "reference": "204e459e7822f2c586463029f5ecec31bb45a1f2",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/5419375b5891add97dc74be71e6c1c34baaddf64",
+ "reference": "5419375b5891add97dc74be71e6c1c34baaddf64",
"shasum": ""
},
"require": {
@@ -532,7 +811,7 @@
],
"support": {
"issues": "https://github.com/phpstan/phpstan/issues",
- "source": "https://github.com/phpstan/phpstan/tree/1.9.17"
+ "source": "https://github.com/phpstan/phpstan/tree/1.10.3"
},
"funding": [
{
@@ -548,27 +827,27 @@
"type": "tidelift"
}
],
- "time": "2023-02-08T12:25:00+00:00"
+ "time": "2023-02-25T14:47:13+00:00"
},
{
"name": "phpunit/php-code-coverage",
- "version": "10.0.0",
+ "version": "10.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "bf4fbc9c13af7da12b3ea807574fb460f255daba"
+ "reference": "b9c21a93dd8c8eed79879374884ee733259475cc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/bf4fbc9c13af7da12b3ea807574fb460f255daba",
- "reference": "bf4fbc9c13af7da12b3ea807574fb460f255daba",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b9c21a93dd8c8eed79879374884ee733259475cc",
+ "reference": "b9c21a93dd8c8eed79879374884ee733259475cc",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
- "nikic/php-parser": "^4.14",
+ "nikic/php-parser": "^4.15",
"php": ">=8.1",
"phpunit/php-file-iterator": "^4.0",
"phpunit/php-text-template": "^3.0",
@@ -617,7 +896,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.0.0"
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.0.1"
},
"funding": [
{
@@ -625,7 +904,7 @@
"type": "github"
}
],
- "time": "2023-02-03T07:14:34+00:00"
+ "time": "2023-02-25T05:35:03+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -870,16 +1149,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "10.0.7",
+ "version": "10.0.14",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "a6f61c5629dd95db79af72f1e94d56702187479a"
+ "reference": "7065dbebcb0f66cf16a45fc9cfc28c2351e06169"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a6f61c5629dd95db79af72f1e94d56702187479a",
- "reference": "a6f61c5629dd95db79af72f1e94d56702187479a",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7065dbebcb0f66cf16a45fc9cfc28c2351e06169",
+ "reference": "7065dbebcb0f66cf16a45fc9cfc28c2351e06169",
"shasum": ""
},
"require": {
@@ -950,7 +1229,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/10.0.7"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/10.0.14"
},
"funding": [
{
@@ -966,7 +1245,110 @@
"type": "tidelift"
}
],
- "time": "2023-02-08T15:16:31+00:00"
+ "time": "2023-03-01T05:37:49+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": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
+ "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.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/3.0.0"
+ },
+ "time": "2021-07-14T16:46:02+00:00"
},
{
"name": "sebastian/cli-parser",
@@ -1876,6 +2258,681 @@
],
"time": "2023-02-07T11:34:05+00:00"
},
+ {
+ "name": "squizlabs/php_codesniffer",
+ "version": "3.7.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
+ "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879",
+ "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879",
+ "shasum": ""
+ },
+ "require": {
+ "ext-simplexml": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
+ },
+ "bin": [
+ "bin/phpcs",
+ "bin/phpcbf"
+ ],
+ "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": "lead"
+ }
+ ],
+ "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
+ "homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
+ "keywords": [
+ "phpcs",
+ "standards",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
+ "source": "https://github.com/squizlabs/PHP_CodeSniffer",
+ "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
+ },
+ "time": "2023-02-22T23:07:41+00:00"
+ },
+ {
+ "name": "symfony/config",
+ "version": "v6.2.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/config.git",
+ "reference": "249271da6f545d6579e0663374f8249a80be2893"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/config/zipball/249271da6f545d6579e0663374f8249a80be2893",
+ "reference": "249271da6f545d6579e0663374f8249a80be2893",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "symfony/deprecation-contracts": "^2.1|^3",
+ "symfony/filesystem": "^5.4|^6.0",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "conflict": {
+ "symfony/finder": "<5.4"
+ },
+ "require-dev": {
+ "symfony/event-dispatcher": "^5.4|^6.0",
+ "symfony/finder": "^5.4|^6.0",
+ "symfony/messenger": "^5.4|^6.0",
+ "symfony/service-contracts": "^1.1|^2|^3",
+ "symfony/yaml": "^5.4|^6.0"
+ },
+ "suggest": {
+ "symfony/yaml": "To use the yaml reference dumper"
+ },
+ "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.2.7"
+ },
+ "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-02-14T08:44:56+00:00"
+ },
+ {
+ "name": "symfony/dependency-injection",
+ "version": "v6.2.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/dependency-injection.git",
+ "reference": "83369dd4ec84bba9673524d25b79dfbde9e6e84c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/83369dd4ec84bba9673524d25b79dfbde9e6e84c",
+ "reference": "83369dd4ec84bba9673524d25b79dfbde9e6e84c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/container": "^1.1|^2.0",
+ "symfony/deprecation-contracts": "^2.1|^3",
+ "symfony/service-contracts": "^1.1.6|^2.0|^3.0",
+ "symfony/var-exporter": "^6.2.7"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2",
+ "symfony/config": "<6.1",
+ "symfony/finder": "<5.4",
+ "symfony/proxy-manager-bridge": "<6.2",
+ "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",
+ "symfony/expression-language": "^5.4|^6.0",
+ "symfony/yaml": "^5.4|^6.0"
+ },
+ "suggest": {
+ "symfony/config": "",
+ "symfony/expression-language": "For using expressions in service container configuration",
+ "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required",
+ "symfony/yaml": ""
+ },
+ "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.2.7"
+ },
+ "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-02-16T14:11:02+00:00"
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e",
+ "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.3-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.2.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-03-01T10:25:55+00:00"
+ },
+ {
+ "name": "symfony/filesystem",
+ "version": "v6.2.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/filesystem.git",
+ "reference": "82b6c62b959f642d000456f08c6d219d749215b3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/82b6c62b959f642d000456f08c6d219d749215b3",
+ "reference": "82b6c62b959f642d000456f08c6d219d749215b3",
+ "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.2.7"
+ },
+ "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-02-14T08:44:56+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.27.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "5bbc823adecdae860bb64756d639ecfec17b050a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a",
+ "reference": "5bbc823adecdae860bb64756d639ecfec17b050a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "provide": {
+ "ext-ctype": "*"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.27-dev"
+ },
+ "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.27.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": "2022-11-03T14:55:06+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.27.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
+ "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "provide": {
+ "ext-mbstring": "*"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.27-dev"
+ },
+ "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.27.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": "2022-11-03T14:55:06+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "a8c9cedf55f314f3a186041d19537303766df09a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a8c9cedf55f314f3a186041d19537303766df09a",
+ "reference": "a8c9cedf55f314f3a186041d19537303766df09a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/container": "^2.0"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2"
+ },
+ "suggest": {
+ "symfony/service-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.3-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.2.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-03-01T10:32:47+00:00"
+ },
+ {
+ "name": "symfony/var-exporter",
+ "version": "v6.2.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/var-exporter.git",
+ "reference": "86062dd0103530e151588c8f60f5b85a139f1442"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/var-exporter/zipball/86062dd0103530e151588c8f60f5b85a139f1442",
+ "reference": "86062dd0103530e151588c8f60f5b85a139f1442",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "symfony/var-dumper": "^5.4|^6.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.2.7"
+ },
+ "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-02-24T10:42:00+00:00"
+ },
{
"name": "theseer/tokenizer",
"version": "1.2.1",
diff --git a/example/01-shop.php b/example/01-shop.php
new file mode 100644
index 0000000..b313d8b
--- /dev/null
+++ b/example/01-shop.php
@@ -0,0 +1,82 @@
+
+
+
+HTML;
+
+function example(HTMLDocument $document, array $input) {
+ $validator = new Validator();
+ $form = $document->forms[0];
+
+ try {
+ $validator->validate($form, $input);
+ } catch(ValidationException $exception) {
+ foreach($validator->getLastErrorList() as $name => $message) {
+ $errorElement = $form->querySelector("[name=$name]");
+ $errorElement->parentNode->dataset->validationError = $message;
+ }
+ return;
+ }
+
+ echo "Payment succeeded!";
+ exit;
+}
+
+$document = new HTMLDocument($html);
+
+if(isset($_POST["do"]) && $_POST["do"] === "buy") {
+ example($document, $_POST);
+}
+
+echo $document;
diff --git a/phpcs.xml b/phpcs.xml
new file mode 100644
index 0000000..7eaee99
--- /dev/null
+++ b/phpcs.xml
@@ -0,0 +1,72 @@
+
+
+ ./src
+
+ Created from PHP.Gt/Styleguide
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
diff --git a/phpmd.xml b/phpmd.xml
new file mode 100644
index 0000000..404bc5d
--- /dev/null
+++ b/phpmd.xml
@@ -0,0 +1,63 @@
+
+
+ Custom ruleset
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ErrorList.php b/src/ErrorList.php
index 9157c6f..9111fd2 100644
--- a/src/ErrorList.php
+++ b/src/ErrorList.php
@@ -5,7 +5,7 @@
use Gt\Dom\Element;
use Iterator;
-/** @implements Iterator */
+/** @implements Iterator */
class ErrorList implements Countable, Iterator {
/** @var array */
protected array $errorArray;
@@ -38,9 +38,12 @@ public function valid():bool {
return isset($keys[$this->iteratorKey]);
}
- public function current():array {
+ public function current():string {
$keys = array_keys($this->errorArray);
- return $this->errorArray[$keys[$this->iteratorKey]];
+ return implode(
+ "; ",
+ array_unique($this->errorArray[$keys[$this->iteratorKey]] ?? [])
+ );
}
public function next():void {
diff --git a/src/Rule/Pattern.php b/src/Rule/Pattern.php
index 6a25114..4971d4c 100644
--- a/src/Rule/Pattern.php
+++ b/src/Rule/Pattern.php
@@ -8,8 +8,12 @@ class Pattern extends Rule {
"pattern",
];
- public function isValid(Element $element, string $value, array $inputKvp):bool {
- $pattern = "/" . $element->getAttribute("pattern") . "/";
+ public function isValid(
+ Element $element,
+ string $value,
+ array $inputKvp,
+ ):bool {
+ $pattern = "/" . $element->getAttribute("pattern") . "/u";
return (bool)preg_match($pattern, $value);
}
diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php
index e96d8cb..ce9f41c 100644
--- a/src/Rule/Rule.php
+++ b/src/Rule/Rule.php
@@ -19,7 +19,12 @@ public function getAttributes():array {
return $this->attributes;
}
- abstract public function isValid(Element $element, string $value, array $inputKvp):bool;
+ /** @param array $inputKvp */
+ abstract public function isValid(
+ Element $element,
+ string $value,
+ array $inputKvp,
+ ):bool;
abstract public function getHint(Element $element, string $value):string;
}
diff --git a/src/Rule/TypeDate.php b/src/Rule/TypeDate.php
index e71dfab..344eda6 100644
--- a/src/Rule/TypeDate.php
+++ b/src/Rule/TypeDate.php
@@ -20,63 +20,21 @@ class TypeDate extends Rule {
"type=time",
];
- public function isValid(Element $element, string $value, array $inputKvp):bool {
+ public function isValid(
+ Element $element,
+ string $value,
+ array $inputKvp
+ ):bool {
if($value === "") {
return true;
}
- $dateTime = null;
+ $dateTime = $this->extractDateTime(
+ $value,
+ $element->getAttribute("type")
+ );
- switch($element->getAttribute("type")) {
- case "date":
- $dateTime = DateTime::createFromFormat(
- self::FORMAT_DATE,
- $value
- );
- break;
-
- case "month":
- $dateTime = DateTime::createFromFormat(
- self::FORMAT_MONTH,
- $value
- );
- break;
-
- case "week":
- $success = preg_match(
- "/^(?P\d{4})-W(?P\d{1,2})$/",
- $value,
- $matches
- );
- if(!$success) {
- return false;
- }
-
- if($matches["week"] < 1 || $matches["week"] > 52) {
- return false;
- }
-
- $dateTime = new DateTime();
- $dateTime->setISODate((int)$matches["year"], (int)$matches["week"]);
-
- break;
-
- case "datetime-local":
- $dateTime = DateTime::createFromFormat(
- self::FORMAT_DATETIME_LOCAL,
- $value
- );
- break;
-
- case "time":
- $dateTime = DateTime::createFromFormat(
- self::FORMAT_TIME,
- $value
- );
- break;
- }
-
- return $dateTime !== false;
+ return !is_null($dateTime);
}
public function getHint(Element $element, string $value):string {
@@ -107,4 +65,43 @@ public function getHint(Element $element, string $value):string {
return "Field must be a $type in the format $format";
}
+
+ private function extractDateTime(
+ string $value,
+ string $type,
+ ):?DateTime {
+ if($type === "week") {
+ return $this->extractDateTimeWeek($value);
+ }
+ else {
+ $format = match($type) {
+ "date" => self::FORMAT_DATE,
+ "month" => self::FORMAT_MONTH,
+ "datetime-local" => self::FORMAT_DATETIME_LOCAL,
+ "time" => self::FORMAT_TIME,
+ default => "",
+ };
+
+ return DateTime::createFromFormat($format, $value) ?: null;
+ }
+ }
+
+ private function extractDateTimeWeek(string $value):?DateTime {
+ $success = preg_match(
+ "/^(?P\d{4})-W(?P\d{1,2})$/",
+ $value,
+ $matches
+ );
+ if(!$success) {
+ return null;
+ }
+
+ if($matches["week"] < 1 || $matches["week"] > 52) {
+ return null;
+ }
+
+ $dateTime = new DateTime();
+ $dateTime->setISODate((int)$matches["year"], (int)$matches["week"]);
+ return $dateTime;
+ }
}
diff --git a/src/Rule/TypeNumber.php b/src/Rule/TypeNumber.php
index c7ecc65..ab622d6 100644
--- a/src/Rule/TypeNumber.php
+++ b/src/Rule/TypeNumber.php
@@ -10,88 +10,139 @@ class TypeNumber extends Rule {
"type=range",
];
- public function isValid(Element $element, string $value, array $inputKvp):bool {
- if($min = $element->getAttribute("min") ?: null) {
- $min = (float)$min;
- }
- if($max = $element->getAttribute("max") ?: null) {
- $max = (float)$max;
- }
- if($step = $element->getAttribute("step") ?: null) {
- $step = (float)$step;
- }
-
+ public function isValid(
+ Element $element,
+ string $value,
+ array $inputKvp,
+ ):bool {
if($value === "") {
- $validity = true;
+ return true;
}
- elseif(is_numeric($value)) {
+
+ if(is_numeric($value)) {
$value = (float)$value;
- if(!is_null($min)
- && $value < $min) {
- $validity = false;
+ if(false === $this->isValidMin(
+ $element->getAttribute("min"),
+ $value,
+ )) {
+ return false;
}
- elseif(!is_null($max)
- && $value > $max) {
- $validity = false;
+ if(false === $this->isValidMax(
+ $element->getAttribute("max"),
+ $value,
+ )) {
+ return false;
}
- elseif(!is_null($step)) {
- if($min) {
- $validity = ($value - $min) % $step === 0;
- }
- else {
- $validity = $value % $step === 0;
- }
-
- }
- else {
- $validity = true;
+ if(false === $this->isValidStep(
+ $element->getAttribute("min"),
+ $element->getAttribute("step"),
+ $value,
+ )) {
+ return false;
}
}
else {
- $validity = false;
+ return false;
}
- return $validity;
+ return true;
}
public function getHint(Element $element, string $value):string {
- if($min = $element->getAttribute("min") ?: null) {
- $min = (float)$min;
+ if(!is_numeric($value)) {
+ return "Field must be a number";
}
- if($max = $element->getAttribute("max") ?: null) {
- $max = (float)$max;
+
+ $value = (float)$value;
+
+ if($message = $this->getHintMinMax(
+ $value,
+ $element->getAttribute("min"),
+ $element->getAttribute("max"),
+ )) {
+ return $message;
}
- if($step = $element->getAttribute("step") ?: null) {
- $step = (float)$step;
+
+ if($message = $this->getHintStep(
+ $value,
+ $element->getAttribute("min"),
+ $element->getAttribute("step"),
+ )) {
+ return $message;
}
- $hint = "";
- if(is_numeric($value)) {
- $value = (float)$value;
+ return "";
+ }
- if(!is_null($min)
- && $value < $min) {
- $hint = "Field value must not be less than $min";
+ private function getHintMinMax(
+ float $value,
+ ?string $min,
+ ?string $max,
+ ):?string {
+ if(!is_null($min)) {
+ if($value < $min) {
+ return "Field value must not be lower than $min";
}
- elseif(!is_null($max)
- && $value > $max) {
- $hint = "Field value must not be greater than $max";
+ }
+ if(!is_null($max)) {
+ if($value > $max) {
+ return "Field value must not be higher than $max";
}
- elseif(!is_null($step)) {
- if(!is_null($min)
- && ($value - $min) % $step !== 0) {
- $hint = "Field value must be $min plus a multiple of $step";
- }
- elseif($value % $step !== 0) {
- $hint = "Field value must be a multiple of $step";
- }
+ }
+
+ return null;
+ }
+
+ private function getHintStep(
+ float $value,
+ ?string $min,
+ ?string $step,
+ ):?string {
+ if(!is_null($min)) {
+ $min = (float)$min;
+
+ if(($value - $min) % $step !== 0) {
+ return "Field value must be $min plus a multiple of $step";
}
}
- else {
- $hint = "Field must be a number";
+
+ if($step && $value % $step !== 0) {
+ return "Field value must be a multiple of $step";
+ }
+
+ return null;
+ }
+
+ private function isValidMin(?string $min, float $value):bool {
+ if(is_null($min)) {
+ return true;
+ }
+ $min = (float)$min;
+
+ return $value >= $min;
+ }
+
+ private function isValidMax(?string $max, float $value):bool {
+ if(is_null($max)) {
+ return true;
+ }
+ $max = (float)$max;
+
+ return $value <= $max;
+ }
+
+ private function isValidStep(?string $min, ?string $step, float $value):bool {
+ if(!$step) {
+ return true;
+ }
+ $step = (float)$step;
+
+ if(is_null($min)) {
+ return $value % $step === 0;
}
+ $min = (float)$min;
- return $hint;
+ return ($value - $min) % $step === 0;
}
}
diff --git a/src/ValidationException.php b/src/ValidationException.php
index 498d777..4009c0f 100644
--- a/src/ValidationException.php
+++ b/src/ValidationException.php
@@ -3,4 +3,4 @@
use RuntimeException;
-class ValidationException extends RuntimeException {}
\ No newline at end of file
+class ValidationException extends RuntimeException {}
diff --git a/src/Validator.php b/src/Validator.php
index ff77097..e8c4b0c 100644
--- a/src/Validator.php
+++ b/src/Validator.php
@@ -3,6 +3,8 @@
use Gt\Dom\Element;
use Gt\DomValidation\Rule\Rule;
+use Stringable;
+use Traversable;
class Validator {
protected ?ValidationRules $rules;
@@ -16,35 +18,83 @@ public function __construct(?ValidationRules $rules = null) {
$this->rules = $rules;
}
- /** @param array $inputKvp Associative array of user input */
- public function validate(Element $form, array $inputKvp):void {
+ /** @param iterable $inputKvp Associative array of user input */
+ public function validate(Element $form, iterable|object $inputKvp):void {
$this->errorList = new ErrorList();
+ if(is_object($inputKvp)) {
+ $inputKvp = $this->convertObjectToKvp($inputKvp);
+ }
+
foreach($this->rules?->getAttributeRuleList() ?? [] as $attrString => $ruleArray) {
- /** @var Element $element */
- foreach($form->querySelectorAll("[$attrString]") as $element) {
- $name = $element->getAttribute("name");
-
- foreach($ruleArray as $rule) {
- if(!$rule->isValid($element, $inputKvp[$name] ?? "", $inputKvp)) {
- $this->errorList->add(
- $element,
- $rule->getHint($element, $inputKvp[$name] ?? "")
- );
- }
- }
- }
+ $this->buildErrorList(
+ $form,
+ $attrString,
+ $ruleArray,
+ $inputKvp
+ );
}
$errorCount = count($this->errorList);
if($errorCount > 0) {
$collectiveNoun = $errorCount === 1 ? "is" : "are";
$fieldWord = $errorCount === 1 ? "field" : "fields";
- throw new ValidationException("There $collectiveNoun $errorCount invalid $fieldWord");
+ throw new ValidationException(
+ "There $collectiveNoun $errorCount invalid $fieldWord"
+ );
}
}
public function getLastErrorList():ErrorList {
return $this->errorList;
}
+
+ /**
+ * @param array $ruleArray
+ * @param array $inputKvp
+ */
+ protected function buildErrorList(
+ Element $form,
+ int|string $attrString,
+ array $ruleArray,
+ array $inputKvp,
+ ): void {
+ /** @var Element $element */
+ foreach ($form->querySelectorAll("[$attrString]") as $element) {
+ $name = $element->getAttribute("name");
+
+ foreach ($ruleArray as $rule) {
+ if (!$rule->isValid($element, $inputKvp[$name] ?? "", $inputKvp)) {
+ $this->errorList->add(
+ $element,
+ $rule->getHint($element, $inputKvp[$name] ?? "")
+ );
+ }
+ }
+ }
+ }
+
+ /** @return array */
+ private function convertObjectToKvp(object $obj):array {
+ if(method_exists($obj, "asArray")) {
+ return $obj->asArray();
+ }
+
+ if($obj instanceof Traversable) {
+ return iterator_to_array($obj);
+ }
+
+ $array = [];
+ foreach(get_object_vars($obj) as $key => $value) {
+ if(is_scalar($value) || $value instanceof Stringable) {
+ $value = (string)$value;
+ }
+ else {
+ $value = "";
+ }
+
+ $array[$key] = $value;
+ }
+ return $array;
+ }
}
diff --git a/test/phpunit/FormValidatorTest.php b/test/phpunit/FormValidatorTest.php
deleted file mode 100644
index 0ea83b7..0000000
--- a/test/phpunit/FormValidatorTest.php
+++ /dev/null
@@ -1,78 +0,0 @@
-forms[0];
- $validator = new Validator();
-
- $exception = null;
-
- try {
- $validator->validate($form, [
- "username" => "g105b",
- "password" => "hunter222222",
- ]);
- }
- catch(ValidationException $exception) {
- }
-
- self::assertNull($exception);
- }
-
- /**
- * In this example, the password field will be invalid if it contains
- * the username.
- */
- public function testCustomRule() {
- $usernameNotWithinPasswordRule = new class extends Rule {
- public function isValid(Element $element, string $value, array $inputKvp):bool {
- if($element->type !== "password") {
- return true;
- }
-
- return !str_contains($value, $inputKvp["username"]);
- }
-
- public function getHint(Element $element, string $value):string {
- return "The password must not contain the username";
- }
- };
-
- $document = new HTMLDocument(Helper::HTML_USERNAME_PASSWORD);
- $form = $document->forms[0];
- $rules = new DefaultValidationRules();
- $rules->addRule($usernameNotWithinPasswordRule);
- $validator = new Validator($rules);
-
- $exception = null;
-
- try {
- $validator->validate($form, [
- "username" => "g105b",
- "password" => "g105b-password",
- ]);
- }
- catch(ValidationException $exception) {
- $errorArray = iterator_to_array($validator->getLastErrorList());
- self::assertCount(1, $errorArray);
- $passwordErrorArray = $errorArray["password"];
- self::assertContains(
- "The password must not contain the username",
- $passwordErrorArray,
- );
- }
-
- self::assertNotNull($exception);
- }
-}
diff --git a/test/phpunit/Rule/EmailTypeTest.php b/test/phpunit/Rule/EmailTypeTest.php
index 3d02f41..c010a1f 100644
--- a/test/phpunit/Rule/EmailTypeTest.php
+++ b/test/phpunit/Rule/EmailTypeTest.php
@@ -42,10 +42,10 @@ public function testEmailInvalid() {
catch(ValidationException) {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(1, $errorArray);
- $emailErrorArray = $errorArray["email"];
- self::assertContains(
+ $emailError = $errorArray["email"];
+ self::assertSame(
"Field must be an email address",
- $emailErrorArray
+ $emailError
);
}
}
diff --git a/test/phpunit/Rule/MaxLengthTest.php b/test/phpunit/Rule/MaxLengthTest.php
index f7d4087..ea59bf7 100644
--- a/test/phpunit/Rule/MaxLengthTest.php
+++ b/test/phpunit/Rule/MaxLengthTest.php
@@ -38,7 +38,7 @@ public function testMaxLength() {
}
catch(ValidationException $exception) {
$errorList = iterator_to_array($validator->getLastErrorList());
- self::assertContains(
+ self::assertSame(
"This field's value must not contain more than 120 characters",
$errorList["tweet"]
);
diff --git a/test/phpunit/Rule/MinLengthTest.php b/test/phpunit/Rule/MinLengthTest.php
index 20bd01d..8d70c2a 100644
--- a/test/phpunit/Rule/MinLengthTest.php
+++ b/test/phpunit/Rule/MinLengthTest.php
@@ -40,7 +40,7 @@ public function testMinLength() {
}
catch(ValidationException $exception) {
$errorList = iterator_to_array($validator->getLastErrorList());
- self::assertContains(
+ self::assertSame(
"This field's value must contain at least 12 characters",
$errorList["password"]
);
diff --git a/test/phpunit/Rule/PatternTest.php b/test/phpunit/Rule/PatternTest.php
index 55fb080..ada30f7 100644
--- a/test/phpunit/Rule/PatternTest.php
+++ b/test/phpunit/Rule/PatternTest.php
@@ -45,11 +45,10 @@ public function testPatternInvalid() {
catch(ValidationException $exception) {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(1, $errorArray);
- $creditCardErrorArray = $errorArray["credit-card"];
- self::assertCount(1, $creditCardErrorArray);
- self::assertEquals(
+ $creditCardError = $errorArray["credit-card"];
+ self::assertSame(
"This field does not match the required pattern",
- $creditCardErrorArray[0]
+ $creditCardError
);
}
}
@@ -68,15 +67,9 @@ public function testPatternWithMissingRequiredFields() {
catch(ValidationException $exception) {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(3, $errorArray);
- $expiryMonthErrorArray = $errorArray["expiry-month"];
- $expiryYearErrorArray = $errorArray["expiry-year"];
- $amountErrorArray = $errorArray["amount"];
- self::assertCount(1, $expiryMonthErrorArray);
- self::assertCount(1, $expiryYearErrorArray);
- self::assertCount(1, $amountErrorArray);
- self::assertEquals($expiryMonthErrorArray[0], "This field is required");
- self::assertEquals($expiryYearErrorArray[0], "This field is required");
- self::assertEquals($amountErrorArray[0], "This field is required");
+ self::assertEquals($errorArray["expiry-month"], "This field is required");
+ self::assertEquals($errorArray["expiry-year"], "This field is required");
+ self::assertEquals($errorArray["amount"], "This field is required");
}
}
@@ -93,8 +86,8 @@ public function testPatternTitleShown() {
}
catch(ValidationException $exception) {
$errorArray = iterator_to_array($validator->getLastErrorList());
- self::assertContains(
- "This field does not match the required pattern: The 16 digit number on the front of your card",
+ self::assertSame(
+ "This field is required; This field does not match the required pattern: The 16 digit number on the front of your card",
$errorArray["credit-card"]
);
}
diff --git a/test/phpunit/Rule/RequiredTest.php b/test/phpunit/Rule/RequiredTest.php
index 77b7f15..dccd421 100644
--- a/test/phpunit/Rule/RequiredTest.php
+++ b/test/phpunit/Rule/RequiredTest.php
@@ -42,9 +42,8 @@ public function testSimpleMissingRequiredInputErrorList() {
$validator->validate($form, ["username" => "g105b"]);
}
catch(ValidationException $exception) {
- foreach($validator->getLastErrorList() as $name => $errors) {
- self::assertIsArray($errors);
- self::assertContains("This field is required", $errors);
+ foreach($validator->getLastErrorList() as $error) {
+ self::assertSame("This field is required; This field's value must contain at least 12 characters", $error);
}
}
}
@@ -61,10 +60,9 @@ public function testSimpleEmptyRequiredInputErrorList() {
]);
}
catch(ValidationException $exception) {
- foreach($validator->getLastErrorList() as $name => $errors) {
- self::assertIsArray($errors);
- self::assertContains("This field is required", $errors);
- self::assertEquals("password", $name);
+ foreach($validator->getLastErrorList() as $name => $error) {
+ self::assertSame("This field is required; This field's value must contain at least 12 characters", $error);
+ self::assertSame("password", $name);
}
}
}
diff --git a/test/phpunit/Rule/SelectElementTest.php b/test/phpunit/Rule/SelectElementTest.php
index 9b8b16b..bd0738c 100644
--- a/test/phpunit/Rule/SelectElementTest.php
+++ b/test/phpunit/Rule/SelectElementTest.php
@@ -39,10 +39,9 @@ public function testSelectMissingRequired() {
catch(ValidationException) {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(1, $errorArray);
- $currencyErrorArray = $errorArray["currency"];
- self::assertContains(
+ self::assertSame(
"This field is required",
- $currencyErrorArray
+ $errorArray["currency"]
);
}
}
@@ -80,10 +79,9 @@ public function testSelectTextContentInvalid() {
catch(ValidationException) {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(1, $errorArray);
- $currencyErrorArray = $errorArray["sort"];
- self::assertContains(
+ self::assertSame(
"This field's value must match one of the available options",
- $currencyErrorArray
+ $errorArray["sort"]
);
}
}
@@ -122,10 +120,9 @@ public function testSelectValue_invalid() {
catch(ValidationException $exception) {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(1, $errorArray);
- $currencyErrorArray = $errorArray["connections"];
- self::assertContains(
+ self::assertSame(
"This field's value must match one of the available options",
- $currencyErrorArray
+ $errorArray["connections"]
);
}
}
@@ -144,20 +141,17 @@ public function testSelectTwoInvalidOptionsAndOneMissing() {
catch(ValidationException $exception) {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(3, $errorArray);
- $currencyErrorArray = $errorArray["currency"];
- $sortErrorArray = $errorArray["sort"];
- $connectionsErrorArray = $errorArray["connections"];
- self::assertContains(
+ self::assertSame(
"This field is required",
- $currencyErrorArray
+ $errorArray["currency"]
);
- self::assertContains(
+ self::assertSame(
"This field's value must match one of the available options",
- $sortErrorArray
+ $errorArray["sort"]
);
- self::assertContains(
+ self::assertSame(
"This field's value must match one of the available options",
- $connectionsErrorArray
+ $errorArray["connections"]
);
}
}
diff --git a/test/phpunit/Rule/TypeDateTest.php b/test/phpunit/Rule/TypeDateTest.php
index 339fcb2..c212e47 100644
--- a/test/phpunit/Rule/TypeDateTest.php
+++ b/test/phpunit/Rule/TypeDateTest.php
@@ -39,10 +39,9 @@ public function testTypeDateInvalid() {
catch(ValidationException $exception) {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(1, $errorArray);
- $dobErrorArray = $errorArray["dob"];
- self::assertContains(
+ self::assertSame(
"Field must be a date in the format Y-m-d",
- $dobErrorArray
+ $errorArray["dob"]
);
}
}
@@ -78,10 +77,9 @@ public function testTypeMonthInvalid() {
catch(ValidationException $exception) {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(1, $errorArray);
- $monthErrorArray = $errorArray["month"];
- self::assertContains(
+ self::assertSame(
"Field must be a month in the format Y-m",
- $monthErrorArray
+ $errorArray["month"]
);
}
}
@@ -118,7 +116,7 @@ public function testTypeWeekInvalid() {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(1, $errorArray);
$monthErrorArray = $errorArray["week"];
- self::assertContains(
+ self::assertSame(
"Field must be a week in the format Y-\WW",
$monthErrorArray
);
@@ -138,10 +136,9 @@ public function testTypeWeekOutOfBounds() {
catch(ValidationException $exception) {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(1, $errorArray);
- $monthErrorArray = $errorArray["week"];
- self::assertContains(
+ self::assertSame(
"Field must be a week in the format Y-\WW",
- $monthErrorArray
+ $errorArray["week"]
);
}
}
@@ -177,10 +174,9 @@ public function testTypeDatetimeLocalInvalid() {
catch(ValidationException $exception) {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(1, $errorArray);
- $monthErrorArray = $errorArray["datetime"];
- self::assertContains(
+ self::assertSame(
"Field must be a datetime-local in the format Y-m-d\TH:i",
- $monthErrorArray
+ $errorArray["datetime"]
);
}
}
@@ -216,11 +212,45 @@ public function testTypeTimeInvalid() {
catch(ValidationException $exception) {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(1, $errorArray);
- $timeErrorArray = $errorArray["time"];
- self::assertContains(
+ self::assertSame(
"Field must be a time in the format H:i",
- $timeErrorArray
+ $errorArray["time"]
);
}
}
+
+ public function testTypeAttributeMissing() {
+ $document = new HTMLDocument("");
+ $form = $document->forms[0];
+ $validator = new Validator();
+
+ $exception = null;
+ try {
+ $validator->validate($form, [
+ "time" => "3:37pm",
+ ]);
+ }
+ catch(ValidationException $exception) {}
+ self::assertNull($exception);
+ }
+
+ public function testTypeNotKnown() {
+ $document = new HTMLDocument(Helper::HTML_DATE_TIME);
+ $form = $document->forms[0];
+ $timeInput = $form->querySelector("[type='time']");
+ $timeInput->type = "unknown";
+
+ $validator = new Validator();
+
+ $exception = null;
+
+ try {
+ $validator->validate($form, [
+ "time" => "3:37pm",
+ ]);
+ }
+ catch(ValidationException $exception) {}
+
+ self::assertNull($exception);
+ }
}
diff --git a/test/phpunit/Rule/TypeNumberTest.php b/test/phpunit/Rule/TypeNumberTest.php
index 255d119..a1ada71 100644
--- a/test/phpunit/Rule/TypeNumberTest.php
+++ b/test/phpunit/Rule/TypeNumberTest.php
@@ -1,7 +1,9 @@
"100",
]);
}
- catch(ValidationException $exception) {
+ catch(ValidationException) {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(1, $errorArray);
- $expiryMonthError = $errorArray["expiry-month"];
- self::assertContains(
+ self::assertSame(
"Field must be a number",
- $expiryMonthError
+ $errorArray["expiry-month"]
);
}
}
@@ -131,10 +132,9 @@ public function testStepInvalid() {
catch(ValidationException $exception) {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(1, $errorArray);
- $step1Error = $errorArray["step1"];
- self::assertContains(
+ self::assertSame(
"Field value must be a multiple of 7",
- $step1Error
+ $errorArray["step1"]
);
}
}
@@ -188,10 +188,9 @@ public function testStepStartingFrom2Invalid() {
catch(ValidationException $exception) {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(1, $errorArray);
- $step1Error = $errorArray["step2"];
- self::assertContains(
+ self::assertSame(
"Field value must be 2 plus a multiple of 7",
- $step1Error
+ $errorArray["step2"]
);
}
}
@@ -203,16 +202,15 @@ public function testStepWithMinBust() {
try {
$validator->validate($form, [
- "step2" => -4,
+ "step2" => "-4",
]);
}
- catch(ValidationException $exception) {
+ catch(ValidationException) {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(1, $errorArray);
- $step1Error = $errorArray["step2"];
- self::assertContains(
- "Field value must not be less than 2",
- $step1Error
+ self::assertSame(
+ "Field value must not be lower than 2",
+ $errorArray["step2"]
);
}
}
@@ -229,8 +227,7 @@ public function testStepWithMax() {
"step3" => 7.2 * 3, // within range
]);
}
- catch(ValidationException $exception) {
- }
+ catch(ValidationException $exception) {}
self::assertNull($exception);
}
@@ -248,10 +245,9 @@ public function testStepWithMaxBust() {
catch(ValidationException $exception) {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(1, $errorArray);
- $step1Error = $errorArray["step3"];
- self::assertContains(
- "Field value must not be greater than 25.1",
- $step1Error
+ self::assertSame(
+ "Field value must not be higher than 25.1",
+ $errorArray["step3"]
);
}
}
@@ -284,14 +280,21 @@ public function testStepWithDecimalStartBust() {
"step4" => 3.5 + (7.2 * 4),
]);
}
- catch(ValidationException $exception) {
+ catch(ValidationException) {
$errorArray = iterator_to_array($validator->getLastErrorList());
- self::assertCount(1, $errorArray);
- $step1Error = $errorArray["step4"];
- self::assertContains(
- "Field value must not be greater than 25.1",
- $step1Error
+ self::assertSame(
+ "Field value must not be higher than 25.1",
+ $errorArray["step4"]
);
}
}
+
+ public function testGetHint_ok() {
+ $element = self::createMock(Element::class);
+ $element->method("getAttribute")
+ ->willReturn(null);
+
+ $sut = new TypeNumber();
+ self::assertEmpty($sut->getHint($element, 1));
+ }
}
diff --git a/test/phpunit/Rule/RadioElementTest.php b/test/phpunit/Rule/TypeRadioTest.php
similarity index 78%
rename from test/phpunit/Rule/RadioElementTest.php
rename to test/phpunit/Rule/TypeRadioTest.php
index b0afd60..d962e21 100644
--- a/test/phpunit/Rule/RadioElementTest.php
+++ b/test/phpunit/Rule/TypeRadioTest.php
@@ -1,13 +1,16 @@
forms[0];
@@ -38,11 +41,9 @@ public function testRadioMissingRequired() {
}
catch(ValidationException) {
$errorArray = iterator_to_array($validator->getLastErrorList());
- self::assertCount(1, $errorArray);
- $currencyErrorArray = $errorArray["currency"];
- self::assertContains(
+ self::assertSame(
"This field is required",
- $currencyErrorArray
+ $errorArray["currency"],
);
}
}
@@ -80,11 +81,20 @@ public function testRadioTextContentInvalid() {
catch(ValidationException) {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(1, $errorArray);
- $currencyErrorArray = $errorArray["sort"];
- self::assertContains(
+ self::assertSame(
"This field's value must match one of the available options",
- $currencyErrorArray
+ $errorArray["sort"]
);
}
}
+
+ public function testIsValid_noForm() {
+ $document = new HTMLDocument();
+ $element = $document->createElement("input");
+ $element->type = "radio";
+ $sut = new TypeRadio();
+
+ $validity = $sut->isValid($element, "anything", []);
+ self::assertTrue($validity);
+ }
}
diff --git a/test/phpunit/Rule/UrlTypeTest.php b/test/phpunit/Rule/UrlTypeTest.php
index aa4bd34..53ec791 100644
--- a/test/phpunit/Rule/UrlTypeTest.php
+++ b/test/phpunit/Rule/UrlTypeTest.php
@@ -41,10 +41,9 @@ public function testUrlInvalid() {
catch(ValidationException) {
$errorArray = iterator_to_array($validator->getLastErrorList());
self::assertCount(1, $errorArray);
- $emailErrorArray = $errorArray["website"];
- self::assertContains(
+ self::assertSame(
"Field must be a URL",
- $emailErrorArray
+ $errorArray["website"]
);
}
}
diff --git a/test/phpunit/ValidatorTest.php b/test/phpunit/ValidatorTest.php
new file mode 100644
index 0000000..fbd44bb
--- /dev/null
+++ b/test/phpunit/ValidatorTest.php
@@ -0,0 +1,157 @@
+forms[0];
+ $validator = new Validator();
+
+ $exception = null;
+
+ try {
+ $validator->validate($form, [
+ "username" => "g105b",
+ "password" => "hunter222222",
+ ]);
+ }
+ catch(ValidationException $exception) {
+ }
+
+ self::assertNull($exception);
+ }
+
+ /**
+ * In this example, the password field will be invalid if it contains
+ * the username.
+ */
+ public function testCustomRule() {
+ $usernameNotWithinPasswordRule = new class extends Rule {
+ public function isValid(Element $element, string $value, array $inputKvp):bool {
+ if($element->type !== "password") {
+ return true;
+ }
+
+ return !str_contains($value, $inputKvp["username"]);
+ }
+
+ public function getHint(Element $element, string $value):string {
+ return "The password must not contain the username";
+ }
+ };
+
+ $document = new HTMLDocument(Helper::HTML_USERNAME_PASSWORD);
+ $form = $document->forms[0];
+ $rules = new DefaultValidationRules();
+ $rules->addRule($usernameNotWithinPasswordRule);
+ $validator = new Validator($rules);
+
+ $exception = null;
+
+ try {
+ $validator->validate($form, [
+ "username" => "g105b",
+ "password" => "g105b-password",
+ ]);
+ }
+ catch(ValidationException $exception) {
+ $errorArray = iterator_to_array($validator->getLastErrorList());
+ self::assertCount(1, $errorArray);
+ self::assertSame(
+ "The password must not contain the username",
+ $errorArray["password"],
+ );
+ }
+
+ self::assertNotNull($exception);
+ }
+
+ public function testValidate_objectAsArray():void {
+ $kvpInput = [
+ "name" => "Andrew Chi-Chih Yao",
+ "dob" => "1946-12-24",
+ ];
+
+ /** @var ArrayIterator|MockObject $inputObject */
+ $inputObject = self::getMockBuilder(ArrayIterator::class)
+ ->addMethods(["asArray"])
+ ->getMock();
+ $inputObject->expects(self::once())
+ ->method("asArray")
+ ->willReturn($kvpInput);
+
+ $form = self::createMock(Element::class);
+
+ $sut = new Validator();
+ $sut->validate($form, $inputObject);
+ }
+
+ public function testValidate_kvpObject():void {
+ $document = new HTMLDocument(Helper::HTML_USERNAME_PASSWORD);
+ $form = $document->forms[0];
+ $validator = new Validator();
+ $input = new stdClass();
+ $input->username = "g105b";
+ $input->password = "hunter222222";
+
+ $exception = null;
+
+ try {
+ $validator->validate($form, $input);
+ }
+ catch(ValidationException $exception) {}
+
+ self::assertNull($exception);
+ }
+
+ public function testValidate_kvpTraversible():void {
+ $document = new HTMLDocument(Helper::HTML_USERNAME_PASSWORD);
+ $form = $document->forms[0];
+ $validator = new Validator();
+ $input = new class([
+ "username" => "g105b",
+ "password" => "hunter222222",
+ ]) extends ArrayIterator {};
+
+ $exception = null;
+
+ try {
+ $validator->validate($form, $input);
+ }
+ catch(ValidationException $exception) {}
+
+ self::assertNull($exception);
+ }
+
+ public function testValidate_kvpObject_notStringable():void {
+ $document = new HTMLDocument(Helper::HTML_USERNAME_PASSWORD);
+ $form = $document->forms[0];
+ $validator = new Validator();
+ $input = new stdClass();
+ $input->username = "g105b";
+ $input->password = "hunter222222";
+ $input->somethingElse = new stdClass();
+
+ $exception = null;
+
+ try {
+ $validator->validate($form, $input);
+ }
+ catch(ValidationException $exception) {}
+
+ self::assertNull($exception);
+ }
+}