├── .github
├── CODEOWNERS
└── dependabot.yml
├── .gitignore
├── docker-compose.yml
├── src
├── DomainNameValidator.php
├── StringList.php
├── SucceedingDomainNameValidator.php
├── ArrayBasedStringList.php
├── Validators
│ ├── IntegerValueValidator.php
│ ├── SucceedingEmailValidator.php
│ ├── RequiredFieldValidator.php
│ ├── StringLengthValidator.php
│ ├── AllowedValuesValidator.php
│ ├── AmountPolicyValidator.php
│ ├── EmailValidator.php
│ ├── TextPolicyValidator.php
│ └── AddressValidator.php
├── ConstraintViolation.php
├── ValidationResponse.php
└── ValidationResult.php
├── .scrutinizer.yml
├── tests
├── bootstrap.php
├── Unit
│ ├── Validators
│ │ ├── IntegerValueValidatorTest.php
│ │ ├── AllowedValuesValidatorTest.php
│ │ ├── AmountPolicyValidatorTest.php
│ │ ├── EmailValidatorTest.php
│ │ └── AddressValidatorTest.php
│ ├── ValidationResultTest.php
│ └── ConstraintViolationTest.php
└── System
│ └── TextPolicyValidatorTest.php
├── phpcs.xml
├── phpunit.xml.dist
├── composer.json
├── .woodpecker
└── ci.yml
├── Makefile
├── README.md
├── COPYING.txt
└── composer.lock
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # This marks the default owner group of all files in this repository
2 | * @wmde/funtech-core
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | !.*
3 |
4 | composer.phar
5 |
6 | vendor/
7 | coverage/
8 |
9 | .idea/
10 |
11 | .phpunit.result.cache
12 | .phpunit.cache
13 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | fun-validators:
3 | image: "registry.gitlab.com/fun-tech/fundraising-frontend-docker:latest"
4 | volumes:
5 | - ./:/usr/src/app
6 | working_dir: /usr/src/app
7 |
--------------------------------------------------------------------------------
/src/DomainNameValidator.php:
--------------------------------------------------------------------------------
1 | arrayOfString;
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | src/
9 | tests/
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/Validators/IntegerValueValidator.php:
--------------------------------------------------------------------------------
1 | $maxLength ) {
14 | return new ValidationResult( new ConstraintViolation( $value, 'incorrect_length' ) );
15 | }
16 |
17 | return new ValidationResult();
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
15 | tests/Unit
16 |
17 |
18 | tests/System
19 |
20 |
21 |
22 |
23 | src
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/Validators/AllowedValuesValidator.php:
--------------------------------------------------------------------------------
1 | allowedValues, true ) ) {
28 | return new ValidationResult();
29 | }
30 | return new ValidationResult( new ConstraintViolation( $value, 'Not an allowed value' ) );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wmde/fun-validators",
3 | "license": "GPL-2.0-or-later",
4 | "description": "General and shared validation services created as part of the WMDE fundraising software",
5 | "require": {
6 | "php": ">=8.4"
7 | },
8 | "require-dev": {
9 | "phpunit/phpunit": "~12.0",
10 | "wmde/fundraising-phpcs": "~13.0",
11 | "phpstan/phpstan": "^2.1"
12 | },
13 | "repositories": [
14 | {
15 | "type": "vcs",
16 | "url": "https://github.com/wmde/fundraising-phpcs",
17 | "no-api": true
18 | }
19 | ],
20 | "autoload": {
21 | "psr-4": {
22 | "WMDE\\FunValidators\\": "src/"
23 | }
24 | },
25 | "autoload-dev": {
26 | "psr-4": {
27 | "WMDE\\FunValidators\\Tests\\": "tests/"
28 | }
29 | },
30 | "extra": {
31 | "branch-alias": {
32 | "dev-master": "1.0.x-dev"
33 | }
34 | },
35 | "config": {
36 | "allow-plugins": {
37 | "dealerdirect/phpcodesniffer-composer-installer": true
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Unit/Validators/IntegerValueValidatorTest.php:
--------------------------------------------------------------------------------
1 | assertTrue( $validator->validate( '1234567890' )->isSuccessful() );
17 | $this->assertTrue( $validator->validate( '000123456789' )->isSuccessful() );
18 | }
19 |
20 | public function testGivenInvalidValues_validationFails(): void {
21 | $validator = new IntegerValueValidator();
22 | $this->assertFalse( $validator->validate( '-1234567890' )->isSuccessful() );
23 | $this->assertFalse( $validator->validate( '21391e213123' )->isSuccessful() );
24 | $this->assertFalse( $validator->validate( '21391e213123' )->isSuccessful() );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/ConstraintViolation.php:
--------------------------------------------------------------------------------
1 | value;
25 | }
26 |
27 | public function getMessageIdentifier(): string {
28 | return $this->messageIdentifier;
29 | }
30 |
31 | public function getSource(): string {
32 | return $this->source;
33 | }
34 |
35 | public function setSource( string $source ): void {
36 | if ( $this->source && $this->source !== $source ) {
37 | trigger_error( 'Source is already set, overwriting it will be a logic exception in the future', E_USER_DEPRECATED );
38 | }
39 | $this->source = $source;
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/ValidationResponse.php:
--------------------------------------------------------------------------------
1 | validationErrors;
39 | }
40 |
41 | public function isSuccessful(): bool {
42 | return count( $this->validationErrors ) == 0;
43 | }
44 |
45 | public function needsModeration(): bool {
46 | return $this->needsModerationValue;
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/Validators/AmountPolicyValidator.php:
--------------------------------------------------------------------------------
1 | isOneTimeAmountTooHigh( $amount, $interval ) ||
22 | $this->isAnuallyRecurringAmountTooHigh( $amount, $interval ) ) {
23 | return new ValidationResult( new ConstraintViolation( $amount, self::VIOLATION_TOO_HIGH ) );
24 | }
25 |
26 | return new ValidationResult();
27 | }
28 |
29 | private function isOneTimeAmountTooHigh( float $amount, int $interval ): bool {
30 | if ( $interval === 0 ) {
31 | return $amount >= $this->maxAmountOneTime;
32 | }
33 | return false;
34 | }
35 |
36 | private function isAnuallyRecurringAmountTooHigh( float $amount, int $interval ): bool {
37 | if ( $interval > 0 ) {
38 | return ( 12 / $interval ) * $amount >= $this->maxAmountRecurringAnnually;
39 | }
40 | return false;
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/tests/Unit/Validators/AllowedValuesValidatorTest.php:
--------------------------------------------------------------------------------
1 | expectException( UnexpectedValueException::class );
17 | new AllowedValuesValidator( [] );
18 | }
19 |
20 | public function testGivenAllowedValues_theyAreAccepted(): void {
21 | $validator = new AllowedValuesValidator( [ 'kittens', 'unicorns' ] );
22 | $this->assertTrue( $validator->validate( 'kittens' )->isSuccessful() );
23 | $this->assertTrue( $validator->validate( 'unicorns' )->isSuccessful() );
24 |
25 | $this->assertFalse( $validator->validate( 'dragons' )->isSuccessful() );
26 | }
27 |
28 | public function testAllowedValuesAreCheckedStrictly(): void {
29 | $validator = new AllowedValuesValidator( [ '1', '2' ] );
30 | $this->assertTrue( $validator->validate( '1' )->isSuccessful() );
31 | $this->assertFalse( $validator->validate( 1 )->isSuccessful() );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/.woodpecker/ci.yml:
--------------------------------------------------------------------------------
1 | clone:
2 | git:
3 | image: woodpeckerci/plugin-git
4 | settings:
5 | # "lfs: false" disables downloading resources from LFS, which we don't use
6 | lfs: false
7 |
8 | matrix:
9 | CONTAINER_IMAGE:
10 | - registry.gitlab.com/fun-tech/fundraising-frontend-docker:latest
11 | # Use the following image in future (Jan/Feb 2026)
12 | # - registry.gitlab.com/fun-tech/fundraising-frontend-docker:php-8.5
13 |
14 | steps:
15 | - name: build
16 | when:
17 | - event: [ push, pull_request, cron, manual ]
18 | image: ${CONTAINER_IMAGE}
19 | environment:
20 | COMPOSER_CACHE_DIR: /composer_cache
21 | GITHUB_TOKEN:
22 | from_secret: github_token
23 | volumes:
24 | - /tmp/woodpeckerci/cache:/composer_cache
25 | commands:
26 | # Environment setup
27 | - echo -e "machine github.com\n login $GITHUB_TOKEN" > ~/.netrc
28 | - composer config -g github-oauth.github.com "$GITHUB_TOKEN"
29 | - composer install --prefer-dist --no-progress --no-interaction
30 | # CI
31 | - vendor/bin/phpcs
32 | - php -d xdebug.mode=coverage vendor/bin/phpunit --coverage-clover coverage.xml
33 | - php -d memory_limit=1G vendor/bin/phpstan analyse --level=9 --no-progress src/ tests/
34 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | current_user := $(shell id -u)
2 | current_group := $(shell id -g)
3 | BUILD_DIR := $(PWD)
4 | DOCKER_FLAGS := --interactive --tty
5 | DOCKER_IMAGE := registry.gitlab.com/fun-tech/fundraising-frontend-docker
6 | COVERAGE_FLAGS := --coverage-html coverage
7 |
8 | install-php:
9 | docker run --rm $(DOCKER_FLAGS) --volume $(BUILD_DIR):/app -w /app --volume ~/.composer:/composer --user $(current_user):$(current_group) $(DOCKER_IMAGE) composer install $(COMPOSER_FLAGS)
10 |
11 | update-php:
12 | docker run --rm $(DOCKER_FLAGS) --volume $(BUILD_DIR):/app -w /app --volume ~/.composer:/composer --user $(current_user):$(current_group) $(DOCKER_IMAGE) composer update $(COMPOSER_FLAGS)
13 |
14 | ci: phpunit cs stan
15 |
16 | ci-with-coverage: phpunit-with-coverage cs stan
17 |
18 | phpunit:
19 | docker compose run --rm fun-validators ./vendor/bin/phpunit
20 |
21 | phpunit-with-coverage:
22 | docker compose run --rm -e XDEBUG_MODE=coverage fun-validators ./vendor/bin/phpunit $(COVERAGE_FLAGS)
23 |
24 | cs:
25 | docker compose run --rm fun-validators ./vendor/bin/phpcs
26 |
27 | fix-cs:
28 | docker compose run --rm fun-validators ./vendor/bin/phpcbf
29 |
30 | stan:
31 | docker compose run --rm fun-validators ./vendor/bin/phpstan analyse --level=9 --no-progress src/ tests/
32 |
33 | .PHONY: install-php update-php ci ci-with-coverage phpunit phpunit-with-coverage cs fix-cs stan
34 |
--------------------------------------------------------------------------------
/src/ValidationResult.php:
--------------------------------------------------------------------------------
1 | violations = $violations;
16 | }
17 |
18 | public function isSuccessful(): bool {
19 | return empty( $this->violations );
20 | }
21 |
22 | public function hasViolations(): bool {
23 | return !empty( $this->violations );
24 | }
25 |
26 | /**
27 | * @return ConstraintViolation[]
28 | */
29 | public function getViolations(): array {
30 | return $this->violations;
31 | }
32 |
33 | /**
34 | * Set source of an error in all violations.
35 | *
36 | * This method is meant for validation results that come from one source,
37 | * e.g. a validator like {@see WMDE\FunValidators\Validators\RequiredFieldValidator}
38 | * or {@see WMDE\FunValidators\Validators\StringLengthValidator}
39 | *
40 | * DO NOT call it on validation results that come from validators
41 | * that validate several sources. In those cases, the responsibility to
42 | * set the source is with the validator and not its calling code.
43 | */
44 | public function setSourceForAllViolations( string $sourceName ): self {
45 | foreach ( $this->violations as $violation ) {
46 | $violation->setSource( $sourceName );
47 | }
48 | return $this;
49 | }
50 |
51 | public function getFirstViolation(): ?ConstraintViolation {
52 | return $this->violations[0] ?? null;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Validators/EmailValidator.php:
--------------------------------------------------------------------------------
1 | domainValidator->isValid( $normalizedDomain ) ) {
39 | return new ValidationResult(
40 | new ConstraintViolation( $emailAddress, 'email_address_domain_record_not_found' )
41 | );
42 | }
43 |
44 | return new ValidationResult();
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # fun-validators
2 |
3 | [](https://travis-ci.org/wmde/fun-validators)
4 | [](https://packagist.org/packages/wmde/fun-validators)
5 | [](https://packagist.org/packages/wmde/fun-validators)
6 |
7 | General and shared validation services created as part of the WMDE fundraising software.
8 |
9 | ## Installation
10 |
11 | To use the fun-validators library in your project, simply add a dependency on wmde/fun-validators
12 | to your project's `composer.json` file. Here is a minimal example of a `composer.json`
13 | file that just defines a dependency on fun-validators 1.x:
14 |
15 | ```json
16 | {
17 | "require": {
18 | "wmde/fun-validators": "~1.0"
19 | }
20 | }
21 | ```
22 |
23 | ## Development
24 |
25 | For development you need to have Docker and compose Docker plugin installed. Local PHP and Composer are not needed.
26 |
27 | ### Installing dependencies
28 |
29 | To install the project dependencies via Composer, run:
30 |
31 | make install-php
32 |
33 | To update the dependencies, run
34 |
35 | make update-php
36 |
37 |
38 | To update a specific dependency, you can run
39 |
40 |
41 | make update-php COMPOSER_FLAGS=dependency-name
42 |
43 |
44 | ### Running the CI checks
45 |
46 | To run all CI checks, which includes PHPUnit tests, PHPCS style checks and coverage tag validation, run:
47 |
48 | make
49 |
50 | ### Running the tests
51 |
52 | To run just the PHPUnit tests run
53 |
54 | make test
55 |
56 | To run only a subset of PHPUnit tests or otherwise pass flags to PHPUnit, run
57 |
58 | docker compose run --rm fun-validators ./vendor/bin/phpunit --filter SomeClassNameOrFilter
59 |
60 |
61 |
--------------------------------------------------------------------------------
/tests/Unit/ValidationResultTest.php:
--------------------------------------------------------------------------------
1 | assertTrue( $result->isSuccessful() );
18 | $this->assertFalse( $result->hasViolations() );
19 | }
20 |
21 | public function testResultWithViolationsIsNotSuccessful(): void {
22 | $violation1 = new ConstraintViolation( 'value?', 'test_message_1' );
23 | $violation2 = new ConstraintViolation( 'value!', 'test_message_2' );
24 | $result = new ValidationResult( $violation1, $violation2 );
25 |
26 | $this->assertFalse( $result->isSuccessful() );
27 | $this->assertTrue( $result->hasViolations() );
28 | $this->assertSame( [ $violation1, $violation2 ], $result->getViolations() );
29 | }
30 |
31 | public function testSetSourceForAllViolationsChangesAllViolations(): void {
32 | $violation1 = new ConstraintViolation( 'value?', 'test_message_1' );
33 | $violation2 = new ConstraintViolation( 'value!', 'test_message_2' );
34 | $result = new ValidationResult( $violation1, $violation2 );
35 |
36 | $result->setSourceForAllViolations( 'a_test_field' );
37 |
38 | $this->assertSame( 'a_test_field', $violation1->getSource() );
39 | $this->assertSame( 'a_test_field', $violation2->getSource() );
40 | }
41 |
42 | public function testGetFirstViolationReturnsFirstViolation(): void {
43 | $violation1 = new ConstraintViolation( 'value?', 'test_message_1' );
44 | $violation2 = new ConstraintViolation( 'value!', 'test_message_2' );
45 | $result = new ValidationResult( $violation1, $violation2 );
46 |
47 | $this->assertSame( $violation1, $result->getFirstViolation() );
48 | }
49 |
50 | public function testGetFirstViolationReturnsNullWhenThereAreNoViolations(): void {
51 | $result = new ValidationResult();
52 |
53 | $this->assertNull( $result->getFirstViolation() );
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/tests/Unit/Validators/AmountPolicyValidatorTest.php:
--------------------------------------------------------------------------------
1 | assertTrue( $this->newAmountValidator()->validate( $amount, $interval )->isSuccessful() );
28 | }
29 |
30 | /**
31 | * @return array
32 | */
33 | public static function smallAmountProvider(): array {
34 | return [
35 | [ 750.0, self::INTERVAL_ONCE ],
36 | [ 20.0, self::INTERVAL_MONTHLY ],
37 | [ 100.5, self::INTERVAL_QUARTERLY ],
38 | [ 499.98, self::INTERVAL_SEMIANNUAL ],
39 | [ 999.99, self::INTERVAL_YEARLY ]
40 | ];
41 | }
42 |
43 | /**
44 | * @param float $amount
45 | * @param int $interval
46 | */
47 | #[DataProvider( 'offLimitAmountProvider' )]
48 | public function testGivenAmountTooHigh_validationFails( float $amount, int $interval ): void {
49 | $this->assertFalse( $this->newAmountValidator()->validate( $amount, $interval )->isSuccessful() );
50 | }
51 |
52 | /**
53 | * @return array
54 | */
55 | public static function offLimitAmountProvider(): array {
56 | return [
57 | [ 1750.0, self::INTERVAL_ONCE ],
58 | [ 101.0, self::INTERVAL_MONTHLY ],
59 | [ 250.5, self::INTERVAL_QUARTERLY ],
60 | [ 600, self::INTERVAL_SEMIANNUAL ],
61 | [ 1337, self::INTERVAL_YEARLY ]
62 | ];
63 | }
64 |
65 | private function newAmountValidator(): AmountPolicyValidator {
66 | return new AmountPolicyValidator( 1000, 1000 );
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/tests/Unit/ConstraintViolationTest.php:
--------------------------------------------------------------------------------
1 | assertSame( 'no_mix_of_numbers_and_letters', $constraintViolation->getMessageIdentifier() );
17 | $this->assertSame( '1nval1d', $constraintViolation->getValue() );
18 | $this->assertSame( 'username', $constraintViolation->getSource() );
19 | }
20 |
21 | public function testSetSource(): void {
22 | $constraintViolation = new ConstraintViolation( "1nval1d", "no_mix_of_numbers_and_letters" );
23 |
24 | $constraintViolation->setSource( 'access_code' );
25 |
26 | $this->assertSame( 'access_code', $constraintViolation->getSource() );
27 | }
28 |
29 | /**
30 | * We'll change this to an exception check when our other libraries don't trigger deprecations any more
31 | */
32 | public function testSetSourceDeprecatesOverridingExistingSource(): void {
33 | $constraintViolation = new ConstraintViolation( "1nval1d", "no_mix_of_numbers_and_letters", 'username' );
34 | $hasTriggeredDeprecation = false;
35 | // @phpstan-ignore-next-line argument.type
36 | set_error_handler( static function ()use( &$hasTriggeredDeprecation ){
37 | $hasTriggeredDeprecation = true;
38 | }, E_USER_DEPRECATED );
39 |
40 | $constraintViolation->setSource( 'access_code' );
41 |
42 | restore_error_handler();
43 | $this->assertTrue( $hasTriggeredDeprecation );
44 | }
45 |
46 | /**
47 | * We'll change this to an exception check when our other libraries don't trigger deprecations any more
48 | */
49 | public function testSetSourceAllowsOverridingWhenSourceNameMatches(): void {
50 | $constraintViolation = new ConstraintViolation( "1nval1d", "no_mix_of_numbers_and_letters", 'username' );
51 | $hasTriggeredDeprecation = false;
52 | // @phpstan-ignore-next-line argument.type
53 | set_error_handler( static function ()use( &$hasTriggeredDeprecation ){
54 | $hasTriggeredDeprecation = true;
55 | }, E_USER_DEPRECATED );
56 |
57 | $constraintViolation->setSource( 'username' );
58 |
59 | restore_error_handler();
60 | $this->assertFalse( $hasTriggeredDeprecation );
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/tests/Unit/Validators/EmailValidatorTest.php:
--------------------------------------------------------------------------------
1 | newStubDomainValidator() );
39 |
40 | $this->assertTrue( $mailValidator->validate( $validEmail )->isSuccessful() );
41 | }
42 |
43 | /**
44 | * @return array>
45 | */
46 | public static function fullyValidEmailProvider(): array {
47 | return [
48 | [ 'christoph.fischer@wikimedia.de' ],
49 | [ 'test@nick.berlin' ],
50 | [ 'A-Za-z0-9.!#$%&\'*+-/=?^_`{|}~info@nick.berlin' ],
51 | [ 'info@triebwerk-grün.de' ],
52 | [ 'info@triebwerk-grün.de' ],
53 | [ 'info@موقع.وزارة-الاتصالات.مصر' ],
54 | ];
55 | }
56 |
57 | /**
58 | * @param string $invalidEmail
59 | */
60 | #[DataProvider( 'emailWithInvalidDomainProvider' )]
61 | public function testGivenMailWithInvalidDomain_validationWithDomainNameCheckFails( string $invalidEmail ): void {
62 | $mailValidator = new EmailValidator( $this->newStubDomainValidator() );
63 |
64 | $this->assertFalse( $mailValidator->validate( $invalidEmail )->isSuccessful() );
65 | }
66 |
67 | /**
68 | * @return array>
69 | */
70 | public static function emailWithInvalidDomainProvider(): array {
71 | return [
72 | [ 'chrifi.asfsfas.de ' ],
73 | [ ' ' ],
74 | [ 'fibor@fgagaadadfafasfasfasfasffasfsfe.com' ],
75 | [ 'hllo909a()_9a=f9@dsafadsff' ],
76 | [ 'christoph.fischer@wikimedia.de ' ],
77 | [ 'christoph.füscher@wikimedia.de ' ],
78 | [ 'ich@ort...' ]
79 | ];
80 | }
81 |
82 | /**
83 | * @param string $invalidEmail
84 | */
85 | #[DataProvider( 'emailWithInvalidFormatProvider' )]
86 | public function testGivenMailWithInvalidFormat_validationWithoutDomainCheckFails( string $invalidEmail ): void {
87 | $mailValidator = new EmailValidator( new SucceedingDomainNameValidator() );
88 |
89 | $this->assertFalse( $mailValidator->validate( $invalidEmail )->isSuccessful() );
90 | }
91 |
92 | /**
93 | * @return array>
94 | */
95 | public static function emailWithInvalidFormatProvider(): array {
96 | return [
97 | [ 'chrifi.asfsfas.de ' ],
98 | [ ' ' ],
99 | [ 'hllo909a()_9a=f9@dsafadsff' ],
100 | [ 'christoph.fischer@wikimedia.de ' ],
101 | [ 'christoph.füscher@wikimedia.de ' ],
102 | ];
103 | }
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/src/Validators/TextPolicyValidator.php:
--------------------------------------------------------------------------------
1 | deniedWords = $deniedWords ?? new ArrayBasedStringList( [] );
27 | $this->allowedWords = $allowedWords ?? new ArrayBasedStringList( [] );
28 | }
29 |
30 | /**
31 | * @return string[]
32 | */
33 | private function getDeniedWords(): array {
34 | return $this->deniedWords->toArray();
35 | }
36 |
37 | /**
38 | * @return string[]
39 | */
40 | private function getAllowedWords(): array {
41 | return $this->allowedWords->toArray();
42 | }
43 |
44 | public function textIsHarmless( string $text ): bool {
45 | return $this->hasHarmlessContent(
46 | $text,
47 | self::CHECK_DENIED_WORDS
48 | | self::IGNORE_ALLOWED_WORDS
49 | | self::CHECK_URLS
50 | );
51 | }
52 |
53 | public function hasHarmlessContent( string $text, int $flags ): bool {
54 | $ignoreAllowedWords = (bool)( $flags & self::IGNORE_ALLOWED_WORDS );
55 |
56 | if ( $flags & self::CHECK_URLS ) {
57 | $testWithDNS = (bool)( $flags & self::CHECK_URLS_DNS );
58 |
59 | if ( $this->hasUrls( $text, $testWithDNS, $ignoreAllowedWords ) ) {
60 | return false;
61 | }
62 | }
63 |
64 | if ( $flags & self::CHECK_DENIED_WORDS ) {
65 | if ( count( $this->getDeniedWords() ) > 0 && $this->hasDeniedWords( $text, $ignoreAllowedWords ) ) {
66 | return false;
67 | }
68 | }
69 |
70 | return true;
71 | }
72 |
73 | /**
74 | * @param string[] $newDeniedWordsArray
75 | */
76 | public function addDeniedWordsFromArray( array $newDeniedWordsArray ): void {
77 | $this->deniedWords = new ArrayBasedStringList( array_merge( $this->getDeniedWords(), $newDeniedWordsArray ) );
78 | }
79 |
80 | /**
81 | * @param string[] $newAllowedWordsArray
82 | */
83 | public function addAllowedWordsFromArray( array $newAllowedWordsArray ): void {
84 | $this->allowedWords = new ArrayBasedStringList( array_merge( $this->getAllowedWords(), $newAllowedWordsArray ) );
85 | }
86 |
87 | private function hasDeniedWords( string $text, bool $ignoreAllowedWords ): bool {
88 | $deniedMatches = $this->getMatches( $text, $this->getDeniedWords() );
89 |
90 | if ( $ignoreAllowedWords ) {
91 | $allowedMatches = $this->getMatches( $text, $this->getAllowedWords() );
92 |
93 | if ( count( $allowedMatches ) > 0 ) {
94 | return $this->hasDeniedWordNotMatchingAllowedWords( $deniedMatches, $allowedMatches );
95 | }
96 |
97 | }
98 |
99 | return count( $deniedMatches ) > 0;
100 | }
101 |
102 | /**
103 | * @param string $text
104 | * @param string[] $wordArray
105 | * @return string[]
106 | */
107 | private function getMatches( string $text, array $wordArray ): array {
108 | $matches = [];
109 | preg_match_all( $this->composeRegex( $wordArray ), $text, $matches );
110 | return $matches[0];
111 | }
112 |
113 | /**
114 | * @param string[] $deniedMatches
115 | * @param string[] $allowedMatches
116 | */
117 | private function hasDeniedWordNotMatchingAllowedWords( array $deniedMatches, array $allowedMatches ): bool {
118 | return count(
119 | array_udiff(
120 | $deniedMatches,
121 | $allowedMatches,
122 | function ( $deniedMatch, $allowedMatch ) {
123 | return (int)!preg_match( $this->composeRegex( [ $deniedMatch ] ), $allowedMatch );
124 | }
125 | )
126 | ) > 0;
127 | }
128 |
129 | private function wordMatchesAllowedWords( string $word ): bool {
130 | return in_array( strtolower( $word ), array_map( 'strtolower', $this->getAllowedWords() ) );
131 | }
132 |
133 | private function hasUrls( string $text, bool $testWithDNS, bool $ignoreAllowedWords ): bool {
134 | // check for obvious URLs
135 | if ( preg_match( '|https?://www\.[a-z\.0-9]+|i', $text ) || preg_match( '|www\.[a-z\.0-9]+|i', $text ) ) {
136 | return true;
137 | }
138 |
139 | // check for non-obvious URLs with dns lookup
140 | if ( $testWithDNS ) {
141 | $possibleDomainNames = $this->extractPossibleDomainNames( $text );
142 | foreach ( $possibleDomainNames as $domainName ) {
143 | if ( !( $ignoreAllowedWords && $this->wordMatchesAllowedWords( $domainName ) ) && $this->isExistingDomain(
144 | $domainName
145 | ) ) {
146 | return true;
147 | }
148 | }
149 | }
150 |
151 | return false;
152 | }
153 |
154 | /**
155 | * @return string[]
156 | */
157 | private function extractPossibleDomainNames( string $text ): array {
158 | preg_match_all( '|[a-z\.0-9]+\.[a-z]{2,6}|i', $text, $possibleDomainNames );
159 | return $possibleDomainNames[0];
160 | }
161 |
162 | private function isExistingDomain( string $domainName ): bool {
163 | if ( filter_var( 'http://' . $domainName, FILTER_VALIDATE_URL ) === false ) {
164 | return false;
165 | }
166 | return checkdnsrr( $domainName, 'A' );
167 | }
168 |
169 | /**
170 | * @param string[] $wordArray
171 | */
172 | private function composeRegex( array $wordArray ): string {
173 | $quotedWords = array_map(
174 | static function ( string $word ) {
175 | return str_replace( ' ', '\\s*', preg_quote( trim( $word ), '#' ) );
176 | },
177 | $wordArray
178 | );
179 | return '#(.*?)(' . implode( '|', $quotedWords ) . ')#i';
180 | }
181 |
182 | }
183 |
--------------------------------------------------------------------------------
/src/Validators/AddressValidator.php:
--------------------------------------------------------------------------------
1 |
33 | */
34 | private array $maximumFieldLengths = [
35 | self::SOURCE_COMPANY => 100,
36 | self::SOURCE_FIRST_NAME => 50,
37 | self::SOURCE_LAST_NAME => 50,
38 | self::SOURCE_SALUTATION => 16,
39 | self::SOURCE_TITLE => 16,
40 | self::SOURCE_STREET_ADDRESS => 100,
41 | self::SOURCE_CITY => 100,
42 | self::SOURCE_COUNTRY => 8,
43 | self::SOURCE_POSTAL_CODE => 16,
44 | ];
45 |
46 | /**
47 | * @param string[] $countriesPostcodePatterns
48 | * @param string[] $addressPatterns
49 | */
50 | public function __construct(
51 | private array $countriesPostcodePatterns,
52 | private array $addressPatterns
53 | ) {
54 | }
55 |
56 | public function validatePostalAddress( string $streetAddress, string $postalCode, string $city, string $countryCode ): ValidationResult {
57 | $violations = [];
58 |
59 | if ( $streetAddress === '' ) {
60 | $violations[] = new ConstraintViolation(
61 | $streetAddress,
62 | self::VIOLATION_MISSING,
63 | self::SOURCE_STREET_ADDRESS
64 | );
65 | } else {
66 | $violations[] = $this->validateFieldLength( $streetAddress, self::SOURCE_STREET_ADDRESS );
67 | }
68 |
69 | $violations[] = $postalCodeLengthViolation = $this->validateFieldLength( $postalCode, self::SOURCE_POSTAL_CODE );
70 | if ( $postalCodeLengthViolation === null ) {
71 | if ( isset( $this->countriesPostcodePatterns[$countryCode] ) ) {
72 | $violations[] = $this->validatePostalCode( $this->countriesPostcodePatterns[$countryCode], $postalCode );
73 | } else {
74 | $violations[] = $this->validatePostalCode( $this->addressPatterns['postcode'], $postalCode );
75 | }
76 | }
77 |
78 | if ( $city === '' ) {
79 | $violations[] = new ConstraintViolation(
80 | $city,
81 | self::VIOLATION_MISSING,
82 | self::SOURCE_CITY
83 | );
84 | } else {
85 | $violations[] = $this->validateFieldLength( $city, self::SOURCE_CITY );
86 | }
87 |
88 | if ( $countryCode === '' ) {
89 | $violations[] = new ConstraintViolation(
90 | $countryCode,
91 | self::VIOLATION_MISSING,
92 | self::SOURCE_COUNTRY
93 | );
94 | } else {
95 | $violations[] = $this->validateFieldLength( $countryCode, self::SOURCE_COUNTRY );
96 | }
97 |
98 | return new ValidationResult( ...array_filter( $violations ) );
99 | }
100 |
101 | private function validatePostalCode( string $pattern, string $postalCode ): ?ConstraintViolation {
102 | return $this->validateMatchPattern(
103 | $pattern,
104 | $postalCode,
105 | self::VIOLATION_NOT_POSTCODE,
106 | self::SOURCE_POSTAL_CODE
107 | );
108 | }
109 |
110 | private function validateMatchPattern( string $pattern, string $value, string $messageIdentifier, string $source ): ?ConstraintViolation {
111 | if ( !preg_match( $pattern, $value ) ) {
112 | return new ConstraintViolation( $value, $messageIdentifier, $source );
113 | }
114 | return null;
115 | }
116 |
117 | public function validatePersonName( string $salutation, string $title, string $firstname, string $lastname ): ValidationResult {
118 | $violations = [];
119 |
120 | if ( $salutation === '' ) {
121 | $violations[] = new ConstraintViolation(
122 | $salutation,
123 | self::VIOLATION_MISSING,
124 | self::SOURCE_SALUTATION
125 | );
126 | } else {
127 | $violations[] = $this->validateFieldLength( $salutation, self::SOURCE_SALUTATION );
128 | }
129 |
130 | $violations[] = $this->validateFieldLength( $title, self::SOURCE_TITLE );
131 |
132 | if ( $firstname === '' ) {
133 | $violations[] = new ConstraintViolation(
134 | $firstname,
135 | self::VIOLATION_MISSING,
136 | self::SOURCE_FIRST_NAME
137 | );
138 | } else {
139 | $violations[] = $this->validateFieldLength( $firstname, self::SOURCE_FIRST_NAME );
140 | $violations[] = $this->validateMatchPattern(
141 | $this->addressPatterns['firstName'],
142 | $firstname,
143 | self::VIOLATION_PATTERN_MATCH,
144 | self::SOURCE_FIRST_NAME
145 | );
146 | }
147 |
148 | if ( $lastname === '' ) {
149 | $violations[] = new ConstraintViolation(
150 | $lastname,
151 | self::VIOLATION_MISSING,
152 | self::SOURCE_LAST_NAME
153 | );
154 | } else {
155 | $violations[] = $this->validateFieldLength( $lastname, self::SOURCE_LAST_NAME );
156 | $violations[] = $this->validateMatchPattern(
157 | $this->addressPatterns['lastName'],
158 | $lastname,
159 | self::VIOLATION_PATTERN_MATCH,
160 | self::SOURCE_LAST_NAME
161 | );
162 | }
163 | return new ValidationResult( ...array_filter( $violations ) );
164 | }
165 |
166 | public function validateCompanyName( string $companyName ): ValidationResult {
167 | $violations = [];
168 | if ( $companyName === '' ) {
169 | $violations[] = new ConstraintViolation(
170 | $companyName,
171 | self::VIOLATION_MISSING,
172 | self::SOURCE_COMPANY
173 | );
174 | } else {
175 | $violations[] = $this->validateFieldLength( $companyName, self::SOURCE_COMPANY );
176 | }
177 | return new ValidationResult( ...array_filter( $violations ) );
178 | }
179 |
180 | private function validateFieldLength( string $value, string $fieldName ): ?ConstraintViolation {
181 | if ( strlen( $value ) > $this->maximumFieldLengths[$fieldName] ) {
182 | return new ConstraintViolation(
183 | $value,
184 | self::VIOLATION_WRONG_LENGTH,
185 | $fieldName
186 | );
187 | }
188 | return null;
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/tests/System/TextPolicyValidatorTest.php:
--------------------------------------------------------------------------------
1 | skipIfNoInternet();
22 |
23 | $textPolicyValidator = new TextPolicyValidator();
24 |
25 | $this->assertFalse( $textPolicyValidator->hasHarmlessContent(
26 | $commentToTest,
27 | TextPolicyValidator::CHECK_URLS | TextPolicyValidator::CHECK_URLS_DNS
28 | ) );
29 | }
30 |
31 | private function skipIfNoInternet(): void {
32 | try {
33 | if ( !(bool)fsockopen( 'www.google.com', 80, $num, $error, 1 ) ) {
34 | $this->markTestSkipped( 'No internet connection' );
35 | }
36 | } catch ( Exception $exception ) {
37 | $this->markTestSkipped( 'No internet connection' );
38 | }
39 | }
40 |
41 | /**
42 | * @return array>
43 | */
44 | public static function urlTestProvider(): array {
45 | return [
46 | [ 'www.example.com' ],
47 | [ 'http://www.example.com' ],
48 | [ 'https://www.example.com' ],
49 | [ 'example.com' ],
50 | [ 'example.com/test' ],
51 | [ 'example.com/teKAst/index.php' ],
52 | [ 'Ich mag Wikipedia. Aber meine Seite ist auch toll:example.com/teKAst/index.php' ],
53 | [ 'inwx.berlin' ],
54 | [ 'wwwwwww.website.com' ],
55 | [ 'TriebWerk-Grün.de' ],
56 | ];
57 | }
58 |
59 | /**
60 | * @param string $commentToTest
61 | */
62 | #[DataProvider( 'harmlessTestProvider' )]
63 | public function testWhenGivenHarmlessComment_validatorReturnsTrue( string $commentToTest ): void {
64 | $this->skipIfNoInternet();
65 |
66 | $textPolicyValidator = $this->getPreFilledTextPolicyValidator();
67 |
68 | $this->assertTrue( $textPolicyValidator->hasHarmlessContent(
69 | $commentToTest,
70 | TextPolicyValidator::CHECK_URLS | TextPolicyValidator::CHECK_URLS_DNS | TextPolicyValidator::CHECK_DENIED_WORDS
71 | ) );
72 | }
73 |
74 | /**
75 | * @return array>
76 | */
77 | public static function harmlessTestProvider(): array {
78 | return [
79 | [ 'Wikipedia ist so super, meine Eltern sagen es ist eine toll Seite. Berlin ist auch Super.' ],
80 | [ 'Ich mag Wikipedia. Aber meine Seite ist auch toll. Googelt mal nach Bunsenbrenner!!!1' ],
81 | [ 'Bei Wikipedia kann man eine Menge zum Thema Hamster finden. Hamster fressen voll viel Zeug alter!' ],
82 | // this also tests the domain detection
83 | [ 'Manche Seiten haben keinen Inhalt, das finde ich sch...e' ],
84 | ];
85 | }
86 |
87 | public function testHarmlessContentWithDns(): void {
88 | $this->skipIfNoInternet();
89 |
90 | if ( checkdnsrr( 'some-non-existing-domain-drfeszrfdaesr.sdferdyerdhgty', 'A' ) ) {
91 | // https://www.youtube.com/watch?v=HGBOeLdm-1s
92 | $this->markTestSkipped( 'Your DNS/ISP provider gives results for impossible host names.' );
93 | }
94 |
95 | $textPolicyValidator = new TextPolicyValidator();
96 |
97 | $this->assertTrue( $textPolicyValidator->hasHarmlessContent(
98 | 'Ich mag Wikipedia.Wieso ? Weil ich es so toll finde!',
99 | TextPolicyValidator::CHECK_URLS | TextPolicyValidator::CHECK_URLS_DNS | TextPolicyValidator::CHECK_DENIED_WORDS
100 | ) );
101 | }
102 |
103 | /**
104 | * @param string $commentToTest
105 | */
106 | #[DataProvider( 'insultingTestProvider' )]
107 | public function testWhenGivenInsultingComment_validatorReturnsFalse( string $commentToTest ): void {
108 | $textPolicyValidator = $this->getPreFilledTextPolicyValidator();
109 |
110 | $this->assertFalse( $textPolicyValidator->hasHarmlessContent(
111 | $commentToTest,
112 | TextPolicyValidator::CHECK_DENIED_WORDS
113 | ) );
114 | }
115 |
116 | /**
117 | * @return array>
118 | */
119 | public static function insultingTestProvider(): array {
120 | return [
121 | [ 'Alles Deppen!' ],
122 | [ 'Heil Hitler!' ],
123 | [ 'Duhamsterfresse!!!' ],
124 | [ 'Alles nur HAMSTERFRESSEN!!!!!!!!1111111111' ],
125 | [ 'SiegHeil' ],
126 | [ 'Sieg Heil' ],
127 | [ "Sieg \n\tHeil!" ]
128 | ];
129 | }
130 |
131 | /**
132 | * @param string $commentToTest
133 | */
134 | #[DataProvider( 'allowedWordsInsultingTestProvider' )]
135 | public function testWhenGivenInsultingCommentAndAllowedWords_validatorReturnsFalse( string $commentToTest ): void {
136 | $textPolicyValidator = $this->getPreFilledTextPolicyValidator();
137 |
138 | $this->assertFalse(
139 | $textPolicyValidator->hasHarmlessContent(
140 | $commentToTest,
141 | TextPolicyValidator::CHECK_URLS
142 | | TextPolicyValidator::CHECK_URLS_DNS
143 | | TextPolicyValidator::CHECK_DENIED_WORDS
144 | | TextPolicyValidator::IGNORE_ALLOWED_WORDS
145 | )
146 | );
147 | }
148 |
149 | /**
150 | * @return array>
151 | */
152 | public static function allowedWordsInsultingTestProvider(): array {
153 | return [
154 | [ 'Ich heisse Deppendorf ihr Deppen und das ist auch gut so!' ],
155 | [ 'Ihr Arschgeigen, ich wohne in Marsch und das ist auch gut so!' ],
156 | [ 'Bei Wikipedia gibts echt tolle Arschkrampen!' ],
157 | ];
158 | }
159 |
160 | /**
161 | * @param string $commentToTest
162 | */
163 | #[DataProvider( 'allowedWordsHarmlessTestProvider' )]
164 | public function testWhenGivenHarmlessCommentAndAllowedWords_validatorReturnsTrue( string $commentToTest ): void {
165 | $textPolicyValidator = $this->getPreFilledTextPolicyValidator();
166 |
167 | $this->assertTrue(
168 | $textPolicyValidator->hasHarmlessContent(
169 | $commentToTest,
170 | TextPolicyValidator::CHECK_URLS
171 | | TextPolicyValidator::CHECK_URLS_DNS
172 | | TextPolicyValidator::CHECK_DENIED_WORDS
173 | | TextPolicyValidator::IGNORE_ALLOWED_WORDS
174 | )
175 | );
176 | }
177 |
178 | /**
179 | * @return array>
180 | */
181 | public static function allowedWordsHarmlessTestProvider(): array {
182 | return [
183 | [ 'Wikipedia ist so super, meine Eltern sagen es ist eine toll Seite. Berlin ist auch Super.' ],
184 | [ 'Ich heisse Deppendorf ihr und das ist auch gut so!' ],
185 | [ 'Bei Wikipedia gibts echt tolle Dinge!' ],
186 | [ 'Ick spend richtig Kohle, denn ick hab ne GmbH & Co.KG' ],
187 | ];
188 | }
189 |
190 | /**
191 | * @param string $commentToTest
192 | */
193 | #[DataProvider( 'insultingTestProviderWithRegexChars' )]
194 | public function testGivenBadWordMatchContainingRegexChars_validatorReturnsFalse( string $commentToTest ): void {
195 | $textPolicyValidator = $this->getPreFilledTextPolicyValidator();
196 |
197 | $this->assertFalse(
198 | $textPolicyValidator->hasHarmlessContent(
199 | $commentToTest,
200 | TextPolicyValidator::CHECK_URLS
201 | | TextPolicyValidator::CHECK_URLS_DNS
202 | | TextPolicyValidator::CHECK_DENIED_WORDS
203 | | TextPolicyValidator::IGNORE_ALLOWED_WORDS
204 | )
205 | );
206 | }
207 |
208 | /**
209 | * @return array>
210 | */
211 | public static function insultingTestProviderWithRegexChars(): array {
212 | return [
213 | [ 'Ich heisse Deppendorf (ihr Deppen und das ist auch gut so!' ],
214 | [ 'Ihr [Arschgeigen], ich wohne in //Marsch// und das ist auch gut so!' ],
215 | [ 'Bei #Wikipedia gibts echt tolle Arschkrampen!' ],
216 | ];
217 | }
218 |
219 | private function getPreFilledTextPolicyValidator(): TextPolicyValidator {
220 | $textPolicyValidator = new TextPolicyValidator();
221 | $textPolicyValidator->addDeniedWordsFromArray(
222 | [
223 | 'deppen',
224 | 'hitler',
225 | 'hamsterfresse',
226 | 'arsch',
227 | 'sieg heil'
228 | ] );
229 | $textPolicyValidator->addAllowedWordsFromArray(
230 | [
231 | 'Deppendorf',
232 | 'Marsch',
233 | 'Co.KG',
234 | ] );
235 | return $textPolicyValidator;
236 | }
237 |
238 | }
239 |
--------------------------------------------------------------------------------
/tests/Unit/Validators/AddressValidatorTest.php:
--------------------------------------------------------------------------------
1 | '/^[0-9]{5}$/',
16 | 'AT' => '/^[0-9]{4}$/',
17 | 'CH' => '/^[0-9]{4}$/',
18 | 'BE' => '/^[0-9]{4}$/',
19 | 'IT' => '/^[0-9]{5}$/',
20 | 'LI' => '/^[0-9]{4}$/',
21 | 'LU' => '/^[0-9]{4}$/',
22 | ];
23 |
24 | private const ADDRESS_PATTERNS = [
25 | 'firstName' => "/^[A-Za-z\x{00C0}-\x{00D6}\x{00D8}-\x{00f6}\x{00f8}-\x{00ff}\\s\\-\\.\\']+$/",
26 | 'lastName' => "/^[A-Za-z\x{00C0}-\x{00D6}\x{00D8}-\x{00f6}\x{00f8}-\x{00ff}\\s\\-\\.\\']+$/",
27 | 'postcode' => '/^.+$/',
28 | ];
29 |
30 | public function testGivenValidPostalAddress_noViolationsAreReturned(): void {
31 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS );
32 | $validationResult = $validator->validatePostalAddress( 'Test 1234', '12345', 'Test City', 'Germany' );
33 | $this->assertTrue( $validationResult->isSuccessful() );
34 | }
35 |
36 | public function testGivenValidPersonName_noViolationsAreReturned(): void {
37 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS );
38 | $validationResult = $validator->validatePersonName( 'Herr', 'Prof. Dr.', 'Tester', 'Testing' );
39 | $this->assertTrue( $validationResult->isSuccessful() );
40 | }
41 |
42 | public function testGivenValidCompany_noViolationsAreReturned(): void {
43 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS );
44 | $validationResult = $validator->validateCompanyName( 'Test Company GmbH & Co. KG' );
45 | $this->assertTrue( $validationResult->isSuccessful() );
46 | }
47 |
48 | public function testGivenTooLongPostalValues_correctViolationsAreReturned(): void {
49 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS );
50 | $validationResult = $validator->validatePostalAddress(
51 | str_repeat( 'a', 101 ),
52 | str_repeat( '1', 17 ),
53 | str_repeat( 'a', 101 ),
54 | str_repeat( 'a', 9 )
55 | );
56 | $this->assertFalse( $validationResult->isSuccessful() );
57 | $this->assertCount( 4, $validationResult->getViolations() );
58 | $this->assertSame( 'street', $validationResult->getViolations()[0]->getSource() );
59 | $this->assertSame( 'postcode', $validationResult->getViolations()[1]->getSource() );
60 | $this->assertSame( 'city', $validationResult->getViolations()[2]->getSource() );
61 | $this->assertSame( 'country', $validationResult->getViolations()[3]->getSource() );
62 | }
63 |
64 | public function testGivenTooLongNameValues_correctViolationsAreReturned(): void {
65 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS );
66 | $validationResult = $validator->validatePersonName(
67 | str_repeat( 'a', 17 ),
68 | str_repeat( 'a', 17 ),
69 | str_repeat( 'a', 51 ),
70 | str_repeat( 'a', 51 )
71 | );
72 | $this->assertFalse( $validationResult->isSuccessful() );
73 | $this->assertCount( 4, $validationResult->getViolations() );
74 | $this->assertSame( 'salutation', $validationResult->getViolations()[0]->getSource() );
75 | $this->assertSame( 'title', $validationResult->getViolations()[1]->getSource() );
76 | $this->assertSame( 'firstName', $validationResult->getViolations()[2]->getSource() );
77 | $this->assertSame( 'lastName', $validationResult->getViolations()[3]->getSource() );
78 | }
79 |
80 | public function testGivenTooLongCompanyValues_correctViolationsAreReturned(): void {
81 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS );
82 | $validationResult = $validator->validateCompanyName(
83 | str_repeat( 'a', 101 )
84 | );
85 | $this->assertFalse( $validationResult->isSuccessful() );
86 | $this->assertCount( 1, $validationResult->getViolations() );
87 | $this->assertSame( 'companyName', $validationResult->getViolations()[0]->getSource() );
88 | }
89 |
90 | public function testGivenEmptyPostalValues_correctViolationsAreReturned(): void {
91 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS );
92 | $validationResult = $validator->validatePostalAddress(
93 | '',
94 | '',
95 | '',
96 | ''
97 | );
98 | $this->assertFalse( $validationResult->isSuccessful() );
99 | $violations = array_values( $validationResult->getViolations() );
100 | $this->assertCount( 4, $violations );
101 | $this->assertSame( 'street', $violations[0]->getSource() );
102 | $this->assertSame( 'postcode', $violations[1]->getSource() );
103 | $this->assertSame( 'city', $violations[2]->getSource() );
104 | $this->assertSame( 'country', $violations[3]->getSource() );
105 | }
106 |
107 | public function testGivenEmptyNameValues_correctViolationsAreReturned(): void {
108 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS );
109 | $validationResult = $validator->validatePersonName(
110 | '',
111 | '',
112 | '',
113 | ''
114 | );
115 | $this->assertFalse( $validationResult->isSuccessful() );
116 | // Title is optional, no violation expected here
117 | $this->assertCount( 3, $validationResult->getViolations() );
118 | $this->assertSame( 'salutation', $validationResult->getViolations()[0]->getSource() );
119 | $this->assertSame( 'firstName', $validationResult->getViolations()[1]->getSource() );
120 | $this->assertSame( 'lastName', $validationResult->getViolations()[2]->getSource() );
121 | }
122 |
123 | public function testGivenEmptyCompanyValues_correctViolationsAreReturned(): void {
124 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS );
125 | $validationResult = $validator->validateCompanyName( '' );
126 | $this->assertFalse( $validationResult->isSuccessful() );
127 | $this->assertCount( 1, $validationResult->getViolations() );
128 | $this->assertSame( 'companyName', $validationResult->getViolations()[0]->getSource() );
129 | }
130 |
131 | public function testGivenBadPostcodeForCountry_correctViolationsAreReturned(): void {
132 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS );
133 | $validationResult = $validator->validatePostalAddress( 'Test 1234', '123', 'Test City', 'DE' );
134 | $this->assertSame( 'postcode', $validationResult->getViolations()[0]->getSource() );
135 | }
136 |
137 | public function testGivenBadPostcodeForCountryWithoutPatterns_addressPatternIsUsedViolationsAreReturned(): void {
138 | $addressPatterns = [
139 | 'firstName' => "/.+$/",
140 | 'lastName' => "/.+$/",
141 | // Weird pattern to make numbers fail
142 | 'postcode' => '/^[bao]{5}$/',
143 | ];
144 | $validator = new AddressValidator( [], $addressPatterns );
145 | $validationResult = $validator->validatePostalAddress( 'Test 1234', '123', 'Test City', 'US' );
146 | $this->assertSame( 'postcode', $validationResult->getViolations()[0]->getSource() );
147 | }
148 |
149 | public function testGivenLengthValidationFailsForPostCode_violationsContainOnlyLengthViolationsAndNoPatternViolations(): void {
150 | $addressPatterns = [
151 | 'firstName' => "/.+$/",
152 | 'lastName' => "/.+$/",
153 | 'postcode' => '/^[0-9]{10}$/',
154 | ];
155 | $countryPatterns = [
156 | 'DE' => '/^[0-9]{5}$/',
157 | ];
158 | // has to be longer than maximum field length in AddressValidator
159 | $longPostalCode = '1234567890123456789';
160 | $validator = new AddressValidator( $countryPatterns, $addressPatterns );
161 | $validationResultForUnknownCountry = $validator->validatePostalAddress( 'Test 1234', $longPostalCode, 'Test City', 'US' );
162 | $validationResultForKnownCountry = $validator->validatePostalAddress( 'Test 1234', $longPostalCode, 'Test City', 'DE' );
163 |
164 | $this->assertCount( 1, $validationResultForUnknownCountry->getViolations() );
165 | $this->assertSame( 'postcode', $validationResultForUnknownCountry->getViolations()[0]->getSource() );
166 | $this->assertSame( 'wrong-length', $validationResultForUnknownCountry->getViolations()[0]->getMessageIdentifier() );
167 | $this->assertCount( 1, $validationResultForKnownCountry->getViolations() );
168 | $this->assertSame( 'postcode', $validationResultForKnownCountry->getViolations()[0]->getSource() );
169 | $this->assertSame( 'wrong-length', $validationResultForKnownCountry->getViolations()[0]->getMessageIdentifier() );
170 | }
171 |
172 | public function testGivenBadFirstAndLastName_correctViolationsAreReturned(): void {
173 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS );
174 | $validationResult = $validator->validatePersonName( 'Herr', '', '£$%^&*()', '£$%^&*()' );
175 | $this->assertSame( 'firstName', $validationResult->getViolations()[0]->getSource() );
176 | $this->assertSame( 'lastName', $validationResult->getViolations()[1]->getSource() );
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/COPYING.txt:
--------------------------------------------------------------------------------
1 | The license text below "----" applies to all files within this distribution, other
2 | than those that are in a directory which contains files named "LICENSE" or
3 | "COPYING", or a subdirectory thereof. For those files, the license text contained in
4 | said file overrides any license information contained in directories of smaller depth.
5 | Alternative licenses are typically used for software that is provided by external
6 | parties, and merely packaged with this software for convenience.
7 | ----
8 |
9 | GNU GENERAL PUBLIC LICENSE
10 | Version 2, June 1991
11 |
12 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
13 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
14 | Everyone is permitted to copy and distribute verbatim copies
15 | of this license document, but changing it is not allowed.
16 |
17 | Preamble
18 |
19 | The licenses for most software are designed to take away your
20 | freedom to share and change it. By contrast, the GNU General Public
21 | License is intended to guarantee your freedom to share and change free
22 | software--to make sure the software is free for all its users. This
23 | General Public License applies to most of the Free Software
24 | Foundation's software and to any other program whose authors commit to
25 | using it. (Some other Free Software Foundation software is covered by
26 | the GNU Lesser General Public License instead.) You can apply it to
27 | your programs, too.
28 |
29 | When we speak of free software, we are referring to freedom, not
30 | price. Our General Public Licenses are designed to make sure that you
31 | have the freedom to distribute copies of free software (and charge for
32 | this service if you wish), that you receive source code or can get it
33 | if you want it, that you can change the software or use pieces of it
34 | in new free programs; and that you know you can do these things.
35 |
36 | To protect your rights, we need to make restrictions that forbid
37 | anyone to deny you these rights or to ask you to surrender the rights.
38 | These restrictions translate to certain responsibilities for you if you
39 | distribute copies of the software, or if you modify it.
40 |
41 | For example, if you distribute copies of such a program, whether
42 | gratis or for a fee, you must give the recipients all the rights that
43 | you have. You must make sure that they, too, receive or can get the
44 | source code. And you must show them these terms so they know their
45 | rights.
46 |
47 | We protect your rights with two steps: (1) copyright the software, and
48 | (2) offer you this license which gives you legal permission to copy,
49 | distribute and/or modify the software.
50 |
51 | Also, for each author's protection and ours, we want to make certain
52 | that everyone understands that there is no warranty for this free
53 | software. If the software is modified by someone else and passed on, we
54 | want its recipients to know that what they have is not the original, so
55 | that any problems introduced by others will not reflect on the original
56 | authors' reputations.
57 |
58 | Finally, any free program is threatened constantly by software
59 | patents. We wish to avoid the danger that redistributors of a free
60 | program will individually obtain patent licenses, in effect making the
61 | program proprietary. To prevent this, we have made it clear that any
62 | patent must be licensed for everyone's free use or not licensed at all.
63 |
64 | The precise terms and conditions for copying, distribution and
65 | modification follow.
66 |
67 | GNU GENERAL PUBLIC LICENSE
68 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
69 |
70 | 0. This License applies to any program or other work which contains
71 | a notice placed by the copyright holder saying it may be distributed
72 | under the terms of this General Public License. The "Program", below,
73 | refers to any such program or work, and a "work based on the Program"
74 | means either the Program or any derivative work under copyright law:
75 | that is to say, a work containing the Program or a portion of it,
76 | either verbatim or with modifications and/or translated into another
77 | language. (Hereinafter, translation is included without limitation in
78 | the term "modification".) Each licensee is addressed as "you".
79 |
80 | Activities other than copying, distribution and modification are not
81 | covered by this License; they are outside its scope. The act of
82 | running the Program is not restricted, and the output from the Program
83 | is covered only if its contents constitute a work based on the
84 | Program (independent of having been made by running the Program).
85 | Whether that is true depends on what the Program does.
86 |
87 | 1. You may copy and distribute verbatim copies of the Program's
88 | source code as you receive it, in any medium, provided that you
89 | conspicuously and appropriately publish on each copy an appropriate
90 | copyright notice and disclaimer of warranty; keep intact all the
91 | notices that refer to this License and to the absence of any warranty;
92 | and give any other recipients of the Program a copy of this License
93 | along with the Program.
94 |
95 | You may charge a fee for the physical act of transferring a copy, and
96 | you may at your option offer warranty protection in exchange for a fee.
97 |
98 | 2. You may modify your copy or copies of the Program or any portion
99 | of it, thus forming a work based on the Program, and copy and
100 | distribute such modifications or work under the terms of Section 1
101 | above, provided that you also meet all of these conditions:
102 |
103 | a) You must cause the modified files to carry prominent notices
104 | stating that you changed the files and the date of any change.
105 |
106 | b) You must cause any work that you distribute or publish, that in
107 | whole or in part contains or is derived from the Program or any
108 | part thereof, to be licensed as a whole at no charge to all third
109 | parties under the terms of this License.
110 |
111 | c) If the modified program normally reads commands interactively
112 | when run, you must cause it, when started running for such
113 | interactive use in the most ordinary way, to print or display an
114 | announcement including an appropriate copyright notice and a
115 | notice that there is no warranty (or else, saying that you provide
116 | a warranty) and that users may redistribute the program under
117 | these conditions, and telling the user how to view a copy of this
118 | License. (Exception: if the Program itself is interactive but
119 | does not normally print such an announcement, your work based on
120 | the Program is not required to print an announcement.)
121 |
122 | These requirements apply to the modified work as a whole. If
123 | identifiable sections of that work are not derived from the Program,
124 | and can be reasonably considered independent and separate works in
125 | themselves, then this License, and its terms, do not apply to those
126 | sections when you distribute them as separate works. But when you
127 | distribute the same sections as part of a whole which is a work based
128 | on the Program, the distribution of the whole must be on the terms of
129 | this License, whose permissions for other licensees extend to the
130 | entire whole, and thus to each and every part regardless of who wrote it.
131 |
132 | Thus, it is not the intent of this section to claim rights or contest
133 | your rights to work written entirely by you; rather, the intent is to
134 | exercise the right to control the distribution of derivative or
135 | collective works based on the Program.
136 |
137 | In addition, mere aggregation of another work not based on the Program
138 | with the Program (or with a work based on the Program) on a volume of
139 | a storage or distribution medium does not bring the other work under
140 | the scope of this License.
141 |
142 | 3. You may copy and distribute the Program (or a work based on it,
143 | under Section 2) in object code or executable form under the terms of
144 | Sections 1 and 2 above provided that you also do one of the following:
145 |
146 | a) Accompany it with the complete corresponding machine-readable
147 | source code, which must be distributed under the terms of Sections
148 | 1 and 2 above on a medium customarily used for software interchange; or,
149 |
150 | b) Accompany it with a written offer, valid for at least three
151 | years, to give any third party, for a charge no more than your
152 | cost of physically performing source distribution, a complete
153 | machine-readable copy of the corresponding source code, to be
154 | distributed under the terms of Sections 1 and 2 above on a medium
155 | customarily used for software interchange; or,
156 |
157 | c) Accompany it with the information you received as to the offer
158 | to distribute corresponding source code. (This alternative is
159 | allowed only for noncommercial distribution and only if you
160 | received the program in object code or executable form with such
161 | an offer, in accord with Subsection b above.)
162 |
163 | The source code for a work means the preferred form of the work for
164 | making modifications to it. For an executable work, complete source
165 | code means all the source code for all modules it contains, plus any
166 | associated interface definition files, plus the scripts used to
167 | control compilation and installation of the executable. However, as a
168 | special exception, the source code distributed need not include
169 | anything that is normally distributed (in either source or binary
170 | form) with the major components (compiler, kernel, and so on) of the
171 | operating system on which the executable runs, unless that component
172 | itself accompanies the executable.
173 |
174 | If distribution of executable or object code is made by offering
175 | access to copy from a designated place, then offering equivalent
176 | access to copy the source code from the same place counts as
177 | distribution of the source code, even though third parties are not
178 | compelled to copy the source along with the object code.
179 |
180 | 4. You may not copy, modify, sublicense, or distribute the Program
181 | except as expressly provided under this License. Any attempt
182 | otherwise to copy, modify, sublicense or distribute the Program is
183 | void, and will automatically terminate your rights under this License.
184 | However, parties who have received copies, or rights, from you under
185 | this License will not have their licenses terminated so long as such
186 | parties remain in full compliance.
187 |
188 | 5. You are not required to accept this License, since you have not
189 | signed it. However, nothing else grants you permission to modify or
190 | distribute the Program or its derivative works. These actions are
191 | prohibited by law if you do not accept this License. Therefore, by
192 | modifying or distributing the Program (or any work based on the
193 | Program), you indicate your acceptance of this License to do so, and
194 | all its terms and conditions for copying, distributing or modifying
195 | the Program or works based on it.
196 |
197 | 6. Each time you redistribute the Program (or any work based on the
198 | Program), the recipient automatically receives a license from the
199 | original licensor to copy, distribute or modify the Program subject to
200 | these terms and conditions. You may not impose any further
201 | restrictions on the recipients' exercise of the rights granted herein.
202 | You are not responsible for enforcing compliance by third parties to
203 | this License.
204 |
205 | 7. If, as a consequence of a court judgment or allegation of patent
206 | infringement or for any other reason (not limited to patent issues),
207 | conditions are imposed on you (whether by court order, agreement or
208 | otherwise) that contradict the conditions of this License, they do not
209 | excuse you from the conditions of this License. If you cannot
210 | distribute so as to satisfy simultaneously your obligations under this
211 | License and any other pertinent obligations, then as a consequence you
212 | may not distribute the Program at all. For example, if a patent
213 | license would not permit royalty-free redistribution of the Program by
214 | all those who receive copies directly or indirectly through you, then
215 | the only way you could satisfy both it and this License would be to
216 | refrain entirely from distribution of the Program.
217 |
218 | If any portion of this section is held invalid or unenforceable under
219 | any particular circumstance, the balance of the section is intended to
220 | apply and the section as a whole is intended to apply in other
221 | circumstances.
222 |
223 | It is not the purpose of this section to induce you to infringe any
224 | patents or other property right claims or to contest validity of any
225 | such claims; this section has the sole purpose of protecting the
226 | integrity of the free software distribution system, which is
227 | implemented by public license practices. Many people have made
228 | generous contributions to the wide range of software distributed
229 | through that system in reliance on consistent application of that
230 | system; it is up to the author/donor to decide if he or she is willing
231 | to distribute software through any other system and a licensee cannot
232 | impose that choice.
233 |
234 | This section is intended to make thoroughly clear what is believed to
235 | be a consequence of the rest of this License.
236 |
237 | 8. If the distribution and/or use of the Program is restricted in
238 | certain countries either by patents or by copyrighted interfaces, the
239 | original copyright holder who places the Program under this License
240 | may add an explicit geographical distribution limitation excluding
241 | those countries, so that distribution is permitted only in or among
242 | countries not thus excluded. In such case, this License incorporates
243 | the limitation as if written in the body of this License.
244 |
245 | 9. The Free Software Foundation may publish revised and/or new versions
246 | of the General Public License from time to time. Such new versions will
247 | be similar in spirit to the present version, but may differ in detail to
248 | address new problems or concerns.
249 |
250 | Each version is given a distinguishing version number. If the Program
251 | specifies a version number of this License which applies to it and "any
252 | later version", you have the option of following the terms and conditions
253 | either of that version or of any later version published by the Free
254 | Software Foundation. If the Program does not specify a version number of
255 | this License, you may choose any version ever published by the Free Software
256 | Foundation.
257 |
258 | 10. If you wish to incorporate parts of the Program into other free
259 | programs whose distribution conditions are different, write to the author
260 | to ask for permission. For software which is copyrighted by the Free
261 | Software Foundation, write to the Free Software Foundation; we sometimes
262 | make exceptions for this. Our decision will be guided by the two goals
263 | of preserving the free status of all derivatives of our free software and
264 | of promoting the sharing and reuse of software generally.
265 |
266 | NO WARRANTY
267 |
268 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
269 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
270 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
271 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
272 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
273 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
274 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
275 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
276 | REPAIR OR CORRECTION.
277 |
278 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
279 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
280 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
281 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
282 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
283 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
284 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
285 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
286 | POSSIBILITY OF SUCH DAMAGES.
287 |
288 | END OF TERMS AND CONDITIONS
289 |
290 | How to Apply These Terms to Your New Programs
291 |
292 | If you develop a new program, and you want it to be of the greatest
293 | possible use to the public, the best way to achieve this is to make it
294 | free software which everyone can redistribute and change under these terms.
295 |
296 | To do so, attach the following notices to the program. It is safest
297 | to attach them to the start of each source file to most effectively
298 | convey the exclusion of warranty; and each file should have at least
299 | the "copyright" line and a pointer to where the full notice is found.
300 |
301 |
302 | Copyright (C)
303 |
304 | This program is free software; you can redistribute it and/or modify
305 | it under the terms of the GNU General Public License as published by
306 | the Free Software Foundation; either version 2 of the License, or
307 | (at your option) any later version.
308 |
309 | This program is distributed in the hope that it will be useful,
310 | but WITHOUT ANY WARRANTY; without even the implied warranty of
311 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
312 | GNU General Public License for more details.
313 |
314 | You should have received a copy of the GNU General Public License along
315 | with this program; if not, write to the Free Software Foundation, Inc.,
316 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
317 |
318 | Also add information on how to contact you by electronic and paper mail.
319 |
320 | If the program is interactive, make it output a short notice like this
321 | when it starts in an interactive mode:
322 |
323 | Gnomovision version 69, Copyright (C) year name of author
324 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
325 | This is free software, and you are welcome to redistribute it
326 | under certain conditions; type `show c' for details.
327 |
328 | The hypothetical commands `show w' and `show c' should show the appropriate
329 | parts of the General Public License. Of course, the commands you use may
330 | be called something other than `show w' and `show c'; they could even be
331 | mouse-clicks or menu items--whatever suits your program.
332 |
333 | You should also get your employer (if you work as a programmer) or your
334 | school, if any, to sign a "copyright disclaimer" for the program, if
335 | necessary. Here is a sample; alter the names:
336 |
337 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
338 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
339 |
340 | , 1 April 1989
341 | Ty Coon, President of Vice
342 |
343 | This General Public License does not permit incorporating your program into
344 | proprietary programs. If your program is a subroutine library, you may
345 | consider it more useful to permit linking proprietary applications with the
346 | library. If this is what you want to do, use the GNU Lesser General
347 | Public License instead of this License.
348 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "8bda1b2a9a3dbd26d691a8a7e92699ea",
8 | "packages": [],
9 | "packages-dev": [
10 | {
11 | "name": "composer/semver",
12 | "version": "3.4.4",
13 | "source": {
14 | "type": "git",
15 | "url": "https://github.com/composer/semver.git",
16 | "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95"
17 | },
18 | "dist": {
19 | "type": "zip",
20 | "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95",
21 | "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95",
22 | "shasum": ""
23 | },
24 | "require": {
25 | "php": "^5.3.2 || ^7.0 || ^8.0"
26 | },
27 | "require-dev": {
28 | "phpstan/phpstan": "^1.11",
29 | "symfony/phpunit-bridge": "^3 || ^7"
30 | },
31 | "type": "library",
32 | "extra": {
33 | "branch-alias": {
34 | "dev-main": "3.x-dev"
35 | }
36 | },
37 | "autoload": {
38 | "psr-4": {
39 | "Composer\\Semver\\": "src"
40 | }
41 | },
42 | "notification-url": "https://packagist.org/downloads/",
43 | "license": [
44 | "MIT"
45 | ],
46 | "authors": [
47 | {
48 | "name": "Nils Adermann",
49 | "email": "naderman@naderman.de",
50 | "homepage": "http://www.naderman.de"
51 | },
52 | {
53 | "name": "Jordi Boggiano",
54 | "email": "j.boggiano@seld.be",
55 | "homepage": "http://seld.be"
56 | },
57 | {
58 | "name": "Rob Bast",
59 | "email": "rob.bast@gmail.com",
60 | "homepage": "http://robbast.nl"
61 | }
62 | ],
63 | "description": "Semver library that offers utilities, version constraint parsing and validation.",
64 | "keywords": [
65 | "semantic",
66 | "semver",
67 | "validation",
68 | "versioning"
69 | ],
70 | "support": {
71 | "irc": "ircs://irc.libera.chat:6697/composer",
72 | "issues": "https://github.com/composer/semver/issues",
73 | "source": "https://github.com/composer/semver/tree/3.4.4"
74 | },
75 | "funding": [
76 | {
77 | "url": "https://packagist.com",
78 | "type": "custom"
79 | },
80 | {
81 | "url": "https://github.com/composer",
82 | "type": "github"
83 | }
84 | ],
85 | "time": "2025-08-20T19:15:30+00:00"
86 | },
87 | {
88 | "name": "composer/spdx-licenses",
89 | "version": "1.5.9",
90 | "source": {
91 | "type": "git",
92 | "url": "https://github.com/composer/spdx-licenses.git",
93 | "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f"
94 | },
95 | "dist": {
96 | "type": "zip",
97 | "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/edf364cefe8c43501e21e88110aac10b284c3c9f",
98 | "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f",
99 | "shasum": ""
100 | },
101 | "require": {
102 | "php": "^5.3.2 || ^7.0 || ^8.0"
103 | },
104 | "require-dev": {
105 | "phpstan/phpstan": "^1.11",
106 | "symfony/phpunit-bridge": "^3 || ^7"
107 | },
108 | "type": "library",
109 | "extra": {
110 | "branch-alias": {
111 | "dev-main": "1.x-dev"
112 | }
113 | },
114 | "autoload": {
115 | "psr-4": {
116 | "Composer\\Spdx\\": "src"
117 | }
118 | },
119 | "notification-url": "https://packagist.org/downloads/",
120 | "license": [
121 | "MIT"
122 | ],
123 | "authors": [
124 | {
125 | "name": "Nils Adermann",
126 | "email": "naderman@naderman.de",
127 | "homepage": "http://www.naderman.de"
128 | },
129 | {
130 | "name": "Jordi Boggiano",
131 | "email": "j.boggiano@seld.be",
132 | "homepage": "http://seld.be"
133 | },
134 | {
135 | "name": "Rob Bast",
136 | "email": "rob.bast@gmail.com",
137 | "homepage": "http://robbast.nl"
138 | }
139 | ],
140 | "description": "SPDX licenses list and validation library.",
141 | "keywords": [
142 | "license",
143 | "spdx",
144 | "validator"
145 | ],
146 | "support": {
147 | "irc": "ircs://irc.libera.chat:6697/composer",
148 | "issues": "https://github.com/composer/spdx-licenses/issues",
149 | "source": "https://github.com/composer/spdx-licenses/tree/1.5.9"
150 | },
151 | "funding": [
152 | {
153 | "url": "https://packagist.com",
154 | "type": "custom"
155 | },
156 | {
157 | "url": "https://github.com/composer",
158 | "type": "github"
159 | },
160 | {
161 | "url": "https://tidelift.com/funding/github/packagist/composer/composer",
162 | "type": "tidelift"
163 | }
164 | ],
165 | "time": "2025-05-12T21:07:07+00:00"
166 | },
167 | {
168 | "name": "dealerdirect/phpcodesniffer-composer-installer",
169 | "version": "v1.1.2",
170 | "source": {
171 | "type": "git",
172 | "url": "https://github.com/PHPCSStandards/composer-installer.git",
173 | "reference": "e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1"
174 | },
175 | "dist": {
176 | "type": "zip",
177 | "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1",
178 | "reference": "e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1",
179 | "shasum": ""
180 | },
181 | "require": {
182 | "composer-plugin-api": "^2.2",
183 | "php": ">=5.4",
184 | "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0"
185 | },
186 | "require-dev": {
187 | "composer/composer": "^2.2",
188 | "ext-json": "*",
189 | "ext-zip": "*",
190 | "php-parallel-lint/php-parallel-lint": "^1.4.0",
191 | "phpcompatibility/php-compatibility": "^9.0",
192 | "yoast/phpunit-polyfills": "^1.0"
193 | },
194 | "type": "composer-plugin",
195 | "extra": {
196 | "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
197 | },
198 | "autoload": {
199 | "psr-4": {
200 | "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
201 | }
202 | },
203 | "notification-url": "https://packagist.org/downloads/",
204 | "license": [
205 | "MIT"
206 | ],
207 | "authors": [
208 | {
209 | "name": "Franck Nijhof",
210 | "email": "opensource@frenck.dev",
211 | "homepage": "https://frenck.dev",
212 | "role": "Open source developer"
213 | },
214 | {
215 | "name": "Contributors",
216 | "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors"
217 | }
218 | ],
219 | "description": "PHP_CodeSniffer Standards Composer Installer Plugin",
220 | "keywords": [
221 | "PHPCodeSniffer",
222 | "PHP_CodeSniffer",
223 | "code quality",
224 | "codesniffer",
225 | "composer",
226 | "installer",
227 | "phpcbf",
228 | "phpcs",
229 | "plugin",
230 | "qa",
231 | "quality",
232 | "standard",
233 | "standards",
234 | "style guide",
235 | "stylecheck",
236 | "tests"
237 | ],
238 | "support": {
239 | "issues": "https://github.com/PHPCSStandards/composer-installer/issues",
240 | "security": "https://github.com/PHPCSStandards/composer-installer/security/policy",
241 | "source": "https://github.com/PHPCSStandards/composer-installer"
242 | },
243 | "funding": [
244 | {
245 | "url": "https://github.com/PHPCSStandards",
246 | "type": "github"
247 | },
248 | {
249 | "url": "https://github.com/jrfnl",
250 | "type": "github"
251 | },
252 | {
253 | "url": "https://opencollective.com/php_codesniffer",
254 | "type": "open_collective"
255 | },
256 | {
257 | "url": "https://thanks.dev/u/gh/phpcsstandards",
258 | "type": "thanks_dev"
259 | }
260 | ],
261 | "time": "2025-07-17T20:45:56+00:00"
262 | },
263 | {
264 | "name": "mediawiki/mediawiki-codesniffer",
265 | "version": "v48.0.0",
266 | "source": {
267 | "type": "git",
268 | "url": "https://github.com/wikimedia/mediawiki-tools-codesniffer.git",
269 | "reference": "6d46ca2334d5e1c5be10bf28e01f6010cfbff212"
270 | },
271 | "dist": {
272 | "type": "zip",
273 | "url": "https://api.github.com/repos/wikimedia/mediawiki-tools-codesniffer/zipball/6d46ca2334d5e1c5be10bf28e01f6010cfbff212",
274 | "reference": "6d46ca2334d5e1c5be10bf28e01f6010cfbff212",
275 | "shasum": ""
276 | },
277 | "require": {
278 | "composer/semver": "^3.4.2",
279 | "composer/spdx-licenses": "~1.5.2",
280 | "ext-json": "*",
281 | "ext-mbstring": "*",
282 | "php": ">=8.1.0",
283 | "phpcsstandards/phpcsextra": "1.4.0",
284 | "squizlabs/php_codesniffer": "3.13.2"
285 | },
286 | "require-dev": {
287 | "ext-dom": "*",
288 | "mediawiki/mediawiki-phan-config": "0.17.0",
289 | "mediawiki/minus-x": "1.1.3",
290 | "php-parallel-lint/php-console-highlighter": "1.0.0",
291 | "php-parallel-lint/php-parallel-lint": "1.4.0",
292 | "phpunit/phpunit": "9.6.21"
293 | },
294 | "type": "phpcodesniffer-standard",
295 | "autoload": {
296 | "psr-4": {
297 | "MediaWiki\\Sniffs\\": "MediaWiki/Sniffs/"
298 | }
299 | },
300 | "notification-url": "https://packagist.org/downloads/",
301 | "license": [
302 | "GPL-2.0-or-later"
303 | ],
304 | "description": "MediaWiki CodeSniffer Standards",
305 | "homepage": "https://www.mediawiki.org/wiki/Manual:Coding_conventions/PHP",
306 | "keywords": [
307 | "codesniffer",
308 | "mediawiki"
309 | ],
310 | "support": {
311 | "source": "https://github.com/wikimedia/mediawiki-tools-codesniffer/tree/v48.0.0"
312 | },
313 | "time": "2025-09-04T20:12:57+00:00"
314 | },
315 | {
316 | "name": "myclabs/deep-copy",
317 | "version": "1.13.4",
318 | "source": {
319 | "type": "git",
320 | "url": "https://github.com/myclabs/DeepCopy.git",
321 | "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
322 | },
323 | "dist": {
324 | "type": "zip",
325 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
326 | "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
327 | "shasum": ""
328 | },
329 | "require": {
330 | "php": "^7.1 || ^8.0"
331 | },
332 | "conflict": {
333 | "doctrine/collections": "<1.6.8",
334 | "doctrine/common": "<2.13.3 || >=3 <3.2.2"
335 | },
336 | "require-dev": {
337 | "doctrine/collections": "^1.6.8",
338 | "doctrine/common": "^2.13.3 || ^3.2.2",
339 | "phpspec/prophecy": "^1.10",
340 | "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
341 | },
342 | "type": "library",
343 | "autoload": {
344 | "files": [
345 | "src/DeepCopy/deep_copy.php"
346 | ],
347 | "psr-4": {
348 | "DeepCopy\\": "src/DeepCopy/"
349 | }
350 | },
351 | "notification-url": "https://packagist.org/downloads/",
352 | "license": [
353 | "MIT"
354 | ],
355 | "description": "Create deep copies (clones) of your objects",
356 | "keywords": [
357 | "clone",
358 | "copy",
359 | "duplicate",
360 | "object",
361 | "object graph"
362 | ],
363 | "support": {
364 | "issues": "https://github.com/myclabs/DeepCopy/issues",
365 | "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
366 | },
367 | "funding": [
368 | {
369 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
370 | "type": "tidelift"
371 | }
372 | ],
373 | "time": "2025-08-01T08:46:24+00:00"
374 | },
375 | {
376 | "name": "nikic/php-parser",
377 | "version": "v5.6.2",
378 | "source": {
379 | "type": "git",
380 | "url": "https://github.com/nikic/PHP-Parser.git",
381 | "reference": "3a454ca033b9e06b63282ce19562e892747449bb"
382 | },
383 | "dist": {
384 | "type": "zip",
385 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb",
386 | "reference": "3a454ca033b9e06b63282ce19562e892747449bb",
387 | "shasum": ""
388 | },
389 | "require": {
390 | "ext-ctype": "*",
391 | "ext-json": "*",
392 | "ext-tokenizer": "*",
393 | "php": ">=7.4"
394 | },
395 | "require-dev": {
396 | "ircmaxell/php-yacc": "^0.0.7",
397 | "phpunit/phpunit": "^9.0"
398 | },
399 | "bin": [
400 | "bin/php-parse"
401 | ],
402 | "type": "library",
403 | "extra": {
404 | "branch-alias": {
405 | "dev-master": "5.x-dev"
406 | }
407 | },
408 | "autoload": {
409 | "psr-4": {
410 | "PhpParser\\": "lib/PhpParser"
411 | }
412 | },
413 | "notification-url": "https://packagist.org/downloads/",
414 | "license": [
415 | "BSD-3-Clause"
416 | ],
417 | "authors": [
418 | {
419 | "name": "Nikita Popov"
420 | }
421 | ],
422 | "description": "A PHP parser written in PHP",
423 | "keywords": [
424 | "parser",
425 | "php"
426 | ],
427 | "support": {
428 | "issues": "https://github.com/nikic/PHP-Parser/issues",
429 | "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2"
430 | },
431 | "time": "2025-10-21T19:32:17+00:00"
432 | },
433 | {
434 | "name": "phar-io/manifest",
435 | "version": "2.0.4",
436 | "source": {
437 | "type": "git",
438 | "url": "https://github.com/phar-io/manifest.git",
439 | "reference": "54750ef60c58e43759730615a392c31c80e23176"
440 | },
441 | "dist": {
442 | "type": "zip",
443 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
444 | "reference": "54750ef60c58e43759730615a392c31c80e23176",
445 | "shasum": ""
446 | },
447 | "require": {
448 | "ext-dom": "*",
449 | "ext-libxml": "*",
450 | "ext-phar": "*",
451 | "ext-xmlwriter": "*",
452 | "phar-io/version": "^3.0.1",
453 | "php": "^7.2 || ^8.0"
454 | },
455 | "type": "library",
456 | "extra": {
457 | "branch-alias": {
458 | "dev-master": "2.0.x-dev"
459 | }
460 | },
461 | "autoload": {
462 | "classmap": [
463 | "src/"
464 | ]
465 | },
466 | "notification-url": "https://packagist.org/downloads/",
467 | "license": [
468 | "BSD-3-Clause"
469 | ],
470 | "authors": [
471 | {
472 | "name": "Arne Blankerts",
473 | "email": "arne@blankerts.de",
474 | "role": "Developer"
475 | },
476 | {
477 | "name": "Sebastian Heuer",
478 | "email": "sebastian@phpeople.de",
479 | "role": "Developer"
480 | },
481 | {
482 | "name": "Sebastian Bergmann",
483 | "email": "sebastian@phpunit.de",
484 | "role": "Developer"
485 | }
486 | ],
487 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
488 | "support": {
489 | "issues": "https://github.com/phar-io/manifest/issues",
490 | "source": "https://github.com/phar-io/manifest/tree/2.0.4"
491 | },
492 | "funding": [
493 | {
494 | "url": "https://github.com/theseer",
495 | "type": "github"
496 | }
497 | ],
498 | "time": "2024-03-03T12:33:53+00:00"
499 | },
500 | {
501 | "name": "phar-io/version",
502 | "version": "3.2.1",
503 | "source": {
504 | "type": "git",
505 | "url": "https://github.com/phar-io/version.git",
506 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
507 | },
508 | "dist": {
509 | "type": "zip",
510 | "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
511 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
512 | "shasum": ""
513 | },
514 | "require": {
515 | "php": "^7.2 || ^8.0"
516 | },
517 | "type": "library",
518 | "autoload": {
519 | "classmap": [
520 | "src/"
521 | ]
522 | },
523 | "notification-url": "https://packagist.org/downloads/",
524 | "license": [
525 | "BSD-3-Clause"
526 | ],
527 | "authors": [
528 | {
529 | "name": "Arne Blankerts",
530 | "email": "arne@blankerts.de",
531 | "role": "Developer"
532 | },
533 | {
534 | "name": "Sebastian Heuer",
535 | "email": "sebastian@phpeople.de",
536 | "role": "Developer"
537 | },
538 | {
539 | "name": "Sebastian Bergmann",
540 | "email": "sebastian@phpunit.de",
541 | "role": "Developer"
542 | }
543 | ],
544 | "description": "Library for handling version information and constraints",
545 | "support": {
546 | "issues": "https://github.com/phar-io/version/issues",
547 | "source": "https://github.com/phar-io/version/tree/3.2.1"
548 | },
549 | "time": "2022-02-21T01:04:05+00:00"
550 | },
551 | {
552 | "name": "phpcsstandards/phpcsextra",
553 | "version": "1.4.0",
554 | "source": {
555 | "type": "git",
556 | "url": "https://github.com/PHPCSStandards/PHPCSExtra.git",
557 | "reference": "fa4b8d051e278072928e32d817456a7fdb57b6ca"
558 | },
559 | "dist": {
560 | "type": "zip",
561 | "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/fa4b8d051e278072928e32d817456a7fdb57b6ca",
562 | "reference": "fa4b8d051e278072928e32d817456a7fdb57b6ca",
563 | "shasum": ""
564 | },
565 | "require": {
566 | "php": ">=5.4",
567 | "phpcsstandards/phpcsutils": "^1.1.0",
568 | "squizlabs/php_codesniffer": "^3.13.0 || ^4.0"
569 | },
570 | "require-dev": {
571 | "php-parallel-lint/php-console-highlighter": "^1.0",
572 | "php-parallel-lint/php-parallel-lint": "^1.4.0",
573 | "phpcsstandards/phpcsdevcs": "^1.1.6",
574 | "phpcsstandards/phpcsdevtools": "^1.2.1",
575 | "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4"
576 | },
577 | "type": "phpcodesniffer-standard",
578 | "extra": {
579 | "branch-alias": {
580 | "dev-stable": "1.x-dev",
581 | "dev-develop": "1.x-dev"
582 | }
583 | },
584 | "notification-url": "https://packagist.org/downloads/",
585 | "license": [
586 | "LGPL-3.0-or-later"
587 | ],
588 | "authors": [
589 | {
590 | "name": "Juliette Reinders Folmer",
591 | "homepage": "https://github.com/jrfnl",
592 | "role": "lead"
593 | },
594 | {
595 | "name": "Contributors",
596 | "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors"
597 | }
598 | ],
599 | "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.",
600 | "keywords": [
601 | "PHP_CodeSniffer",
602 | "phpcbf",
603 | "phpcodesniffer-standard",
604 | "phpcs",
605 | "standards",
606 | "static analysis"
607 | ],
608 | "support": {
609 | "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues",
610 | "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy",
611 | "source": "https://github.com/PHPCSStandards/PHPCSExtra"
612 | },
613 | "funding": [
614 | {
615 | "url": "https://github.com/PHPCSStandards",
616 | "type": "github"
617 | },
618 | {
619 | "url": "https://github.com/jrfnl",
620 | "type": "github"
621 | },
622 | {
623 | "url": "https://opencollective.com/php_codesniffer",
624 | "type": "open_collective"
625 | },
626 | {
627 | "url": "https://thanks.dev/u/gh/phpcsstandards",
628 | "type": "thanks_dev"
629 | }
630 | ],
631 | "time": "2025-06-14T07:40:39+00:00"
632 | },
633 | {
634 | "name": "phpcsstandards/phpcsutils",
635 | "version": "1.1.1",
636 | "source": {
637 | "type": "git",
638 | "url": "https://github.com/PHPCSStandards/PHPCSUtils.git",
639 | "reference": "f7eb16f2fa4237d5db9e8fed8050239bee17a9bd"
640 | },
641 | "dist": {
642 | "type": "zip",
643 | "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/f7eb16f2fa4237d5db9e8fed8050239bee17a9bd",
644 | "reference": "f7eb16f2fa4237d5db9e8fed8050239bee17a9bd",
645 | "shasum": ""
646 | },
647 | "require": {
648 | "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0",
649 | "php": ">=5.4",
650 | "squizlabs/php_codesniffer": "^3.13.0 || ^4.0"
651 | },
652 | "require-dev": {
653 | "ext-filter": "*",
654 | "php-parallel-lint/php-console-highlighter": "^1.0",
655 | "php-parallel-lint/php-parallel-lint": "^1.4.0",
656 | "phpcsstandards/phpcsdevcs": "^1.1.6",
657 | "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0 || ^3.0.0"
658 | },
659 | "type": "phpcodesniffer-standard",
660 | "extra": {
661 | "branch-alias": {
662 | "dev-stable": "1.x-dev",
663 | "dev-develop": "1.x-dev"
664 | }
665 | },
666 | "autoload": {
667 | "classmap": [
668 | "PHPCSUtils/"
669 | ]
670 | },
671 | "notification-url": "https://packagist.org/downloads/",
672 | "license": [
673 | "LGPL-3.0-or-later"
674 | ],
675 | "authors": [
676 | {
677 | "name": "Juliette Reinders Folmer",
678 | "homepage": "https://github.com/jrfnl",
679 | "role": "lead"
680 | },
681 | {
682 | "name": "Contributors",
683 | "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors"
684 | }
685 | ],
686 | "description": "A suite of utility functions for use with PHP_CodeSniffer",
687 | "homepage": "https://phpcsutils.com/",
688 | "keywords": [
689 | "PHP_CodeSniffer",
690 | "phpcbf",
691 | "phpcodesniffer-standard",
692 | "phpcs",
693 | "phpcs3",
694 | "phpcs4",
695 | "standards",
696 | "static analysis",
697 | "tokens",
698 | "utility"
699 | ],
700 | "support": {
701 | "docs": "https://phpcsutils.com/",
702 | "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues",
703 | "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy",
704 | "source": "https://github.com/PHPCSStandards/PHPCSUtils"
705 | },
706 | "funding": [
707 | {
708 | "url": "https://github.com/PHPCSStandards",
709 | "type": "github"
710 | },
711 | {
712 | "url": "https://github.com/jrfnl",
713 | "type": "github"
714 | },
715 | {
716 | "url": "https://opencollective.com/php_codesniffer",
717 | "type": "open_collective"
718 | },
719 | {
720 | "url": "https://thanks.dev/u/gh/phpcsstandards",
721 | "type": "thanks_dev"
722 | }
723 | ],
724 | "time": "2025-08-10T01:04:45+00:00"
725 | },
726 | {
727 | "name": "phpstan/phpstan",
728 | "version": "2.1.32",
729 | "dist": {
730 | "type": "zip",
731 | "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e126cad1e30a99b137b8ed75a85a676450ebb227",
732 | "reference": "e126cad1e30a99b137b8ed75a85a676450ebb227",
733 | "shasum": ""
734 | },
735 | "require": {
736 | "php": "^7.4|^8.0"
737 | },
738 | "conflict": {
739 | "phpstan/phpstan-shim": "*"
740 | },
741 | "bin": [
742 | "phpstan",
743 | "phpstan.phar"
744 | ],
745 | "type": "library",
746 | "autoload": {
747 | "files": [
748 | "bootstrap.php"
749 | ]
750 | },
751 | "notification-url": "https://packagist.org/downloads/",
752 | "license": [
753 | "MIT"
754 | ],
755 | "description": "PHPStan - PHP Static Analysis Tool",
756 | "keywords": [
757 | "dev",
758 | "static analysis"
759 | ],
760 | "support": {
761 | "docs": "https://phpstan.org/user-guide/getting-started",
762 | "forum": "https://github.com/phpstan/phpstan/discussions",
763 | "issues": "https://github.com/phpstan/phpstan/issues",
764 | "security": "https://github.com/phpstan/phpstan/security/policy",
765 | "source": "https://github.com/phpstan/phpstan-src"
766 | },
767 | "funding": [
768 | {
769 | "url": "https://github.com/ondrejmirtes",
770 | "type": "github"
771 | },
772 | {
773 | "url": "https://github.com/phpstan",
774 | "type": "github"
775 | }
776 | ],
777 | "time": "2025-11-11T15:18:17+00:00"
778 | },
779 | {
780 | "name": "phpunit/php-code-coverage",
781 | "version": "12.5.0",
782 | "source": {
783 | "type": "git",
784 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
785 | "reference": "bca180c050dd3ae15f87c26d25cabb34fe1a0a5a"
786 | },
787 | "dist": {
788 | "type": "zip",
789 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/bca180c050dd3ae15f87c26d25cabb34fe1a0a5a",
790 | "reference": "bca180c050dd3ae15f87c26d25cabb34fe1a0a5a",
791 | "shasum": ""
792 | },
793 | "require": {
794 | "ext-dom": "*",
795 | "ext-libxml": "*",
796 | "ext-xmlwriter": "*",
797 | "nikic/php-parser": "^5.6.2",
798 | "php": ">=8.3",
799 | "phpunit/php-file-iterator": "^6.0",
800 | "phpunit/php-text-template": "^5.0",
801 | "sebastian/complexity": "^5.0",
802 | "sebastian/environment": "^8.0.3",
803 | "sebastian/lines-of-code": "^4.0",
804 | "sebastian/version": "^6.0",
805 | "theseer/tokenizer": "^1.3.1"
806 | },
807 | "require-dev": {
808 | "phpunit/phpunit": "^12.4.4"
809 | },
810 | "suggest": {
811 | "ext-pcov": "PHP extension that provides line coverage",
812 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
813 | },
814 | "type": "library",
815 | "extra": {
816 | "branch-alias": {
817 | "dev-main": "12.5.x-dev"
818 | }
819 | },
820 | "autoload": {
821 | "classmap": [
822 | "src/"
823 | ]
824 | },
825 | "notification-url": "https://packagist.org/downloads/",
826 | "license": [
827 | "BSD-3-Clause"
828 | ],
829 | "authors": [
830 | {
831 | "name": "Sebastian Bergmann",
832 | "email": "sebastian@phpunit.de",
833 | "role": "lead"
834 | }
835 | ],
836 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
837 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
838 | "keywords": [
839 | "coverage",
840 | "testing",
841 | "xunit"
842 | ],
843 | "support": {
844 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
845 | "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
846 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.0"
847 | },
848 | "funding": [
849 | {
850 | "url": "https://github.com/sebastianbergmann",
851 | "type": "github"
852 | },
853 | {
854 | "url": "https://liberapay.com/sebastianbergmann",
855 | "type": "liberapay"
856 | },
857 | {
858 | "url": "https://thanks.dev/u/gh/sebastianbergmann",
859 | "type": "thanks_dev"
860 | },
861 | {
862 | "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage",
863 | "type": "tidelift"
864 | }
865 | ],
866 | "time": "2025-11-29T07:15:54+00:00"
867 | },
868 | {
869 | "name": "phpunit/php-file-iterator",
870 | "version": "6.0.0",
871 | "source": {
872 | "type": "git",
873 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
874 | "reference": "961bc913d42fe24a257bfff826a5068079ac7782"
875 | },
876 | "dist": {
877 | "type": "zip",
878 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/961bc913d42fe24a257bfff826a5068079ac7782",
879 | "reference": "961bc913d42fe24a257bfff826a5068079ac7782",
880 | "shasum": ""
881 | },
882 | "require": {
883 | "php": ">=8.3"
884 | },
885 | "require-dev": {
886 | "phpunit/phpunit": "^12.0"
887 | },
888 | "type": "library",
889 | "extra": {
890 | "branch-alias": {
891 | "dev-main": "6.0-dev"
892 | }
893 | },
894 | "autoload": {
895 | "classmap": [
896 | "src/"
897 | ]
898 | },
899 | "notification-url": "https://packagist.org/downloads/",
900 | "license": [
901 | "BSD-3-Clause"
902 | ],
903 | "authors": [
904 | {
905 | "name": "Sebastian Bergmann",
906 | "email": "sebastian@phpunit.de",
907 | "role": "lead"
908 | }
909 | ],
910 | "description": "FilterIterator implementation that filters files based on a list of suffixes.",
911 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
912 | "keywords": [
913 | "filesystem",
914 | "iterator"
915 | ],
916 | "support": {
917 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
918 | "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy",
919 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.0"
920 | },
921 | "funding": [
922 | {
923 | "url": "https://github.com/sebastianbergmann",
924 | "type": "github"
925 | }
926 | ],
927 | "time": "2025-02-07T04:58:37+00:00"
928 | },
929 | {
930 | "name": "phpunit/php-invoker",
931 | "version": "6.0.0",
932 | "source": {
933 | "type": "git",
934 | "url": "https://github.com/sebastianbergmann/php-invoker.git",
935 | "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406"
936 | },
937 | "dist": {
938 | "type": "zip",
939 | "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406",
940 | "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406",
941 | "shasum": ""
942 | },
943 | "require": {
944 | "php": ">=8.3"
945 | },
946 | "require-dev": {
947 | "ext-pcntl": "*",
948 | "phpunit/phpunit": "^12.0"
949 | },
950 | "suggest": {
951 | "ext-pcntl": "*"
952 | },
953 | "type": "library",
954 | "extra": {
955 | "branch-alias": {
956 | "dev-main": "6.0-dev"
957 | }
958 | },
959 | "autoload": {
960 | "classmap": [
961 | "src/"
962 | ]
963 | },
964 | "notification-url": "https://packagist.org/downloads/",
965 | "license": [
966 | "BSD-3-Clause"
967 | ],
968 | "authors": [
969 | {
970 | "name": "Sebastian Bergmann",
971 | "email": "sebastian@phpunit.de",
972 | "role": "lead"
973 | }
974 | ],
975 | "description": "Invoke callables with a timeout",
976 | "homepage": "https://github.com/sebastianbergmann/php-invoker/",
977 | "keywords": [
978 | "process"
979 | ],
980 | "support": {
981 | "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
982 | "security": "https://github.com/sebastianbergmann/php-invoker/security/policy",
983 | "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0"
984 | },
985 | "funding": [
986 | {
987 | "url": "https://github.com/sebastianbergmann",
988 | "type": "github"
989 | }
990 | ],
991 | "time": "2025-02-07T04:58:58+00:00"
992 | },
993 | {
994 | "name": "phpunit/php-text-template",
995 | "version": "5.0.0",
996 | "source": {
997 | "type": "git",
998 | "url": "https://github.com/sebastianbergmann/php-text-template.git",
999 | "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53"
1000 | },
1001 | "dist": {
1002 | "type": "zip",
1003 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53",
1004 | "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53",
1005 | "shasum": ""
1006 | },
1007 | "require": {
1008 | "php": ">=8.3"
1009 | },
1010 | "require-dev": {
1011 | "phpunit/phpunit": "^12.0"
1012 | },
1013 | "type": "library",
1014 | "extra": {
1015 | "branch-alias": {
1016 | "dev-main": "5.0-dev"
1017 | }
1018 | },
1019 | "autoload": {
1020 | "classmap": [
1021 | "src/"
1022 | ]
1023 | },
1024 | "notification-url": "https://packagist.org/downloads/",
1025 | "license": [
1026 | "BSD-3-Clause"
1027 | ],
1028 | "authors": [
1029 | {
1030 | "name": "Sebastian Bergmann",
1031 | "email": "sebastian@phpunit.de",
1032 | "role": "lead"
1033 | }
1034 | ],
1035 | "description": "Simple template engine.",
1036 | "homepage": "https://github.com/sebastianbergmann/php-text-template/",
1037 | "keywords": [
1038 | "template"
1039 | ],
1040 | "support": {
1041 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
1042 | "security": "https://github.com/sebastianbergmann/php-text-template/security/policy",
1043 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/5.0.0"
1044 | },
1045 | "funding": [
1046 | {
1047 | "url": "https://github.com/sebastianbergmann",
1048 | "type": "github"
1049 | }
1050 | ],
1051 | "time": "2025-02-07T04:59:16+00:00"
1052 | },
1053 | {
1054 | "name": "phpunit/php-timer",
1055 | "version": "8.0.0",
1056 | "source": {
1057 | "type": "git",
1058 | "url": "https://github.com/sebastianbergmann/php-timer.git",
1059 | "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc"
1060 | },
1061 | "dist": {
1062 | "type": "zip",
1063 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc",
1064 | "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc",
1065 | "shasum": ""
1066 | },
1067 | "require": {
1068 | "php": ">=8.3"
1069 | },
1070 | "require-dev": {
1071 | "phpunit/phpunit": "^12.0"
1072 | },
1073 | "type": "library",
1074 | "extra": {
1075 | "branch-alias": {
1076 | "dev-main": "8.0-dev"
1077 | }
1078 | },
1079 | "autoload": {
1080 | "classmap": [
1081 | "src/"
1082 | ]
1083 | },
1084 | "notification-url": "https://packagist.org/downloads/",
1085 | "license": [
1086 | "BSD-3-Clause"
1087 | ],
1088 | "authors": [
1089 | {
1090 | "name": "Sebastian Bergmann",
1091 | "email": "sebastian@phpunit.de",
1092 | "role": "lead"
1093 | }
1094 | ],
1095 | "description": "Utility class for timing",
1096 | "homepage": "https://github.com/sebastianbergmann/php-timer/",
1097 | "keywords": [
1098 | "timer"
1099 | ],
1100 | "support": {
1101 | "issues": "https://github.com/sebastianbergmann/php-timer/issues",
1102 | "security": "https://github.com/sebastianbergmann/php-timer/security/policy",
1103 | "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0"
1104 | },
1105 | "funding": [
1106 | {
1107 | "url": "https://github.com/sebastianbergmann",
1108 | "type": "github"
1109 | }
1110 | ],
1111 | "time": "2025-02-07T04:59:38+00:00"
1112 | },
1113 | {
1114 | "name": "phpunit/phpunit",
1115 | "version": "12.4.5",
1116 | "source": {
1117 | "type": "git",
1118 | "url": "https://github.com/sebastianbergmann/phpunit.git",
1119 | "reference": "5af317802efd27d5b9cbe048e3760d4a2f687f45"
1120 | },
1121 | "dist": {
1122 | "type": "zip",
1123 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5af317802efd27d5b9cbe048e3760d4a2f687f45",
1124 | "reference": "5af317802efd27d5b9cbe048e3760d4a2f687f45",
1125 | "shasum": ""
1126 | },
1127 | "require": {
1128 | "ext-dom": "*",
1129 | "ext-json": "*",
1130 | "ext-libxml": "*",
1131 | "ext-mbstring": "*",
1132 | "ext-xml": "*",
1133 | "ext-xmlwriter": "*",
1134 | "myclabs/deep-copy": "^1.13.4",
1135 | "phar-io/manifest": "^2.0.4",
1136 | "phar-io/version": "^3.2.1",
1137 | "php": ">=8.3",
1138 | "phpunit/php-code-coverage": "^12.4.0",
1139 | "phpunit/php-file-iterator": "^6.0.0",
1140 | "phpunit/php-invoker": "^6.0.0",
1141 | "phpunit/php-text-template": "^5.0.0",
1142 | "phpunit/php-timer": "^8.0.0",
1143 | "sebastian/cli-parser": "^4.2.0",
1144 | "sebastian/comparator": "^7.1.3",
1145 | "sebastian/diff": "^7.0.0",
1146 | "sebastian/environment": "^8.0.3",
1147 | "sebastian/exporter": "^7.0.2",
1148 | "sebastian/global-state": "^8.0.2",
1149 | "sebastian/object-enumerator": "^7.0.0",
1150 | "sebastian/type": "^6.0.3",
1151 | "sebastian/version": "^6.0.0",
1152 | "staabm/side-effects-detector": "^1.0.5"
1153 | },
1154 | "bin": [
1155 | "phpunit"
1156 | ],
1157 | "type": "library",
1158 | "extra": {
1159 | "branch-alias": {
1160 | "dev-main": "12.4-dev"
1161 | }
1162 | },
1163 | "autoload": {
1164 | "files": [
1165 | "src/Framework/Assert/Functions.php"
1166 | ],
1167 | "classmap": [
1168 | "src/"
1169 | ]
1170 | },
1171 | "notification-url": "https://packagist.org/downloads/",
1172 | "license": [
1173 | "BSD-3-Clause"
1174 | ],
1175 | "authors": [
1176 | {
1177 | "name": "Sebastian Bergmann",
1178 | "email": "sebastian@phpunit.de",
1179 | "role": "lead"
1180 | }
1181 | ],
1182 | "description": "The PHP Unit Testing framework.",
1183 | "homepage": "https://phpunit.de/",
1184 | "keywords": [
1185 | "phpunit",
1186 | "testing",
1187 | "xunit"
1188 | ],
1189 | "support": {
1190 | "issues": "https://github.com/sebastianbergmann/phpunit/issues",
1191 | "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
1192 | "source": "https://github.com/sebastianbergmann/phpunit/tree/12.4.5"
1193 | },
1194 | "funding": [
1195 | {
1196 | "url": "https://phpunit.de/sponsors.html",
1197 | "type": "custom"
1198 | },
1199 | {
1200 | "url": "https://github.com/sebastianbergmann",
1201 | "type": "github"
1202 | },
1203 | {
1204 | "url": "https://liberapay.com/sebastianbergmann",
1205 | "type": "liberapay"
1206 | },
1207 | {
1208 | "url": "https://thanks.dev/u/gh/sebastianbergmann",
1209 | "type": "thanks_dev"
1210 | },
1211 | {
1212 | "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
1213 | "type": "tidelift"
1214 | }
1215 | ],
1216 | "time": "2025-12-01T07:40:15+00:00"
1217 | },
1218 | {
1219 | "name": "sebastian/cli-parser",
1220 | "version": "4.2.0",
1221 | "source": {
1222 | "type": "git",
1223 | "url": "https://github.com/sebastianbergmann/cli-parser.git",
1224 | "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04"
1225 | },
1226 | "dist": {
1227 | "type": "zip",
1228 | "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/90f41072d220e5c40df6e8635f5dafba2d9d4d04",
1229 | "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04",
1230 | "shasum": ""
1231 | },
1232 | "require": {
1233 | "php": ">=8.3"
1234 | },
1235 | "require-dev": {
1236 | "phpunit/phpunit": "^12.0"
1237 | },
1238 | "type": "library",
1239 | "extra": {
1240 | "branch-alias": {
1241 | "dev-main": "4.2-dev"
1242 | }
1243 | },
1244 | "autoload": {
1245 | "classmap": [
1246 | "src/"
1247 | ]
1248 | },
1249 | "notification-url": "https://packagist.org/downloads/",
1250 | "license": [
1251 | "BSD-3-Clause"
1252 | ],
1253 | "authors": [
1254 | {
1255 | "name": "Sebastian Bergmann",
1256 | "email": "sebastian@phpunit.de",
1257 | "role": "lead"
1258 | }
1259 | ],
1260 | "description": "Library for parsing CLI options",
1261 | "homepage": "https://github.com/sebastianbergmann/cli-parser",
1262 | "support": {
1263 | "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
1264 | "security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
1265 | "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.2.0"
1266 | },
1267 | "funding": [
1268 | {
1269 | "url": "https://github.com/sebastianbergmann",
1270 | "type": "github"
1271 | },
1272 | {
1273 | "url": "https://liberapay.com/sebastianbergmann",
1274 | "type": "liberapay"
1275 | },
1276 | {
1277 | "url": "https://thanks.dev/u/gh/sebastianbergmann",
1278 | "type": "thanks_dev"
1279 | },
1280 | {
1281 | "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser",
1282 | "type": "tidelift"
1283 | }
1284 | ],
1285 | "time": "2025-09-14T09:36:45+00:00"
1286 | },
1287 | {
1288 | "name": "sebastian/comparator",
1289 | "version": "7.1.3",
1290 | "source": {
1291 | "type": "git",
1292 | "url": "https://github.com/sebastianbergmann/comparator.git",
1293 | "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148"
1294 | },
1295 | "dist": {
1296 | "type": "zip",
1297 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/dc904b4bb3ab070865fa4068cd84f3da8b945148",
1298 | "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148",
1299 | "shasum": ""
1300 | },
1301 | "require": {
1302 | "ext-dom": "*",
1303 | "ext-mbstring": "*",
1304 | "php": ">=8.3",
1305 | "sebastian/diff": "^7.0",
1306 | "sebastian/exporter": "^7.0"
1307 | },
1308 | "require-dev": {
1309 | "phpunit/phpunit": "^12.2"
1310 | },
1311 | "suggest": {
1312 | "ext-bcmath": "For comparing BcMath\\Number objects"
1313 | },
1314 | "type": "library",
1315 | "extra": {
1316 | "branch-alias": {
1317 | "dev-main": "7.1-dev"
1318 | }
1319 | },
1320 | "autoload": {
1321 | "classmap": [
1322 | "src/"
1323 | ]
1324 | },
1325 | "notification-url": "https://packagist.org/downloads/",
1326 | "license": [
1327 | "BSD-3-Clause"
1328 | ],
1329 | "authors": [
1330 | {
1331 | "name": "Sebastian Bergmann",
1332 | "email": "sebastian@phpunit.de"
1333 | },
1334 | {
1335 | "name": "Jeff Welch",
1336 | "email": "whatthejeff@gmail.com"
1337 | },
1338 | {
1339 | "name": "Volker Dusch",
1340 | "email": "github@wallbash.com"
1341 | },
1342 | {
1343 | "name": "Bernhard Schussek",
1344 | "email": "bschussek@2bepublished.at"
1345 | }
1346 | ],
1347 | "description": "Provides the functionality to compare PHP values for equality",
1348 | "homepage": "https://github.com/sebastianbergmann/comparator",
1349 | "keywords": [
1350 | "comparator",
1351 | "compare",
1352 | "equality"
1353 | ],
1354 | "support": {
1355 | "issues": "https://github.com/sebastianbergmann/comparator/issues",
1356 | "security": "https://github.com/sebastianbergmann/comparator/security/policy",
1357 | "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.3"
1358 | },
1359 | "funding": [
1360 | {
1361 | "url": "https://github.com/sebastianbergmann",
1362 | "type": "github"
1363 | },
1364 | {
1365 | "url": "https://liberapay.com/sebastianbergmann",
1366 | "type": "liberapay"
1367 | },
1368 | {
1369 | "url": "https://thanks.dev/u/gh/sebastianbergmann",
1370 | "type": "thanks_dev"
1371 | },
1372 | {
1373 | "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator",
1374 | "type": "tidelift"
1375 | }
1376 | ],
1377 | "time": "2025-08-20T11:27:00+00:00"
1378 | },
1379 | {
1380 | "name": "sebastian/complexity",
1381 | "version": "5.0.0",
1382 | "source": {
1383 | "type": "git",
1384 | "url": "https://github.com/sebastianbergmann/complexity.git",
1385 | "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb"
1386 | },
1387 | "dist": {
1388 | "type": "zip",
1389 | "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb",
1390 | "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb",
1391 | "shasum": ""
1392 | },
1393 | "require": {
1394 | "nikic/php-parser": "^5.0",
1395 | "php": ">=8.3"
1396 | },
1397 | "require-dev": {
1398 | "phpunit/phpunit": "^12.0"
1399 | },
1400 | "type": "library",
1401 | "extra": {
1402 | "branch-alias": {
1403 | "dev-main": "5.0-dev"
1404 | }
1405 | },
1406 | "autoload": {
1407 | "classmap": [
1408 | "src/"
1409 | ]
1410 | },
1411 | "notification-url": "https://packagist.org/downloads/",
1412 | "license": [
1413 | "BSD-3-Clause"
1414 | ],
1415 | "authors": [
1416 | {
1417 | "name": "Sebastian Bergmann",
1418 | "email": "sebastian@phpunit.de",
1419 | "role": "lead"
1420 | }
1421 | ],
1422 | "description": "Library for calculating the complexity of PHP code units",
1423 | "homepage": "https://github.com/sebastianbergmann/complexity",
1424 | "support": {
1425 | "issues": "https://github.com/sebastianbergmann/complexity/issues",
1426 | "security": "https://github.com/sebastianbergmann/complexity/security/policy",
1427 | "source": "https://github.com/sebastianbergmann/complexity/tree/5.0.0"
1428 | },
1429 | "funding": [
1430 | {
1431 | "url": "https://github.com/sebastianbergmann",
1432 | "type": "github"
1433 | }
1434 | ],
1435 | "time": "2025-02-07T04:55:25+00:00"
1436 | },
1437 | {
1438 | "name": "sebastian/diff",
1439 | "version": "7.0.0",
1440 | "source": {
1441 | "type": "git",
1442 | "url": "https://github.com/sebastianbergmann/diff.git",
1443 | "reference": "7ab1ea946c012266ca32390913653d844ecd085f"
1444 | },
1445 | "dist": {
1446 | "type": "zip",
1447 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f",
1448 | "reference": "7ab1ea946c012266ca32390913653d844ecd085f",
1449 | "shasum": ""
1450 | },
1451 | "require": {
1452 | "php": ">=8.3"
1453 | },
1454 | "require-dev": {
1455 | "phpunit/phpunit": "^12.0",
1456 | "symfony/process": "^7.2"
1457 | },
1458 | "type": "library",
1459 | "extra": {
1460 | "branch-alias": {
1461 | "dev-main": "7.0-dev"
1462 | }
1463 | },
1464 | "autoload": {
1465 | "classmap": [
1466 | "src/"
1467 | ]
1468 | },
1469 | "notification-url": "https://packagist.org/downloads/",
1470 | "license": [
1471 | "BSD-3-Clause"
1472 | ],
1473 | "authors": [
1474 | {
1475 | "name": "Sebastian Bergmann",
1476 | "email": "sebastian@phpunit.de"
1477 | },
1478 | {
1479 | "name": "Kore Nordmann",
1480 | "email": "mail@kore-nordmann.de"
1481 | }
1482 | ],
1483 | "description": "Diff implementation",
1484 | "homepage": "https://github.com/sebastianbergmann/diff",
1485 | "keywords": [
1486 | "diff",
1487 | "udiff",
1488 | "unidiff",
1489 | "unified diff"
1490 | ],
1491 | "support": {
1492 | "issues": "https://github.com/sebastianbergmann/diff/issues",
1493 | "security": "https://github.com/sebastianbergmann/diff/security/policy",
1494 | "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0"
1495 | },
1496 | "funding": [
1497 | {
1498 | "url": "https://github.com/sebastianbergmann",
1499 | "type": "github"
1500 | }
1501 | ],
1502 | "time": "2025-02-07T04:55:46+00:00"
1503 | },
1504 | {
1505 | "name": "sebastian/environment",
1506 | "version": "8.0.3",
1507 | "source": {
1508 | "type": "git",
1509 | "url": "https://github.com/sebastianbergmann/environment.git",
1510 | "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68"
1511 | },
1512 | "dist": {
1513 | "type": "zip",
1514 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/24a711b5c916efc6d6e62aa65aa2ec98fef77f68",
1515 | "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68",
1516 | "shasum": ""
1517 | },
1518 | "require": {
1519 | "php": ">=8.3"
1520 | },
1521 | "require-dev": {
1522 | "phpunit/phpunit": "^12.0"
1523 | },
1524 | "suggest": {
1525 | "ext-posix": "*"
1526 | },
1527 | "type": "library",
1528 | "extra": {
1529 | "branch-alias": {
1530 | "dev-main": "8.0-dev"
1531 | }
1532 | },
1533 | "autoload": {
1534 | "classmap": [
1535 | "src/"
1536 | ]
1537 | },
1538 | "notification-url": "https://packagist.org/downloads/",
1539 | "license": [
1540 | "BSD-3-Clause"
1541 | ],
1542 | "authors": [
1543 | {
1544 | "name": "Sebastian Bergmann",
1545 | "email": "sebastian@phpunit.de"
1546 | }
1547 | ],
1548 | "description": "Provides functionality to handle HHVM/PHP environments",
1549 | "homepage": "https://github.com/sebastianbergmann/environment",
1550 | "keywords": [
1551 | "Xdebug",
1552 | "environment",
1553 | "hhvm"
1554 | ],
1555 | "support": {
1556 | "issues": "https://github.com/sebastianbergmann/environment/issues",
1557 | "security": "https://github.com/sebastianbergmann/environment/security/policy",
1558 | "source": "https://github.com/sebastianbergmann/environment/tree/8.0.3"
1559 | },
1560 | "funding": [
1561 | {
1562 | "url": "https://github.com/sebastianbergmann",
1563 | "type": "github"
1564 | },
1565 | {
1566 | "url": "https://liberapay.com/sebastianbergmann",
1567 | "type": "liberapay"
1568 | },
1569 | {
1570 | "url": "https://thanks.dev/u/gh/sebastianbergmann",
1571 | "type": "thanks_dev"
1572 | },
1573 | {
1574 | "url": "https://tidelift.com/funding/github/packagist/sebastian/environment",
1575 | "type": "tidelift"
1576 | }
1577 | ],
1578 | "time": "2025-08-12T14:11:56+00:00"
1579 | },
1580 | {
1581 | "name": "sebastian/exporter",
1582 | "version": "7.0.2",
1583 | "source": {
1584 | "type": "git",
1585 | "url": "https://github.com/sebastianbergmann/exporter.git",
1586 | "reference": "016951ae10980765e4e7aee491eb288c64e505b7"
1587 | },
1588 | "dist": {
1589 | "type": "zip",
1590 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/016951ae10980765e4e7aee491eb288c64e505b7",
1591 | "reference": "016951ae10980765e4e7aee491eb288c64e505b7",
1592 | "shasum": ""
1593 | },
1594 | "require": {
1595 | "ext-mbstring": "*",
1596 | "php": ">=8.3",
1597 | "sebastian/recursion-context": "^7.0"
1598 | },
1599 | "require-dev": {
1600 | "phpunit/phpunit": "^12.0"
1601 | },
1602 | "type": "library",
1603 | "extra": {
1604 | "branch-alias": {
1605 | "dev-main": "7.0-dev"
1606 | }
1607 | },
1608 | "autoload": {
1609 | "classmap": [
1610 | "src/"
1611 | ]
1612 | },
1613 | "notification-url": "https://packagist.org/downloads/",
1614 | "license": [
1615 | "BSD-3-Clause"
1616 | ],
1617 | "authors": [
1618 | {
1619 | "name": "Sebastian Bergmann",
1620 | "email": "sebastian@phpunit.de"
1621 | },
1622 | {
1623 | "name": "Jeff Welch",
1624 | "email": "whatthejeff@gmail.com"
1625 | },
1626 | {
1627 | "name": "Volker Dusch",
1628 | "email": "github@wallbash.com"
1629 | },
1630 | {
1631 | "name": "Adam Harvey",
1632 | "email": "aharvey@php.net"
1633 | },
1634 | {
1635 | "name": "Bernhard Schussek",
1636 | "email": "bschussek@gmail.com"
1637 | }
1638 | ],
1639 | "description": "Provides the functionality to export PHP variables for visualization",
1640 | "homepage": "https://www.github.com/sebastianbergmann/exporter",
1641 | "keywords": [
1642 | "export",
1643 | "exporter"
1644 | ],
1645 | "support": {
1646 | "issues": "https://github.com/sebastianbergmann/exporter/issues",
1647 | "security": "https://github.com/sebastianbergmann/exporter/security/policy",
1648 | "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.2"
1649 | },
1650 | "funding": [
1651 | {
1652 | "url": "https://github.com/sebastianbergmann",
1653 | "type": "github"
1654 | },
1655 | {
1656 | "url": "https://liberapay.com/sebastianbergmann",
1657 | "type": "liberapay"
1658 | },
1659 | {
1660 | "url": "https://thanks.dev/u/gh/sebastianbergmann",
1661 | "type": "thanks_dev"
1662 | },
1663 | {
1664 | "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter",
1665 | "type": "tidelift"
1666 | }
1667 | ],
1668 | "time": "2025-09-24T06:16:11+00:00"
1669 | },
1670 | {
1671 | "name": "sebastian/global-state",
1672 | "version": "8.0.2",
1673 | "source": {
1674 | "type": "git",
1675 | "url": "https://github.com/sebastianbergmann/global-state.git",
1676 | "reference": "ef1377171613d09edd25b7816f05be8313f9115d"
1677 | },
1678 | "dist": {
1679 | "type": "zip",
1680 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/ef1377171613d09edd25b7816f05be8313f9115d",
1681 | "reference": "ef1377171613d09edd25b7816f05be8313f9115d",
1682 | "shasum": ""
1683 | },
1684 | "require": {
1685 | "php": ">=8.3",
1686 | "sebastian/object-reflector": "^5.0",
1687 | "sebastian/recursion-context": "^7.0"
1688 | },
1689 | "require-dev": {
1690 | "ext-dom": "*",
1691 | "phpunit/phpunit": "^12.0"
1692 | },
1693 | "type": "library",
1694 | "extra": {
1695 | "branch-alias": {
1696 | "dev-main": "8.0-dev"
1697 | }
1698 | },
1699 | "autoload": {
1700 | "classmap": [
1701 | "src/"
1702 | ]
1703 | },
1704 | "notification-url": "https://packagist.org/downloads/",
1705 | "license": [
1706 | "BSD-3-Clause"
1707 | ],
1708 | "authors": [
1709 | {
1710 | "name": "Sebastian Bergmann",
1711 | "email": "sebastian@phpunit.de"
1712 | }
1713 | ],
1714 | "description": "Snapshotting of global state",
1715 | "homepage": "https://www.github.com/sebastianbergmann/global-state",
1716 | "keywords": [
1717 | "global state"
1718 | ],
1719 | "support": {
1720 | "issues": "https://github.com/sebastianbergmann/global-state/issues",
1721 | "security": "https://github.com/sebastianbergmann/global-state/security/policy",
1722 | "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.2"
1723 | },
1724 | "funding": [
1725 | {
1726 | "url": "https://github.com/sebastianbergmann",
1727 | "type": "github"
1728 | },
1729 | {
1730 | "url": "https://liberapay.com/sebastianbergmann",
1731 | "type": "liberapay"
1732 | },
1733 | {
1734 | "url": "https://thanks.dev/u/gh/sebastianbergmann",
1735 | "type": "thanks_dev"
1736 | },
1737 | {
1738 | "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state",
1739 | "type": "tidelift"
1740 | }
1741 | ],
1742 | "time": "2025-08-29T11:29:25+00:00"
1743 | },
1744 | {
1745 | "name": "sebastian/lines-of-code",
1746 | "version": "4.0.0",
1747 | "source": {
1748 | "type": "git",
1749 | "url": "https://github.com/sebastianbergmann/lines-of-code.git",
1750 | "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f"
1751 | },
1752 | "dist": {
1753 | "type": "zip",
1754 | "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/97ffee3bcfb5805568d6af7f0f893678fc076d2f",
1755 | "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f",
1756 | "shasum": ""
1757 | },
1758 | "require": {
1759 | "nikic/php-parser": "^5.0",
1760 | "php": ">=8.3"
1761 | },
1762 | "require-dev": {
1763 | "phpunit/phpunit": "^12.0"
1764 | },
1765 | "type": "library",
1766 | "extra": {
1767 | "branch-alias": {
1768 | "dev-main": "4.0-dev"
1769 | }
1770 | },
1771 | "autoload": {
1772 | "classmap": [
1773 | "src/"
1774 | ]
1775 | },
1776 | "notification-url": "https://packagist.org/downloads/",
1777 | "license": [
1778 | "BSD-3-Clause"
1779 | ],
1780 | "authors": [
1781 | {
1782 | "name": "Sebastian Bergmann",
1783 | "email": "sebastian@phpunit.de",
1784 | "role": "lead"
1785 | }
1786 | ],
1787 | "description": "Library for counting the lines of code in PHP source code",
1788 | "homepage": "https://github.com/sebastianbergmann/lines-of-code",
1789 | "support": {
1790 | "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
1791 | "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
1792 | "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.0"
1793 | },
1794 | "funding": [
1795 | {
1796 | "url": "https://github.com/sebastianbergmann",
1797 | "type": "github"
1798 | }
1799 | ],
1800 | "time": "2025-02-07T04:57:28+00:00"
1801 | },
1802 | {
1803 | "name": "sebastian/object-enumerator",
1804 | "version": "7.0.0",
1805 | "source": {
1806 | "type": "git",
1807 | "url": "https://github.com/sebastianbergmann/object-enumerator.git",
1808 | "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894"
1809 | },
1810 | "dist": {
1811 | "type": "zip",
1812 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894",
1813 | "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894",
1814 | "shasum": ""
1815 | },
1816 | "require": {
1817 | "php": ">=8.3",
1818 | "sebastian/object-reflector": "^5.0",
1819 | "sebastian/recursion-context": "^7.0"
1820 | },
1821 | "require-dev": {
1822 | "phpunit/phpunit": "^12.0"
1823 | },
1824 | "type": "library",
1825 | "extra": {
1826 | "branch-alias": {
1827 | "dev-main": "7.0-dev"
1828 | }
1829 | },
1830 | "autoload": {
1831 | "classmap": [
1832 | "src/"
1833 | ]
1834 | },
1835 | "notification-url": "https://packagist.org/downloads/",
1836 | "license": [
1837 | "BSD-3-Clause"
1838 | ],
1839 | "authors": [
1840 | {
1841 | "name": "Sebastian Bergmann",
1842 | "email": "sebastian@phpunit.de"
1843 | }
1844 | ],
1845 | "description": "Traverses array structures and object graphs to enumerate all referenced objects",
1846 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
1847 | "support": {
1848 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
1849 | "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy",
1850 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0"
1851 | },
1852 | "funding": [
1853 | {
1854 | "url": "https://github.com/sebastianbergmann",
1855 | "type": "github"
1856 | }
1857 | ],
1858 | "time": "2025-02-07T04:57:48+00:00"
1859 | },
1860 | {
1861 | "name": "sebastian/object-reflector",
1862 | "version": "5.0.0",
1863 | "source": {
1864 | "type": "git",
1865 | "url": "https://github.com/sebastianbergmann/object-reflector.git",
1866 | "reference": "4bfa827c969c98be1e527abd576533293c634f6a"
1867 | },
1868 | "dist": {
1869 | "type": "zip",
1870 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a",
1871 | "reference": "4bfa827c969c98be1e527abd576533293c634f6a",
1872 | "shasum": ""
1873 | },
1874 | "require": {
1875 | "php": ">=8.3"
1876 | },
1877 | "require-dev": {
1878 | "phpunit/phpunit": "^12.0"
1879 | },
1880 | "type": "library",
1881 | "extra": {
1882 | "branch-alias": {
1883 | "dev-main": "5.0-dev"
1884 | }
1885 | },
1886 | "autoload": {
1887 | "classmap": [
1888 | "src/"
1889 | ]
1890 | },
1891 | "notification-url": "https://packagist.org/downloads/",
1892 | "license": [
1893 | "BSD-3-Clause"
1894 | ],
1895 | "authors": [
1896 | {
1897 | "name": "Sebastian Bergmann",
1898 | "email": "sebastian@phpunit.de"
1899 | }
1900 | ],
1901 | "description": "Allows reflection of object attributes, including inherited and non-public ones",
1902 | "homepage": "https://github.com/sebastianbergmann/object-reflector/",
1903 | "support": {
1904 | "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
1905 | "security": "https://github.com/sebastianbergmann/object-reflector/security/policy",
1906 | "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0"
1907 | },
1908 | "funding": [
1909 | {
1910 | "url": "https://github.com/sebastianbergmann",
1911 | "type": "github"
1912 | }
1913 | ],
1914 | "time": "2025-02-07T04:58:17+00:00"
1915 | },
1916 | {
1917 | "name": "sebastian/recursion-context",
1918 | "version": "7.0.1",
1919 | "source": {
1920 | "type": "git",
1921 | "url": "https://github.com/sebastianbergmann/recursion-context.git",
1922 | "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c"
1923 | },
1924 | "dist": {
1925 | "type": "zip",
1926 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c",
1927 | "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c",
1928 | "shasum": ""
1929 | },
1930 | "require": {
1931 | "php": ">=8.3"
1932 | },
1933 | "require-dev": {
1934 | "phpunit/phpunit": "^12.0"
1935 | },
1936 | "type": "library",
1937 | "extra": {
1938 | "branch-alias": {
1939 | "dev-main": "7.0-dev"
1940 | }
1941 | },
1942 | "autoload": {
1943 | "classmap": [
1944 | "src/"
1945 | ]
1946 | },
1947 | "notification-url": "https://packagist.org/downloads/",
1948 | "license": [
1949 | "BSD-3-Clause"
1950 | ],
1951 | "authors": [
1952 | {
1953 | "name": "Sebastian Bergmann",
1954 | "email": "sebastian@phpunit.de"
1955 | },
1956 | {
1957 | "name": "Jeff Welch",
1958 | "email": "whatthejeff@gmail.com"
1959 | },
1960 | {
1961 | "name": "Adam Harvey",
1962 | "email": "aharvey@php.net"
1963 | }
1964 | ],
1965 | "description": "Provides functionality to recursively process PHP variables",
1966 | "homepage": "https://github.com/sebastianbergmann/recursion-context",
1967 | "support": {
1968 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
1969 | "security": "https://github.com/sebastianbergmann/recursion-context/security/policy",
1970 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1"
1971 | },
1972 | "funding": [
1973 | {
1974 | "url": "https://github.com/sebastianbergmann",
1975 | "type": "github"
1976 | },
1977 | {
1978 | "url": "https://liberapay.com/sebastianbergmann",
1979 | "type": "liberapay"
1980 | },
1981 | {
1982 | "url": "https://thanks.dev/u/gh/sebastianbergmann",
1983 | "type": "thanks_dev"
1984 | },
1985 | {
1986 | "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context",
1987 | "type": "tidelift"
1988 | }
1989 | ],
1990 | "time": "2025-08-13T04:44:59+00:00"
1991 | },
1992 | {
1993 | "name": "sebastian/type",
1994 | "version": "6.0.3",
1995 | "source": {
1996 | "type": "git",
1997 | "url": "https://github.com/sebastianbergmann/type.git",
1998 | "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d"
1999 | },
2000 | "dist": {
2001 | "type": "zip",
2002 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/e549163b9760b8f71f191651d22acf32d56d6d4d",
2003 | "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d",
2004 | "shasum": ""
2005 | },
2006 | "require": {
2007 | "php": ">=8.3"
2008 | },
2009 | "require-dev": {
2010 | "phpunit/phpunit": "^12.0"
2011 | },
2012 | "type": "library",
2013 | "extra": {
2014 | "branch-alias": {
2015 | "dev-main": "6.0-dev"
2016 | }
2017 | },
2018 | "autoload": {
2019 | "classmap": [
2020 | "src/"
2021 | ]
2022 | },
2023 | "notification-url": "https://packagist.org/downloads/",
2024 | "license": [
2025 | "BSD-3-Clause"
2026 | ],
2027 | "authors": [
2028 | {
2029 | "name": "Sebastian Bergmann",
2030 | "email": "sebastian@phpunit.de",
2031 | "role": "lead"
2032 | }
2033 | ],
2034 | "description": "Collection of value objects that represent the types of the PHP type system",
2035 | "homepage": "https://github.com/sebastianbergmann/type",
2036 | "support": {
2037 | "issues": "https://github.com/sebastianbergmann/type/issues",
2038 | "security": "https://github.com/sebastianbergmann/type/security/policy",
2039 | "source": "https://github.com/sebastianbergmann/type/tree/6.0.3"
2040 | },
2041 | "funding": [
2042 | {
2043 | "url": "https://github.com/sebastianbergmann",
2044 | "type": "github"
2045 | },
2046 | {
2047 | "url": "https://liberapay.com/sebastianbergmann",
2048 | "type": "liberapay"
2049 | },
2050 | {
2051 | "url": "https://thanks.dev/u/gh/sebastianbergmann",
2052 | "type": "thanks_dev"
2053 | },
2054 | {
2055 | "url": "https://tidelift.com/funding/github/packagist/sebastian/type",
2056 | "type": "tidelift"
2057 | }
2058 | ],
2059 | "time": "2025-08-09T06:57:12+00:00"
2060 | },
2061 | {
2062 | "name": "sebastian/version",
2063 | "version": "6.0.0",
2064 | "source": {
2065 | "type": "git",
2066 | "url": "https://github.com/sebastianbergmann/version.git",
2067 | "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c"
2068 | },
2069 | "dist": {
2070 | "type": "zip",
2071 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c",
2072 | "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c",
2073 | "shasum": ""
2074 | },
2075 | "require": {
2076 | "php": ">=8.3"
2077 | },
2078 | "type": "library",
2079 | "extra": {
2080 | "branch-alias": {
2081 | "dev-main": "6.0-dev"
2082 | }
2083 | },
2084 | "autoload": {
2085 | "classmap": [
2086 | "src/"
2087 | ]
2088 | },
2089 | "notification-url": "https://packagist.org/downloads/",
2090 | "license": [
2091 | "BSD-3-Clause"
2092 | ],
2093 | "authors": [
2094 | {
2095 | "name": "Sebastian Bergmann",
2096 | "email": "sebastian@phpunit.de",
2097 | "role": "lead"
2098 | }
2099 | ],
2100 | "description": "Library that helps with managing the version number of Git-hosted PHP projects",
2101 | "homepage": "https://github.com/sebastianbergmann/version",
2102 | "support": {
2103 | "issues": "https://github.com/sebastianbergmann/version/issues",
2104 | "security": "https://github.com/sebastianbergmann/version/security/policy",
2105 | "source": "https://github.com/sebastianbergmann/version/tree/6.0.0"
2106 | },
2107 | "funding": [
2108 | {
2109 | "url": "https://github.com/sebastianbergmann",
2110 | "type": "github"
2111 | }
2112 | ],
2113 | "time": "2025-02-07T05:00:38+00:00"
2114 | },
2115 | {
2116 | "name": "squizlabs/php_codesniffer",
2117 | "version": "3.13.2",
2118 | "source": {
2119 | "type": "git",
2120 | "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
2121 | "reference": "5b5e3821314f947dd040c70f7992a64eac89025c"
2122 | },
2123 | "dist": {
2124 | "type": "zip",
2125 | "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5b5e3821314f947dd040c70f7992a64eac89025c",
2126 | "reference": "5b5e3821314f947dd040c70f7992a64eac89025c",
2127 | "shasum": ""
2128 | },
2129 | "require": {
2130 | "ext-simplexml": "*",
2131 | "ext-tokenizer": "*",
2132 | "ext-xmlwriter": "*",
2133 | "php": ">=5.4.0"
2134 | },
2135 | "require-dev": {
2136 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4"
2137 | },
2138 | "bin": [
2139 | "bin/phpcbf",
2140 | "bin/phpcs"
2141 | ],
2142 | "type": "library",
2143 | "extra": {
2144 | "branch-alias": {
2145 | "dev-master": "3.x-dev"
2146 | }
2147 | },
2148 | "notification-url": "https://packagist.org/downloads/",
2149 | "license": [
2150 | "BSD-3-Clause"
2151 | ],
2152 | "authors": [
2153 | {
2154 | "name": "Greg Sherwood",
2155 | "role": "Former lead"
2156 | },
2157 | {
2158 | "name": "Juliette Reinders Folmer",
2159 | "role": "Current lead"
2160 | },
2161 | {
2162 | "name": "Contributors",
2163 | "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors"
2164 | }
2165 | ],
2166 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
2167 | "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
2168 | "keywords": [
2169 | "phpcs",
2170 | "standards",
2171 | "static analysis"
2172 | ],
2173 | "support": {
2174 | "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues",
2175 | "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy",
2176 | "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
2177 | "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki"
2178 | },
2179 | "funding": [
2180 | {
2181 | "url": "https://github.com/PHPCSStandards",
2182 | "type": "github"
2183 | },
2184 | {
2185 | "url": "https://github.com/jrfnl",
2186 | "type": "github"
2187 | },
2188 | {
2189 | "url": "https://opencollective.com/php_codesniffer",
2190 | "type": "open_collective"
2191 | },
2192 | {
2193 | "url": "https://thanks.dev/u/gh/phpcsstandards",
2194 | "type": "thanks_dev"
2195 | }
2196 | ],
2197 | "time": "2025-06-17T22:17:01+00:00"
2198 | },
2199 | {
2200 | "name": "staabm/side-effects-detector",
2201 | "version": "1.0.5",
2202 | "source": {
2203 | "type": "git",
2204 | "url": "https://github.com/staabm/side-effects-detector.git",
2205 | "reference": "d8334211a140ce329c13726d4a715adbddd0a163"
2206 | },
2207 | "dist": {
2208 | "type": "zip",
2209 | "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163",
2210 | "reference": "d8334211a140ce329c13726d4a715adbddd0a163",
2211 | "shasum": ""
2212 | },
2213 | "require": {
2214 | "ext-tokenizer": "*",
2215 | "php": "^7.4 || ^8.0"
2216 | },
2217 | "require-dev": {
2218 | "phpstan/extension-installer": "^1.4.3",
2219 | "phpstan/phpstan": "^1.12.6",
2220 | "phpunit/phpunit": "^9.6.21",
2221 | "symfony/var-dumper": "^5.4.43",
2222 | "tomasvotruba/type-coverage": "1.0.0",
2223 | "tomasvotruba/unused-public": "1.0.0"
2224 | },
2225 | "type": "library",
2226 | "autoload": {
2227 | "classmap": [
2228 | "lib/"
2229 | ]
2230 | },
2231 | "notification-url": "https://packagist.org/downloads/",
2232 | "license": [
2233 | "MIT"
2234 | ],
2235 | "description": "A static analysis tool to detect side effects in PHP code",
2236 | "keywords": [
2237 | "static analysis"
2238 | ],
2239 | "support": {
2240 | "issues": "https://github.com/staabm/side-effects-detector/issues",
2241 | "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5"
2242 | },
2243 | "funding": [
2244 | {
2245 | "url": "https://github.com/staabm",
2246 | "type": "github"
2247 | }
2248 | ],
2249 | "time": "2024-10-20T05:08:20+00:00"
2250 | },
2251 | {
2252 | "name": "theseer/tokenizer",
2253 | "version": "1.3.1",
2254 | "source": {
2255 | "type": "git",
2256 | "url": "https://github.com/theseer/tokenizer.git",
2257 | "reference": "b7489ce515e168639d17feec34b8847c326b0b3c"
2258 | },
2259 | "dist": {
2260 | "type": "zip",
2261 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c",
2262 | "reference": "b7489ce515e168639d17feec34b8847c326b0b3c",
2263 | "shasum": ""
2264 | },
2265 | "require": {
2266 | "ext-dom": "*",
2267 | "ext-tokenizer": "*",
2268 | "ext-xmlwriter": "*",
2269 | "php": "^7.2 || ^8.0"
2270 | },
2271 | "type": "library",
2272 | "autoload": {
2273 | "classmap": [
2274 | "src/"
2275 | ]
2276 | },
2277 | "notification-url": "https://packagist.org/downloads/",
2278 | "license": [
2279 | "BSD-3-Clause"
2280 | ],
2281 | "authors": [
2282 | {
2283 | "name": "Arne Blankerts",
2284 | "email": "arne@blankerts.de",
2285 | "role": "Developer"
2286 | }
2287 | ],
2288 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
2289 | "support": {
2290 | "issues": "https://github.com/theseer/tokenizer/issues",
2291 | "source": "https://github.com/theseer/tokenizer/tree/1.3.1"
2292 | },
2293 | "funding": [
2294 | {
2295 | "url": "https://github.com/theseer",
2296 | "type": "github"
2297 | }
2298 | ],
2299 | "time": "2025-11-17T20:03:58+00:00"
2300 | },
2301 | {
2302 | "name": "wmde/fundraising-phpcs",
2303 | "version": "v13.0.0",
2304 | "source": {
2305 | "type": "git",
2306 | "url": "https://github.com/wmde/fundraising-phpcs",
2307 | "reference": "b63239c82ca7af1074594490ee7e8280cf32aa67"
2308 | },
2309 | "dist": {
2310 | "type": "zip",
2311 | "url": "https://api.github.com/repos/wmde/fundraising-phpcs/zipball/b63239c82ca7af1074594490ee7e8280cf32aa67",
2312 | "reference": "b63239c82ca7af1074594490ee7e8280cf32aa67",
2313 | "shasum": ""
2314 | },
2315 | "require": {
2316 | "mediawiki/mediawiki-codesniffer": "~48.0"
2317 | },
2318 | "type": "library",
2319 | "license": [
2320 | "GPL-2.0-or-later"
2321 | ],
2322 | "description": "PHP coding style for the WMDE FUN team",
2323 | "time": "2025-09-08T10:21:07+00:00"
2324 | }
2325 | ],
2326 | "aliases": [],
2327 | "minimum-stability": "stable",
2328 | "stability-flags": {},
2329 | "prefer-stable": false,
2330 | "prefer-lowest": false,
2331 | "platform": {
2332 | "php": ">=8.4"
2333 | },
2334 | "platform-dev": {},
2335 | "plugin-api-version": "2.6.0"
2336 | }
2337 |
--------------------------------------------------------------------------------