commit 8c671904315868015d6351a0a8a619569dc48f66 Author: Ismo Vuorinen Date: Mon Jul 28 15:00:37 2025 +0300 feat: initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1d52233 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig helps maintain consistent coding styles across editors and IDEs + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 +max_line_length = 100 + +[*.md] +trim_trailing_whitespace = false +max_line_length = 120 + +[*.{md,json,yml,yaml,xml}] +indent_size = 2 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..79fa72b --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Global owners for the repository +* @ivuorinen diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..2d86e93 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,145 @@ +# Citizen Code of Conduct + +## 1. Purpose + +A primary goal of @ivuorinen's repositories is to be inclusive to the largest +number of contributors, with the most varied and diverse backgrounds possible. +As such, we are committed to providing a friendly, safe and welcoming +environment for all, regardless of gender, sexual orientation, ability, +ethnicity, socioeconomic status, and religion (or lack thereof). + +This code of conduct outlines our expectations for all those who participate in +our community, as well as the consequences for unacceptable behavior. + +We invite all those who participate in @ivuorinen's repositories to help us +create safe and positive experiences for everyone. + +## 2. Open [Source/Culture/Tech] Citizenship + +A supplemental goal of this Code of Conduct is to increase +open [source/culture/tech] citizenship by encouraging participants to recognize +and strengthen the relationships between our actions and their effects on our +community. + +Communities mirror the societies in which they exist and positive action is +essential to counteract the many forms of inequality and abuses of power that +exist in society. + +If you see someone who is making an extra effort to ensure our community is +welcoming, friendly, and encourages all participants to contribute to the +fullest extent, we want to know. + +## 3. Expected Behavior + +The following behaviors are expected and requested of all community members: + +* Participate in an authentic and active way. In doing so, you contribute to the + health and longevity of this community. +* Exercise consideration and respect in your speech and actions. +* Attempt collaboration before conflict. +* Refrain from demeaning, discriminatory, or harassing behavior and speech. +* Be mindful of your surroundings and of your fellow participants. Alert + community leaders if you notice a dangerous situation, someone in distress, or + violations of this Code of Conduct, even if they seem inconsequential. +* Remember that community event venues may be shared with members of the public; + please be respectful to all patrons of these locations. + +## 4. Unacceptable Behavior + +The following behaviors are considered harassment and are unacceptable within +our community: + +* Violence, threats of violence or violent language directed against another + person. +* Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory + jokes and language. +* Posting or displaying sexually explicit or violent material. +* Posting or threatening to post other people's personally identifying + information ("doxing"). +* Personal insults, particularly those related to gender, sexual orientation, + race, religion, or disability. +* Inappropriate photography or recording. +* Inappropriate physical contact. You should have someone's consent before + touching them. +* Unwelcome sexual attention. This includes, sexualized comments or jokes; + inappropriate touching, groping, and unwelcomed sexual advances. +* Deliberate intimidation, stalking or following (online or in person). +* Advocating for, or encouraging, any of the above behavior. +* Sustained disruption of community events, including talks and presentations. + +## 5. Weapons Policy + +No weapons will be allowed at @ivuorinen's repositories events, community +spaces, or in other spaces covered by the scope of this Code of Conduct. Weapons +include but are not limited to guns, explosives (including fireworks), and large +knives such as those used for hunting or display, as well as any other item used +for the purpose of causing injury or harm to others. Anyone seen in possession +of one of these items will be asked to leave immediately, and will only be +allowed to return without the weapon. Community members are further expected to +comply with all state and local laws on this matter. + +## 6. Consequences of Unacceptable Behavior + +Unacceptable behavior from any community member, including sponsors and those +with decision-making authority, will not be tolerated. + +Anyone asked to stop unacceptable behavior is expected to comply immediately. + +If a community member engages in unacceptable behavior, the community organizers +may take any action they deem appropriate, up to and including a temporary ban +or permanent expulsion from the community without warning (and without refund in +the case of a paid event). + +## 7. Reporting Guidelines + +If you are subject to or witness unacceptable behavior, or have any other +concerns, please notify a community organizer as soon as possible: + + +Additionally, community organizers are available to help community members +engage with local law enforcement or to otherwise help those experiencing +unacceptable behavior feel safe. In the context of in-person events, organizers +will also provide escorts as desired by the person experiencing distress. + +## 8. Addressing Grievances + +If you feel you have been falsely or unfairly accused of violating this Code of +Conduct, you should notify @ivuorinen with a concise description of your +grievance. Your grievance will be handled in accordance with our existing +governing policies. + +## 9. Scope + +We expect all community participants (contributors, paid or otherwise; sponsors; +and other guests) to abide by this Code of Conduct in all community +venues--online and in-person--as well as in all one-on-one communications +pertaining to community business. + +This code of conduct and its related procedures also applies to unacceptable +behavior occurring outside the scope of community activities when such behavior +has the potential to adversely affect the safety and well-being of community +members. + +## 10. Contact info + +@ivuorinen + + +## 11. License and attribution + +The Citizen Code of Conduct is distributed by [Stumptown Syndicate][stumptown] +under a [Creative Commons Attribution-ShareAlike license][cc-by-sa]. + +Portions of text derived from the [Django Code of Conduct][django] and +the [Geek Feminism Anti-Harassment Policy][geek-feminism]. + +* _Revision 2.3. Posted 6 March 2017._ +* _Revision 2.2. Posted 4 February 2016._ +* _Revision 2.1. Posted 23 June 2014._ +* _Revision 2.0, adopted by the [Stumptown Syndicate][stumptown] board on 10 + January 2013. Posted 17 March 2013._ + +[stumptown]: https://github.com/stumpsyn +[cc-by-sa]: https://creativecommons.org/licenses/by-sa/3.0/ +[django]: https://www.djangoproject.com/conduct/ +[geek-feminism]: http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..f57b5f9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: ivuorinen + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..abdc2e8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: ivuorinen + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..810f90b --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,79 @@ +# GitHub Copilot Instructions for monolog-gdpr-filter + +## Project Overview + +This project is a PHP library that provides a Monolog processor for GDPR compliance. + +It allows masking, removing, or replacing sensitive data in logs using regex patterns, +field-level configuration, and custom callbacks. It is designed for easy integration with Monolog and Laravel. + +## Coding Conventions + +- **Language:** PHP 8.2+ + - **PHP Version:** Ensure compatibility with PHP 8.2 and above. +- **PSR Standards:** Follow PSR-12 for code style and autoloading. +- **Testing:** Use PHPUnit for all tests. Place tests in the `tests/` directory. Run `composer test` to execute tests. + - All tests should be written in a way that they can run independently. + - Use Attribute-based annotations for test methods (e.g., `#[Test]`). + - Use Attribute-based annotations for Covers (e.g., `#[CoversClass(GdprProcessor::class)]`). + - **PHPUnit Version:** Use PHPUnit 10.x or above. + - **PHPUnit Configuration:** Use `phpunit.xml` for configuration. + - **Code Coverage:** Use PHPUnit's code coverage features. Generate reports in the `build/` directory. +- **Type Declarations:** Use strict typing and type declarations for all functions and methods. +- **Namespaces:** Use appropriate namespaces for all classes + (e.g., `Ivuorinen\MonologGdprFilter`, or `Tests\` for tests). +- **Error Handling:** Use exceptions for error handling. +- **Static Analysis:** + - Use Psalm and PHPStan for static analysis. + - Config files: `psalm.xml`, `phpstan.neon` (if present). +- **Linting:** Use PHP_CodeSniffer with `phpcs.xml` for code style checks. All code must pass linting before merging. +- **Composer:** Use Composer for dependency management. Follow PSR-4 autoloading. + - Use `composer install` to install dependencies. + - Use `composer update` to update dependencies. +- **Formatting:** Use 4 spaces for indentation. No trailing whitespace. Use Unix line endings. + See `.editorconfig` and `phpcs.xml` for more details. +- **Version Control:** + - Use Git for version control. + - Follow semantic versioning (MAJOR.MINOR.PATCH). + - Do not commit anything, user will do it themselves. +- **Documentation:** + - Public classes and methods should have PHPDoc blocks. + - Update `README.md` for usage and installation changes. + +## Pull Request Guidelines + +- Ensure your branch is up-to-date with `main`. +- Use descriptive branch names (e.g., `feature/add-gdpr-processor`, `fix/issue-42`). +- Include a clear description of changes in the PR. +- Link to any relevant issues (e.g., `fixes #42`). +- Ensure all tests pass (`vendor/bin/phpunit`). +- Run static analysis (`vendor/bin/psalm` and/or `vendor/bin/phpstan`). +- Run code style checks (`vendor/bin/phpcs`). +- Add or update tests for new features or bug fixes. +- Update documentation as needed. + +## Directory Structure + +- `src/` — Main library source code +- `tests/` — PHPUnit tests +- `build/` — Build artifacts and coverage reports +- `vendor/` — Composer dependencies + +## Commit Message Guidelines + +- Use clear, concise messages in semantic commits style + (e.g., `fix: mask email addresses in context`, `feat(logger): add audit logger option`). +- Reference issues when relevant (e.g., `fix: #12 ...`). + +## Security & Privacy + +- Do not log or expose real sensitive data in tests or documentation. +- Ensure all masking/removal logic is covered by tests. + +## Automation + +- Use Composer scripts for automation if needed. +- CI will run tests, static analysis, and code style checks. + +--- +For questions, see the `README.md` or open an issue. diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..eae0b58 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["github>ivuorinen/renovate-config"], + "packageRules": [ + { + "matchUpdateTypes": ["minor", "patch"], + "matchCurrentVersion": "!/^0/", + "automerge": true + }, + { + "matchDepTypes": ["devDependencies"], + "automerge": true + } + ], + "schedule": ["before 4am on monday"], + "vulnerabilityAlerts": { + "labels": ["security"], + "assignees": ["ivuorinen"] + } +} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..c75d036 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,46 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: 'CodeQL' + +on: + push: + branches: ['main'] + pull_request: + branches: ['main'] + schedule: + - cron: '30 1 * * 0' # Run at 1:30 AM UTC every Sunday + merge_group: + +permissions: + actions: read + contents: read + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + security-events: write + + strategy: + fail-fast: false + matrix: + language: ['javascript'] # Add languages used in your actions + + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Initialize CodeQL + uses: github/codeql-action/init@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 + with: + languages: ${{ matrix.language }} + queries: security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 + with: + category: '/language:${{matrix.language}}' diff --git a/.github/workflows/phpcs.yaml b/.github/workflows/phpcs.yaml new file mode 100644 index 0000000..675d71c --- /dev/null +++ b/.github/workflows/phpcs.yaml @@ -0,0 +1,22 @@ +name: Code Style Check + +on: + pull_request: + branches: [main] + push: + branches: [main] + +jobs: + phpcs: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: shivammathur/setup-php@0f7f1d08e3e32076e51cae65eb0b0c871405b16e # 2.34.1 + - name: Install dependencies + run: composer install --no-interaction --prefer-dist + - name: Run PHP_CodeSniffer (PSR-12) + shell: bash + run: composer lint:tool:phpcs diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml new file mode 100644 index 0000000..75e08c0 --- /dev/null +++ b/.github/workflows/pr-lint.yml @@ -0,0 +1,30 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: Lint Code Base + +on: + push: + branches: [master, main] + pull_request: + branches: [master, main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: read-all + +jobs: + Linter: + name: PR Lint + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + statuses: write + contents: read + packages: read + + steps: + - name: Run PR Lint + # https://github.com/ivuorinen/actions + uses: ivuorinen/actions/pr-lint@8476cd4675ea8210eadf4a267bbeb13bddea4e75 # 25.7.21 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..667f284 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,26 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: Stale + +on: + schedule: + - cron: '0 8 * * *' # Every day at 08:00 + workflow_call: + workflow_dispatch: + +permissions: + contents: read + packages: read + statuses: read + +jobs: + stale: + name: 🧹 Clean up stale issues and PRs + runs-on: ubuntu-latest + + permissions: + contents: write # only for delete-branch option + issues: write + pull-requests: write + steps: + - uses: ivuorinen/actions/stale@8476cd4675ea8210eadf4a267bbeb13bddea4e75 # 25.7.21 diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml new file mode 100644 index 0000000..5f88668 --- /dev/null +++ b/.github/workflows/sync-labels.yml @@ -0,0 +1,41 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: Sync Labels + +on: + push: + branches: + - main + - master + paths: + - '.github/labels.yml' + - '.github/workflows/sync-labels.yml' + schedule: + - cron: '34 5 * * *' # Run every day at 05:34 AM UTC + workflow_call: + workflow_dispatch: + merge_group: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: read-all + +jobs: + labels: + name: ♻️ Sync Labels + runs-on: ubuntu-latest + timeout-minutes: 10 + + permissions: + contents: read + issues: write + + steps: + - name: ⤵️ Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + - name: ⤵️ Sync Latest Labels Definitions + uses: ivuorinen/actions/sync-labels@8476cd4675ea8210eadf4a267bbeb13bddea4e75 # 25.7.21 diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml new file mode 100644 index 0000000..4252241 --- /dev/null +++ b/.github/workflows/test-coverage.yaml @@ -0,0 +1,59 @@ +name: Test & Coverage + +on: + pull_request: + branches: [main] + push: + branches: [main] + +permissions: read-all + +jobs: + test: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup PHP + uses: shivammathur/setup-php@0f7f1d08e3e32076e51cae65eb0b0c871405b16e # 2.34.1 + with: + coverage: pcov + + - name: Install dependencies + run: composer install --no-interaction --prefer-dist + + - name: Configure matchers + uses: mheap/phpunit-matcher-action@5fe8d131daf5183c6137caea0d4e04a2b8afbb55 # v1.3.0 + + - name: Run Tests (teamcity format) + shell: bash + run: composer test:ci + + - name: Upload coverage report + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: coverage-report + path: coverage.xml + + - name: Code Coverage Summary Report + id: coverage-summary + uses: saschanowak/CloverCodeCoverageSummary@217593f67675e88fe1e6afeab0175018eb37deaa # 1.1.0 + with: + filename: coverage.xml + + - name: 'Add Code Coverage to Job Summary' + run: | + cat code-coverage-summary.md >> $GITHUB_STEP_SUMMARY + cat code-coverage-details.md >> $GITHUB_STEP_SUMMARY + + - name: 'Add Code Coverage Summary as PR Comment' + uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4 + if: github.event_name == 'pull_request' + with: + recreate: true + path: code-coverage-summary.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c97053f --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Generated files +*.cache +*.log +*.tmp +*.swp +# Ignore IDE specific files +*.idea/ +*.vscode/ + +# Composer files +/vendor/ +/composer.lock +/composer.phar + +# Build data +/build/ + +# Ignore test coverage reports +/coverage/ +coverage.xml diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..3de10f3 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,13 @@ +{ + "default": true, + "MD013": { + "line_length": 200, + "code_blocks": false, + "tables": false + }, + "MD024": { + "siblings_only": true + }, + "MD033": false, + "MD041": false +} diff --git a/.mega-linter.yml b/.mega-linter.yml new file mode 100644 index 0000000..82e546d --- /dev/null +++ b/.mega-linter.yml @@ -0,0 +1,35 @@ +--- +# Configuration file for MegaLinter +# See all available variables at +# https://megalinter.io/configuration/ and in linters documentation + +APPLY_FIXES: all +SHOW_ELAPSED_TIME: false # Show elapsed time at the end of MegaLinter run +PARALLEL: true +VALIDATE_ALL_CODEBASE: true +FILEIO_REPORTER: false # Generate file.io report +GITHUB_STATUS_REPORTER: true # Generate GitHub status report +IGNORE_GENERATED_FILES: true # Ignore generated files +JAVASCRIPT_DEFAULT_STYLE: prettier # Default style for JavaScript +PRINT_ALPACA: false # Print Alpaca logo in console +SARIF_REPORTER: true # Generate SARIF report +SHOW_SKIPPED_LINTERS: false # Show skipped linters in MegaLinter log + +DISABLE_LINTERS: + - REPOSITORY_DEVSKIM + +ENABLE_LINTERS: + - YAML_YAMLLINT + - MARKDOWN_MARKDOWNLINT + - YAML_PRETTIER + - JSON_PRETTIER + - JAVASCRIPT_ES + - TYPESCRIPT_ES + +YAML_YAMLLINT_CONFIG_FILE: .yamllint.yml +MARKDOWN_MARKDOWNLINT_CONFIG_FILE: .markdownlint.json +JAVASCRIPT_ES_CONFIG_FILE: .eslintrc.json +TYPESCRIPT_ES_CONFIG_FILE: .eslintrc.json + +FILTER_REGEX_EXCLUDE: > + (node_modules|\.automation/test|docs/json-schemas|\.github/workflows) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..ccfa22d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,63 @@ +--- +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: requirements-txt-fixer + - id: detect-private-key + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + - id: check-case-conflict + - id: check-merge-conflict + - id: check-executables-have-shebangs + - id: check-shebang-scripts-are-executable + - id: check-symlinks + - id: check-toml + - id: check-xml + - id: check-yaml + args: [--allow-multiple-documents] + - id: end-of-file-fixer + - id: mixed-line-ending + args: [--fix=auto] + - id: pretty-format-json + args: [--autofix, --no-sort-keys] + + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.44.0 + hooks: + - id: markdownlint + args: [-c, .markdownlint.json, --fix] + + - repo: https://github.com/adrienverge/yamllint + rev: v1.37.0 + hooks: + - id: yamllint + + - repo: https://github.com/scop/pre-commit-shfmt + rev: v3.11.0-1 + hooks: + - id: shfmt + + - repo: https://github.com/koalaman/shellcheck-precommit + rev: v0.10.0 + hooks: + - id: shellcheck + args: ['--severity=warning'] + + - repo: https://github.com/rhysd/actionlint + rev: v1.7.7 + hooks: + - id: actionlint + args: ['-shellcheck='] + + - repo: https://github.com/renovatebot/pre-commit-hooks + rev: 39.227.2 + hooks: + - id: renovate-config-validator + + - repo: https://github.com/bridgecrewio/checkov.git + rev: '3.2.400' + hooks: + - id: checkov + args: + - '--quiet' diff --git a/.shellcheckrc b/.shellcheckrc new file mode 100644 index 0000000..b430800 --- /dev/null +++ b/.shellcheckrc @@ -0,0 +1 @@ +disable=SC2129 diff --git a/.yamlignore b/.yamlignore new file mode 100644 index 0000000..e69de29 diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 0000000..065bc60 --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,13 @@ +--- +extends: default + +rules: + line-length: + max: 200 + level: warning + truthy: + check-keys: false + comments: + min-spaces-from-content: 1 + trailing-spaces: + level: warning diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8d1507b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Ismo Vuorinen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..8d1507b --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Ismo Vuorinen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..92bdf7a --- /dev/null +++ b/README.md @@ -0,0 +1,224 @@ +# Monolog GDPR Filter + +Monolog GDPR Filter is a PHP library that provides a Monolog processor for GDPR compliance. It allows masking, removing, +or replacing sensitive data in logs using regex patterns, field-level configuration, and custom callbacks. Designed for +easy integration with Monolog and Laravel. + +## Features + +- **Regex-based masking** for patterns like SSNs, credit cards, emails +- **Field-level masking/removal/replacement** using dot-notation paths +- **Custom callbacks** for advanced masking logic per field +- **Audit logging** for compliance tracking +- **Easy integration with Monolog and Laravel** + +## Installation + +Install via Composer: + +```bash +composer require ivuorinen/monolog-gdpr-filter +``` + +## Usage + +### Basic Monolog Setup + +```php +use Monolog\Logger; +use Monolog\Handler\StreamHandler; +use Ivuorinen\MonologGdprFilter\GdprProcessor; +use Ivuorinen\MonologGdprFilter\FieldMaskConfig; + +$patterns = GdprProcessor::getDefaultPatterns(); +$fieldPaths = [ + 'user.ssn' => GdprProcessor::removeField(), + 'payment.card' => GdprProcessor::replaceWith('[CC]'), + 'contact.email' => GdprProcessor::maskWithRegex(), + 'metadata.session' => GdprProcessor::replaceWith('[SESSION]'), +]; + +// Optional: custom callback for advanced masking +$customCallbacks = [ + 'user.name' => fn($value) => strtoupper($value), +]; + +// Optional: audit logger for compliance +$auditLogger = function($path, $original, $masked) { + error_log("GDPR mask: $path: $original => $masked"); +}; + +$logger = new Logger('app'); +$logger->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); +$logger->pushProcessor( + new GdprProcessor($patterns, $fieldPaths, $customCallbacks, $auditLogger) +); + +$logger->warning('This is a warning message.', [ + 'user' => ['ssn' => '123456-900T'], + 'contact' => ['email' => 'user@example.com'], + 'payment' => ['card' => '1234567812345678'], +]); +``` + +### FieldMaskConfig Options + +- `GdprProcessor::maskWithRegex()` — Mask field value using regex patterns +- `GdprProcessor::removeField()` — Remove field from context +- `GdprProcessor::replaceWith($value)` — Replace field value with static value + +### Custom Callbacks + +Provide custom callbacks for specific fields: + +```php +$customCallbacks = [ + 'user.name' => fn($value) => strtoupper($value), +]; +``` + +### Audit Logger + +Optionally provide an audit logger callback to record masking actions: + +```php +$auditLogger = function($path, $original, $masked) { + // Log or store audit info +}; +``` + +> **IMPORTANT**: Be mindful what you send to your audit log. Passing the original value might defeat the whole purpose +> of this project. + +## Laravel Integration + +You can integrate the GDPR processor with Laravel logging in two ways: + +### 1. Service Provider + +```php +// app/Providers/AppServiceProvider.php +use Illuminate\Support\ServiceProvider; +use Ivuorinen\MonologGdprFilter\GdprProcessor; + +class AppServiceProvider extends ServiceProvider +{ + public function boot() + { + $patterns = GdprProcessor::getDefaultPatterns(); + $fieldPaths = [ + 'user.ssn' => '[GDPR]', + 'payment.card' => '[CC]', + 'contact.email' => '', // empty string = regex mask + 'metadata.session' => '[SESSION]', + ]; + $this->app['log']->getLogger() + ->pushProcessor(new GdprProcessor($patterns, $fieldPaths)); + } +} +``` + +### 2. Tap Class (config/logging.php) + +```php +// app/Logging/GdprTap.php +namespace App\Logging; +use Monolog\Logger; +use Ivuorinen\MonologGdprFilter\GdprProcessor; + +class GdprTap +{ + public function __invoke(Logger $logger) + { + $patterns = GdprProcessor::getDefaultPatterns(); + $fieldPaths = [ + 'user.ssn' => '[GDPR]', + 'payment.card' => '[CC]', + 'contact.email' => '', + 'metadata.session' => '[SESSION]', + ]; + $logger->pushProcessor(new GdprProcessor($patterns, $fieldPaths)); + } +} +``` + +Reference in `config/logging.php`: + +```php +'channels' => [ + 'stack' => [ + 'driver' => 'stack', + 'channels' => ['single'], + 'tap' => [App\Logging\GdprTap::class], + ], + // ... +], +``` + +## Configuration + +You can configure the processor to filter out sensitive data by specifying: + +- **Regex patterns:** Used for masking values in messages and context +- **Field paths:** Dot-notation paths for masking/removal/replacement +- **Custom callbacks:** For advanced per-field masking +- **Audit logger:** For compliance tracking + +## Testing & Quality + +This project uses PHPUnit for testing, Psalm and PHPStan for static analysis, and PHP_CodeSniffer for code style checks. + +### Running Tests + +To run the test suite: + +```bash +composer test +``` + +To generate a code coverage report (HTML output in the `coverage/` directory): + +```bash +composer test:coverage +``` + +### Linting & Static Analysis + +To run all linters and static analysis: + +```bash +composer lint +``` + +To automatically fix code style and static analysis issues: + +```bash +composer lint:fix +``` + +## Notable Implementation Details + +- If a regex replacement in `regExpMessage` results in an empty string or the string "0", the original message is + returned. This is covered by dedicated PHPUnit tests. +- If a regex pattern is invalid, the audit logger (if set) is called, and the original message is returned. + +## Directory Structure + +- `src/` — Main library source code +- `tests/` — PHPUnit tests +- `coverage/` — Code coverage reports +- `vendor/` — Composer dependencies + +## Legal Disclaimer + +> **CAUTION**: This library helps mask/filter sensitive data for GDPR compliance, but it is your responsibility to +> ensure your application fully complies with all legal requirements. Review your logging and data handling policies +> regularly. + +## Contributing + +If you would like to contribute to this project, please fork the repository and submit a pull request. + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..4beec76 --- /dev/null +++ b/composer.json @@ -0,0 +1,59 @@ +{ + "$ref": "https://getcomposer.org/schema.json", + "name": "ivuorinen/monolog-gdpr-filter", + "description": "Monolog processor for GDPR masking with regex and dot-notation paths", + "version": "1.0.0", + "type": "library", + "scripts": { + "lint": [ + "@lint:tool:psalm", + "@lint:tool:phpcs" + ], + "lint:fix": [ + "@lint:tool:rector", + "@lint:tool:psalm:fix", + "@lint:tool:phpcbf" + ], + "test": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-text", + "test:coverage": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-text --coverage-html=coverage", + "test:ci": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --teamcity --coverage-clover=coverage.xml", + "lint:tool:phpcs": "./vendor/bin/phpcs src/ tests/ rector.php", + "lint:tool:phpcbf": "./vendor/bin/phpcbf src/ tests/ rector.php", + "lint:tool:psalm": "./vendor/bin/psalm --show-info=true", + "lint:tool:psalm:fix": "./vendor/bin/psalm --alter --issues=all", + "lint:tool:rector": "./vendor/bin/rector" + }, + "require": { + "php": "^8.2", + "monolog/monolog": "^3.0", + "adbario/php-dot-notation": "^3.3" + }, + "require-dev": { + "phpunit/phpunit": "^11", + "squizlabs/php_codesniffer": "^3.9", + "rector/rector": "^2.1", + "vimeo/psalm": "^6.13", + "psalm/plugin-phpunit": "^0.19.5", + "orklah/psalm-strict-equality": "^3.1", + "guuzen/psalm-enum-plugin": "^1.1", + "ergebnis/composer-normalize": "^2.47" + }, + "autoload": { + "psr-4": { + "Ivuorinen\\MonologGdprFilter\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests" + } + }, + "config": { + "allow-plugins": { + "ergebnis/composer-normalize": true + }, + "sort-packages": true + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..7f00bca --- /dev/null +++ b/composer.lock @@ -0,0 +1,6070 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "bfd694822e3c66caa103feb4b9402c90", + "packages": [ + { + "name": "adbario/php-dot-notation", + "version": "3.3.0", + "source": { + "type": "git", + "url": "https://github.com/adbario/php-dot-notation.git", + "reference": "a94ce4493d19ea430baa8d7d210a2c9bd7129fc2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/adbario/php-dot-notation/zipball/a94ce4493d19ea430baa8d7d210a2c9bd7129fc2", + "reference": "a94ce4493d19ea430baa8d7d210a2c9bd7129fc2", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Adbar\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Riku Särkinen", + "email": "riku@adbar.io" + } + ], + "description": "PHP dot notation access to arrays", + "homepage": "https://github.com/adbario/php-dot-notation", + "keywords": [ + "ArrayAccess", + "dotnotation" + ], + "support": { + "issues": "https://github.com/adbario/php-dot-notation/issues", + "source": "https://github.com/adbario/php-dot-notation/tree/3.3.0" + }, + "time": "2023-02-24T20:27:50+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.9.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6", + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.9.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2025-03-24T10:02:05+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + } + ], + "packages-dev": [ + { + "name": "amphp/amp", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/amp.git", + "reference": "7cf7fef3d667bfe4b2560bc87e67d5387a7bcde9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/amp/zipball/7cf7fef3d667bfe4b2560bc87e67d5387a7bcde9", + "reference": "7cf7fef3d667bfe4b2560bc87e67d5387a7bcde9", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "5.23.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Future/functions.php", + "src/Internal/functions.php" + ], + "psr-4": { + "Amp\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "https://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v3.1.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-01-26T16:07:39+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v2.1.2", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/55a6bd071aec26fa2a3e002618c20c35e3df1b46", + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/parser": "^1.1", + "amphp/pipeline": "^1", + "amphp/serialization": "^1", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2.3" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.22.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Internal/functions.php" + ], + "psr-4": { + "Amp\\ByteStream\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "https://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v2.1.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-03-16T17:10:27+00:00" + }, + { + "name": "amphp/cache", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/cache.git", + "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/cache/zipball/46912e387e6aa94933b61ea1ead9cf7540b7797c", + "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/serialization": "^1", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Cache\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + } + ], + "description": "A fiber-aware cache API based on Amp and Revolt.", + "homepage": "https://amphp.org/cache", + "support": { + "issues": "https://github.com/amphp/cache/issues", + "source": "https://github.com/amphp/cache/tree/v2.0.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-19T03:38:06+00:00" + }, + { + "name": "amphp/dns", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/dns.git", + "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/dns/zipball/78eb3db5fc69bf2fc0cb503c4fcba667bc223c71", + "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/cache": "^2", + "amphp/parser": "^1", + "amphp/process": "^2", + "daverandom/libdns": "^2.0.2", + "ext-filter": "*", + "ext-json": "*", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.20" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Dns\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Wright", + "email": "addr@daverandom.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + } + ], + "description": "Async DNS resolution for Amp.", + "homepage": "https://github.com/amphp/dns", + "keywords": [ + "amp", + "amphp", + "async", + "client", + "dns", + "resolve" + ], + "support": { + "issues": "https://github.com/amphp/dns/issues", + "source": "https://github.com/amphp/dns/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-01-19T15:43:40+00:00" + }, + { + "name": "amphp/parallel", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/parallel.git", + "reference": "5113111de02796a782f5d90767455e7391cca190" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/parallel/zipball/5113111de02796a782f5d90767455e7391cca190", + "reference": "5113111de02796a782f5d90767455e7391cca190", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/cache": "^2", + "amphp/parser": "^1", + "amphp/pipeline": "^1", + "amphp/process": "^2", + "amphp/serialization": "^1", + "amphp/socket": "^2", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.18" + }, + "type": "library", + "autoload": { + "files": [ + "src/Context/functions.php", + "src/Context/Internal/functions.php", + "src/Ipc/functions.php", + "src/Worker/functions.php" + ], + "psr-4": { + "Amp\\Parallel\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Stephen Coakley", + "email": "me@stephencoakley.com" + } + ], + "description": "Parallel processing component for Amp.", + "homepage": "https://github.com/amphp/parallel", + "keywords": [ + "async", + "asynchronous", + "concurrent", + "multi-processing", + "multi-threading" + ], + "support": { + "issues": "https://github.com/amphp/parallel/issues", + "source": "https://github.com/amphp/parallel/tree/v2.3.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-12-21T01:56:09+00:00" + }, + { + "name": "amphp/parser", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/parser.git", + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/parser/zipball/3cf1f8b32a0171d4b1bed93d25617637a77cded7", + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7", + "shasum": "" + }, + "require": { + "php": ">=7.4" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Parser\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A generator parser to make streaming parsers simple.", + "homepage": "https://github.com/amphp/parser", + "keywords": [ + "async", + "non-blocking", + "parser", + "stream" + ], + "support": { + "issues": "https://github.com/amphp/parser/issues", + "source": "https://github.com/amphp/parser/tree/v1.1.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-03-21T19:16:53+00:00" + }, + { + "name": "amphp/pipeline", + "version": "v1.2.3", + "source": { + "type": "git", + "url": "https://github.com/amphp/pipeline.git", + "reference": "7b52598c2e9105ebcddf247fc523161581930367" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/pipeline/zipball/7b52598c2e9105ebcddf247fc523161581930367", + "reference": "7b52598c2e9105ebcddf247fc523161581930367", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "php": ">=8.1", + "revolt/event-loop": "^1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.18" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Pipeline\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Asynchronous iterators and operators.", + "homepage": "https://amphp.org/pipeline", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "iterator", + "non-blocking" + ], + "support": { + "issues": "https://github.com/amphp/pipeline/issues", + "source": "https://github.com/amphp/pipeline/tree/v1.2.3" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-03-16T16:33:53+00:00" + }, + { + "name": "amphp/process", + "version": "v2.0.3", + "source": { + "type": "git", + "url": "https://github.com/amphp/process.git", + "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/process/zipball/52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", + "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Process\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A fiber-aware process manager based on Amp and Revolt.", + "homepage": "https://amphp.org/process", + "support": { + "issues": "https://github.com/amphp/process/issues", + "source": "https://github.com/amphp/process/tree/v2.0.3" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-19T03:13:44+00:00" + }, + { + "name": "amphp/serialization", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/serialization.git", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/serialization/zipball/693e77b2fb0b266c3c7d622317f881de44ae94a1", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "phpunit/phpunit": "^9 || ^8 || ^7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Serialization\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Serialization tools for IPC and data storage in PHP.", + "homepage": "https://github.com/amphp/serialization", + "keywords": [ + "async", + "asynchronous", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/amphp/serialization/issues", + "source": "https://github.com/amphp/serialization/tree/master" + }, + "time": "2020-03-25T21:39:07+00:00" + }, + { + "name": "amphp/socket", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/socket.git", + "reference": "58e0422221825b79681b72c50c47a930be7bf1e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/socket/zipball/58e0422221825b79681b72c50c47a930be7bf1e1", + "reference": "58e0422221825b79681b72c50c47a930be7bf1e1", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/dns": "^2", + "ext-openssl": "*", + "kelunik/certificate": "^1.1", + "league/uri": "^6.5 | ^7", + "league/uri-interfaces": "^2.3 | ^7", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "amphp/process": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "5.20" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Internal/functions.php", + "src/SocketAddress/functions.php" + ], + "psr-4": { + "Amp\\Socket\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@gmail.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Non-blocking socket connection / server implementations based on Amp and Revolt.", + "homepage": "https://github.com/amphp/socket", + "keywords": [ + "amp", + "async", + "encryption", + "non-blocking", + "sockets", + "tcp", + "tls" + ], + "support": { + "issues": "https://github.com/amphp/socket/issues", + "source": "https://github.com/amphp/socket/tree/v2.3.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-21T14:33:03+00:00" + }, + { + "name": "amphp/sync", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/sync.git", + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/sync/zipball/217097b785130d77cfcc58ff583cf26cd1770bf1", + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/pipeline": "^1", + "amphp/serialization": "^1", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.23" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Sync\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Stephen Coakley", + "email": "me@stephencoakley.com" + } + ], + "description": "Non-blocking synchronization primitives for PHP based on Amp and Revolt.", + "homepage": "https://github.com/amphp/sync", + "keywords": [ + "async", + "asynchronous", + "mutex", + "semaphore", + "synchronization" + ], + "support": { + "issues": "https://github.com/amphp/sync/issues", + "source": "https://github.com/amphp/sync/tree/v2.3.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-08-03T19:31:26+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-09-19T14:15:21+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "danog/advanced-json-rpc", + "version": "v3.2.2", + "source": { + "type": "git", + "url": "https://github.com/danog/php-advanced-json-rpc.git", + "reference": "aadb1c4068a88c3d0530cfe324b067920661efcb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/danog/php-advanced-json-rpc/zipball/aadb1c4068a88c3d0530cfe324b067920661efcb", + "reference": "aadb1c4068a88c3d0530cfe324b067920661efcb", + "shasum": "" + }, + "require": { + "netresearch/jsonmapper": "^5", + "php": ">=8.1", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" + }, + "replace": { + "felixfbecker/php-advanced-json-rpc": "^3" + }, + "require-dev": { + "phpunit/phpunit": "^9" + }, + "type": "library", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + }, + { + "name": "Daniil Gentili", + "email": "daniil@daniil.it" + } + ], + "description": "A more advanced JSONRPC implementation", + "support": { + "issues": "https://github.com/danog/php-advanced-json-rpc/issues", + "source": "https://github.com/danog/php-advanced-json-rpc/tree/v3.2.2" + }, + "time": "2025-02-14T10:55:15+00:00" + }, + { + "name": "daverandom/libdns", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/DaveRandom/LibDNS.git", + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DaveRandom/LibDNS/zipball/b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "Required for IDN support" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "LibDNS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "DNS protocol implementation written in pure PHP", + "keywords": [ + "dns" + ], + "support": { + "issues": "https://github.com/DaveRandom/LibDNS/issues", + "source": "https://github.com/DaveRandom/LibDNS/tree/v2.1.0" + }, + "time": "2024-04-12T12:12:48+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" + }, + "time": "2025-04-07T20:06:18+00:00" + }, + { + "name": "ergebnis/composer-normalize", + "version": "2.47.0", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/composer-normalize.git", + "reference": "ed24b9f8901f8fbafeca98f662eaca39427f0544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/composer-normalize/zipball/ed24b9f8901f8fbafeca98f662eaca39427f0544", + "reference": "ed24b9f8901f8fbafeca98f662eaca39427f0544", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0.0", + "ergebnis/json": "^1.4.0", + "ergebnis/json-normalizer": "^4.9.0", + "ergebnis/json-printer": "^3.7.0", + "ext-json": "*", + "justinrainbow/json-schema": "^5.2.12 || ^6.0.0", + "localheinz/diff": "^1.2.0", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "require-dev": { + "composer/composer": "^2.8.3", + "ergebnis/license": "^2.6.0", + "ergebnis/php-cs-fixer-config": "^6.46.0", + "ergebnis/phpunit-slow-test-detector": "^2.19.1", + "fakerphp/faker": "^1.24.1", + "infection/infection": "~0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.11", + "phpstan/phpstan-deprecation-rules": "^2.0.1", + "phpstan/phpstan-phpunit": "^2.0.6", + "phpstan/phpstan-strict-rules": "^2.0.4", + "phpunit/phpunit": "^9.6.20", + "rector/rector": "^2.0.11", + "symfony/filesystem": "^5.4.41" + }, + "type": "composer-plugin", + "extra": { + "class": "Ergebnis\\Composer\\Normalize\\NormalizePlugin", + "branch-alias": { + "dev-main": "2.44-dev" + }, + "plugin-optional": true, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" + } + }, + "autoload": { + "psr-4": { + "Ergebnis\\Composer\\Normalize\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Provides a composer plugin for normalizing composer.json.", + "homepage": "https://github.com/ergebnis/composer-normalize", + "keywords": [ + "composer", + "normalize", + "normalizer", + "plugin" + ], + "support": { + "issues": "https://github.com/ergebnis/composer-normalize/issues", + "security": "https://github.com/ergebnis/composer-normalize/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/composer-normalize" + }, + "time": "2025-04-15T11:09:27+00:00" + }, + { + "name": "ergebnis/json", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/json.git", + "reference": "7656ac2aa6c2ca4408f96f599e9a17a22c464f69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/json/zipball/7656ac2aa6c2ca4408f96f599e9a17a22c464f69", + "reference": "7656ac2aa6c2ca4408f96f599e9a17a22c464f69", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "require-dev": { + "ergebnis/data-provider": "^3.3.0", + "ergebnis/license": "^2.5.0", + "ergebnis/php-cs-fixer-config": "^6.37.0", + "ergebnis/phpunit-slow-test-detector": "^2.16.1", + "fakerphp/faker": "^1.24.0", + "infection/infection": "~0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.10", + "phpstan/phpstan-deprecation-rules": "^1.2.1", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-strict-rules": "^1.6.1", + "phpunit/phpunit": "^9.6.18", + "rector/rector": "^1.2.10" + }, + "type": "library", + "extra": { + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" + } + }, + "autoload": { + "psr-4": { + "Ergebnis\\Json\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Provides a Json value object for representing a valid JSON string.", + "homepage": "https://github.com/ergebnis/json", + "keywords": [ + "json" + ], + "support": { + "issues": "https://github.com/ergebnis/json/issues", + "security": "https://github.com/ergebnis/json/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/json" + }, + "time": "2024-11-17T11:51:22+00:00" + }, + { + "name": "ergebnis/json-normalizer", + "version": "4.9.0", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/json-normalizer.git", + "reference": "cc4dcf3890448572a2d9bea97133c4d860e59fb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/json-normalizer/zipball/cc4dcf3890448572a2d9bea97133c4d860e59fb1", + "reference": "cc4dcf3890448572a2d9bea97133c4d860e59fb1", + "shasum": "" + }, + "require": { + "ergebnis/json": "^1.2.0", + "ergebnis/json-pointer": "^3.4.0", + "ergebnis/json-printer": "^3.5.0", + "ergebnis/json-schema-validator": "^4.2.0", + "ext-json": "*", + "justinrainbow/json-schema": "^5.2.12 || ^6.0.0", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "require-dev": { + "composer/semver": "^3.4.3", + "ergebnis/composer-normalize": "^2.44.0", + "ergebnis/data-provider": "^3.3.0", + "ergebnis/license": "^2.5.0", + "ergebnis/php-cs-fixer-config": "^6.37.0", + "ergebnis/phpunit-slow-test-detector": "^2.16.1", + "fakerphp/faker": "^1.24.0", + "infection/infection": "~0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.10", + "phpstan/phpstan-deprecation-rules": "^1.2.1", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-strict-rules": "^1.6.1", + "phpunit/phpunit": "^9.6.19", + "rector/rector": "^1.2.10" + }, + "suggest": { + "composer/semver": "If you want to use ComposerJsonNormalizer or VersionConstraintNormalizer" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.8-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" + } + }, + "autoload": { + "psr-4": { + "Ergebnis\\Json\\Normalizer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Provides generic and vendor-specific normalizers for normalizing JSON documents.", + "homepage": "https://github.com/ergebnis/json-normalizer", + "keywords": [ + "json", + "normalizer" + ], + "support": { + "issues": "https://github.com/ergebnis/json-normalizer/issues", + "security": "https://github.com/ergebnis/json-normalizer/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/json-normalizer" + }, + "time": "2025-04-10T13:13:04+00:00" + }, + { + "name": "ergebnis/json-pointer", + "version": "3.6.0", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/json-pointer.git", + "reference": "4fc85d8edb74466d282119d8d9541ec7cffc0798" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/json-pointer/zipball/4fc85d8edb74466d282119d8d9541ec7cffc0798", + "reference": "4fc85d8edb74466d282119d8d9541ec7cffc0798", + "shasum": "" + }, + "require": { + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.43.0", + "ergebnis/data-provider": "^3.2.0", + "ergebnis/license": "^2.4.0", + "ergebnis/php-cs-fixer-config": "^6.32.0", + "ergebnis/phpunit-slow-test-detector": "^2.15.0", + "fakerphp/faker": "^1.23.1", + "infection/infection": "~0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.10", + "phpstan/phpstan-deprecation-rules": "^1.2.1", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-strict-rules": "^1.6.1", + "phpunit/phpunit": "^9.6.19", + "rector/rector": "^1.2.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.6-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" + } + }, + "autoload": { + "psr-4": { + "Ergebnis\\Json\\Pointer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Provides an abstraction of a JSON pointer.", + "homepage": "https://github.com/ergebnis/json-pointer", + "keywords": [ + "RFC6901", + "json", + "pointer" + ], + "support": { + "issues": "https://github.com/ergebnis/json-pointer/issues", + "security": "https://github.com/ergebnis/json-pointer/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/json-pointer" + }, + "time": "2024-11-17T12:37:06+00:00" + }, + { + "name": "ergebnis/json-printer", + "version": "3.7.0", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/json-printer.git", + "reference": "ced41fce7854152f0e8f38793c2ffe59513cdd82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/json-printer/zipball/ced41fce7854152f0e8f38793c2ffe59513cdd82", + "reference": "ced41fce7854152f0e8f38793c2ffe59513cdd82", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "require-dev": { + "ergebnis/data-provider": "^3.3.0", + "ergebnis/license": "^2.5.0", + "ergebnis/php-cs-fixer-config": "^6.37.0", + "ergebnis/phpunit-slow-test-detector": "^2.16.1", + "fakerphp/faker": "^1.24.0", + "infection/infection": "~0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.10", + "phpstan/phpstan-deprecation-rules": "^1.2.1", + "phpstan/phpstan-phpunit": "^1.4.1", + "phpstan/phpstan-strict-rules": "^1.6.1", + "phpunit/phpunit": "^9.6.21", + "rector/rector": "^1.2.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ergebnis\\Json\\Printer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Provides a JSON printer, allowing for flexible indentation.", + "homepage": "https://github.com/ergebnis/json-printer", + "keywords": [ + "formatter", + "json", + "printer" + ], + "support": { + "issues": "https://github.com/ergebnis/json-printer/issues", + "security": "https://github.com/ergebnis/json-printer/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/json-printer" + }, + "time": "2024-11-17T11:20:51+00:00" + }, + { + "name": "ergebnis/json-schema-validator", + "version": "4.4.0", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/json-schema-validator.git", + "reference": "85f90c81f718aebba1d738800af83eeb447dc7ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/json-schema-validator/zipball/85f90c81f718aebba1d738800af83eeb447dc7ec", + "reference": "85f90c81f718aebba1d738800af83eeb447dc7ec", + "shasum": "" + }, + "require": { + "ergebnis/json": "^1.2.0", + "ergebnis/json-pointer": "^3.4.0", + "ext-json": "*", + "justinrainbow/json-schema": "^5.2.12 || ^6.0.0", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.44.0", + "ergebnis/data-provider": "^3.3.0", + "ergebnis/license": "^2.5.0", + "ergebnis/php-cs-fixer-config": "^6.37.0", + "ergebnis/phpunit-slow-test-detector": "^2.16.1", + "fakerphp/faker": "^1.24.0", + "infection/infection": "~0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.10", + "phpstan/phpstan-deprecation-rules": "^1.2.1", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-strict-rules": "^1.6.1", + "phpunit/phpunit": "^9.6.20", + "rector/rector": "^1.2.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.4-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" + } + }, + "autoload": { + "psr-4": { + "Ergebnis\\Json\\SchemaValidator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Provides a JSON schema validator, building on top of justinrainbow/json-schema.", + "homepage": "https://github.com/ergebnis/json-schema-validator", + "keywords": [ + "json", + "schema", + "validator" + ], + "support": { + "issues": "https://github.com/ergebnis/json-schema-validator/issues", + "security": "https://github.com/ergebnis/json-schema-validator/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/json-schema-validator" + }, + "time": "2024-11-18T06:32:28+00:00" + }, + { + "name": "felixfbecker/language-server-protocol", + "version": "v1.5.3", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server-protocol.git", + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/a9e113dbc7d849e35b8776da39edaf4313b7b6c9", + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "*", + "squizlabs/php_codesniffer": "^3.1", + "vimeo/psalm": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "LanguageServerProtocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "PHP classes for the Language Server Protocol", + "keywords": [ + "language", + "microsoft", + "php", + "server" + ], + "support": { + "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.3" + }, + "time": "2024-04-30T00:40:11+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "8520451a140d3f46ac33042715115e290cf5785f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", + "reference": "8520451a140d3f46ac33042715115e290cf5785f", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2024-08-06T10:04:20+00:00" + }, + { + "name": "guuzen/psalm-enum-plugin", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/Guuzen/psalm-enum-plugin.git", + "reference": "8b9d95e7cf9abe2da05c54b04fab9e2fa8348481" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Guuzen/psalm-enum-plugin/zipball/8b9d95e7cf9abe2da05c54b04fab9e2fa8348481", + "reference": "8b9d95e7cf9abe2da05c54b04fab9e2fa8348481", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "vimeo/psalm": "^6" + }, + "require-dev": { + "psalm/plugin-phpunit": "^0.19.0", + "roave/security-advisories": "dev-latest" + }, + "type": "psalm-plugin", + "extra": { + "psalm": { + "pluginClass": "Guuzen\\PsalmEnumPlugin\\Plugin" + } + }, + "autoload": { + "psr-4": { + "Guuzen\\PsalmEnumPlugin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guuzen" + } + ], + "description": "Reports attempts to create enum with a wrong case", + "support": { + "issues": "https://github.com/Guuzen/psalm-enum-plugin/issues", + "source": "https://github.com/Guuzen/psalm-enum-plugin/tree/1.1.0" + }, + "time": "2025-07-11T20:31:54+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "6.4.2", + "source": { + "type": "git", + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/ce1fd2d47799bb60668643bc6220f6278a4c1d02", + "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02", + "shasum": "" + }, + "require": { + "ext-json": "*", + "marc-mabe/php-enum": "^4.0", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.3.0", + "json-schema/json-schema-test-suite": "1.2.0", + "marc-mabe/php-enum-phpstan": "^2.0", + "phpspec/prophecy": "^1.19", + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^8.5" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/jsonrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/6.4.2" + }, + "time": "2025-06-03T18:27:04+00:00" + }, + { + "name": "kelunik/certificate", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/kelunik/certificate.git", + "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kelunik/certificate/zipball/7e00d498c264d5eb4f78c69f41c8bd6719c0199e", + "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "php": ">=7.0" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^6 | 7 | ^8 | ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Kelunik\\Certificate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Access certificate details and transform between different formats.", + "keywords": [ + "DER", + "certificate", + "certificates", + "openssl", + "pem", + "x509" + ], + "support": { + "issues": "https://github.com/kelunik/certificate/issues", + "source": "https://github.com/kelunik/certificate/tree/v1.1.3" + }, + "time": "2023-02-03T21:26:53+00:00" + }, + { + "name": "league/uri", + "version": "7.5.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri.git", + "reference": "81fb5145d2644324614cc532b28efd0215bda430" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/81fb5145d2644324614cc532b28efd0215bda430", + "reference": "81fb5145d2644324614cc532b28efd0215bda430", + "shasum": "" + }, + "require": { + "league/uri-interfaces": "^7.5", + "php": "^8.1" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", + "league/uri-components": "Needed to easily manipulate URI objects components", + "php-64bit": "to improve IPV4 host parsing", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri/tree/7.5.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2024-12-08T08:40:02+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.5.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1", + "psr/http-factory": "^1", + "psr/http-message": "^1.1 || ^2.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "php-64bit": "to improve IPV4 host parsing", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common interfaces and classes for URI representation and interaction", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.5.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2024-12-08T08:18:47+00:00" + }, + { + "name": "localheinz/diff", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/localheinz/diff.git", + "reference": "ec413943c2b518464865673fd5b38f7df867a010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/localheinz/diff/zipball/ec413943c2b518464865673fd5b38f7df867a010", + "reference": "ec413943c2b518464865673fd5b38f7df867a010", + "shasum": "" + }, + "require": { + "php": "~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5.0 || ^8.5.23", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Fork of sebastian/diff for use with ergebnis/composer-normalize", + "homepage": "https://github.com/localheinz/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/localheinz/diff/issues", + "source": "https://github.com/localheinz/diff/tree/1.2.0" + }, + "time": "2024-12-04T14:16:01+00:00" + }, + { + "name": "marc-mabe/php-enum", + "version": "v4.7.1", + "source": { + "type": "git", + "url": "https://github.com/marc-mabe/php-enum.git", + "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/7159809e5cfa041dca28e61f7f7ae58063aae8ed", + "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed", + "shasum": "" + }, + "require": { + "ext-reflection": "*", + "php": "^7.1 | ^8.0" + }, + "require-dev": { + "phpbench/phpbench": "^0.16.10 || ^1.0.4", + "phpstan/phpstan": "^1.3.1", + "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", + "vimeo/psalm": "^4.17.0 | ^5.26.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.2-dev", + "dev-master": "4.7-dev" + } + }, + "autoload": { + "psr-4": { + "MabeEnum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marc Bennewitz", + "email": "dev@mabe.berlin", + "homepage": "https://mabe.berlin/", + "role": "Lead" + } + ], + "description": "Simple and fast implementation of enumerations with native PHP", + "homepage": "https://github.com/marc-mabe/php-enum", + "keywords": [ + "enum", + "enum-map", + "enum-set", + "enumeration", + "enumerator", + "enummap", + "enumset", + "map", + "set", + "type", + "type-hint", + "typehint" + ], + "support": { + "issues": "https://github.com/marc-mabe/php-enum/issues", + "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.1" + }, + "time": "2024-11-28T04:54:44+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.3", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36", + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-07-05T12:25:42+00:00" + }, + { + "name": "netresearch/jsonmapper", + "version": "v5.0.0", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c", + "reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/v5.0.0" + }, + "time": "2024-09-08T10:20:00+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.6.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56", + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0" + }, + "time": "2025-07-27T20:03:57+00:00" + }, + { + "name": "orklah/psalm-strict-equality", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/orklah/psalm-strict-equality.git", + "reference": "ab6ae670d4befcd3678ac2fc02387b1eb9bc793d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/orklah/psalm-strict-equality/zipball/ab6ae670d4befcd3678ac2fc02387b1eb9bc793d", + "reference": "ab6ae670d4befcd3678ac2fc02387b1eb9bc793d", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.0|^5.0", + "php": "^7.3|^8.0", + "vimeo/psalm": "^4.6.1|^5.0|^6" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "psalm/plugin-phpunit": "^0.15.0" + }, + "type": "psalm-plugin", + "extra": { + "psalm": { + "pluginClass": "Orklah\\StrictEquality\\Plugin" + } + }, + "autoload": { + "psr-4": { + "Orklah\\StrictEquality\\": [ + "./src" + ], + "Orklah\\StrictEquality\\Tests\\": [ + "./tests" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "orklah" + } + ], + "description": "Automatically change == into === when safe ", + "support": { + "issues": "https://github.com/orklah/psalm-strict-equality/issues", + "source": "https://github.com/orklah/psalm-strict-equality/tree/v3.1.0" + }, + "time": "2025-01-31T18:01:26+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.6.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/92dde6a5919e34835c506ac8c523ef095a95ed62", + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.2" + }, + "time": "2025-04-13T19:20:35+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" + }, + "time": "2024-11-09T15:12:26+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/b9e61a61e39e02dd90944e9115241c7f7e76bfd8", + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.2.0" + }, + "time": "2025-07-13T07:04:09+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.20", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "a9ccfef95210f92ba6feea6e8d1eef42b5605499" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/a9ccfef95210f92ba6feea6e8d1eef42b5605499", + "reference": "a9ccfef95210f92ba6feea6e8d1eef42b5605499", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-07-26T20:45:26+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "11.0.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "1a800a7446add2d79cc6b3c01c45381810367d76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1a800a7446add2d79cc6b3c01c45381810367d76", + "reference": "1a800a7446add2d79cc6b3c01c45381810367d76", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.4.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.0", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.2" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/show" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2025-06-18T08:56:18+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-27T05:02:59+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:07:44+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:08:43+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:09:35+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "11.5.27", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "446d43867314781df7e9adf79c3ec7464956fd8f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/446d43867314781df7e9adf79c3ec7464956fd8f", + "reference": "446d43867314781df7e9adf79c3ec7464956fd8f", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.3", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.10", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.1", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.1", + "sebastian/exporter": "^6.3.0", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.1.2", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.27" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2025-07-11T04:10:06+00:00" + }, + { + "name": "psalm/plugin-phpunit", + "version": "0.19.5", + "source": { + "type": "git", + "url": "https://github.com/psalm/psalm-plugin-phpunit.git", + "reference": "143f9d5e049fffcdbc0da3fbb99f6149f9d3e2dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/psalm/psalm-plugin-phpunit/zipball/143f9d5e049fffcdbc0da3fbb99f6149f9d3e2dc", + "reference": "143f9d5e049fffcdbc0da3fbb99f6149f9d3e2dc", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "php": ">=8.1", + "vimeo/psalm": "dev-master || ^6.10.0" + }, + "conflict": { + "phpspec/prophecy": "<1.20.0", + "phpspec/prophecy-phpunit": "<2.3.0", + "phpunit/phpunit": "<8.5.1" + }, + "require-dev": { + "php": "^7.3 || ^8.0", + "phpunit/phpunit": "^10.0 || ^11.0 || ^12.0", + "squizlabs/php_codesniffer": "^3.3.1", + "weirdan/prophecy-shim": "^1.0 || ^2.0" + }, + "type": "psalm-plugin", + "extra": { + "psalm": { + "pluginClass": "Psalm\\PhpUnitPlugin\\Plugin" + } + }, + "autoload": { + "psr-4": { + "Psalm\\PhpUnitPlugin\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Brown", + "email": "github@muglug.com" + } + ], + "description": "Psalm plugin for PHPUnit", + "support": { + "issues": "https://github.com/psalm/psalm-plugin-phpunit/issues", + "source": "https://github.com/psalm/psalm-plugin-phpunit/tree/0.19.5" + }, + "time": "2025-03-31T18:49:55+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "rector/rector", + "version": "2.1.2", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "40a71441dd73fa150a66102f5ca1364c44fc8fff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/40a71441dd73fa150a66102f5ca1364c44fc8fff", + "reference": "40a71441dd73fa150a66102f5ca1364c44fc8fff", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "phpstan/phpstan": "^2.1.18" + }, + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" + }, + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "homepage": "https://getrector.com/", + "keywords": [ + "automation", + "dev", + "migration", + "refactoring" + ], + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/2.1.2" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2025-07-17T19:30:06+00:00" + }, + { + "name": "revolt/event-loop", + "version": "v1.0.7", + "source": { + "type": "git", + "url": "https://github.com/revoltphp/event-loop.git", + "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/09bf1bf7f7f574453efe43044b06fafe12216eb3", + "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.15" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Revolt\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "ceesjank@gmail.com" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Rock-solid event loop for concurrent PHP applications.", + "keywords": [ + "async", + "asynchronous", + "concurrency", + "event", + "event-loop", + "non-blocking", + "scheduler" + ], + "support": { + "issues": "https://github.com/revoltphp/event-loop/issues", + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.7" + }, + "time": "2025-01-25T19:27:39+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:41:36+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-19T07:56:08+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:45:54+00:00" + }, + { + "name": "sebastian/comparator", + "version": "6.3.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/24b8fbc2c8e201bb1308e7b05148d6ab393b6959", + "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.4" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-07T06:57:01+00:00" + }, + { + "name": "sebastian/complexity", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:49:50+00:00" + }, + { + "name": "sebastian/diff", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:53:05+00:00" + }, + { + "name": "sebastian/environment", + "version": "7.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" + } + ], + "time": "2025-05-21T11:55:47+00:00" + }, + { + "name": "sebastian/exporter", + "version": "6.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3", + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-12-05T09:17:50+00:00" + }, + { + "name": "sebastian/global-state", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:57:36+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:58:38+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:00:13+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:01:32+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:10:34+00:00" + }, + { + "name": "sebastian/type", + "version": "5.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", + "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-18T13:35:50+00:00" + }, + { + "name": "sebastian/version", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-09T05:16:32+00:00" + }, + { + "name": "spatie/array-to-xml", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/array-to-xml.git", + "reference": "7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67", + "reference": "7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": "^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.2", + "pestphp/pest": "^1.21", + "spatie/pest-plugin-snapshots": "^1.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Spatie\\ArrayToXml\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://freek.dev", + "role": "Developer" + } + ], + "description": "Convert an array to xml", + "homepage": "https://github.com/spatie/array-to-xml", + "keywords": [ + "array", + "convert", + "xml" + ], + "support": { + "source": "https://github.com/spatie/array-to-xml/tree/3.4.0" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2024-12-16T12:45:15+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.13.2", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5b5e3821314f947dd040c70f7992a64eac89025c", + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-06-17T22:17:01+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "symfony/console", + "version": "v7.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/9e27aecde8f506ba0fd1d9989620c04a87697101", + "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.3.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T19:55:54+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-25T15:15:23+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "000df7860439609837bbe28670b0be15783b7fbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/000df7860439609837bbe28670b0be15783b7fbf", + "reference": "000df7860439609837bbe28670b0be15783b7fbf", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-02-20T12:04:08+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-25T09:37:31+00:00" + }, + { + "name": "symfony/string", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", + "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-20T20:19:01+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + }, + { + "name": "vimeo/psalm", + "version": "6.13.0", + "source": { + "type": "git", + "url": "https://github.com/vimeo/psalm.git", + "reference": "70cdf647255a1362b426bb0f522a85817b8c791c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/70cdf647255a1362b426bb0f522a85817b8c791c", + "reference": "70cdf647255a1362b426bb0f522a85817b8c791c", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/parallel": "^2.3", + "composer-runtime-api": "^2", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^2.0 || ^3.0", + "danog/advanced-json-rpc": "^3.1", + "dnoegel/php-xdg-base-dir": "^0.1.1", + "ext-ctype": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "felixfbecker/language-server-protocol": "^1.5.3", + "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", + "netresearch/jsonmapper": "^5.0", + "nikic/php-parser": "^5.0.0", + "php": "~8.1.31 || ~8.2.27 || ~8.3.16 || ~8.4.3", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0 || ^7.0", + "spatie/array-to-xml": "^2.17.0 || ^3.0", + "symfony/console": "^6.0 || ^7.0", + "symfony/filesystem": "~6.3.12 || ~6.4.3 || ^7.0.3", + "symfony/polyfill-php84": "^1.31.0" + }, + "provide": { + "psalm/psalm": "self.version" + }, + "require-dev": { + "amphp/phpunit-util": "^3", + "bamarni/composer-bin-plugin": "^1.4", + "brianium/paratest": "^6.9", + "danog/class-finder": "^0.4.8", + "dg/bypass-finals": "^1.5", + "ext-curl": "*", + "mockery/mockery": "^1.5", + "nunomaduro/mock-final-classes": "^1.1", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpdoc-parser": "^1.6", + "phpunit/phpunit": "^9.6", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.19", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.6", + "symfony/process": "^6.0 || ^7.0" + }, + "suggest": { + "ext-curl": "In order to send data to shepherd", + "ext-igbinary": "^2.0.5 is required, used to serialize caching data" + }, + "bin": [ + "psalm", + "psalm-language-server", + "psalm-plugin", + "psalm-refactor", + "psalm-review", + "psalter" + ], + "type": "project", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev", + "dev-3.x": "3.x-dev", + "dev-4.x": "4.x-dev", + "dev-5.x": "5.x-dev", + "dev-6.x": "6.x-dev", + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psalm\\": "src/Psalm/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Brown" + }, + { + "name": "Daniil Gentili", + "email": "daniil@daniil.it" + } + ], + "description": "A static analysis tool for finding errors in PHP applications", + "keywords": [ + "code", + "inspection", + "php", + "static analysis" + ], + "support": { + "docs": "https://psalm.dev/docs", + "issues": "https://github.com/vimeo/psalm/issues", + "source": "https://github.com/vimeo/psalm" + }, + "time": "2025-07-14T09:59:17+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": {}, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": "^8.2" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..ed6f53a --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,10 @@ + + + PHP_CodeSniffer configuration for PSR-12 coding standard. + + src/ + tests/ + rector.php + vendor/ + diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..a7b44fc --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,28 @@ + + + + + tests + + + + + + src + + + + + + + + + diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..a8fbec6 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..0844403 --- /dev/null +++ b/rector.php @@ -0,0 +1,28 @@ +withPaths([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]) + ->withPhpVersion(80200) + ->withPhpSets(php82: true) + ->withComposerBased(phpunit: true) + ->withImportNames(removeUnusedImports: true) + ->withPreparedSets( + deadCode: true, + codeQuality: true, + codingStyle: true, + earlyReturn: true, + phpunitCodeQuality: true + ); +} catch (InvalidConfigurationException $e) { + echo "Configuration error: " . $e->getMessage() . PHP_EOL; + exit(1); +} diff --git a/src/FieldMaskConfig.php b/src/FieldMaskConfig.php new file mode 100644 index 0000000..92018cd --- /dev/null +++ b/src/FieldMaskConfig.php @@ -0,0 +1,19 @@ + $patterns Regex pattern => replacement + * @param array|string[] $fieldPaths Dot-notation path => FieldMaskConfig + * @param array $customCallbacks Dot-notation path => callback(value): string + * @param callable|null $auditLogger Opt. audit logger callback: + * fn(string $path, mixed $original, mixed $masked) + */ + public function __construct( + private readonly array $patterns, + private readonly array $fieldPaths = [], + private readonly array $customCallbacks = [], + private $auditLogger = null + ) { + } + + /** + * FieldMaskConfig: config for masking/removal per field path using regex. + */ + public static function maskWithRegex(): FieldMaskConfig + { + return new FieldMaskConfig(FieldMaskConfig::MASK_REGEX); + } + + /** + * FieldMaskConfig: Remove field from context. + */ + public static function removeField(): FieldMaskConfig + { + return new FieldMaskConfig(FieldMaskConfig::REMOVE); + } + + /** + * FieldMaskConfig: Replace field value with a static string. + */ + public static function replaceWith(string $replacement): FieldMaskConfig + { + return new FieldMaskConfig(FieldMaskConfig::REPLACE, $replacement); + } + + /** + * Default GDPR regex patterns. Non-exhaustive, should be extended with your own. + * + * @return array + */ + public static function getDefaultPatterns(): array + { + return [ + // Finnish SSN (HETU) + '/\b\d{6}[-+A]?\d{3}[A-Z]\b/u' => '***HETU***', + // US Social Security Number (strict: 3-2-4 digits) + '/^\d{3}-\d{2}-\d{4}$/' => '***USSSN***', + // IBAN (strictly match Finnish IBAN with or without spaces, only valid groupings) + '/^FI\d{2}(?: ?\d{4}){3} ?\d{2}$/u' => '***IBAN***', + // Also match fully compact Finnish IBAN (no spaces) + '/^FI\d{16}$/u' => '***IBAN***', + // International phone numbers (E.164, +countrycode...) + '/^\+\d{1,3}[\s-]?\d{1,4}[\s-]?\d{1,4}[\s-]?\d{1,9}$/' => '***PHONE***', + // Email address + '/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/' => '***EMAIL***', + // Date of birth (YYYY-MM-DD) + '/^(19|20)\d{2}-[01]\d\-[0-3]\d$/' => '***DOB***', + // Date of birth (DD/MM/YYYY) + '/^[0-3]\d\/[01]\d\/(19|20)\d{2}$/' => '***DOB***', + // Passport numbers (A followed by 6 digits) + '/^A\d{6}$/' => '***PASSPORT***', + // Credit card numbers (Visa, MC, Amex, Discover test numbers) + '/^(4111 1111 1111 1111|5500-0000-0000-0004|340000000000009|6011000000000004)$/' => '***CC***', + // Generic 16-digit credit card (for test compatibility) + '/\b[0-9]{16}\b/u' => '***CC***', + // Bearer tokens (JWT, at least 10 chars after Bearer) + '/^Bearer [A-Za-z0-9\-\._~\+\/]{10,}$/' => '***TOKEN***', + // API keys (Stripe-like, 20+ chars, or sk_live|sk_test) + '/^(sk_(live|test)_[A-Za-z0-9]{16,}|[A-Za-z0-9\-_]{20,})$/' => '***APIKEY***', + // MAC addresses + '/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/' => '***MAC***', + ]; + } + + /** + * Process a log record to mask sensitive information. + * + * @param LogRecord $record The log record to process + * @return LogRecord The processed log record with masked message and context + * + * @psalm-suppress MissingOverrideAttribute Override is available from PHP 8.3 + */ + public function __invoke(LogRecord $record): LogRecord + { + $message = $this->regExpMessage($record->message); + $context = $record->context; + $accessor = new Dot($context); + + if ($this->fieldPaths !== []) { + $this->maskFieldPaths($accessor); + $context = $accessor->all(); + } else { + $context = $this->recursiveMask($context); + } + + return $record->with(message: $message, context: $context); + } + + /** + * Mask a string using all regex patterns sequentially. + */ + public function regExpMessage(string $message = ''): string + { + foreach ($this->patterns as $regex => $replacement) { + /** + * @var array $regex + */ + $result = @preg_replace($regex, $replacement, $message); + if ($result === null) { + if (is_callable($this->auditLogger)) { + call_user_func($this->auditLogger, 'preg_replace_error', $message, $message); + } + + continue; + } + + if ($result === '' || $result === '0') { + // If the result is empty, we can skip further processing + return $message; + } + + $message = $result; + } + + return $message; + } + + /** + * Mask only specified paths in context (fieldPaths) + */ + private function maskFieldPaths(Dot $accessor): void + { + foreach ($this->fieldPaths as $path => $config) { + if (!$accessor->has($path)) { + continue; + } + + $value = $accessor->get($path, ""); + $action = $this->maskValue($path, $value, $config); + if ($action['remove'] ?? false) { + $accessor->delete($path); + $this->logAudit($path, $value, null); + continue; + } + + $masked = $action['masked']; + if ($masked !== null && $masked !== $value) { + $accessor->set($path, $masked); + $this->logAudit($path, $value, $masked); + } + } + } + + /** + * Mask a single value according to config or callback + * Returns an array: ['masked' => value|null, 'remove' => bool] + * + * @psalm-return array{masked: string|null, remove: bool} + */ + private function maskValue(string $path, mixed $value, null|FieldMaskConfig|string $config): array + { + /** @noinspection PhpArrayIndexImmediatelyRewrittenInspection */ + $result = ['masked' => null, 'remove' => false]; + if (array_key_exists($path, $this->customCallbacks) && $this->customCallbacks[$path] !== null) { + $result['masked'] = call_user_func($this->customCallbacks[$path], $value); + return $result; + } + + if ($config instanceof FieldMaskConfig) { + switch ($config->type) { + case FieldMaskConfig::MASK_REGEX: + $result['masked'] = $this->regExpMessage($value); + break; + case FieldMaskConfig::REMOVE: + $result['masked'] = null; + $result['remove'] = true; + break; + case FieldMaskConfig::REPLACE: + $result['masked'] = $config->replacement; + break; + default: + // Return the type as string for unknown types + $result['masked'] = $config->type; + break; + } + } else { + // Backward compatibility: treat string as replacement + $result['masked'] = $config; + } + + return $result; + } + + /** + * Audit logger helper + * + * @param string $path Dot-notation path of the field + * @param mixed $original Original value before masking + * @param null|string $masked Masked value after processing, or null if removed + */ + private function logAudit(string $path, mixed $original, string|null $masked): void + { + if (is_callable($this->auditLogger) && $original !== $masked) { + // Only log if the value was actually changed + call_user_func($this->auditLogger, $path, $original, $masked); + } + } + + /** + * Recursively mask all string values in an array using regex patterns. + */ + protected function recursiveMask(string|array $data): string|array + { + if (is_string($data)) { + return $this->regExpMessage($data); + } + + foreach ($data as $key => $value) { + $data[$key] = $this->recursiveMask($value); + } + + return $data; + } + + /** + * Mask a string using all regex patterns at once. + */ + public function maskMessage(string $value = ''): string + { + /** @var array $keys */ + $keys = array_keys($this->patterns); + $values = array_values($this->patterns); + $result = @preg_replace($keys, $values, $value); + if ($result === null) { + if (is_callable($this->auditLogger)) { + call_user_func($this->auditLogger, 'preg_replace_error', $value, $value); + } + + return $value; + } + + return $result; + } + + /** + * Set the audit logger callable. + * + * @param callable|null $auditLogger + * @return void + */ + public function setAuditLogger(?callable $auditLogger): void + { + $this->auditLogger = $auditLogger; + } +} diff --git a/tests/AdvancedRegexMaskProcessorTest.php b/tests/AdvancedRegexMaskProcessorTest.php new file mode 100644 index 0000000..1fa5cea --- /dev/null +++ b/tests/AdvancedRegexMaskProcessorTest.php @@ -0,0 +1,77 @@ + "***HETU***", + "/\b[0-9]{16}\b/u" => "***CC***", + "/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/" => "***EMAIL***", + ]; + + $fieldPaths = [ + "user.ssn" => "[GDPR]", + "payment.card" => "[CC]", + "contact.email" => GdprProcessor::maskWithRegex(), // use regex-masked + "metadata.session" => "[SESSION]", + ]; + + $this->processor = new GdprProcessor($patterns, $fieldPaths); + } + + public function testMaskCreditCardInMessage(): void + { + $record = $this->logEntry()->with(message: "Card: 1234567812345678"); + $result = ($this->processor)($record); + $this->assertSame("Card: ***CC***", $result["message"]); + } + + public function testMaskEmailInMessage(): void + { + $record = $this->logEntry()->with(message: "Email: user@example.com"); + + $result = ($this->processor)($record); + $this->assertSame("Email: ***EMAIL***", $result["message"]); + } + + public function testContextFieldPathReplacements(): void + { + $record = $this->logEntry()->with( + message: "Mixed data", + context: [ + "user" => ["ssn" => self::TEST_HETU], + "payment" => ["card" => self::TEST_CC], + "contact" => ["email" => self::TEST_EMAIL], + "metadata" => ["session" => "abc123xyz"], + ], + extra: [], + ); + + $result = ($this->processor)($record); + + $this->assertSame("[GDPR]", $result["context"]["user"]["ssn"]); + $this->assertSame("[CC]", $result["context"]["payment"]["card"]); + // empty replacement uses regex-masked value + $this->assertSame("***EMAIL***", $result["context"]["contact"]["email"]); + $this->assertSame("[SESSION]", $result["context"]["metadata"]["session"]); + } +} diff --git a/tests/FieldMaskConfigTest.php b/tests/FieldMaskConfigTest.php new file mode 100644 index 0000000..c9e48ed --- /dev/null +++ b/tests/FieldMaskConfigTest.php @@ -0,0 +1,36 @@ +assertSame(FieldMaskConfig::MASK_REGEX, $config->type); + $this->assertNull($config->replacement); + } + + public function testRemoveConfig(): void + { + $config = new FieldMaskConfig(FieldMaskConfig::REMOVE); + $this->assertSame(FieldMaskConfig::REMOVE, $config->type); + $this->assertNull($config->replacement); + } + + public function testReplaceConfig(): void + { + $config = new FieldMaskConfig(FieldMaskConfig::REPLACE, 'MASKED'); + $this->assertSame(FieldMaskConfig::REPLACE, $config->type); + $this->assertSame('MASKED', $config->replacement); + } +} diff --git a/tests/GdprDefaultPatternsTest.php b/tests/GdprDefaultPatternsTest.php new file mode 100644 index 0000000..b15fd40 --- /dev/null +++ b/tests/GdprDefaultPatternsTest.php @@ -0,0 +1,146 @@ +maskMessage($iban); + $this->assertSame('***IBAN***', $masked); + // Finnish IBAN without spaces + $ibanWithoutSpaces = 'FI2112345600000785'; + $this->assertSame('***IBAN***', $processor->maskMessage($ibanWithoutSpaces)); + $this->assertNotSame($ibanWithoutSpaces, $processor->maskMessage($ibanWithoutSpaces)); + + // Edge: not an IBAN + $notIban = 'FI21 1234 5600 000 85A'; + $this->assertSame($notIban, $processor->maskMessage($notIban)); + } + + public function testPatternPhone(): void + { + $patterns = GdprProcessor::getDefaultPatterns(); + $processor = new GdprProcessor($patterns); + $phone = '+358 40 1234567'; + $masked = $processor->maskMessage($phone); + $this->assertSame('***PHONE***', $masked); + // Edge: not a phone + $notPhone = 'Call me maybe'; + $this->assertSame($notPhone, $processor->maskMessage($notPhone)); + } + + public function testPatternUsSsn(): void + { + $patterns = GdprProcessor::getDefaultPatterns(); + $processor = new GdprProcessor($patterns); + $ssn = '123-45-6789'; + $masked = $processor->maskMessage($ssn); + $this->assertSame('***USSSN***', $masked); + // Edge: not a SSN + $notSsn = '123456789'; + $this->assertSame($notSsn, $processor->maskMessage($notSsn)); + } + + public function testPatternDob(): void + { + $patterns = GdprProcessor::getDefaultPatterns(); + $processor = new GdprProcessor($patterns); + $dob1 = '1990-12-31'; + $dob2 = '31/12/1990'; + $masked1 = $processor->maskMessage($dob1); + $masked2 = $processor->maskMessage($dob2); + $this->assertSame('***DOB***', $masked1); + $this->assertSame('***DOB***', $masked2); + // Edge: not a DOB + $notDob = '1990/31/12'; + $this->assertSame($notDob, $processor->maskMessage($notDob)); + } + + public function testPatternPassport(): void + { + $patterns = GdprProcessor::getDefaultPatterns(); + $processor = new GdprProcessor($patterns); + $passport = 'A123456'; + $masked = $processor->maskMessage($passport); + $this->assertSame('***PASSPORT***', $masked); + // Edge: too short + $notPassport = 'A1234'; + $this->assertSame($notPassport, $processor->maskMessage($notPassport)); + } + + + public function testPatternCreditCard(): void + { + $patterns = GdprProcessor::getDefaultPatterns(); + $processor = new GdprProcessor($patterns); + $cc1 = '4111 1111 1111 1111'; // Visa + $cc2 = '5500-0000-0000-0004'; // MasterCard + $cc3 = '340000000000009'; // Amex (15 digits) + $cc4 = '6011000000000004'; // Discover + $masked1 = $processor->maskMessage($cc1); + $masked2 = $processor->maskMessage($cc2); + $masked3 = $processor->maskMessage($cc3); + $masked4 = $processor->maskMessage($cc4); + $this->assertSame('***CC***', $masked1); + $this->assertSame('***CC***', $masked2); + $this->assertSame('***CC***', $masked3); + $this->assertSame('***CC***', $masked4); + // Edge: not a CC + $notCc = '1234 5678 9012'; + $this->assertSame($notCc, $processor->maskMessage($notCc)); + } + + public function testPatternBearerToken(): void + { + $patterns = GdprProcessor::getDefaultPatterns(); + $processor = new GdprProcessor($patterns); + $token = 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'; + $masked = $processor->maskMessage($token); + $this->assertSame('***TOKEN***', $masked); + // Edge: not a token + $notToken = 'bearer token'; + $this->assertSame($notToken, $processor->maskMessage($notToken)); + } + + public function testPatternApiKey(): void + { + $patterns = GdprProcessor::getDefaultPatterns(); + $processor = new GdprProcessor($patterns); + $apiKey = 'sk_test_4eC39HqLyjWDarj'; + $masked = $processor->maskMessage($apiKey); + $this->assertSame('***APIKEY***', $masked); + // Edge: short string + $notApiKey = 'shortkey'; + $this->assertSame($notApiKey, $processor->maskMessage($notApiKey)); + } + + + + public function testPatternMac(): void + { + $patterns = GdprProcessor::getDefaultPatterns(); + $processor = new GdprProcessor($patterns); + $mac = '00:1A:2B:3C:4D:5E'; + $masked = $processor->maskMessage($mac); + $this->assertSame('***MAC***', $masked); + $mac2 = '00-1A-2B-3C-4D-5E'; + $masked2 = $processor->maskMessage($mac2); + $this->assertSame('***MAC***', $masked2); + // Edge: not a MAC + $notMac = '001A2B3C4D5E'; + $this->assertSame($notMac, $processor->maskMessage($notMac)); + } +} diff --git a/tests/GdprProcessorMethodsTest.php b/tests/GdprProcessorMethodsTest.php new file mode 100644 index 0000000..e7a024d --- /dev/null +++ b/tests/GdprProcessorMethodsTest.php @@ -0,0 +1,128 @@ + 'bar', + ]; + $fieldPaths = [ + 'user.email' => GdprProcessor::maskWithRegex(), + 'user.ssn' => GdprProcessor::removeField(), + 'user.card' => GdprProcessor::replaceWith('MASKED'), + ]; + $context = [ + 'user' => [ + 'email' => self::TEST_EMAIL, + 'ssn' => self::TEST_HETU, + 'card' => self::TEST_CC, + ], + ]; + $accessor = new Dot($context); + $processor = new GdprProcessor($patterns, $fieldPaths); + $method = $this->getReflection($processor, 'maskFieldPaths'); + $method->invoke($processor, $accessor); + + $result = $accessor->all(); + $this->assertSame('bar@example.com', $result['user']['email']); + $this->assertSame('MASKED', $result['user']['card']); + $this->assertArrayNotHasKey('ssn', $result['user']); + } + + public function testMaskValueWithCustomCallback(): void + { + $patterns = []; + $fieldPaths = [ + 'user.name' => GdprProcessor::maskWithRegex(), + ]; + $customCallbacks = [ + 'user.name' => fn($value) => strtoupper((string) $value), + ]; + $processor = new GdprProcessor($patterns, $fieldPaths, $customCallbacks); + $method = $this->getReflection($processor, 'maskValue'); + $result = $method->invoke($processor, 'user.name', 'john', $fieldPaths['user.name']); + $this->assertSame(['masked' => 'JOHN', 'remove' => false], $result); + } + + public function testMaskValueWithRemove(): void + { + $patterns = []; + $fieldPaths = [ + 'user.ssn' => GdprProcessor::removeField(), + ]; + $processor = new GdprProcessor($patterns, $fieldPaths); + $method = $this->getReflection($processor, 'maskValue'); + $result = $method->invoke($processor, 'user.ssn', self::TEST_HETU, $fieldPaths['user.ssn']); + $this->assertSame(['masked' => null, 'remove' => true], $result); + } + + public function testMaskValueWithReplace(): void + { + $patterns = []; + $fieldPaths = [ + 'user.card' => GdprProcessor::replaceWith('MASKED'), + ]; + $processor = new GdprProcessor($patterns, $fieldPaths); + $method = $this->getReflection($processor, 'maskValue'); + $result = $method->invoke($processor, 'user.card', self::TEST_CC, $fieldPaths['user.card']); + $this->assertSame(['masked' => 'MASKED', 'remove' => false], $result); + } + + public function testLogAuditIsCalled(): void + { + $patterns = []; + $fieldPaths = []; + $calls = []; + $auditLogger = function ($path, $original, $masked) use (&$calls): void { + $calls[] = [$path, $original, $masked]; + }; + $processor = new GdprProcessor($patterns, $fieldPaths, [], $auditLogger); + $method = $this->getReflection($processor, 'logAudit'); + $method->invoke($processor, 'user.email', self::TEST_EMAIL, 'MASKED'); + $this->assertNotEmpty($calls); + $this->assertSame(['user.email', self::TEST_EMAIL, 'MASKED'], $calls[0]); + } + + public function testMaskValueWithDefaultCase(): void + { + $patterns = []; + $fieldPaths = [ + 'user.unknown' => new FieldMaskConfig('999'), // unknown type + ]; + $processor = new GdprProcessor($patterns, $fieldPaths); + $method = $this->getReflection($processor, 'maskValue'); + $result = $method->invoke($processor, 'user.unknown', 'foo', $fieldPaths['user.unknown']); + $this->assertSame(['masked' => '999', 'remove' => false], $result); + } + + public function testMaskValueWithStringConfigBackwardCompatibility(): void + { + $patterns = []; + $fieldPaths = [ + 'user.simple' => 'MASKED', + ]; + $processor = new GdprProcessor($patterns, $fieldPaths); + $method = $this->getReflection($processor, 'maskValue'); + $result = $method->invoke($processor, 'user.simple', 'foo', $fieldPaths['user.simple']); + $this->assertSame(['masked' => 'MASKED', 'remove' => false], $result); + } +} diff --git a/tests/GdprProcessorTest.php b/tests/GdprProcessorTest.php new file mode 100644 index 0000000..4d26007 --- /dev/null +++ b/tests/GdprProcessorTest.php @@ -0,0 +1,330 @@ + GdprProcessor::maskWithRegex(), + ]; + $processor = new GdprProcessor($patterns, $fieldPaths); + $record = new LogRecord( + datetime: new JsonSerializableDateTimeImmutable(true), + channel: 'test', + level: Level::Info, + message: static::USER_REGISTERED, + context: ['user' => ['email' => self::TEST_EMAIL]], + extra: [] + ); + $processed = $processor($record); + $this->assertSame(self::MASKED_EMAIL, $processed->context['user']['email']); + } + + public function testRemoveField(): void + { + $patterns = GdprProcessor::getDefaultPatterns(); + $fieldPaths = [ + 'user.ssn' => GdprProcessor::removeField(), + ]; + $processor = new GdprProcessor($patterns, $fieldPaths); + $record = new LogRecord( + datetime: new JsonSerializableDateTimeImmutable(true), + channel: 'test', + level: Level::Info, + message: 'Sensitive info', + context: ['user' => ['ssn' => '123456-789A', 'name' => 'John']], + extra: [] + ); + $processed = $processor($record); + $this->assertArrayNotHasKey('ssn', $processed->context['user']); + $this->assertSame('John', $processed->context['user']['name']); + } + + public function testReplaceWithField(): void + { + $patterns = GdprProcessor::getDefaultPatterns(); + $fieldPaths = [ + 'user.card' => GdprProcessor::replaceWith('MASKED'), + ]; + $processor = new GdprProcessor($patterns, $fieldPaths); + $record = new LogRecord( + datetime: new JsonSerializableDateTimeImmutable(true), + channel: 'test', + level: Level::Info, + message: 'Payment processed', + context: ['user' => ['card' => '1234123412341234']], + extra: [] + ); + $processed = $processor($record); + $this->assertSame('MASKED', $processed->context['user']['card']); + } + + public function testCustomCallback(): void + { + $patterns = GdprProcessor::getDefaultPatterns(); + $fieldPaths = [ + 'user.name' => GdprProcessor::maskWithRegex(), + ]; + $customCallbacks = [ + 'user.name' => fn($value): string => strtoupper((string) $value), + ]; + $processor = new GdprProcessor($patterns, $fieldPaths, $customCallbacks); + $record = new LogRecord( + datetime: new JsonSerializableDateTimeImmutable(true), + channel: 'test', + level: Level::Info, + message: 'Name logged', + context: ['user' => ['name' => 'john']], + extra: [] + ); + $processed = $processor($record); + $this->assertSame('JOHN', $processed->context['user']['name']); + } + + public function testAuditLoggerIsCalled(): void + { + $patterns = GdprProcessor::getDefaultPatterns(); + $fieldPaths = [ + 'user.email' => GdprProcessor::maskWithRegex(), + ]; + $auditCalls = []; + $auditLogger = function ($path, $original, $masked) use (&$auditCalls): void { + $auditCalls[] = [$path, $original, $masked]; + }; + $processor = new GdprProcessor($patterns, $fieldPaths, [], $auditLogger); + $record = new LogRecord( + datetime: new JsonSerializableDateTimeImmutable(true), + channel: 'test', + level: Level::Info, + message: static::USER_REGISTERED, + context: ['user' => ['email' => static::TEST_EMAIL]], + extra: [] + ); + $processor($record); + $this->assertNotEmpty($auditCalls); + $this->assertSame(['user.email', 'john.doe@example.com', '***EMAIL***'], $auditCalls[0]); + } + + public function testMaskMessage(): void + { + $patterns = [ + '/foo/' => 'bar', + '/baz/' => 'qux', + ]; + $processor = new GdprProcessor($patterns); + $masked = $processor->maskMessage('foo and baz'); + $this->assertSame('bar and qux', $masked); + } + + public function testRecursiveMask(): void + { + $patterns = [ + '/secret/' => self::MASKED_SECRET, + ]; + $processor = new class ($patterns) extends GdprProcessor { + public function callRecursiveMask($data) + { + return $this->recursiveMask($data); + } + }; + $data = [ + 'a' => 'secret', + 'b' => ['c' => 'secret'], + 'd' => 123, + ]; + $masked = $processor->callRecursiveMask($data); + $this->assertSame([ + 'a' => self::MASKED_SECRET, + 'b' => ['c' => self::MASKED_SECRET], + 'd' => '123', + ], $masked); + } + + public function testStaticHelpers(): void + { + $regex = GdprProcessor::maskWithRegex(); + $remove = GdprProcessor::removeField(); + $replace = GdprProcessor::replaceWith('MASKED'); + $this->assertSame('mask_regex', $regex->type); + $this->assertSame('remove', $remove->type); + $this->assertSame('replace', $replace->type); + $this->assertSame('MASKED', $replace->replacement); + } + + public function testRecursiveMasking(): void + { + $patterns = GdprProcessor::getDefaultPatterns(); + $processor = new GdprProcessor($patterns); + $record = new LogRecord( + datetime: new JsonSerializableDateTimeImmutable(true), + channel: 'test', + level: Level::Info, + message: 'Sensitive info', + context: [ + 'user' => [ + 'email' => self::TEST_EMAIL, + 'ssn' => self::TEST_HETU, + 'card' => self::TEST_CC, + ], + 'other' => 'plain', + ], + extra: [] + ); + $processed = $processor($record); + $this->assertSame(self::MASKED_EMAIL, $processed->context['user']['email']); + $this->assertSame('***HETU***', $processed->context['user']['ssn']); + $this->assertSame('***CC***', $processed->context['user']['card']); + } + + public function testStringReplacementBackwardCompatibility(): void + { + $patterns = GdprProcessor::getDefaultPatterns(); + $fieldPaths = [ + 'user.email' => '[MASKED]', // string, not FieldMaskConfig + ]; + $processor = new GdprProcessor($patterns, $fieldPaths); + $record = new LogRecord( + datetime: new JsonSerializableDateTimeImmutable(true), + channel: 'test', + level: Level::Info, + message: static::USER_REGISTERED, + context: ['user' => ['email' => self::TEST_EMAIL]], + extra: [] + ); + $processed = $processor($record); + $this->assertSame('[MASKED]', $processed->context['user']['email']); + } + + public function testNonStringValueInContext(): void + { + $patterns = GdprProcessor::getDefaultPatterns(); + $fieldPaths = [ + 'user.id' => GdprProcessor::maskWithRegex(), + ]; + $processor = new GdprProcessor($patterns, $fieldPaths); + $record = new LogRecord( + datetime: new JsonSerializableDateTimeImmutable(true), + channel: 'test', + level: Level::Info, + message: 'User registered', + context: ['user' => ['id' => 12345]], + extra: [] + ); + $processed = $processor($record); + $this->assertSame('12345', $processed->context['user']['id']); + } + + public function testMissingFieldInContext(): void + { + $patterns = GdprProcessor::getDefaultPatterns(); + $fieldPaths = [ + 'user.missing' => GdprProcessor::maskWithRegex(), + ]; + $processor = new GdprProcessor($patterns, $fieldPaths); + $record = new LogRecord( + datetime: new JsonSerializableDateTimeImmutable(true), + channel: 'test', + level: Level::Info, + message: static::USER_REGISTERED, + context: ['user' => ['email' => self::TEST_EMAIL]], + extra: [] + ); + $processed = $processor($record); + $this->assertArrayNotHasKey('missing', $processed->context['user']); + } + + public function testPregReplaceErrorInMaskMessage(): void + { + // Invalid pattern triggers preg_replace error + $patterns = [ + self::INVALID_REGEX => 'MASKED', + ]; + + $calls = []; + $auditLogger = function ($path, $original, $masked) use (&$calls): void { + $calls[] = [$path, $original, $masked]; + }; + $processor = new GdprProcessor($patterns, [], [], $auditLogger); + $result = $processor->maskMessage('test'); + $this->assertSame('test', $result); + $this->assertNotEmpty($calls); + $this->assertSame(['preg_replace_error', 'test', 'test'], $calls[0]); + } + + public function testPregReplaceErrorInRegExpMessage(): void + { + $patterns = [ + self::INVALID_REGEX => 'MASKED', + ]; + $calls = []; + $auditLogger = function ($path, $original, $masked) use (&$calls): void { + $calls[] = [$path, $original, $masked]; + }; + $processor = new GdprProcessor($patterns, [], [], $auditLogger); + $result = $processor->regExpMessage('test'); + $this->assertSame('test', $result); + $this->assertNotEmpty($calls); + $this->assertSame(['preg_replace_error', 'test', 'test'], $calls[0]); + } + + public function testRegExpMessageHandlesPregReplaceError(): void + { + $invalidPattern = ['/(unclosed[' => 'REPLACED']; + $called = false; + $logger = function ($type, $original, $message) use (&$called) { + $called = true; + $this->assertSame('preg_replace_error', $type); + $this->assertSame('test', $original); + $this->assertSame('test', $message); + }; + $processor = new GdprProcessor($invalidPattern); + $processor->setAuditLogger($logger); + $result = $processor->regExpMessage('test'); + $this->assertTrue($called, 'Audit logger should be called on preg_replace error'); + $this->assertSame('test', $result, 'Message should be unchanged if preg_replace fails'); + } + + public function testRegExpMessageReturnsOriginalIfResultIsEmptyString(): void + { + $patterns = [ + '/^foo$/' => '', + ]; + $processor = new GdprProcessor($patterns); + $result = $processor->regExpMessage('foo'); + $this->assertSame('foo', $result, 'Should return original message if preg_replace result is empty string'); + } + + public function testRegExpMessageReturnsOriginalIfResultIsStringZero(): void + { + $patterns = [ + '/^foo$/' => '0', + ]; + $processor = new GdprProcessor($patterns); + $result = $processor->regExpMessage('foo'); + $this->assertSame('foo', $result, 'Should return original message if preg_replace result is string "0"'); + } +} diff --git a/tests/RegexMaskProcessorTest.php b/tests/RegexMaskProcessorTest.php new file mode 100644 index 0000000..cb58356 --- /dev/null +++ b/tests/RegexMaskProcessorTest.php @@ -0,0 +1,267 @@ + "***MASKED***", + ]; + $fieldPaths = [ + "user.ssn" => self::GDPR_REPLACEMENT, + "order.total" => GdprProcessor::maskWithRegex(), + ]; + $this->processor = new GdprProcessor($patterns, $fieldPaths); + } + + public function testRemoveFieldRemovesKey(): void + { + $patterns = $this->processor::getDefaultPatterns(); + $fieldPaths = ["user.ssn" => GdprProcessor::removeField()]; + $processor = new GdprProcessor($patterns, $fieldPaths); + $record = $this->logEntry()->with( + message: "Remove SSN", + context: ["user" => ["ssn" => self::TEST_HETU, "name" => "John"]], + ); + $result = ($processor)($record); + $this->assertArrayNotHasKey("ssn", $result["context"]["user"]); + $this->assertSame("John", $result["context"]["user"]["name"]); + } + + public function testReplaceWithFieldReplacesValue(): void + { + $patterns = $this->processor::getDefaultPatterns(); + $fieldPaths = ["user.card" => GdprProcessor::replaceWith("MASKED")]; + $processor = new GdprProcessor($patterns, $fieldPaths); + $record = $this->logEntry()->with( + message: "Payment processed", + context: ["user" => ["card" => "1234123412341234"]], + ); + $result = ($processor)($record); + $this->assertSame("MASKED", $result["context"]["user"]["card"]); + } + + public function testCustomCallbackIsUsed(): void + { + $patterns = $this->processor::getDefaultPatterns(); + $fieldPaths = ["user.name" => GdprProcessor::maskWithRegex()]; + $customCallbacks = ["user.name" => fn($value): string => strtoupper((string)$value)]; + $processor = new GdprProcessor($patterns, $fieldPaths, $customCallbacks); + $record = $this->logEntry()->with( + message: "Name logged", + context: ["user" => ["name" => "john"]], + ); + $result = ($processor)($record); + $this->assertSame("JOHN", $result["context"]["user"]["name"]); + } + + public function testAuditLoggerIsCalled(): void + { + $patterns = $this->processor::getDefaultPatterns(); + $fieldPaths = ["user.email" => GdprProcessor::maskWithRegex()]; + $auditCalls = []; + $auditLogger = function ($path, $original, $masked) use (&$auditCalls): void { + $auditCalls[] = [$path, $original, $masked]; + }; + $processor = new GdprProcessor($patterns, $fieldPaths, [], $auditLogger); + $record = $this->logEntry()->with( + message: self::USER_REGISTERED, + context: ["user" => ["email" => self::TEST_EMAIL]], + ); + $processor($record); + $this->assertNotEmpty($auditCalls); + $this->assertSame(["user.email", "john.doe@example.com", "***EMAIL***"], $auditCalls[0]); + } + + public function testMaskMessagePregReplaceError(): void + { + $patterns = [ + self::INVALID_REGEX => 'MASKED', + ]; + $calls = []; + $auditLogger = function ($path, $original, $masked) use (&$calls): void { + $calls[] = [$path, $original, $masked]; + }; + $processor = new GdprProcessor($patterns, [], [], $auditLogger); + $result = $processor->maskMessage('test'); + $this->assertSame('test', $result); + $this->assertNotEmpty($calls); + $this->assertSame(['preg_replace_error', 'test', 'test'], $calls[0]); + } + + public function testRegExpMessagePregReplaceError(): void + { + $patterns = [ + self::INVALID_REGEX => 'MASKED', + ]; + $calls = []; + $auditLogger = function ($path, $original, $masked) use (&$calls): void { + $calls[] = [$path, $original, $masked]; + }; + $processor = new GdprProcessor($patterns, [], [], $auditLogger); + $result = $processor->regExpMessage('test'); + $this->assertSame('test', $result); + $this->assertNotEmpty($calls); + $this->assertSame(['preg_replace_error', 'test', 'test'], $calls[0]); + } + + public function testStringReplacementBackwardCompatibility(): void + { + $patterns = $this->processor::getDefaultPatterns(); + $fieldPaths = ["user.email" => '[MASKED]']; + $processor = new GdprProcessor($patterns, $fieldPaths); + $record = $this->logEntry()->with( + message: self::USER_REGISTERED, + context: ["user" => ["email" => self::TEST_EMAIL]], + ); + $result = ($processor)($record); + $this->assertSame('[MASKED]', $result["context"]["user"]["email"]); + } + + public function testNonStringValueInContextIsUnchanged(): void + { + $patterns = $this->processor::getDefaultPatterns(); + $fieldPaths = ["user.id" => GdprProcessor::maskWithRegex()]; + $processor = new GdprProcessor($patterns, $fieldPaths); + $record = $this->logEntry()->with( + message: self::USER_REGISTERED, + context: ["user" => ["id" => 12345]], + ); + $result = ($processor)($record); + $this->assertSame('12345', $result["context"]["user"]["id"]); + } + + public function testMissingFieldInContextIsIgnored(): void + { + $patterns = $this->processor::getDefaultPatterns(); + $fieldPaths = ["user.missing" => GdprProcessor::maskWithRegex()]; + $processor = new GdprProcessor($patterns, $fieldPaths); + $record = $this->logEntry()->with( + message: self::USER_REGISTERED, + context: ["user" => ["email" => self::TEST_EMAIL]], + ); + $result = ($processor)($record); + $this->assertArrayNotHasKey('missing', $result["context"]["user"]); + } + + public function testHetuMasking(): void + { + $testHetu = [self::TEST_HETU, "131052+308T", "131052A308T"]; + foreach ($testHetu as $hetu) { + $record = $this->logEntry()->with(message: 'ID: ' . $hetu); + $result = ($this->processor)($record); + $this->assertSame("ID: ***MASKED***", $result["message"]); + } + } + + public function testReplacesContextUserSsnWithCustomReplacement(): void + { + $record = $this->logEntry()->with( + message: "Login", + context: ["user" => ["ssn" => self::TEST_HETU]], + ); + $result = ($this->processor)($record); + $this->assertSame(self::GDPR_REPLACEMENT, $result["context"]["user"]["ssn"]); + } + + public function testMasksOrderTotalUsingRegexWhenReplacementIsEmpty(): void + { + $record = $this->logEntry()->with( + message: "Order created", + context: ["order" => ["total" => self::TEST_HETU . " €150"]], + ); + $result = ($this->processor)($record); + $this->assertSame("***MASKED*** €150", $result["context"]["order"]["total"]); + } + + public function testNoMaskingWhenPatternDoesNotMatch(): void + { + $record = $this->logEntry()->with( + message: "No sensitive data here", + context: ["user" => ["ssn" => "not-a-hetu"]], + ); + $result = ($this->processor)($record); + $this->assertSame("No sensitive data here", $result["message"]); + $this->assertSame(self::GDPR_REPLACEMENT, $result["context"]["user"]["ssn"]); + } + + public function testMissingFieldPathIsIgnored(): void + { + $record = $this->logEntry()->with( + message: "Missing field", + context: ["user" => ["name" => "John"]], + ); + $result = ($this->processor)($record); + $this->assertArrayNotHasKey("ssn", $result["context"]["user"]); + } + + public function testMaskMessageDirect(): void + { + $patterns = [ + '/foo/' => 'bar', + '/baz/' => 'qux', + ]; + $processor = new GdprProcessor($patterns); + $masked = $processor->maskMessage('foo and baz'); + $this->assertSame('bar and qux', $masked); + } + + public function testRecursiveMaskDirect(): void + { + $patterns = [ + '/secret/' => 'MASKED', + ]; + $processor = new class ($patterns) extends GdprProcessor { + public function callRecursiveMask($data) + { + return $this->recursiveMask($data); + } + }; + $data = [ + 'a' => 'secret', + 'b' => ['c' => 'secret'], + 'd' => 123, + ]; + $masked = $processor->callRecursiveMask($data); + $this->assertSame([ + 'a' => 'MASKED', + 'b' => ['c' => 'MASKED'], + 'd' => '123', + ], $masked); + } + + public function testStaticHelpers(): void + { + $regex = GdprProcessor::maskWithRegex(); + $remove = GdprProcessor::removeField(); + $replace = GdprProcessor::replaceWith('MASKED'); + $this->assertSame('mask_regex', $regex->type); + $this->assertSame('remove', $remove->type); + $this->assertSame('replace', $replace->type); + $this->assertSame('MASKED', $replace->replacement); + } +} diff --git a/tests/TestHelpers.php b/tests/TestHelpers.php new file mode 100644 index 0000000..daf7603 --- /dev/null +++ b/tests/TestHelpers.php @@ -0,0 +1,87 @@ +setAccessible(true); + return $method; + } + + /** + * Returns a reflection of the given class. + * + * @psalm-api + * @noinspection PhpUnused + */ + protected function noOperation(): void + { + // This method intentionally left blank. + // It can be used to indicate a no-operation in tests. + } +}