From 50d3cd0565b6c11177309a760db72ab9062c4e19 Mon Sep 17 00:00:00 2001 From: Daryll Doyle Date: Thu, 30 Jul 2026 18:26:10 +0100 Subject: [PATCH 1/2] test!: migrate to Pest 4 and Mantle Testkit with real WordPress Replace PHPUnit 9 and Brain Monkey with Pest 4 (built on PHPUnit 12) and Mantle Testkit, so the suite runs against an actual WordPress install and MySQL database instead of mocked WordPress functions. The point is confidence rather than tooling fashion. A mocked register_post_type() can only prove the function was called; it cannot show the post type exists, that labels resolved, or that taxonomies attached. AbstractPostType and AbstractTaxonomy each had exactly one test for that reason, and now have sixteen between them, including inserting and reading back a real post and term. Structure: - tests/Unit/ uses Testkit's Unit_Test_Case, for code that calls no WordPress functions (the class cache, the bin script, asset sidecars). - tests/Integration/ uses Integration_Test_Case, for everything touching WordPress. Each test runs in a transaction that rolls back. - tests/Arch/ holds Pest architecture rules: strict_types everywhere, no debugging leftovers, and src/ never referencing test namespaces. Notable replacements of assertions that could not fail: - HeadOverrides and Emoji asserted on the *source code* of register() as a string, because mocked remove_action() proves nothing. They now assert the hooks are detached and that the markup disappears from wp_head. - BlockRegistrar pointed at directories that never existed, so most tests only showed register_blocks() did not throw. It now builds real block.json files and asserts against WP_Block_Type_Registry. - LoaderDebug's page rendering now runs with a real administrator and real nonces rather than stubbed capability checks. Test isolation notes, which are not obvious: - Testkit's Unit_Test_Case carries #[RunTestsInSeparateProcesses], but the attribute does not survive Pest's generated test classes. A constant defined in a test therefore leaks, so those cases shell out to tests/scripts/ instead. - WP_CORE_DIR is set per-package. Mantle defaults to a bare /tmp/wordpress and reuses any wp-tests-config.php it finds there, so a shared temp directory points the destructive installer at whichever database another project configured. The bootstrap also refuses to run if the resolved config does not name the expected database. - The database rollback does not reset process state: the ModuleInitialization singleton, LoaderDebug's static records, BlockRegistrar's registries and the post type/block registries all need explicit teardown. Also: - composer test no longer forces XDEBUG_MODE=coverage, and phpunit.xml.dist declares no coverage reports. Either makes PHPUnit 12 enable coverage collection and hard-fail when no driver is installed. Use composer test-coverage when Xdebug or PCOV is present. - The CI test job gains a MySQL service and per-package WP_CORE_DIR. - Exclude PHPCompatibility's ThisFoundOutsideClass from tests/: Pest binds $this in every closure, and the warning concerns PHP 5.3. BREAKING CHANGE: contributors now need a MySQL database to run the test suite. Nothing about the shipped package changes; this is development tooling only. See the Testing section of CLAUDE.md. --- .github/workflows/php.yml | 32 +- .gitignore | 4 + CHANGELOG.md | 1 + CLAUDE.md | 91 + composer.json | 19 +- composer.lock | 9889 ++++++++++++++--- phpcs.xml | 9 + phpunit.xml.dist | 38 +- tests/Arch/ConventionsTest.php | 24 + tests/Assets/GetAssetInfoTest.php | 246 - tests/Bin/GenerateClassCacheTest.php | 232 - tests/BlockRegistrarTest.php | 346 - .../ReadOnlyFileDiscoverCacheDriverTest.php | 118 - tests/Core/EmojiTest.php | 226 - tests/Core/HeadOverridesTest.php | 137 - tests/Debug/LoaderDebugTest.php | 522 - tests/Doubles/ConfigurableBlockRegistrar.php | 46 + tests/Doubles/SecondBlockRegistrar.php | 20 + tests/FrameworkTestSetup.php | 133 - tests/Helpers.php | 363 + tests/Integration/BlockRegistrarTest.php | 307 + tests/Integration/Core/EmojiTest.php | 170 + tests/Integration/Core/HeadOverridesTest.php | 86 + tests/Integration/Debug/LoaderDebugTest.php | 370 + .../LoaderRecordingTest.php | 105 + .../Integration/ModuleInitializationTest.php | 134 + .../PostTypes/AbstractPostTypeTest.php | 154 + tests/Integration/SmokeTest.php | 27 + .../Taxonomies/AbstractTaxonomyTest.php | 110 + tests/ModuleInitializationTest.php | 514 - tests/Pest.php | 21 + tests/PostTypes/AbstractPostTypeTest.php | 38 - tests/Taxonomies/AbstractTaxonomyTest.php | 38 - tests/TestBlockRegistrar.php | 26 - tests/TestEmptyDirectoryBlockRegistrar.php | 26 - tests/TestMultiDirectoryBlockRegistrar.php | 30 - tests/Unit/Assets/GetAssetInfoTest.php | 118 + tests/Unit/Bin/GenerateClassCacheTest.php | 94 + .../ReadOnlyFileDiscoverCacheDriverTest.php | 83 + .../ModuleInitialization/ClassCacheTest.php | 119 + .../DisableCacheConstantTest.php | 33 + tests/Unit/SmokeTest.php | 15 + tests/bootstrap.php | 73 +- tests/scripts/disable-class-cache.php | 39 + 44 files changed, 10912 insertions(+), 4314 deletions(-) create mode 100644 CLAUDE.md create mode 100644 tests/Arch/ConventionsTest.php delete mode 100644 tests/Assets/GetAssetInfoTest.php delete mode 100644 tests/Bin/GenerateClassCacheTest.php delete mode 100644 tests/BlockRegistrarTest.php delete mode 100644 tests/Cache/ReadOnlyFileDiscoverCacheDriverTest.php delete mode 100644 tests/Core/EmojiTest.php delete mode 100644 tests/Core/HeadOverridesTest.php delete mode 100644 tests/Debug/LoaderDebugTest.php create mode 100644 tests/Doubles/ConfigurableBlockRegistrar.php create mode 100644 tests/Doubles/SecondBlockRegistrar.php delete mode 100644 tests/FrameworkTestSetup.php create mode 100644 tests/Helpers.php create mode 100644 tests/Integration/BlockRegistrarTest.php create mode 100644 tests/Integration/Core/EmojiTest.php create mode 100644 tests/Integration/Core/HeadOverridesTest.php create mode 100644 tests/Integration/Debug/LoaderDebugTest.php create mode 100644 tests/Integration/ModuleInitialization/LoaderRecordingTest.php create mode 100644 tests/Integration/ModuleInitializationTest.php create mode 100644 tests/Integration/PostTypes/AbstractPostTypeTest.php create mode 100644 tests/Integration/SmokeTest.php create mode 100644 tests/Integration/Taxonomies/AbstractTaxonomyTest.php delete mode 100644 tests/ModuleInitializationTest.php create mode 100644 tests/Pest.php delete mode 100644 tests/PostTypes/AbstractPostTypeTest.php delete mode 100644 tests/Taxonomies/AbstractTaxonomyTest.php delete mode 100644 tests/TestBlockRegistrar.php delete mode 100644 tests/TestEmptyDirectoryBlockRegistrar.php delete mode 100644 tests/TestMultiDirectoryBlockRegistrar.php create mode 100644 tests/Unit/Assets/GetAssetInfoTest.php create mode 100644 tests/Unit/Bin/GenerateClassCacheTest.php create mode 100644 tests/Unit/Cache/ReadOnlyFileDiscoverCacheDriverTest.php create mode 100644 tests/Unit/ModuleInitialization/ClassCacheTest.php create mode 100644 tests/Unit/ModuleInitialization/DisableCacheConstantTest.php create mode 100644 tests/Unit/SmokeTest.php create mode 100644 tests/scripts/disable-class-cache.php diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index d122ce7..4a206f0 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -55,9 +55,36 @@ jobs: run: composer run static test: - name: Unit Tests + name: Tests runs-on: ubuntu-latest + # The integration suite runs against a real WordPress, which Mantle installs itself into a + # temporary directory. It still needs a database to install into. + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: "yes" + MYSQL_DATABASE: wp_framework_tests + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping --silent" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + env: + # Mantle reads these via getenv(); tests/bootstrap.php only supplies defaults for whatever + # is unset, so these win. WP_CORE_DIR is kept per-package so a shared runner can never + # hand us another project's cached wp-tests-config.php, which would point the destructive + # installer at the wrong database. + WP_DB_NAME: wp_framework_tests + WP_DB_USER: root + WP_DB_PASSWORD: "" + WP_DB_HOST: 127.0.0.1 + WP_CORE_DIR: ${{ github.workspace }}/.wordpress-tests + steps: - uses: actions/checkout@v3 @@ -66,9 +93,10 @@ jobs: with: php-version: ${{ env.PHP_VERSION }} tools: composer:v2 + extensions: mysqli - name: Install dependencies run: composer install --no-progress --no-suggest - - name: Run PHPUnit + - name: Run Pest run: composer run test diff --git a/.gitignore b/.gitignore index 675fea7..62c849c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,9 @@ vendor/ coverage/ .phpunit.result.cache +# WordPress installed for the integration suite (CI points WP_CORE_DIR here; locally it +# defaults to the system temp directory). +.wordpress-tests/ + # Generated class-loader cache (a build artefact, not source) class-loader-cache/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 8455eb4..ff61275 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file, per [the Ke - The loader debug page reports per-loader timing: how long class **discovery** took (a cache read when cached, a live filesystem scan otherwise) and how long **class lookup** (reflection, instantiation and registration) took. The staleness check also reports how long its live discovery ran, so the cache's saving on a given site is measurable. ### Changed +- **Test suite migrated to [Pest 4](https://pestphp.com) + [Mantle Testkit](https://mantle.alley.com/testing/testkit/), replacing PHPUnit 9 and Brain Monkey.** Tests now run against a real WordPress installation and MySQL database instead of mocked WordPress functions, split into `tests/Unit/` (no WordPress), `tests/Integration/` (real WordPress) and `tests/Arch/` (Pest architecture rules). `AbstractPostType` and `AbstractTaxonomy` gain real coverage as a result: mocked `register_post_type()` calls could only prove the function was invoked, so those two classes previously had one test each. Development-only; nothing about the shipped package changes. Contributors need a MySQL database available — see the Testing section of `CLAUDE.md`. - **Breaking: the minimum required PHP version is now 8.3** (raised from 8.2). Full native type coverage relies on [typed class constants](https://www.php.net/manual/en/language.oop5.constants.php), which are a PHP 8.3 feature and are a parse error on 8.2. This reverses the lowering to 8.2 made in 1.2.0 ([#8](https://github.com/10up/wp-framework/pull/8)). See the [Upgrade Guide](docs/Upgrade-Guide.md). - **Breaking (static analysis only): `AbstractPostType::get_name()` and `AbstractTaxonomy::get_name()` now document a `lowercase-string&non-empty-string` return type**, stating what WordPress already requires of a post type or taxonomy key. The native `: string` signature is unchanged, so no subclass breaks and no runtime behaviour differs, but a project running PHPStan may need to narrow how it builds a dynamic key. See the [Upgrade Guide](docs/Upgrade-Guide.md). - Full native type coverage across `src/`: every return type, parameter, property and class constant now declares a native type, and the generic types of every `array` are documented. Enforced by `tomasvotruba/type-coverage` through `composer run static`, which also no longer ignores `missingType.iterableValue`. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2d588e7 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,91 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this is + +`10up/wp-framework` is a **Composer library** (not a runnable plugin/theme) that other 10up WordPress projects `composer require` and extend. It centralizes shared functionality — a module auto-loading system plus abstract base classes for post types, taxonomies, and asset metadata. Code here ships to many downstream projects, so treat the public API (class/method signatures, the `tenup_framework_module_init__{slug}` action, the `Module` trait contract) as stable; breaking changes require deliberate versioning. + +- PHP **8.3+** (typed class constants are used throughout, so 8.2 is a parse error). PSR-4: `TenupFramework\` → `src/`. Test namespaces: `TenupFrameworkTests\` → `tests/`, `TenupFrameworkTestClasses\` → `fixtures/classes/`. +- Tests run **against a real WordPress install with a MySQL database**, via Pest 4 + Mantle Testkit. Brain Monkey is gone. See *Testing* below. + +## Commands + +```bash +composer test # Pest (no coverage driver required) +composer test-coverage # Pest with coverage; needs Xdebug or PCOV +composer lint # PHPCS against ./phpcs.xml (10up-Default standard) +composer lint-fix # PHPCBF auto-fix +composer static # PHPStan, level 10, 1G memory limit + +# Run a subset: +./vendor/bin/pest tests/Unit # one suite +./vendor/bin/pest tests/Integration/PostTypes # one directory +./vendor/bin/pest --filter "registers the post type" # one test by name +``` + +CI (`.github/workflows/php.yml`) runs lint, static analysis, and tests on PHP 8.3 for pushes/PRs to `trunk` and `develop`. `develop` is the default/working branch. The test job provisions a MySQL service. + +## Architecture + +### Module system (the core abstraction) + +A "Module" is any class implementing `TenupFramework\ModuleInterface` (and usually `use`ing the `TenupFramework\Module` trait for a default `load_order()` of 10). The interface contract is three methods: + +- `load_order(): int` — lower runs first. **No relation to WP hook priority** — it only orders registration among modules. Taxonomies default to `9` so they exist before post types (default `10`) associate with them. +- `can_register(): bool` — gate registration by context (admin-only, frontend-only, feature flag). +- `register(): void` — attach hooks/filters here. Keep constructors lightweight. + +`ModuleInitialization` (a singleton) drives discovery and registration. Downstream projects bootstrap with: + +```php +ModuleInitialization::instance()->init_classes( YOUR_PLUGIN_INC ); +``` + +The discovery/init flow in `src/ModuleInitialization.php` is the heart of the library: + +1. Scans the given directory with **`spatie/php-structure-discoverer`**. +2. `withoutChains()` is called deliberately — discovery does **not** resolve inheritance chains. This was an intentional change (see git history "Disable chains"); don't re-enable it without understanding the perf/behavior tradeoff. +3. Each class is reflected and **skipped** unless it is instantiable AND implements `ModuleInterface`. (This is why abstract bases and plain classes like the `Standalone` fixture are never registered.) +4. Fires `do_action( 'tenup_framework_module_init__{slug}', $instance )` before each module registers — the extension/observability hook. `{slug}` = FQN with `\` → `-`, passed through `sanitize_title`. +5. Sorts by `load_order()`, then calls `register()` only when `can_register()` is true. Registered instances are retrievable via `ModuleInitialization::get_module( $fqn )`. + +**Class cache (read-only at runtime, build-time generated):** Optional and opt-in. At runtime `get_classes()` reads `{dir}/class-loader-cache/class-loader-cache-v2.php` if it exists (via `ReadOnlyFileDiscoverCacheDriver`, whose `put()`/`forget()` are no-ops and whose constructor does not `mkdir`), and discovers live otherwise — it **never writes**. This is the fix for the stale-cache bug (issue #30): a server can't hold a cache it never wrote. The cache is produced at build time by `ModuleInitialization::generate_cache()`, exposed via `bin/tenup-framework-generate-class-cache ` (composer `bin`) and the `composer generate-class-cache` alias; both run without WordPress. Define `TENUP_FRAMEWORK_DISABLE_CLASS_CACHE = true` to ignore any shipped cache and always discover live. The `CACHE_FILENAME` constant (`...-v2.php`) is the invalidation lever — bumping it makes the runtime ignore caches from older versions. See `docs/Build-and-Deployment.md`. + +**Loader debug page (`Debug\LoaderDebug`):** A hidden admin page (`admin.php?page=tenup-framework-loaders`, `manage_options`) that shows the state of every class-loader cache on the site and offers an on-demand live-vs-cache staleness diff. It's **admin-only** — `init_classes()` dispatches a loader record to `LoaderDebug::record()` only behind an `is_admin()` check placed *before* any reference to the class, so `LoaderDebug` never autoloads on the front end. Because a site can run 1..n framework copies (per-package installs, possibly php-scoped), aggregation happens over the fixed-string `tenup_framework_debug_loaders` filter rather than class references — every copy contributes its records, and the page registers once via the `$GLOBALS['tenup_framework_debug_page_registered']` guard. Read-only (never writes); disable via the `tenup_framework_enable_loader_debug` filter or `TENUP_FRAMEWORK_DISABLE_LOADER_DEBUG`. See `docs/Debugging.md`. + +### Abstract base classes + +- `PostTypes\AbstractPostType` — implements `ModuleInterface` via the `Module` trait. Subclasses define `get_name()`, `get_singular_label()`, `get_plural_label()`, `get_menu_icon()`; `register()` calls `register_post_type()` + `register_taxonomies()` + `after_register()`. Override `get_options()`/`get_editor_supports()`/`get_supported_taxonomies()` to customize. +- `PostTypes\AbstractCorePostType` — for WP-builtin types (post/page). Labels/icon are no-ops; `register()` only wires taxonomies (the type already exists), and `can_register()` returns true. +- `Taxonomies\AbstractTaxonomy` — `load_order()` of `9`. Subclasses define name + labels; `get_post_types()` returns `[]` by default because **post types declare their own taxonomies**, not the reverse. +- `Assets\GetAssetInfo` (trait) — reads `*.asset.php` sidecar files (version + dependencies) emitted by the build. Call `setup_asset_vars( $dist_path, $fallback_version )` first or `get_asset_info()` throws `RuntimeException`. Looks under `dist/js/`, `dist/css/`, then `dist/blocks/`; falls back to the provided version + empty deps when no sidecar exists. + +## Testing + +**Pest 4 on PHPUnit 12, with Mantle Testkit booting a real WordPress.** `tests/bootstrap.php` calls `\Mantle\Testing\install()`, which downloads and installs WordPress into a temp directory on first run. No WordPress checkout or shell script is needed, but **a MySQL database is required**. + +Three suites, bound to base classes in `tests/Pest.php`: + +| Directory | Base class | Use for | +| --- | --- | --- | +| `tests/Unit/` | `Mantle\Testkit\Unit_Test_Case` | Code that calls no WordPress functions — filesystem, the class cache, subprocess runs | +| `tests/Integration/` | `Mantle\Testkit\Integration_Test_Case` | Anything touching WordPress. Each test runs in a DB transaction that rolls back | +| `tests/Arch/` | none | Pest architecture expectations (`arch()`) | + +Gotchas worth knowing: + +- **Database defaults live in `tests/bootstrap.php`** and are only applied when unset, so CI can override each. It defaults to a database named `wp_framework_tests` and a package-specific `WP_CORE_DIR`, deliberately *not* Mantle's `/tmp/wordpress` + `wordpress_unit_tests` defaults: the installer reuses any `wp-tests-config.php` it finds and then drops/recreates tables, so a shared temp dir will destroy another project's test database. The bootstrap refuses to run if the resolved config does not name the expected database. +- **Process state is not rolled back.** The DB transaction does not reset the `ModuleInitialization` singleton, `LoaderDebug`'s static records, `BlockRegistrar`'s static registries, registered post types/taxonomies, or the block registry. Use `tenup_reset_framework_state()` / `tenup_reset_block_registrar()` in `beforeEach`, and unregister types in `afterEach`. +- **`define()` needs a subprocess.** Testkit's `Unit_Test_Case` carries `#[RunTestsInSeparateProcesses]`, but that attribute does **not** survive Pest's generated test classes, so a constant defined in a test leaks into every later test. Tests that must define one shell out to `tests/scripts/`, e.g. `disable-class-cache.php`. +- **Admin context** comes from Mantle's `Admin_Screen` trait (`uses( Admin_Screen::class )`), which makes `is_admin()` true. `set_current_screen()` is unavailable, since Mantle skips the WP core test suite. +- **`_doing_it_wrong()` fails the test** unless declared with `$this->setExpectedIncorrectUsage( '...' )`. +- Shared helpers live in `tests/Helpers.php`, loaded via composer `autoload-dev.files`. +- `fixtures/classes/` holds sample modules used to exercise discovery — e.g. `Standalone` (no interface, must be skipped), `Loadable/InvalidChildClass` (un-loadable, excluded from PHPStan in `phpstan.neon`). When changing discovery logic, update these fixtures and the assertions in `tests/Integration/ModuleInitializationTest.php`. +- Coverage is measured against `./src/` only. `phpunit.xml.dist` deliberately declares no `` block — doing so makes PHPUnit enable coverage on every run and hard-fail without Xdebug/PCOV. + +## Conventions + +- `declare( strict_types = 1 );` on every file (PHPCS enforces it, but not required on the first line). +- All linted code lives in `src/`, `tests/`, `fixtures/` (per `phpcs.xml`). +- The `tenup-plugin` text domain in base-class labels is a placeholder inherited by downstream projects. diff --git a/composer.json b/composer.json index 61052d3..6c8f6f7 100644 --- a/composer.json +++ b/composer.json @@ -30,27 +30,30 @@ "psr-4": { "TenupFrameworkTests\\": "tests/", "TenupFrameworkTestClasses\\": "fixtures/classes/" - } + }, + "files": [ + "tests/Helpers.php" + ] }, "require": { "php": ">=8.3", "spatie/php-structure-discoverer": "^2.2" }, "require-dev": { - "phpunit/phpunit": "^9.5", - "yoast/phpunit-polyfills": "^2.0", - "brain/monkey": "^2.6", + "pestphp/pest": "^4.7", + "mantle-framework/testkit": "^1.22", + "alleyinteractive/pest-plugin-wordpress": "^1.0", "szepeviktor/phpstan-wordpress": "^2.0", "php-stubs/wp-cli-stubs": "^2.11", "phpstan/phpstan-deprecation-rules": "^2.0", "10up/phpcs-composer": "^3.0", - "phpunit/php-code-coverage": "^9.2", "slevomat/coding-standard": "^8.15", "rector/rector": "^2.0", "tomasvotruba/type-coverage": "^2.0" }, "scripts": { - "test": "XDEBUG_MODE=coverage ./vendor/bin/phpunit", + "test": "./vendor/bin/pest", + "test-coverage": "XDEBUG_MODE=coverage ./vendor/bin/pest --coverage", "lint": "./vendor/bin/phpcs --standard=./phpcs.xml", "lint-fix": "./vendor/bin/phpcbf --standard=./phpcs.xml", "static": [ @@ -64,11 +67,13 @@ "generate-class-cache": "@php bin/tenup-framework-generate-class-cache" }, "scripts-descriptions": { + "test-coverage": "Run the test suite with coverage. Requires Xdebug or PCOV; plain `composer test` does not.", "generate-class-cache": "Generate the class-loader cache for one or more directories, e.g. `composer generate-class-cache -- inc/`." }, "config": { "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true + "dealerdirect/phpcodesniffer-composer-installer": true, + "pestphp/pest-plugin": true } } } diff --git a/composer.lock b/composer.lock index 2406e39..84ba08b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2e94fb31328e23fbe3ad4266f9fd54bf", + "content-hash": "b9b90d45f646ff1057811322180f4c40", "packages": [ { "name": "illuminate/collections", @@ -801,52 +801,173 @@ "time": "2025-12-09T17:31:17+00:00" }, { - "name": "antecedent/patchwork", - "version": "2.2.3", + "name": "alleyinteractive/pest-plugin-wordpress", + "version": "v1.0.1", "source": { "type": "git", - "url": "https://github.com/antecedent/patchwork.git", - "reference": "8b6b235f405af175259c8f56aea5fc23ab9f03ce" + "url": "https://github.com/alleyinteractive/pest-plugin-wordpress.git", + "reference": "5fb4c2042fb8affa1640cc008afc6b74c27dbb76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antecedent/patchwork/zipball/8b6b235f405af175259c8f56aea5fc23ab9f03ce", - "reference": "8b6b235f405af175259c8f56aea5fc23ab9f03ce", + "url": "https://api.github.com/repos/alleyinteractive/pest-plugin-wordpress/zipball/5fb4c2042fb8affa1640cc008afc6b74c27dbb76", + "reference": "5fb4c2042fb8affa1640cc008afc6b74c27dbb76", "shasum": "" }, "require": { - "php": ">=7.1.0" + "mantle-framework/testkit": "^1.16", + "pestphp/pest": "^4.0.0", + "pestphp/pest-plugin": "^4.0.0", + "php": "^8.3" }, "require-dev": { - "phpunit/phpunit": ">=4" + "mantle-framework/console": "^1.16", + "pestphp/pest-dev-tools": "^4.0", + "szepeviktor/phpstan-wordpress": "^2.0" }, "type": "library", + "extra": { + "mantle": { + "aliases": [], + "providers": [ + "Pest\\PestPluginWordPress\\ServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/Autoload.php" + ], + "psr-4": { + "Pest\\PestPluginWordPress\\": "src/" + } + }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], + "description": "WordPress Pest Integration", + "keywords": [ + "framework", + "pest", + "php", + "plugin", + "test", + "testing", + "unit", + "wordpress" + ], + "support": { + "issues": "https://github.com/alleyinteractive/pest-plugin-wordpress/issues", + "source": "https://github.com/alleyinteractive/pest-plugin-wordpress/tree/v1.0.1" + }, + "time": "2025-12-23T19:38:10+00:00" + }, + { + "name": "alleyinteractive/wp-concurrent-remote-requests", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/alleyinteractive/wp-concurrent-remote-requests.git", + "reference": "630e20c0f968b286b736c5770b1dd26b975eb9f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/alleyinteractive/wp-concurrent-remote-requests/zipball/630e20c0f968b286b736c5770b1dd26b975eb9f3", + "reference": "630e20c0f968b286b736c5770b1dd26b975eb9f3", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "alleyinteractive/alley-coding-standards": "*" + }, + "type": "library", + "autoload": { + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], "authors": [ { - "name": "Ignas Rudaitis", - "email": "ignas.rudaitis@gmail.com" + "name": "Sean Fisher", + "email": "srtfisher@gmail.com" } ], - "description": "Method redefinition (monkey-patching) functionality for PHP.", - "homepage": "https://antecedent.github.io/patchwork/", + "description": "Feature plugin for concurrent HTTP remote requests", + "homepage": "https://github.com/alleyinteractive/wp-concurrent-remote-requests", "keywords": [ - "aop", - "aspect", - "interception", - "monkeypatching", - "redefinition", - "runkit", - "testing" + "alleyinteractive", + "wp-concurrent-remote-requests" + ], + "support": { + "issues": "https://github.com/alleyinteractive/wp-concurrent-remote-requests/issues", + "source": "https://github.com/alleyinteractive/wp-concurrent-remote-requests/tree/v1.1.1" + }, + "time": "2025-09-03T19:59:32+00:00" + }, + { + "name": "alleyinteractive/wp-filter-side-effects", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/alleyinteractive/wp-filter-side-effects.git", + "reference": "3107f336e7079782e4d16a9361b512c7d569a780" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/alleyinteractive/wp-filter-side-effects/zipball/3107f336e7079782e4d16a9361b512c7d569a780", + "reference": "3107f336e7079782e4d16a9361b512c7d569a780", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "alleyinteractive/alley-coding-standards": "^1.0.0", + "friendsofphp/php-cs-fixer": "^3.8", + "mantle-framework/testkit": "^0.5" + }, + "type": "library", + "extra": { + "wordpress-autoloader": { + "autoload": { + "Alley\\": "src/alley/" + }, + "autoload-dev": { + "Alley\\": "tests/alley/" + } + } + }, + "autoload": { + "files": [ + "src/alley/wp/filter-side-effects.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Alley", + "email": "info@alley.com" + } ], + "description": "Use a WordPress filter like an action.", "support": { - "issues": "https://github.com/antecedent/patchwork/issues", - "source": "https://github.com/antecedent/patchwork/tree/2.2.3" + "issues": "https://github.com/alleyinteractive/wp-filter-side-effects/issues", + "source": "https://github.com/alleyinteractive/wp-filter-side-effects/tree/v2.0.0" }, - "time": "2025-09-17T09:00:56+00:00" + "time": "2022-12-29T19:26:23+00:00" }, { "name": "automattic/vipwpcs", @@ -903,42 +1024,56 @@ "time": "2026-07-27T14:33:48+00:00" }, { - "name": "brain/monkey", - "version": "2.7.0", + "name": "brianium/paratest", + "version": "v7.20.0", "source": { "type": "git", - "url": "https://github.com/Brain-WP/BrainMonkey.git", - "reference": "ea3aeb3d559ba3c0930b3f4d210b665a4c044d83" + "url": "https://github.com/paratestphp/paratest.git", + "reference": "81c80677c9ec0ed4ef16b246167f11dec81a6e3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Brain-WP/BrainMonkey/zipball/ea3aeb3d559ba3c0930b3f4d210b665a4c044d83", - "reference": "ea3aeb3d559ba3c0930b3f4d210b665a4c044d83", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/81c80677c9ec0ed4ef16b246167f11dec81a6e3d", + "reference": "81c80677c9ec0ed4ef16b246167f11dec81a6e3d", "shasum": "" }, "require": { - "antecedent/patchwork": "^2.1.17", - "mockery/mockery": "~1.3.6 || ~1.4.4 || ~1.5.1 || ^1.6.10", - "php": ">=5.6.0" + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-simplexml": "*", + "fidry/cpu-core-counter": "^1.3.0", + "jean85/pretty-package-versions": "^2.1.1", + "php": "~8.3.0 || ~8.4.0 || ~8.5.0", + "phpunit/php-code-coverage": "^12.5.3 || ^13.0.1", + "phpunit/php-file-iterator": "^6.0.1 || ^7", + "phpunit/php-timer": "^8 || ^9", + "phpunit/phpunit": "^12.5.14 || ^13.0.5", + "sebastian/environment": "^8.0.3 || ^9", + "symfony/console": "^7.4.7 || ^8.0.7", + "symfony/process": "^7.4.5 || ^8.0.5" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^1.0.0", - "phpcompatibility/php-compatibility": "^9.3.0", - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.49 || ^9.6.30" + "doctrine/coding-standard": "^14.0.0", + "ext-pcntl": "*", + "ext-pcov": "*", + "ext-posix": "*", + "phpstan/phpstan": "^2.1.44", + "phpstan/phpstan-deprecation-rules": "^2.0.4", + "phpstan/phpstan-phpunit": "^2.0.16", + "phpstan/phpstan-strict-rules": "^2.0.10", + "symfony/filesystem": "^7.4.6 || ^8.0.6" }, + "bin": [ + "bin/paratest", + "bin/paratest_for_phpstorm" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev", - "dev-version/1": "1.x-dev" - } - }, "autoload": { - "files": [ - "inc/api.php" - ], "psr-4": { - "Brain\\Monkey\\": "src/" + "ParaTest\\": [ + "src/" + ] } }, "notification-url": "https://packagist.org/downloads/", @@ -947,158 +1082,128 @@ ], "authors": [ { - "name": "Giuseppe Mazzapica", - "email": "giuseppe.mazzapica@gmail.com", - "homepage": "https://gmazzap.me", + "name": "Brian Scaturro", + "email": "scaturrob@gmail.com", + "role": "Developer" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com", "role": "Developer" } ], - "description": "Mocking utility for PHP functions and WordPress plugin API", + "description": "Parallel testing for PHP", + "homepage": "https://github.com/paratestphp/paratest", "keywords": [ - "Monkey Patching", - "interception", - "mock", - "mock functions", - "mockery", - "patchwork", - "redefinition", - "runkit", - "test", + "concurrent", + "parallel", + "phpunit", "testing" ], "support": { - "issues": "https://github.com/Brain-WP/BrainMonkey/issues", - "source": "https://github.com/Brain-WP/BrainMonkey" + "issues": "https://github.com/paratestphp/paratest/issues", + "source": "https://github.com/paratestphp/paratest/tree/v7.20.0" }, - "time": "2026-02-05T09:22:14+00:00" + "funding": [ + { + "url": "https://github.com/sponsors/Slamdunk", + "type": "github" + }, + { + "url": "https://paypal.me/filippotessarotto", + "type": "paypal" + } + ], + "time": "2026-03-29T15:46:14+00:00" }, { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v1.2.1", + "name": "brick/math", + "version": "0.18.0", "source": { "type": "git", - "url": "https://github.com/PHPCSStandards/composer-installer.git", - "reference": "963f0c67bffde0eac41b56be71ac0e8ba132f0bd" + "url": "https://github.com/brick/math.git", + "reference": "82944324d1c1bdb2c2618e89978d4e2ad78d69ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/963f0c67bffde0eac41b56be71ac0e8ba132f0bd", - "reference": "963f0c67bffde0eac41b56be71ac0e8ba132f0bd", + "url": "https://api.github.com/repos/brick/math/zipball/82944324d1c1bdb2c2618e89978d4e2ad78d69ad", + "reference": "82944324d1c1bdb2c2618e89978d4e2ad78d69ad", "shasum": "" }, "require": { - "composer-plugin-api": "^2.2", - "php": ">=5.4", - "squizlabs/php_codesniffer": "^3.1.0 || ^4.0" + "php": "^8.2" }, "require-dev": { - "composer/composer": "^2.2", - "ext-json": "*", - "ext-zip": "*", - "php-parallel-lint/php-parallel-lint": "^1.4.0", - "phpcompatibility/php-compatibility": "^9.0 || ^10.0.0@dev", - "yoast/phpunit-polyfills": "^1.0" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + "phpstan/phpstan": "2.1.22", + "phpunit/phpunit": "^11.5" }, + "type": "library", "autoload": { "psr-4": { - "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + "Brick\\Math\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "opensource@frenck.dev", - "homepage": "https://frenck.dev", - "role": "Open source developer" - }, - { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "description": "Arbitrary-precision arithmetic library", "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" ], "support": { - "issues": "https://github.com/PHPCSStandards/composer-installer/issues", - "security": "https://github.com/PHPCSStandards/composer-installer/security/policy", - "source": "https://github.com/PHPCSStandards/composer-installer" + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.18.0" }, "funding": [ { - "url": "https://github.com/PHPCSStandards", - "type": "github" - }, - { - "url": "https://github.com/jrfnl", + "url": "https://github.com/BenMorel", "type": "github" - }, - { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" - }, - { - "url": "https://thanks.dev/u/gh/phpcsstandards", - "type": "thanks_dev" } ], - "time": "2026-05-06T08:26:05+00:00" + "time": "2026-06-14T18:21:03+00:00" }, { - "name": "doctrine/instantiator", - "version": "2.0.0", + "name": "carbonphp/carbon-doctrine-types", + "version": "3.2.0", "source": { "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", "shasum": "" }, "require": { "php": "^8.1" }, + "conflict": { + "doctrine/dbal": "<4.0.0 || >=5.0.0" + }, "require-dev": { - "doctrine/coding-standard": "^11", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^5.4" + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1107,867 +1212,6471 @@ ], "authors": [ { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" } ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "description": "Types to use Carbon in Doctrine", "keywords": [ - "constructor", - "instantiate" + "carbon", + "date", + "datetime", + "doctrine", + "time" ], "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" }, "funding": [ { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" + "url": "https://github.com/kylekatarnls", + "type": "github" }, { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" + "url": "https://opencollective.com/Carbon", + "type": "open_collective" }, { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", "type": "tidelift" } ], - "time": "2022-12-30T00:23:10+00:00" + "time": "2024-02-09T16:56:22+00:00" }, { - "name": "hamcrest/hamcrest-php", - "version": "v2.1.1", + "name": "composer/pcre", + "version": "3.4.0", "source": { "type": "git", - "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" + "url": "https://github.com/composer/pcre.git", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", - "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed", "shasum": "" }, "require": { - "php": "^7.4|^8.0" + "php": "^7.4 || ^8.0" }, - "replace": { - "cordoval/hamcrest-php": "*", - "davedevelopment/hamcrest-php": "*", - "kodova/hamcrest-php": "*" + "conflict": { + "phpstan/phpstan": "<2.2.2" }, "require-dev": { - "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", - "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^9" }, "type": "library", "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, "branch-alias": { - "dev-master": "2.1-dev" + "dev-main": "3.x-dev" } }, "autoload": { - "classmap": [ - "hamcrest" - ] + "psr-4": { + "Composer\\Pcre\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } ], - "description": "This is the PHP port of Hamcrest Matchers", + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", "keywords": [ - "test" + "PCRE", + "preg", + "regex", + "regular expression" ], "support": { - "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.4.0" }, - "time": "2025-04-30T06:54:44+00:00" + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-06-07T11:47:49+00:00" }, { - "name": "mockery/mockery", - "version": "1.6.12", + "name": "composer/xdebug-handler", + "version": "3.0.5", "source": { "type": "git", - "url": "https://github.com/mockery/mockery.git", - "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", - "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", "shasum": "" }, "require": { - "hamcrest/hamcrest-php": "^2.0.1", - "lib-pcre": ">=7.0", - "php": ">=7.3" - }, - "conflict": { - "phpunit/phpunit": "<8.0" + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.6.17", - "symplify/easy-coding-standard": "^12.1.14" + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" }, "type": "library", "autoload": { - "files": [ - "library/helpers.php", - "library/Mockery.php" - ], "psr-4": { - "Mockery\\": "library/Mockery" + "Composer\\XdebugHandler\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Pádraic Brady", - "email": "padraic.brady@gmail.com", - "homepage": "https://github.com/padraic", - "role": "Author" - }, - { - "name": "Dave Marshall", - "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "https://davedevelopment.co.uk", - "role": "Developer" - }, - { - "name": "Nathanael Esayeas", - "email": "nathanael.esayeas@protonmail.com", - "homepage": "https://github.com/ghostwriter", - "role": "Lead Developer" + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" } ], - "description": "Mockery is a simple yet flexible PHP mock object framework", - "homepage": "https://github.com/mockery/mockery", + "description": "Restarts a process without Xdebug.", "keywords": [ - "BDD", - "TDD", - "library", - "mock", - "mock objects", - "mockery", - "stub", - "test", - "test double", - "testing" + "Xdebug", + "performance" ], "support": { - "docs": "https://docs.mockery.io/", - "issues": "https://github.com/mockery/mockery/issues", - "rss": "https://github.com/mockery/mockery/releases.atom", - "security": "https://github.com/mockery/mockery/security/advisories", - "source": "https://github.com/mockery/mockery" + "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" }, - "time": "2024-05-16T03:13:13+00:00" + "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": "myclabs/deep-copy", - "version": "1.13.4", + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.2.1", "source": { "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "963f0c67bffde0eac41b56be71ac0e8ba132f0bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/963f0c67bffde0eac41b56be71ac0e8ba132f0bd", + "reference": "963f0c67bffde0eac41b56be71ac0e8ba132f0bd", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3 <3.2.2" + "composer-plugin-api": "^2.2", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.1.0 || ^4.0" }, "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" + "composer/composer": "^2.2", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcompatibility/php-compatibility": "^9.0 || ^10.0.0@dev", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" }, - "type": "library", "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], "psr-4": { - "DeepCopy\\": "src/DeepCopy/" + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Create deep copies (clones) of your objects", + "authors": [ + { + "name": "Franck Nijhof", + "email": "opensource@frenck.dev", + "homepage": "https://frenck.dev", + "role": "Open source developer" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" ], "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "security": "https://github.com/PHPCSStandards/composer-installer/security/policy", + "source": "https://github.com/PHPCSStandards/composer-installer" }, "funding": [ { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" + "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-08-01T08:46:24+00:00" + "time": "2026-05-06T08:26:05+00:00" }, { - "name": "nette/utils", - "version": "v4.1.5", + "name": "dflydev/dot-access-data", + "version": "v3.0.3", "source": { "type": "git", - "url": "https://github.com/nette/utils.git", - "reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0" + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/b043439dbdf954e6c28b5ea7e34b0100f83165e0", - "reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", "shasum": "" }, "require": { - "php": "8.2 - 8.5" - }, - "conflict": { - "nette/finder": "<3", - "nette/schema": "<1.2.2" + "php": "^7.1 || ^8.0" }, "require-dev": { - "jetbrains/phpstorm-attributes": "^1.2", - "nette/phpstan-rules": "^1.0", - "nette/tester": "^2.5", - "phpstan/extension-installer": "^1.4@stable", - "phpstan/phpstan": "^2.1@stable", - "tracy/tracy": "^2.9" - }, - "suggest": { - "ext-gd": "to use Image", - "ext-iconv": "to use Strings::chr(), ord() and reverse()", - "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", - "ext-json": "to use Nette\\Utils\\Json", - "ext-mbstring": "to use Strings::lower() etc...", - "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-main": "3.x-dev" } }, "autoload": { "psr-4": { - "Nette\\": "src" - }, - "classmap": [ - "src/" - ] + "Dflydev\\DotAccessData\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" + "MIT" ], "authors": [ { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" }, { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" } ], - "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", - "homepage": "https://nette.org", + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", "keywords": [ - "array", - "core", - "datetime", - "images", - "json", - "nette", - "paginator", - "password", - "slugify", - "string", - "unicode", - "utf-8", - "utility", - "validation" + "access", + "data", + "dot", + "notation" ], "support": { - "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.1.5" + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" }, - "time": "2026-07-17T23:02:45+00:00" + "time": "2024-07-08T12:26:09+00:00" }, { - "name": "nikic/php-parser", - "version": "v5.8.0", + "name": "doctrine/deprecations", + "version": "1.1.6", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" + "url": "https://github.com/doctrine/deprecations.git", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", - "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", "shasum": "" }, "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": ">=7.4" + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=14" }, "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", + "psr/log": "^1 || ^2 || ^3" }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" }, + "type": "library", "autoload": { "psr-4": { - "PhpParser\\": "lib/PhpParser" + "Doctrine\\Deprecations\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" + "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/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" }, - "time": "2026-07-04T14:30:18+00:00" + "time": "2026-02-07T07:09:04+00:00" }, { - "name": "phar-io/manifest", - "version": "2.0.4", + "name": "doctrine/inflector", + "version": "2.1.0", "source": { "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" + "url": "https://github.com/doctrine/inflector.git", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", "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" - } + "require-dev": { + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" }, + "type": "library", "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Doctrine\\Inflector\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" }, { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" + "name": "Roman Borschel", + "email": "roman@code-factory.org" }, { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" } ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.4" + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.1.0" }, "funding": [ { - "url": "https://github.com/theseer", - "type": "github" + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" } ], - "time": "2024-03-03T12:33:53+00:00" + "time": "2025-08-10T19:31:58+00:00" }, { - "name": "phar-io/version", - "version": "3.2.1", + "name": "fakerphp/faker", + "version": "v1.24.1", "source": { "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." }, "type": "library", "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Faker\\": "src/Faker/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "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" + "name": "François Zaninotto" } ], - "description": "Library for handling version information and constraints", + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" }, - "time": "2022-02-21T01:04:05+00:00" + "time": "2024-11-21T13:46:39+00:00" }, { - "name": "php-stubs/wordpress-stubs", - "version": "v6.9.4", + "name": "fidry/cpu-core-counter", + "version": "1.3.0", "source": { "type": "git", - "url": "https://github.com/php-stubs/wordpress-stubs.git", - "reference": "90a9412826b9944f93b10bf41d795b5fe68abcd5" + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/90a9412826b9944f93b10bf41d795b5fe68abcd5", - "reference": "90a9412826b9944f93b10bf41d795b5fe68abcd5", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", "shasum": "" }, - "conflict": { - "phpdocumentor/reflection-docblock": "5.6.1" + "require": { + "php": "^7.2 || ^8.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "nikic/php-parser": "^5.5", - "php": "^7.4 || ^8.0", - "php-stubs/generator": "^0.8.6", - "phpdocumentor/reflection-docblock": "^6.0", - "phpstan/phpstan": "^2.1", - "phpunit/phpunit": "^9.5", - "symfony/polyfill-php80": "*", - "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.1.1", - "wp-coding-standards/wpcs": "3.1.0 as 2.3.0" - }, - "suggest": { - "paragonie/sodium_compat": "Pure PHP implementation of libsodium", - "symfony/polyfill-php80": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "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" ], - "description": "WordPress function and class declaration stubs for static analysis.", - "homepage": "https://github.com/php-stubs/wordpress-stubs", + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", "keywords": [ - "PHPStan", - "static analysis", - "wordpress" + "CPU", + "core" ], "support": { - "issues": "https://github.com/php-stubs/wordpress-stubs/issues", - "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.9.4" + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" }, - "time": "2026-05-01T20:36:01+00:00" + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+00:00" }, { - "name": "php-stubs/wp-cli-stubs", - "version": "v2.12.0", + "name": "filp/whoops", + "version": "2.18.4", "source": { "type": "git", - "url": "https://github.com/php-stubs/wp-cli-stubs.git", - "reference": "af16401e299a3fd2229bd0fa9a037638a4174a9d" + "url": "https://github.com/filp/whoops.git", + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-stubs/wp-cli-stubs/zipball/af16401e299a3fd2229bd0fa9a037638a4174a9d", - "reference": "af16401e299a3fd2229bd0fa9a037638a4174a9d", + "url": "https://api.github.com/repos/filp/whoops/zipball/d2102955e48b9fd9ab24280a7ad12ed552752c4d", + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d", "shasum": "" }, "require": { - "php-stubs/wordpress-stubs": "^4.7 || ^5.0 || ^6.0" + "php": "^7.1 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" }, "require-dev": { - "php": "~7.3 || ~8.0", - "php-stubs/generator": "^0.8.0" + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^4.0 || ^5.0" }, "suggest": { - "symfony/polyfill-php73": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", - "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "WP-CLI function and class declaration stubs for static analysis.", - "homepage": "https://github.com/php-stubs/wp-cli-stubs", + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", "keywords": [ - "PHPStan", - "static analysis", - "wordpress", - "wp-cli" + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" ], "support": { - "issues": "https://github.com/php-stubs/wp-cli-stubs/issues", - "source": "https://github.com/php-stubs/wp-cli-stubs/tree/v2.12.0" + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.18.4" }, - "time": "2025-06-10T09:58:05+00:00" + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2025-08-08T12:00:00+00:00" }, { - "name": "phpcompatibility/php-compatibility", - "version": "9.3.5", + "name": "graham-campbell/result-type", + "version": "v1.1.4", "source": { "type": "git", - "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", - "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", - "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", "shasum": "" }, "require": { - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" - }, - "conflict": { - "squizlabs/php_codesniffer": "2.6.2" + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5" }, "require-dev": { - "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" }, - "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", - "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } }, - "type": "phpcodesniffer-standard", "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0-or-later" + "MIT" ], "authors": [ { - "name": "Wim Godden", - "homepage": "https://github.com/wimg", - "role": "lead" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" }, { - "name": "Juliette Reinders Folmer", - "homepage": "https://github.com/jrfnl", - "role": "lead" + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:43:20+00:00" + }, + { + "name": "jean85/pretty-package-versions", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a", + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.1.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^7.5|^8.5|^9.6", + "rector/rector": "^2.0", + "vimeo/psalm": "^4.3 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A library to get pretty versions strings of installed dependencies", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1" + }, + "time": "2025-03-19T14:43:43+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v2.0.15", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "dccd8bcb851bb03fcc005df650b708b57cc52661" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/dccd8bcb851bb03fcc005df650b708b57cc52661", + "reference": "dccd8bcb851bb03fcc005df650b708b57cc52661", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "illuminate/support": "^10.0|^11.0|^12.0|^13.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36|^3.0|^4.0", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0|^8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" }, { - "name": "Contributors", - "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" + "name": "Nuno Maduro", + "email": "nuno@laravel.com" } ], - "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", - "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", "keywords": [ - "compatibility", - "phpcs", - "standards" + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2026-07-21T16:49:22+00:00" + }, + { + "name": "league/commonmark", + "version": "2.8.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "1902f60f984235023acbe03db6ad614a37b3c3e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/1902f60f984235023acbe03db6ad614a37b3c3e7", + "reference": "1902f60f984235023acbe03db6ad614a37b3c3e7", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4 || ^2.0", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^2.0.0", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0 || ^12.0.0 || ^13.0.0", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0 || ^8.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.9-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2026-07-12T15:29:16+00:00" + }, + { + "name": "league/config", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2022-12-11T20:36:23+00:00" + }, + { + "name": "league/flysystem", + "version": "3.35.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "b277b5dc3d56650b68904117124e79c851e12376" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/b277b5dc3d56650b68904117124e79c851e12376", + "reference": "b277b5dc3d56650b68904117124e79c851e12376", + "shasum": "" + }, + "require": { + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.295.10", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-mongodb": "^1.3|^2", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", + "microsoft/azure-storage-blob": "^1.1", + "mongodb/mongodb": "^1.2|^2", + "phpseclib/phpseclib": "^3.0.36", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "File storage abstraction for PHP", + "keywords": [ + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/3.35.2" + }, + "time": "2026-07-06T14:42:07+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.31.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-local/tree/3.31.0" + }, + "time": "2026-01-23T15:30:45+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.17.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/f5f47eff7c48ed1003069a2ca67f316fb4021c76", + "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0 || ^11.0 || ^12.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.17.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2026-07-09T11:49:27+00:00" + }, + { + "name": "league/uri", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri.git", + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/08cf38e3924d4f56238125547b5720496fac8fd4", + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4", + "shasum": "" + }, + "require": { + "league/uri-interfaces": "^7.8.1", + "php": "^8.1", + "psr/http-factory": "^1" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", + "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", + "ext-uri": "to use the PHP native URI class", + "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain", + "league/uri-components": "to provide additional tools to manipulate URI objects components", + "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", + "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": [ + "URN", + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc2141", + "rfc3986", + "rfc3987", + "rfc6570", + "rfc8141", + "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.8.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-03-15T20:22:25+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/85d5c77c5d6d3af6c54db4a78246364908f3c928", + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.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", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", + "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 tools for parsing and resolving RFC3987/RFC3986 URI", + "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.8.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-03-08T20:05:35+00:00" + }, + { + "name": "mantle-framework/cache", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/mantle-framework/cache.git", + "reference": "5197264c9eec22e899d2bd3f0f9141353aad057b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mantle-framework/cache/zipball/5197264c9eec22e899d2bd3f0f9141353aad057b", + "reference": "5197264c9eec22e899d2bd3f0f9141353aad057b", + "shasum": "" + }, + "require": { + "mantle-framework/contracts": "^1.0", + "mantle-framework/support": "^1.0", + "nesbot/carbon": "^3.8.4", + "php": "^8.3" + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ], + "psr-4": { + "Mantle\\Cache\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Alley", + "email": "mantle@alley.com" + } + ], + "description": "The Mantle Framework Cache Package", + "support": { + "source": "https://github.com/mantle-framework/cache/tree/v1.22.0" + }, + "time": "2026-06-24T17:27:25+00:00" + }, + { + "name": "mantle-framework/config", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/mantle-framework/config.git", + "reference": "16cc74d63c26c5a36471accacc33b01244fde134" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mantle-framework/config/zipball/16cc74d63c26c5a36471accacc33b01244fde134", + "reference": "16cc74d63c26c5a36471accacc33b01244fde134", + "shasum": "" + }, + "require": { + "mantle-framework/contracts": "^1.0", + "mantle-framework/support": "^1.0", + "php": "^8.3" + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ], + "psr-4": { + "Mantle\\Config\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Alley", + "email": "mantle@alley.com" + } + ], + "description": "The Mantle Framework Config Package", + "support": { + "source": "https://github.com/mantle-framework/config/tree/v1.22.0" + }, + "time": "2026-06-24T17:27:23+00:00" + }, + { + "name": "mantle-framework/container", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/mantle-framework/container.git", + "reference": "f01617ac68af2b830b4b6a767a0c563340ef16ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mantle-framework/container/zipball/f01617ac68af2b830b4b6a767a0c563340ef16ce", + "reference": "f01617ac68af2b830b4b6a767a0c563340ef16ce", + "shasum": "" + }, + "require": { + "mantle-framework/contracts": "^1.0", + "php": "^8.3", + "psr/container": "^1.1.1 || ^2.0.2" + }, + "provide": { + "psr/container-implementation": "1.1|2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mantle\\Container\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Alley", + "email": "mantle@alley.com" + } + ], + "description": "The Mantle Framework Container Package", + "support": { + "source": "https://github.com/mantle-framework/container/tree/v1.22.0" + }, + "time": "2026-06-24T17:27:24+00:00" + }, + { + "name": "mantle-framework/contracts", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/mantle-framework/contracts.git", + "reference": "41b07278128835033399c6ea98a1cf22b5a7638d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mantle-framework/contracts/zipball/41b07278128835033399c6ea98a1cf22b5a7638d", + "reference": "41b07278128835033399c6ea98a1cf22b5a7638d", + "shasum": "" + }, + "require": { + "php": "^8.3", + "psr/container": "^1.1.1 || ^2.0.2" + }, + "provide": { + "psr/container-implementation": "1.1|2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mantle\\Contracts\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Alley", + "email": "mantle@alley.com" + } + ], + "description": "The Mantle Framework Contracts Package", + "support": { + "source": "https://github.com/mantle-framework/contracts/tree/v1.22.0" + }, + "time": "2026-06-24T17:27:31+00:00" + }, + { + "name": "mantle-framework/database", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/mantle-framework/database.git", + "reference": "fab51c0bb977a75ed5130c89a21f4d35537297bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mantle-framework/database/zipball/fab51c0bb977a75ed5130c89a21f4d35537297bd", + "reference": "fab51c0bb977a75ed5130c89a21f4d35537297bd", + "shasum": "" + }, + "require": { + "alleyinteractive/wp-filter-side-effects": "^1.0 || ^2.0", + "mantle-framework/contracts": "^1.0", + "mantle-framework/support": "^1.0", + "php": "^8.3", + "psr/container": "^1.1.1 || ^2.0.2", + "symfony/finder": "^7.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mantle\\Database\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Alley", + "email": "mantle@alley.com" + } + ], + "description": "The Mantle Framework Database Package", + "support": { + "source": "https://github.com/mantle-framework/database/tree/v1.22.0" + }, + "time": "2026-06-24T17:27:27+00:00" + }, + { + "name": "mantle-framework/events", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/mantle-framework/events.git", + "reference": "b516e66c4e98c38ce44eb325371d6a63be868968" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mantle-framework/events/zipball/b516e66c4e98c38ce44eb325371d6a63be868968", + "reference": "b516e66c4e98c38ce44eb325371d6a63be868968", + "shasum": "" + }, + "require": { + "mantle-framework/container": "^1.0", + "mantle-framework/contracts": "^1.0", + "mantle-framework/support": "^1.0", + "php": "^8.3", + "psr/container": "^1.1.1 || ^2.0.2", + "symfony/finder": "^7.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mantle\\Events\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Alley", + "email": "mantle@alley.com" + } + ], + "description": "The Mantle Framework Events Package", + "support": { + "source": "https://github.com/mantle-framework/events/tree/v1.22.0" + }, + "time": "2026-06-24T17:27:25+00:00" + }, + { + "name": "mantle-framework/faker", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/mantle-framework/faker.git", + "reference": "a56893818fb430e30223869cef2a80c5232880e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mantle-framework/faker/zipball/a56893818fb430e30223869cef2a80c5232880e4", + "reference": "a56893818fb430e30223869cef2a80c5232880e4", + "shasum": "" + }, + "require": { + "fakerphp/faker": "^1.24", + "php": "^8.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mantle\\Faker\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Alley", + "email": "mantle@alley.com" + } + ], + "description": "The Mantle Framework Faker Package", + "support": { + "source": "https://github.com/mantle-framework/faker/tree/v1.22.0" + }, + "time": "2026-06-24T17:27:24+00:00" + }, + { + "name": "mantle-framework/filesystem", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/mantle-framework/filesystem.git", + "reference": "1f5b82829470158a64bf29e2c5c9052adb31aa0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mantle-framework/filesystem/zipball/1f5b82829470158a64bf29e2c5c9052adb31aa0e", + "reference": "1f5b82829470158a64bf29e2c5c9052adb31aa0e", + "shasum": "" + }, + "require": { + "league/flysystem": "^3.29", + "mantle-framework/contracts": "^1.0", + "mantle-framework/support": "^1.0", + "php": "^8.3", + "psr/container": "^1.1.1 || ^2.0.2", + "symfony/finder": "^7.2.0", + "symfony/http-foundation": "^7.3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mantle\\Filesystem\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Alley", + "email": "mantle@alley.com" + } + ], + "description": "The Mantle Framework Filesystem Package", + "support": { + "source": "https://github.com/mantle-framework/filesystem/tree/v1.22.0" + }, + "time": "2026-06-24T17:27:26+00:00" + }, + { + "name": "mantle-framework/framework-views", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/mantle-framework/framework-views.git", + "reference": "c66a546752b02d08add1f211267575a29732da93" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mantle-framework/framework-views/zipball/c66a546752b02d08add1f211267575a29732da93", + "reference": "c66a546752b02d08add1f211267575a29732da93", + "shasum": "" + }, + "require": { + "mantle-framework/view": "^1.0", + "php": "^8.3" + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Alley", + "email": "mantle@alley.com" + } + ], + "description": "The Mantle Framework package for framework views.", + "support": { + "source": "https://github.com/mantle-framework/framework-views/tree/v1.22.0" + }, + "time": "2026-06-24T17:27:30+00:00" + }, + { + "name": "mantle-framework/http", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/mantle-framework/http.git", + "reference": "1fcbe17871e7796105a98746adcd53a33f238ba6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mantle-framework/http/zipball/1fcbe17871e7796105a98746adcd53a33f238ba6", + "reference": "1fcbe17871e7796105a98746adcd53a33f238ba6", + "shasum": "" + }, + "require": { + "mantle-framework/contracts": "^1.0", + "mantle-framework/filesystem": "^1.0", + "mantle-framework/framework-views": "^1.0", + "mantle-framework/support": "^1.0", + "php": "^8.3", + "symfony/http-foundation": "^7.3.7", + "symfony/http-kernel": "^7.2.0", + "symfony/mime": "^7.2.0", + "symfony/routing": "^7.2.0", + "symfony/var-dumper": "^7.2.0" + }, + "suggest": { + "illuminate/view": "For assertions in tests that use Blade templating" + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ], + "psr-4": { + "Mantle\\Http\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Alley", + "email": "mantle@alley.com" + } + ], + "description": "The Mantle Framework Http Package", + "support": { + "source": "https://github.com/mantle-framework/http/tree/v1.22.0" + }, + "time": "2026-07-03T03:08:50+00:00" + }, + { + "name": "mantle-framework/http-client", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/mantle-framework/http-client.git", + "reference": "aeda5d6b7c0f0cee346f950ee34c5412a57ff707" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mantle-framework/http-client/zipball/aeda5d6b7c0f0cee346f950ee34c5412a57ff707", + "reference": "aeda5d6b7c0f0cee346f950ee34c5412a57ff707", + "shasum": "" + }, + "require": { + "alleyinteractive/wp-concurrent-remote-requests": "^1.0.2", + "mantle-framework/cache": "^1.0", + "mantle-framework/support": "^1.0", + "php": "^8.3" + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ], + "psr-4": { + "Mantle\\Http_Client\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Alley", + "email": "mantle@alley.com" + } + ], + "description": "The Mantle Framework Http Client Package", + "support": { + "source": "https://github.com/mantle-framework/http-client/tree/v1.22.0" + }, + "time": "2026-06-24T17:27:22+00:00" + }, + { + "name": "mantle-framework/support", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/mantle-framework/support.git", + "reference": "7880f9583513f317b20d0070b012f733a9a9b68e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mantle-framework/support/zipball/7880f9583513f317b20d0070b012f733a9a9b68e", + "reference": "7880f9583513f317b20d0070b012f733a9a9b68e", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^2.0.8", + "laravel/serializable-closure": "^1.3.1 || ^2.0", + "league/commonmark": "^2.8.2", + "league/uri": "^7.5", + "league/uri-interfaces": "^7.5", + "mantle-framework/contracts": "^1.0", + "monolog/monolog": "^2.9.1", + "nesbot/carbon": "^3.8.4", + "php": "^8.3", + "ramsey/uuid": "^4.7.4", + "spatie/backtrace": "^1.8", + "symfony/dom-crawler": "^7.4.12", + "symfony/finder": "^7.2.0", + "symfony/var-dumper": "^7.2.0", + "vlucas/phpdotenv": "^5.6.1", + "voku/portable-ascii": "^2.0.1" + }, + "conflict": { + "masterminds/html5": "<2.9" + }, + "suggest": { + "mantle-framework/console": "Required to load Mantle Console Commands", + "mantle-framework/container": "Required to use wrapped add_action/add_filter functions", + "vlucas/phpdotenv": "Required to use Environment class (^5.3)" + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ], + "psr-4": { + "Mantle\\Support\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Alley", + "email": "mantle@alley.com" + } + ], + "description": "The Mantle Framework Support Package", + "support": { + "source": "https://github.com/mantle-framework/support/tree/v1.22.0" + }, + "time": "2026-07-03T03:08:52+00:00" + }, + { + "name": "mantle-framework/testing", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/mantle-framework/testing.git", + "reference": "b1b12c568d2d7eb9c241ccda56ac1281e2496d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mantle-framework/testing/zipball/b1b12c568d2d7eb9c241ccda56ac1281e2496d5b", + "reference": "b1b12c568d2d7eb9c241ccda56ac1281e2496d5b", + "shasum": "" + }, + "require": { + "mantle-framework/contracts": "^1.0", + "mantle-framework/database": "^1.0", + "mantle-framework/faker": "^1.0", + "mantle-framework/http": "^1.0", + "mantle-framework/http-client": "^1.0", + "mantle-framework/support": "^1.0", + "mantle-framework/testing-dependencies": "^1.0", + "php": "^8.3" + }, + "suggest": { + "brianium/paratest": "For running tests in parallel.", + "mantle-framework/console": "Required to assert console commands.", + "mantle-framework/testkit": "For running tests against a WordPress install without Mantle", + "nunomaduro/collision": "For better PHPUnit printing.", + "phpunit/phpunit": "Required to use assertions and run tests." + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ], + "psr-4": { + "Mantle\\Testing\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Alley", + "email": "mantle@alley.com" + } + ], + "description": "The Mantle Framework Testing Package", + "keywords": [ + "mantle", + "testing" + ], + "support": { + "source": "https://github.com/mantle-framework/testing/tree/v1.22.0" + }, + "time": "2026-07-03T03:08:49+00:00" + }, + { + "name": "mantle-framework/testing-dependencies", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/mantle-framework/testing-dependencies.git", + "reference": "55fff097fcff17a90fb5ad2ca7f3ff06a0dc0170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mantle-framework/testing-dependencies/zipball/55fff097fcff17a90fb5ad2ca7f3ff06a0dc0170", + "reference": "55fff097fcff17a90fb5ad2ca7f3ff06a0dc0170", + "shasum": "" + }, + "require": { + "fakerphp/faker": "^1.24", + "filp/whoops": "^2.18.1", + "myclabs/deep-copy": "^1.13", + "nunomaduro/termwind": "^1.15.1 || ^2.0", + "php": "^8.3", + "phpunit/phpunit": "^10.5.62 || ^11.5.50 || ^12.5.8 || ^13.1", + "spatie/phpunit-snapshot-assertions": "^4.2 || ^5.1", + "symfony/css-selector": "^7.2.0", + "symfony/http-foundation": "^7.3.7" + }, + "suggest": { + "brianium/paratest": "For running tests in parallel.", + "mantle-framework/testkit": "For running tests against a WordPress install without Mantle", + "nunomaduro/collision": "For better PHPUnit printing." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Alley", + "email": "mantle@alley.com" + } + ], + "description": "The Mantle Framework Testing Dependencies Package", + "keywords": [ + "mantle", + "testing" + ], + "support": { + "source": "https://github.com/mantle-framework/testing-dependencies/tree/v1.22.0" + }, + "time": "2026-06-24T17:27:23+00:00" + }, + { + "name": "mantle-framework/testkit", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/mantle-framework/testkit.git", + "reference": "d01653bbec79cf77c7c26a7c5ae8d923c055ecd0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mantle-framework/testkit/zipball/d01653bbec79cf77c7c26a7c5ae8d923c055ecd0", + "reference": "d01653bbec79cf77c7c26a7c5ae8d923c055ecd0", + "shasum": "" + }, + "require": { + "mantle-framework/config": "^1.0", + "mantle-framework/container": "^1.0", + "mantle-framework/contracts": "^1.0", + "mantle-framework/events": "^1.0", + "mantle-framework/faker": "^1.0", + "mantle-framework/support": "^1.0", + "mantle-framework/testing": "^1.0", + "mantle-framework/testing-dependencies": "^1.0", + "nunomaduro/collision": "^6.0 || ^7.0 || ^8.0", + "php": "^8.3", + "phpunit/phpunit": "^10.5.62 || ^11.5.50 || ^12.5.8 || ^13.1", + "symfony/http-foundation": "^7.3.7" + }, + "conflict": { + "alleyinteractive/mantle-framework": "*" + }, + "suggest": { + "alleyinteractive/mantle-framework": "For the full Mantle Framework, require the base package instead." + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ], + "psr-4": { + "Mantle\\Testkit\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Alley", + "email": "mantle@alley.com" + } + ], + "description": "The Mantle Framework Testkit Package", + "keywords": [ + "mantle", + "testing" + ], + "support": { + "source": "https://github.com/mantle-framework/testkit/tree/v1.22.0" + }, + "time": "2026-06-24T17:27:24+00:00" + }, + { + "name": "mantle-framework/view", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/mantle-framework/view.git", + "reference": "5d0943801a92323b8a349825383991dc348a5c7b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mantle-framework/view/zipball/5d0943801a92323b8a349825383991dc348a5c7b", + "reference": "5d0943801a92323b8a349825383991dc348a5c7b", + "shasum": "" + }, + "require": { + "mantle-framework/contracts": "^1.0", + "php": "^8.3" + }, + "suggest": { + "phpunit/phpunit": "Required to use assertions and run tests." + }, + "type": "library", + "autoload": { + "psr-4": { + "Mantle\\View\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Alley", + "email": "mantle@alley.com" + } + ], + "description": "The Mantle Framework View Package", + "support": { + "source": "https://github.com/mantle-framework/view/tree/v1.22.0" + }, + "time": "2026-07-03T03:08:50+00:00" + }, + { + "name": "masterminds/html5", + "version": "2.10.1", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "fd5018f6815fff903946d0564977b44ce8010e29" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fd5018f6815fff903946d0564977b44ce8010e29", + "reference": "fd5018f6815fff903946d0564977b44ce8010e29", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9 || ^10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.10.1" + }, + "time": "2026-06-23T18:43:15+00:00" + }, + { + "name": "monolog/monolog", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "37308608e599f34a1a4845b16440047ec98a172a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/37308608e599f34a1a4845b16440047ec98a172a", + "reference": "37308608e599f34a1a4845b16440047ec98a172a", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2@dev", + "guzzlehttp/guzzle": "^7.4", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8 || ^2.0", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpspec/prophecy": "^1.15", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.5.38 || ^9.6.19", + "predis/predis": "^1.1 || ^2.0", + "rollbar/rollbar": "^1.3 || ^2 || ^3", + "ruflin/elastica": "^7", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "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": "2.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/2.11.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2026-01-01T13:05:00+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "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.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nesbot/carbon", + "version": "3.13.1", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "2937ad3d1d2c506fd2bc97d571438a95641f44e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/2937ad3d1d2c506fd2bc97d571438a95641f44e2", + "reference": "2937ad3d1d2c506fd2bc97d571438a95641f44e2", + "shasum": "" + }, + "require": { + "carbonphp/carbon-doctrine-types": "<100.0", + "ext-json": "*", + "php": "^8.1", + "psr/clock": "^1.0", + "symfony/clock": "^6.3.12 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^v3.87.1", + "kylekatarnls/multi-tester": "^2.5.3", + "phpmd/phpmd": "^2.15.0", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.22", + "phpunit/phpunit": "^10.5.53", + "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbonphp.github.io/carbon/", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html", + "issues": "https://github.com/CarbonPHP/carbon/issues", + "source": "https://github.com/CarbonPHP/carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2026-07-09T18:23:49+00:00" + }, + { + "name": "nette/schema", + "version": "v1.3.5", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002", + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002", + "shasum": "" + }, + "require": { + "nette/utils": "^4.0", + "php": "8.1 - 8.5" + }, + "require-dev": { + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.6", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1.39@stable", + "tracy/tracy": "^2.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.3.5" + }, + "time": "2026-02-23T03:47:12+00:00" + }, + { + "name": "nette/utils", + "version": "v4.1.5", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/b043439dbdf954e6c28b5ea7e34b0100f83165e0", + "reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0", + "shasum": "" + }, + "require": { + "php": "8.2 - 8.5" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.2", + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.5", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1@stable", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::chr(), ord() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.1.5" + }, + "time": "2026-07-17T23:02:45+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.8.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "shasum": "" + }, + "require": { + "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.x-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.8.0" + }, + "time": "2026-07-04T14:30:18+00:00" + }, + { + "name": "nunomaduro/collision", + "version": "v8.9.5", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "fb53eacd509a1d303858e2d20cfebf2d630254ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/fb53eacd509a1d303858e2d20cfebf2d630254ec", + "reference": "fb53eacd509a1d303858e2d20cfebf2d630254ec", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.18.4", + "nunomaduro/termwind": "^2.4.0", + "php": "^8.2.0", + "symfony/console": "^7.4.14 || ^8.1.1" + }, + "conflict": { + "laravel/framework": "<11.48.0 || >=14.0.0", + "phpunit/phpunit": "<11.5.50 || >=14.0.0" + }, + "require-dev": { + "brianium/paratest": "^7.8.5", + "larastan/larastan": "^3.10.0", + "laravel/framework": "^11.48.0 || ^12.56.0 || ^13.20.0", + "laravel/pint": "^1.29.3", + "orchestra/testbench-core": "^9.12.0 || ^10.12.1 || ^11.3.5", + "pestphp/pest": "^3.8.5 || ^4.7.5 || ^5.0.0", + "sebastian/environment": "^7.2.1 || ^8.1.2 || ^9.3.2" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" + ] + }, + "branch-alias": { + "dev-8.x": "8.x-dev" + } + }, + "autoload": { + "files": [ + "./src/Adapters/Phpunit/Autoload.php" + ], + "psr-4": { + "NunoMaduro\\Collision\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Cli error handling for console/command-line PHP applications.", + "keywords": [ + "artisan", + "cli", + "command-line", + "console", + "dev", + "error", + "handling", + "laravel", + "laravel-zero", + "php", + "symfony" + ], + "support": { + "issues": "https://github.com/nunomaduro/collision/issues", + "source": "https://github.com/nunomaduro/collision" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2026-07-15T19:09:14+00:00" + }, + { + "name": "nunomaduro/termwind", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "712a31b768f5daea284c2169a7d227031001b9a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/712a31b768f5daea284c2169a7d227031001b9a8", + "reference": "712a31b768f5daea284c2169a7d227031001b9a8", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.2", + "symfony/console": "^7.4.4 || ^8.0.4" + }, + "require-dev": { + "illuminate/console": "^11.47.0", + "laravel/pint": "^1.27.1", + "mockery/mockery": "^1.6.12", + "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.3.2", + "phpstan/phpstan": "^1.12.32", + "phpstan/phpstan-strict-rules": "^1.6.2", + "symfony/var-dumper": "^7.3.5 || ^8.0.4", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Termwind\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "It's like Tailwind CSS, but for the console.", + "keywords": [ + "cli", + "console", + "css", + "package", + "php", + "style" + ], + "support": { + "issues": "https://github.com/nunomaduro/termwind/issues", + "source": "https://github.com/nunomaduro/termwind/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://github.com/xiCO2k", + "type": "github" + } + ], + "time": "2026-02-16T23:10:27+00:00" + }, + { + "name": "pestphp/pest", + "version": "v4.7.5", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest.git", + "reference": "5dc49a71d63602a9b98fed0f2017c4679ef9f8e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest/zipball/5dc49a71d63602a9b98fed0f2017c4679ef9f8e0", + "reference": "5dc49a71d63602a9b98fed0f2017c4679ef9f8e0", + "shasum": "" + }, + "require": { + "brianium/paratest": "^7.20.0", + "composer/xdebug-handler": "^3.0.5", + "nunomaduro/collision": "^8.9.4", + "nunomaduro/termwind": "^2.4.0", + "pestphp/pest-plugin": "^4.0.0", + "pestphp/pest-plugin-arch": "^4.0.2", + "pestphp/pest-plugin-mutate": "^4.0.1", + "pestphp/pest-plugin-profanity": "^4.2.1", + "php": "^8.3.0", + "phpunit/phpunit": "^12.5.30", + "symfony/process": "^7.4.13|^8.1.0" + }, + "conflict": { + "filp/whoops": "<2.18.3", + "phpunit/phpunit": ">12.5.30", + "sebastian/exporter": "<7.0.0", + "webmozart/assert": "<1.11.0" + }, + "require-dev": { + "mrpunyapal/peststan": "^0.2.11", + "pestphp/pest-dev-tools": "^4.1.0", + "pestphp/pest-plugin-browser": "^4.3.1", + "pestphp/pest-plugin-type-coverage": "^4.0.4", + "psy/psysh": "^0.12.24" + }, + "bin": [ + "bin/pest" + ], + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Mutate\\Plugins\\Mutate", + "Pest\\Plugins\\Configuration", + "Pest\\Plugins\\Bail", + "Pest\\Plugins\\Cache", + "Pest\\Plugins\\Coverage", + "Pest\\Plugins\\Init", + "Pest\\Plugins\\Environment", + "Pest\\Plugins\\Help", + "Pest\\Plugins\\Memory", + "Pest\\Plugins\\Only", + "Pest\\Plugins\\Printer", + "Pest\\Plugins\\ProcessIsolation", + "Pest\\Plugins\\Profile", + "Pest\\Plugins\\Retry", + "Pest\\Plugins\\Snapshot", + "Pest\\Plugins\\Verbose", + "Pest\\Plugins\\Version", + "Pest\\Plugins\\Shard", + "Pest\\Plugins\\Tia", + "Pest\\Plugins\\Parallel" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "files": [ + "src/Functions.php", + "src/Pest.php" + ], + "psr-4": { + "Pest\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "The elegant PHP Testing Framework.", + "keywords": [ + "framework", + "pest", + "php", + "test", + "testing", + "unit" + ], + "support": { + "issues": "https://github.com/pestphp/pest/issues", + "source": "https://github.com/pestphp/pest/tree/v4.7.5" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2026-07-06T17:06:29+00:00" + }, + { + "name": "pestphp/pest-plugin", + "version": "v4.0.0", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin.git", + "reference": "9d4b93d7f73d3f9c3189bb22c220fef271cdf568" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin/zipball/9d4b93d7f73d3f9c3189bb22c220fef271cdf568", + "reference": "9d4b93d7f73d3f9c3189bb22c220fef271cdf568", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0.0", + "composer-runtime-api": "^2.2.2", + "php": "^8.3" + }, + "conflict": { + "pestphp/pest": "<4.0.0" + }, + "require-dev": { + "composer/composer": "^2.8.10", + "pestphp/pest": "^4.0.0", + "pestphp/pest-dev-tools": "^4.0.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Pest\\Plugin\\Manager" + }, + "autoload": { + "psr-4": { + "Pest\\Plugin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Pest plugin manager", + "keywords": [ + "framework", + "manager", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin/tree/v4.0.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2025-08-20T12:35:58+00:00" + }, + { + "name": "pestphp/pest-plugin-arch", + "version": "v4.0.2", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-arch.git", + "reference": "3fb0d02a91b9da504b139dc7ab2a31efb7c3215c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/3fb0d02a91b9da504b139dc7ab2a31efb7c3215c", + "reference": "3fb0d02a91b9da504b139dc7ab2a31efb7c3215c", + "shasum": "" + }, + "require": { + "pestphp/pest-plugin": "^4.0.0", + "php": "^8.3", + "ta-tikoma/phpunit-architecture-test": "^0.8.7" + }, + "require-dev": { + "pestphp/pest": "^4.4.6", + "pestphp/pest-dev-tools": "^4.1.0" + }, + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Arch\\Plugin" + ] + } + }, + "autoload": { + "files": [ + "src/Autoload.php" + ], + "psr-4": { + "Pest\\Arch\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Arch plugin for Pest PHP.", + "keywords": [ + "arch", + "architecture", + "framework", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-arch/tree/v4.0.2" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2026-04-10T17:20:19+00:00" + }, + { + "name": "pestphp/pest-plugin-mutate", + "version": "v4.0.1", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-mutate.git", + "reference": "d9b32b60b2385e1688a68cc227594738ec26d96c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-mutate/zipball/d9b32b60b2385e1688a68cc227594738ec26d96c", + "reference": "d9b32b60b2385e1688a68cc227594738ec26d96c", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.6.1", + "pestphp/pest-plugin": "^4.0.0", + "php": "^8.3", + "psr/simple-cache": "^3.0.0" + }, + "require-dev": { + "pestphp/pest": "^4.0.0", + "pestphp/pest-dev-tools": "^4.0.0", + "pestphp/pest-plugin-type-coverage": "^4.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Pest\\Mutate\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + }, + { + "name": "Sandro Gehri", + "email": "sandrogehri@gmail.com" + } + ], + "description": "Mutates your code to find untested cases", + "keywords": [ + "framework", + "mutate", + "mutation", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-mutate/tree/v4.0.1" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/gehrisandro", + "type": "github" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2025-08-21T20:19:25+00:00" + }, + { + "name": "pestphp/pest-plugin-profanity", + "version": "v4.2.1", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-profanity.git", + "reference": "343cfa6f3564b7e35df0ebb77b7fa97039f72b27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-profanity/zipball/343cfa6f3564b7e35df0ebb77b7fa97039f72b27", + "reference": "343cfa6f3564b7e35df0ebb77b7fa97039f72b27", + "shasum": "" + }, + "require": { + "pestphp/pest-plugin": "^4.0.0", + "php": "^8.3" + }, + "require-dev": { + "faissaloux/pest-plugin-inside": "^1.9", + "pestphp/pest": "^4.0.0", + "pestphp/pest-dev-tools": "^4.0.0" + }, + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Profanity\\Plugin" + ] + } + }, + "autoload": { + "psr-4": { + "Pest\\Profanity\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Pest Profanity Plugin", + "keywords": [ + "framework", + "pest", + "php", + "plugin", + "profanity", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-profanity/tree/v4.2.1" + }, + "time": "2025-12-08T00:13:17+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "php-stubs/wordpress-stubs", + "version": "v6.9.4", + "source": { + "type": "git", + "url": "https://github.com/php-stubs/wordpress-stubs.git", + "reference": "90a9412826b9944f93b10bf41d795b5fe68abcd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/90a9412826b9944f93b10bf41d795b5fe68abcd5", + "reference": "90a9412826b9944f93b10bf41d795b5fe68abcd5", + "shasum": "" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "5.6.1" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "nikic/php-parser": "^5.5", + "php": "^7.4 || ^8.0", + "php-stubs/generator": "^0.8.6", + "phpdocumentor/reflection-docblock": "^6.0", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^9.5", + "symfony/polyfill-php80": "*", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.1.1", + "wp-coding-standards/wpcs": "3.1.0 as 2.3.0" + }, + "suggest": { + "paragonie/sodium_compat": "Pure PHP implementation of libsodium", + "symfony/polyfill-php80": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WordPress function and class declaration stubs for static analysis.", + "homepage": "https://github.com/php-stubs/wordpress-stubs", + "keywords": [ + "PHPStan", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/php-stubs/wordpress-stubs/issues", + "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.9.4" + }, + "time": "2026-05-01T20:36:01+00:00" + }, + { + "name": "php-stubs/wp-cli-stubs", + "version": "v2.12.0", + "source": { + "type": "git", + "url": "https://github.com/php-stubs/wp-cli-stubs.git", + "reference": "af16401e299a3fd2229bd0fa9a037638a4174a9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-stubs/wp-cli-stubs/zipball/af16401e299a3fd2229bd0fa9a037638a4174a9d", + "reference": "af16401e299a3fd2229bd0fa9a037638a4174a9d", + "shasum": "" + }, + "require": { + "php-stubs/wordpress-stubs": "^4.7 || ^5.0 || ^6.0" + }, + "require-dev": { + "php": "~7.3 || ~8.0", + "php-stubs/generator": "^0.8.0" + }, + "suggest": { + "symfony/polyfill-php73": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WP-CLI function and class declaration stubs for static analysis.", + "homepage": "https://github.com/php-stubs/wp-cli-stubs", + "keywords": [ + "PHPStan", + "static analysis", + "wordpress", + "wp-cli" + ], + "support": { + "issues": "https://github.com/php-stubs/wp-cli-stubs/issues", + "source": "https://github.com/php-stubs/wp-cli-stubs/tree/v2.12.0" + }, + "time": "2025-06-10T09:58:05+00:00" + }, + { + "name": "phpcompatibility/php-compatibility", + "version": "9.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "homepage": "https://github.com/wimg", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibility" + }, + "time": "2019-12-27T09:44:58+00:00" + }, + { + "name": "phpcompatibility/phpcompatibility-paragonie", + "version": "1.3.4", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", + "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/244d7b04fc4bc2117c15f5abe23eb933b5f02bbf", + "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "paragonie/random_compat": "dev-master", + "paragonie/sodium_compat": "dev-master" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "paragonie", + "phpcs", + "polyfill", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/security/policy", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" + }, + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcompatibility", + "type": "thanks_dev" + } + ], + "time": "2025-09-19T17:43:28+00:00" + }, + { + "name": "phpcompatibility/phpcompatibility-wp", + "version": "2.1.8", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", + "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/7c8d18b4d90dac9e86b0869a608fa09158e168fa", + "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0", + "phpcompatibility/phpcompatibility-paragonie": "^1.0", + "squizlabs/php_codesniffer": "^3.3" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "phpcs", + "standards", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityWP/security/policy", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP" + }, + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcompatibility", + "type": "thanks_dev" + } + ], + "time": "2025-10-18T00:05:59+00:00" + }, + { + "name": "phpcsstandards/phpcsextra", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", + "reference": "39467533fdb742446d68c1d10ac33d625ee0311c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/39467533fdb742446d68c1d10ac33d625ee0311c", + "reference": "39467533fdb742446d68c1d10ac33d625ee0311c", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpcsstandards/phpcsutils": "^1.2.3", + "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcsstandards/phpcsdevcs": "^1.2.0", + "phpcsstandards/phpcsdevtools": "^1.2.1", + "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" + } + ], + "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", + "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSExtra" + }, + "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": "2026-07-27T11:13:17+00:00" + }, + { + "name": "phpcsstandards/phpcsutils", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", + "reference": "5f35d9408c54d7b529501f3c688b6eae562aea1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/5f35d9408c54d7b529501f3c688b6eae562aea1f", + "reference": "5f35d9408c54d7b529501f3c688b6eae562aea1f", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1" + }, + "require-dev": { + "ext-filter": "*", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcsstandards/phpcsdevcs": "^1.2.0", + "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0 || ^3.0.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHPCSUtils/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" + } + ], + "description": "A suite of utility functions for use with PHP_CodeSniffer", + "homepage": "https://phpcsutils.com/", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "phpcs3", + "phpcs4", + "standards", + "static analysis", + "tokens", + "utility" + ], + "support": { + "docs": "https://phpcsutils.com/", + "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", + "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSUtils" + }, + "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": "2026-07-27T10:28:41+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": "6.0.3", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/7bae67520aa9f5ecc506d646810bd40d9da54582", + "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^2.0", + "phpstan/phpdoc-parser": "^2.0", + "webmozart/assert": "^1.9.1 || ^2" + }, + "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", + "shipmonk/dead-code-detector": "^0.5.1" + }, + "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/6.0.3" + }, + "time": "2026-03-18T20:49:53+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/327a05bbee54120d4786a0dc67aad30226ad4cf9", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev", + "dev-2.x": "2.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/2.0.0" + }, + "time": "2026-01-06T21:53:42+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.5", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:41:33+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", + "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", + "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.3.3" + }, + "time": "2026-07-08T07:01:06+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.2.7", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/692db47b9dddb0487934e5236e77d48594aef921", + "reference": "692db47b9dddb0487934e5236e77d48594aef921", + "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" + ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], + "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": "2026-07-29T17:39:32+00:00" + }, + { + "name": "phpstan/phpstan-deprecation-rules", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", + "reference": "67bedd65c24bc72840afc45aed48b1059dd44bec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/67bedd65c24bc72840afc45aed48b1059dd44bec", + "reference": "67bedd65c24bc72840afc45aed48b1059dd44bec", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.39" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "shipmonk/name-collision-detector": "^2.1" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.5" + }, + "time": "2026-07-22T06:50:43+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "12.5.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "186dab580576598076de6818596d12b61801880e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/186dab580576598076de6818596d12b61801880e", + "reference": "186dab580576598076de6818596d12b61801880e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.7.0", + "php": ">=8.3", + "phpunit/php-text-template": "^5.0", + "sebastian/complexity": "^5.0", + "sebastian/environment": "^8.1.2", + "sebastian/lines-of-code": "^4.0.1", + "sebastian/version": "^6.0", + "theseer/tokenizer": "^2.0.1" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.28" + }, + "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": "12.5.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/12.5.7" + }, + "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": "2026-06-01T13:24:19+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", + "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.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", + "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/6.0.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/phpunit/php-file-iterator", + "type": "tidelift" + } + ], + "time": "2026-02-02T14:04:18+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^12.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "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", + "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/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:58:58+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.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": "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/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:59:16+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "8.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.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/8.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:59:38+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "12.5.30", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "900400a5b616d6fb306f9549f6da33ba615d3fbb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/900400a5b616d6fb306f9549f6da33ba615d3fbb", + "reference": "900400a5b616d6fb306f9549f6da33ba615d3fbb", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.3", + "phpunit/php-code-coverage": "^12.5.7", + "phpunit/php-file-iterator": "^6.0.1", + "phpunit/php-invoker": "^6.0.0", + "phpunit/php-text-template": "^5.0.0", + "phpunit/php-timer": "^8.0.0", + "sebastian/cli-parser": "^4.2.1", + "sebastian/comparator": "^7.1.8", + "sebastian/diff": "^7.0.0", + "sebastian/environment": "^8.1.2", + "sebastian/exporter": "^7.0.3", + "sebastian/global-state": "^8.0.3", + "sebastian/object-enumerator": "^7.0.0", + "sebastian/recursion-context": "^7.0.1", + "sebastian/type": "^6.0.4", + "sebastian/version": "^6.0.0", + "staabm/side-effects-detector": "^1.0.5" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "12.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/12.5.30" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsoring.html", + "type": "other" + } + ], + "time": "2026-06-15T13:12:30+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-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": "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" + }, + { + "name": "ramsey/collection", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.1" + }, + "time": "2025-03-22T05:38:12+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.9.3", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/1df15849d00943a67d677dc9cfd80795f038c9f8", + "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8", + "shasum": "" + }, + "require": { + "brick/math": ">=0.8.16 <=0.18", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.9.3" + }, + "time": "2026-06-18T03:57:49+00:00" + }, + { + "name": "rector/rector", + "version": "2.5.8", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "861c77fd2a6d45317a401f4e0b617f947e8b6f96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/861c77fd2a6d45317a401f4e0b617f947e8b6f96", + "reference": "861c77fd2a6d45317a401f4e0b617f947e8b6f96", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "phpstan/phpstan": "^2.2.6" + }, + "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.5.8" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2026-07-27T06:19:16+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "4.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "7d05781b13f7dec9043a629a21d086ed74582a15" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/7d05781b13f7dec9043a629a21d086ed74582a15", + "reference": "7d05781b13f7dec9043a629a21d086ed74582a15", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library 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/4.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/cli-parser", + "type": "tidelift" + } + ], + "time": "2026-05-17T05:29:34+00:00" + }, + { + "name": "sebastian/comparator", + "version": "7.1.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "7c65c1e79836812819705b473a90c12399542485" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/7c65c1e79836812819705b473a90c12399542485", + "reference": "7c65c1e79836812819705b473a90c12399542485", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/diff": "^7.0", + "sebastian/exporter": "^7.0.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.25" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.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": "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/7.1.8" + }, + "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/comparator", + "type": "tidelift" + } + ], + "time": "2026-05-21T04:45:25+00:00" + }, + { + "name": "sebastian/complexity", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.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": "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/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:55:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0", + "symfony/process": "^7.2" + }, + "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" + }, + { + "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/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:55:46+00:00" + }, + { + "name": "sebastian/environment", + "version": "8.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/9d32c685773823b1983e256ae4ecd48a10d6e439", + "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.26" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "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/8.1.2" + }, + "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": "2026-05-25T13:40:20+00:00" + }, + { + "name": "sebastian/exporter", + "version": "7.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23", + "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/recursion-context": "^7.0.1" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.25" + }, + "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" + }, + { + "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/7.0.3" + }, + "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/exporter", + "type": "tidelift" + } + ], + "time": "2026-05-20T04:37:17+00:00" + }, + { + "name": "sebastian/global-state", + "version": "8.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "b164d3274d6537ab462591c5755f76a8f5b1aae9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b164d3274d6537ab462591c5755f76a8f5b1aae9", + "reference": "b164d3274d6537ab462591c5755f76a8f5b1aae9", + "shasum": "" + }, + "require": { + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0.1" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^12.5.28" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.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/8.0.3" + }, + "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/global-state", + "type": "tidelift" + } + ], + "time": "2026-06-01T15:10:33+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d543b8ef219dcd8da262cbb958639a96bedba10e", + "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.7.0", + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.25" + }, + "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 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/4.0.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/lines-of-code", + "type": "tidelift" + } + ], + "time": "2026-05-19T16:22:07+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "shasum": "" + }, + "require": { + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^12.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": "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/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:57:48+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.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" + } + ], + "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/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:58:17+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.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" + }, + { + "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/7.0.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/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-13T04:44:59+00:00" + }, + { + "name": "sebastian/type", + "version": "6.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "82ff822c2edc46724be9f7411d3163021f602773" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/82ff822c2edc46724be9f7411d3163021f602773", + "reference": "82ff822c2edc46724be9f7411d3163021f602773", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.25" + }, + "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", + "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/6.0.4" + }, + "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/type", + "type": "tidelift" + } + ], + "time": "2026-05-20T06:45:45+00:00" + }, + { + "name": "sebastian/version", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "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", + "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/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T05:00:38+00:00" + }, + { + "name": "sirbrillig/phpcs-variable-analysis", + "version": "v2.13.0", + "source": { + "type": "git", + "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git", + "reference": "a15e970b8a0bf64cfa5e86d941f5e6b08855f369" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/a15e970b8a0bf64cfa5e86d941f5e6b08855f369", + "reference": "a15e970b8a0bf64cfa5e86d941f5e6b08855f369", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "squizlabs/php_codesniffer": "^3.5.7 || ^4.0.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0", + "phpstan/phpstan": "^1.7 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.5 || ^7.0 || ^8.0 || ^9.0 || ^10.5.32 || ^11.3.3", + "vimeo/psalm": "^0.2 || ^0.3 || ^1.1 || ^4.24 || ^5.0 || ^6.0 || ^7.0" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "VariableAnalysis\\": "VariableAnalysis/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Sam Graham", + "email": "php-codesniffer-variableanalysis@illusori.co.uk" + }, + { + "name": "Payton Swick", + "email": "payton@foolord.com" + } + ], + "description": "A PHPCS sniff to detect problems with variables.", + "keywords": [ + "phpcs", + "static analysis" + ], + "support": { + "issues": "https://github.com/sirbrillig/phpcs-variable-analysis/issues", + "source": "https://github.com/sirbrillig/phpcs-variable-analysis", + "wiki": "https://github.com/sirbrillig/phpcs-variable-analysis/wiki" + }, + "time": "2025-09-30T22:22:48+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "8.22.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "1dd80bf3b93692bedb21a6623c496887fad05fec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/1dd80bf3b93692bedb21a6623c496887fad05fec", + "reference": "1dd80bf3b93692bedb21a6623c496887fad05fec", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.1.2", + "php": "^7.4 || ^8.0", + "phpstan/phpdoc-parser": "^2.3.0", + "squizlabs/php_codesniffer": "^3.13.4" + }, + "require-dev": { + "phing/phing": "3.0.1|3.1.0", + "php-parallel-lint/php-parallel-lint": "1.4.0", + "phpstan/phpstan": "2.1.24", + "phpstan/phpstan-deprecation-rules": "2.0.3", + "phpstan/phpstan-phpunit": "2.0.7", + "phpstan/phpstan-strict-rules": "2.0.6", + "phpunit/phpunit": "9.6.8|10.5.48|11.4.4|11.5.36|12.3.10" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "keywords": [ + "dev", + "phpcs" ], "support": { - "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", - "source": "https://github.com/PHPCompatibility/PHPCompatibility" + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/8.22.1" }, - "time": "2019-12-27T09:44:58+00:00" + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2025-09-13T08:53:30+00:00" }, { - "name": "phpcompatibility/phpcompatibility-paragonie", - "version": "1.3.4", + "name": "spatie/backtrace", + "version": "1.8.2", "source": { "type": "git", - "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", - "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf" + "url": "https://github.com/spatie/backtrace.git", + "reference": "8ffe78be5ed355b5009e3dd989d183433e9a5adc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/244d7b04fc4bc2117c15f5abe23eb933b5f02bbf", - "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/8ffe78be5ed355b5009e3dd989d183433e9a5adc", + "reference": "8ffe78be5ed355b5009e3dd989d183433e9a5adc", "shasum": "" }, "require": { - "phpcompatibility/php-compatibility": "^9.0" + "php": "^7.3 || ^8.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "paragonie/random_compat": "dev-master", - "paragonie/sodium_compat": "dev-master" + "ext-json": "*", + "laravel/serializable-closure": "^1.3 || ^2.0", + "phpunit/phpunit": "^9.3 || ^11.4.3", + "spatie/phpunit-snapshot-assertions": "^4.2 || ^5.1.6", + "symfony/var-dumper": "^5.1|^6.0|^7.0|^8.0" }, - "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", - "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Backtrace\\": "src" + } }, - "type": "phpcodesniffer-standard", "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0-or-later" + "MIT" ], "authors": [ { - "name": "Wim Godden", - "role": "lead" - }, - { - "name": "Juliette Reinders Folmer", - "role": "lead" + "name": "Freek Van de Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" } ], - "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.", - "homepage": "http://phpcompatibility.com/", + "description": "A better backtrace", + "homepage": "https://github.com/spatie/backtrace", "keywords": [ - "compatibility", - "paragonie", - "phpcs", - "polyfill", - "standards", - "static analysis" + "Backtrace", + "spatie" ], "support": { - "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", - "security": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/security/policy", - "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" + "issues": "https://github.com/spatie/backtrace/issues", + "source": "https://github.com/spatie/backtrace/tree/1.8.2" }, "funding": [ { - "url": "https://github.com/PHPCompatibility", - "type": "github" - }, - { - "url": "https://github.com/jrfnl", + "url": "https://github.com/sponsors/spatie", "type": "github" }, { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" - }, - { - "url": "https://thanks.dev/u/gh/phpcompatibility", - "type": "thanks_dev" + "url": "https://spatie.be/open-source/support-us", + "type": "other" } ], - "time": "2025-09-19T17:43:28+00:00" + "time": "2026-03-11T13:48:28+00:00" }, { - "name": "phpcompatibility/phpcompatibility-wp", - "version": "2.1.8", + "name": "spatie/phpunit-snapshot-assertions", + "version": "5.4.0", "source": { "type": "git", - "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", - "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa" + "url": "https://github.com/spatie/phpunit-snapshot-assertions.git", + "reference": "b5ad3efab36e6003c491bb42fb997b226760d5a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/7c8d18b4d90dac9e86b0869a608fa09158e168fa", - "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa", + "url": "https://api.github.com/repos/spatie/phpunit-snapshot-assertions/zipball/b5ad3efab36e6003c491bb42fb997b226760d5a0", + "reference": "b5ad3efab36e6003c491bb42fb997b226760d5a0", "shasum": "" }, "require": { - "phpcompatibility/php-compatibility": "^9.0", - "phpcompatibility/phpcompatibility-paragonie": "^1.0", - "squizlabs/php_codesniffer": "^3.3" + "composer-runtime-api": "^2.0", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "php": "^8.1", + "phpunit/phpunit": "^9.6|^10.0|^11.0|^12.0|^13.0", + "symfony/property-access": "^5.2|^6.2|^7.0|^8.0", + "symfony/serializer": "^5.2|^6.2|^7.0|^8.0", + "symfony/yaml": "^5.2|^6.2|^7.0|^8.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^1.0" + "spatie/pixelmatch-php": "dev-main", + "spatie/ray": "^1.37" }, "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", - "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + "spatie/pixelmatch-php": "Required to use the image snapshot assertions" + }, + "bin": [ + "bin/update-snapshots" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-v5": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "Spatie\\Snapshots\\": "src" + } }, - "type": "phpcodesniffer-standard", "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0-or-later" + "MIT" ], "authors": [ { - "name": "Wim Godden", - "role": "lead" - }, - { - "name": "Juliette Reinders Folmer", - "role": "lead" + "name": "Sebastian De Deyne", + "email": "sebastian@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" } ], - "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.", - "homepage": "http://phpcompatibility.com/", + "description": "Snapshot testing with PHPUnit", + "homepage": "https://github.com/spatie/phpunit-snapshot-assertions", "keywords": [ - "compatibility", - "phpcs", - "standards", - "static analysis", - "wordpress" + "assert", + "phpunit", + "phpunit-snapshot-assertions", + "snapshot", + "spatie", + "testing" ], "support": { - "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues", - "security": "https://github.com/PHPCompatibility/PHPCompatibilityWP/security/policy", - "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP" + "issues": "https://github.com/spatie/phpunit-snapshot-assertions/issues", + "source": "https://github.com/spatie/phpunit-snapshot-assertions/tree/5.4.0" }, "funding": [ { - "url": "https://github.com/PHPCompatibility", - "type": "github" - }, - { - "url": "https://github.com/jrfnl", - "type": "github" - }, - { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" - }, - { - "url": "https://thanks.dev/u/gh/phpcompatibility", - "type": "thanks_dev" + "url": "https://spatie.be/open-source/support-us", + "type": "custom" } ], - "time": "2025-10-18T00:05:59+00:00" + "time": "2026-04-29T09:52:03+00:00" }, { - "name": "phpcsstandards/phpcsextra", - "version": "1.5.1", + "name": "squizlabs/php_codesniffer", + "version": "3.13.5", "source": { "type": "git", - "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", - "reference": "39467533fdb742446d68c1d10ac33d625ee0311c" + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/39467533fdb742446d68c1d10ac33d625ee0311c", - "reference": "39467533fdb742446d68c1d10ac33d625ee0311c", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4", "shasum": "" }, "require": { - "php": ">=5.4", - "phpcsstandards/phpcsutils": "^1.2.3", - "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1" + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" }, "require-dev": { - "php-parallel-lint/php-console-highlighter": "^1.0", - "php-parallel-lint/php-parallel-lint": "^1.4.0", - "phpcsstandards/phpcsdevcs": "^1.2.0", - "phpcsstandards/phpcsdevtools": "^1.2.1", - "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-stable": "1.x-dev", - "dev-develop": "1.x-dev" - } + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0-or-later" + "BSD-3-Clause" ], "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, { "name": "Juliette Reinders Folmer", - "homepage": "https://github.com/jrfnl", - "role": "lead" + "role": "Current lead" }, { "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], - "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", + "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": [ - "PHP_CodeSniffer", - "phpcbf", - "phpcodesniffer-standard", "phpcs", "standards", "static analysis" ], "support": { - "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", - "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy", - "source": "https://github.com/PHPCSStandards/PHPCSExtra" + "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": [ { @@ -1987,171 +7696,260 @@ "type": "thanks_dev" } ], - "time": "2026-07-27T11:13:17+00:00" + "time": "2025-11-04T16:30:35+00:00" }, { - "name": "phpcsstandards/phpcsutils", - "version": "1.2.3", + "name": "staabm/side-effects-detector", + "version": "1.0.5", "source": { "type": "git", - "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", - "reference": "5f35d9408c54d7b529501f3c688b6eae562aea1f" + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/5f35d9408c54d7b529501f3c688b6eae562aea1f", - "reference": "5f35d9408c54d7b529501f3c688b6eae562aea1f", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", "shasum": "" }, "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", - "php": ">=5.4", - "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1" + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" }, "require-dev": { - "ext-filter": "*", - "php-parallel-lint/php-console-highlighter": "^1.0", - "php-parallel-lint/php-parallel-lint": "^1.4.0", - "phpcsstandards/phpcsdevcs": "^1.2.0", - "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0 || ^3.0.0" + "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": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-stable": "1.x-dev", - "dev-develop": "1.x-dev" + "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/clock", + "version": "v7.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "674fa3b98e21531dd040e613479f5f6fa8f32111" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/674fa3b98e21531dd040e613479f5f6fa8f32111", + "reference": "674fa3b98e21531dd040e613479f5f6fa8f32111", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "provide": { + "psr/clock-implementation": "1.0" }, + "type": "library", "autoload": { - "classmap": [ - "PHPCSUtils/" + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0-or-later" + "MIT" ], "authors": [ { - "name": "Juliette Reinders Folmer", - "homepage": "https://github.com/jrfnl", - "role": "lead" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "A suite of utility functions for use with PHP_CodeSniffer", - "homepage": "https://phpcsutils.com/", + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", "keywords": [ - "PHP_CodeSniffer", - "phpcbf", - "phpcodesniffer-standard", - "phpcs", - "phpcs3", - "phpcs4", - "standards", - "static analysis", - "tokens", - "utility" + "clock", + "psr20", + "time" ], "support": { - "docs": "https://phpcsutils.com/", - "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", - "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy", - "source": "https://github.com/PHPCSStandards/PHPCSUtils" + "source": "https://github.com/symfony/clock/tree/v7.4.8" }, "funding": [ { - "url": "https://github.com/PHPCSStandards", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://github.com/jrfnl", + "url": "https://github.com/fabpot", "type": "github" }, { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/phpcsstandards", - "type": "thanks_dev" + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2026-07-27T10:28:41+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { - "name": "phpstan/phpdoc-parser", - "version": "2.3.3", + "name": "symfony/console", + "version": "v7.4.15", "source": { "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3" + "url": "https://github.com/symfony/console.git", + "reference": "088ec6fe0ef6819cbc301174093b6bfa4ad26930" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", - "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", + "url": "https://api.github.com/repos/symfony/console/zipball/088ec6fe0ef6819cbc301174093b6bfa4ad26930", + "reference": "088ec6fe0ef6819cbc301174093b6bfa4ad26930", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" + }, + "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": { - "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" + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", + "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": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.3" + "source": "https://github.com/symfony/console/tree/v7.4.15" }, - "time": "2026-07-08T07:01:06+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-27T13:51:00+00:00" }, { - "name": "phpstan/phpstan", - "version": "2.2.7", + "name": "symfony/css-selector", + "version": "v7.4.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "b75663ed96cf4756e28e3105476f220f92886cc4" + }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/692db47b9dddb0487934e5236e77d48594aef921", - "reference": "692db47b9dddb0487934e5236e77d48594aef921", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/b75663ed96cf4756e28e3105476f220f92886cc4", + "reference": "b75663ed96cf4756e28e3105476f220f92886cc4", "shasum": "" }, "require": { - "php": "^7.4|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" + "php": ">=8.2" }, - "bin": [ - "phpstan", - "phpstan.phar" - ], "type": "library", "autoload": { - "files": [ - "bootstrap.php" + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2160,1774 +7958,2339 @@ ], "authors": [ { - "name": "Ondřej Mirtes" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "name": "Markus Staab" + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" }, { - "name": "Vincent Langlet" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "PHPStan - PHP Static Analysis Tool", - "keywords": [ - "dev", - "static analysis" - ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", "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" + "source": "https://github.com/symfony/css-selector/tree/v7.4.9" }, "funding": [ { - "url": "https://github.com/ondrejmirtes", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", "type": "github" }, { - "url": "https://github.com/phpstan", + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2026-07-29T17:39:32+00:00" + "time": "2026-04-18T13:18:21+00:00" }, { - "name": "phpstan/phpstan-deprecation-rules", - "version": "2.0.5", + "name": "symfony/deprecation-contracts", + "version": "v3.7.1", "source": { "type": "git", - "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "67bedd65c24bc72840afc45aed48b1059dd44bec" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/67bedd65c24bc72840afc45aed48b1059dd44bec", - "reference": "67bedd65c24bc72840afc45aed48b1059dd44bec", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.1.39" - }, - "require-dev": { - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^9.6", - "shipmonk/name-collision-detector": "^2.1" + "php": ">=8.1" }, - "type": "phpstan-extension", + "type": "library", "extra": { - "phpstan": { - "includes": [ - "rules.neon" - ] + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" } }, "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } + "files": [ + "function.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", - "keywords": [ - "static analysis" + "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": { - "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", - "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.5" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" }, - "time": "2026-07-22T06:50:43+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "9.2.32", + "name": "symfony/dom-crawler", + "version": "v7.4.12", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "b59b59122690976550fd142c23fab62c84738db6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", - "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b59b59122690976550fd142c23fab62c84738db6", + "reference": "b59b59122690976550fd142c23fab62c84738db6", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^4.19.1 || ^5.1.0", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.6", - "phpunit/php-text-template": "^2.0.4", - "sebastian/code-unit-reverse-lookup": "^2.0.3", - "sebastian/complexity": "^2.0.3", - "sebastian/environment": "^5.1.5", - "sebastian/lines-of-code": "^1.0.4", - "sebastian/version": "^3.0.2", - "theseer/tokenizer": "^1.2.3" + "masterminds/html5": "^2.6", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "phpunit/phpunit": "^9.6" - }, - "suggest": { - "ext-pcov": "PHP extension that provides line coverage", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + "symfony/css-selector": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "9.2.x-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "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" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], + "description": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" + "source": "https://github.com/symfony/dom-crawler/tree/v7.4.12" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2024-08-22T04:23:01+00:00" + "time": "2026-05-20T07:20:23+00:00" }, { - "name": "phpunit/php-file-iterator", - "version": "3.0.6", + "name": "symfony/error-handler", + "version": "v7.4.15", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + "url": "https://github.com/symfony/error-handler.git", + "reference": "d49f6a19f326db41ae7103bdc38e3eb35a791261" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/d49f6a19f326db41ae7103bdc38e3eb35a791261", + "reference": "d49f6a19f326db41ae7103bdc38e3eb35a791261", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/polyfill-php85": "^1.32", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/webpack-encore-bundle": "^1.0|^2.0" }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + "source": "https://github.com/symfony/error-handler/tree/v7.4.15" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2021-12-02T12:48:52+00:00" + "time": "2026-07-21T15:13:06+00:00" }, { - "name": "phpunit/php-invoker", - "version": "3.1.1", + "name": "symfony/event-dispatcher", + "version": "v7.4.15", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "336e7f3b9e95aba04f93ea9143920c2186abfbb9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/336e7f3b9e95aba04f93ea9143920c2186abfbb9", + "reference": "336e7f3b9e95aba04f93ea9143920c2186abfbb9", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" }, - "suggest": { - "ext-pcntl": "*" + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1-dev" - } + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0|^8.0" }, + "type": "library", "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": [ - "process" - ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.15" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2020-09-28T05:58:55+00:00" + "time": "2026-07-21T15:13:06+00:00" }, { - "name": "phpunit/php-text-template", - "version": "2.0.4", + "name": "symfony/event-dispatcher-contracts", + "version": "v3.7.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", "shasum": "" }, "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "php": ">=8.1", + "psr/event-dispatcher": "^1" }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.7-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", "keywords": [ - "template" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2020-10-26T05:33:50+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { - "name": "phpunit/php-timer", - "version": "5.0.3", + "name": "symfony/http-foundation", + "version": "v7.4.15", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + "url": "https://github.com/symfony/http-foundation.git", + "reference": "1f898ee8188adda9417fb52cf8425a8342c254e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/1f898ee8188adda9417fb52cf8425a8342c254e7", + "reference": "1f898ee8188adda9417fb52cf8425a8342c254e7", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.1" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4.12|^7.1.5|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.15" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2020-10-26T13:16:10+00:00" + "time": "2026-07-29T07:12:33+00:00" }, { - "name": "phpunit/phpunit", - "version": "9.6.35", + "name": "symfony/http-kernel", + "version": "v7.4.15", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0edba2f3a0c48df3553cb9b640810b30df60302b" + "url": "https://github.com/symfony/http-kernel.git", + "reference": "403275d94f94d5626c3288c599b3b48093ba24f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0edba2f3a0c48df3553cb9b640810b30df60302b", - "reference": "0edba2f3a0c48df3553cb9b640810b30df60302b", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/403275d94f94d5626c3288c599b3b48093ba24f7", + "reference": "403275d94f94d5626c3288c599b3b48093ba24f7", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.5.0 || ^2", - "ext-dom": "*", - "ext-filter": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.4", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", - "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.32", - "phpunit/php-file-iterator": "^3.0.6", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.4", - "phpunit/php-timer": "^5.0.3", - "sebastian/cli-parser": "^1.0.2", - "sebastian/code-unit": "^1.0.8", - "sebastian/comparator": "^4.0.10", - "sebastian/diff": "^4.0.6", - "sebastian/environment": "^5.1.5", - "sebastian/exporter": "^4.0.8", - "sebastian/global-state": "^5.0.8", - "sebastian/object-enumerator": "^4.0.4", - "sebastian/resource-operations": "^3.0.4", - "sebastian/type": "^3.2.1", - "sebastian/version": "^3.0.2" + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^7.3|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8" }, - "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/flex": "<2.10", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.12" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.6-dev" - } + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4.1|^7.0.1|^8.0", + "symfony/dom-crawler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^7.1|^8.0", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/serializer": "^7.1|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12|^4.0" }, + "type": "library", "autoload": { - "files": [ - "src/Framework/Assert/Functions.php" - ], - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], + "description": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.35" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.15" }, "funding": [ { - "url": "https://phpunit.de/sponsoring.html", - "type": "other" + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2026-07-06T14:48:07+00:00" + "time": "2026-07-29T11:40:42+00:00" }, { - "name": "rector/rector", - "version": "2.5.8", + "name": "symfony/mime", + "version": "v7.4.15", "source": { "type": "git", - "url": "https://github.com/rectorphp/rector.git", - "reference": "861c77fd2a6d45317a401f4e0b617f947e8b6f96" + "url": "https://github.com/symfony/mime.git", + "reference": "0c1daf58bc931628df0bea26840d1fc8b9a3d34b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/861c77fd2a6d45317a401f4e0b617f947e8b6f96", - "reference": "861c77fd2a6d45317a401f4e0b617f947e8b6f96", + "url": "https://api.github.com/repos/symfony/mime/zipball/0c1daf58bc931628df0bea26840d1fc8b9a3d34b", + "reference": "0c1daf58bc931628df0bea26840d1fc8b9a3d34b", "shasum": "" }, "require": { - "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.2.6" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" }, "conflict": { - "rector/rector-doctrine": "*", - "rector/rector-downgrade-php": "*", - "rector/rector-phpunit": "*", - "rector/rector-symfony": "*" + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" }, - "suggest": { - "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4.3|^7.0.3|^8.0" }, - "bin": [ - "bin/rector" - ], "type": "library", "autoload": { - "files": [ - "bootstrap.php" + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Instant Upgrade and Automated Refactoring of any PHP code", - "homepage": "https://getrector.com/", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", "keywords": [ - "automation", - "dev", - "migration", - "refactoring" + "mime", + "mime-type" ], "support": { - "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.5.8" + "source": "https://github.com/symfony/mime/tree/v7.4.15" }, "funding": [ { - "url": "https://github.com/tomasvotruba", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2026-07-27T06:19:16+00:00" + "time": "2026-07-29T07:59:49+00:00" }, { - "name": "sebastian/cli-parser", - "version": "1.0.2", + "name": "symfony/polyfill-ctype", + "version": "v1.37.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=7.2" }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2024-03-02T06:27:43+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { - "name": "sebastian/code-unit", - "version": "1.0.8", + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.41.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=7.2" }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "suggest": { + "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2020-10-26T13:08:54+00:00" + "time": "2026-07-28T08:25:59+00:00" }, { - "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", + "name": "symfony/polyfill-intl-idn", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "dc21118016c039a66235cf93d96b435ffb282412" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/dc21118016c039a66235cf93d96b435ffb282412", + "reference": "dc21118016c039a66235cf93d96b435ffb282412", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "suggest": { + "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.38.1" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2020-09-28T05:30:19+00:00" + "time": "2026-05-25T15:22:23+00:00" }, { - "name": "sebastian/comparator", - "version": "4.0.10", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.38.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d", - "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" + "php": ">=7.2" }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "suggest": { + "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "4.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, "classmap": [ - "src/" + "Resources/stubs" ] }, - "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" - }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "name": "Volker Dusch", - "email": "github@wallbash.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", "keywords": [ - "comparator", - "compare", - "equality" + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" ], "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.10" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "url": "https://github.com/fabpot", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2026-01-24T09:22:56+00:00" + "time": "2026-05-25T13:48:31+00:00" }, { - "name": "sebastian/complexity", - "version": "2.0.3", + "name": "symfony/polyfill-mbstring", + "version": "v1.38.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=7.3" + "ext-iconv": "*", + "php": ">=7.2" }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2023-12-22T06:19:30+00:00" + "time": "2026-05-27T06:59:30+00:00" }, { - "name": "sebastian/diff", - "version": "4.0.6", + "name": "symfony/polyfill-php80", + "version": "v1.37.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", "shasum": "" }, "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3", - "symfony/process": "^4.2 || ^5" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "4.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, "classmap": [ - "src/" + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" }, { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" + "compatibility", + "polyfill", + "portable", + "shim" ], "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2024-03-02T06:30:58+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { - "name": "sebastian/environment", - "version": "5.1.5", + "name": "symfony/polyfill-php83", + "version": "v1.41.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/5ea99087fb99c273a9b9236ed4c31e78b16103c6", + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6", "shasum": "" }, "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-posix": "*" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "5.1-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, "classmap": [ - "src/" + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", "keywords": [ - "Xdebug", - "environment", - "hhvm" + "compatibility", + "polyfill", + "portable", + "shim" ], "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.41.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2023-02-03T06:03:51+00:00" + "time": "2026-07-01T12:47:55+00:00" }, { - "name": "sebastian/exporter", - "version": "4.0.8", + "name": "symfony/process", + "version": "v7.4.13", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c" + "url": "https://github.com/symfony/process.git", + "reference": "f5804be144caceb570f6747519999636b664f24c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c", - "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c", + "url": "https://api.github.com/repos/symfony/process/zipball/f5804be144caceb570f6747519999636b664f24c", + "reference": "f5804be144caceb570f6747519999636b664f24c", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" + "php": ">=8.2" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "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": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8" + "source": "https://github.com/symfony/process/tree/v7.4.13" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "url": "https://github.com/fabpot", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-09-24T06:03:27+00:00" + "time": "2026-05-23T16:05:06+00:00" }, { - "name": "sebastian/global-state", - "version": "5.0.8", + "name": "symfony/property-access", + "version": "v7.4.8", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6" + "url": "https://github.com/symfony/property-access.git", + "reference": "b7dad9dae8b8a47ef7ecc76c8569e7d8c7d90cfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6", - "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6", + "url": "https://api.github.com/repos/symfony/property-access/zipball/b7dad9dae8b8a47ef7ecc76c8569e7d8c7d90cfc", + "reference": "b7dad9dae8b8a47ef7ecc76c8569e7d8c7d90cfc", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.2", + "symfony/property-info": "^6.4.32|~7.3.10|^7.4.4|^8.0.4" }, "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4.1|^7.0.1|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\PropertyAccess\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "description": "Provides functions to read and write from/to an object or array using a simple string notation", + "homepage": "https://symfony.com", "keywords": [ - "global state" + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property-path", + "reflection" ], "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.8" + "source": "https://github.com/symfony/property-access/tree/v7.4.8" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "url": "https://github.com/fabpot", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-08-10T07:10:35+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { - "name": "sebastian/lines-of-code", - "version": "1.0.4", + "name": "symfony/property-info", + "version": "v7.4.15", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + "url": "https://github.com/symfony/property-info.git", + "reference": "fce3f4d9cfeb4ddc674c357b5a4c3a23ccf408ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "url": "https://api.github.com/repos/symfony/property-info/zipball/fce3f4d9cfeb4ddc674c357b5a4c3a23ccf408ad", + "reference": "fce3f4d9cfeb4ddc674c357b5a4c3a23ccf408ad", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=7.3" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0|^8.0", + "symfony/type-info": "^7.4.7|^8.0.7" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/cache": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/serializer": "<6.4" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\PropertyInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "description": "Extracts information about PHP class' properties using metadata of popular sources", + "homepage": "https://symfony.com", + "keywords": [ + "doctrine", + "phpdoc", + "property", + "symfony", + "type", + "validator" + ], "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + "source": "https://github.com/symfony/property-info/tree/v7.4.15" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2023-12-22T06:20:34+00:00" + "time": "2026-07-28T07:09:44+00:00" }, { - "name": "sebastian/object-enumerator", - "version": "4.0.4", + "name": "symfony/routing", + "version": "v7.4.15", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + "url": "https://github.com/symfony/routing.git", + "reference": "80c0a93d3f8e7499f716204a1fb38ead942a7a2b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "url": "https://api.github.com/repos/symfony/routing/zipball/80c0a93d3f8e7499f716204a1fb38ead942a7a2b", + "reference": "80c0a93d3f8e7499f716204a1fb38ead942a7a2b", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + "source": "https://github.com/symfony/routing/tree/v7.4.15" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2020-10-26T13:12:34+00:00" + "time": "2026-07-21T15:13:06+00:00" }, { - "name": "sebastian/object-reflector", - "version": "2.0.4", + "name": "symfony/serializer", + "version": "v7.4.15", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + "url": "https://github.com/symfony/serializer.git", + "reference": "917f1575bec2853f45e012d8718f518365a9d258" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "url": "https://api.github.com/repos/symfony/serializer/zipball/917f1575bec2853f45e012d8718f518365a9d258", + "reference": "917f1575bec2853f45e012d8718f518365a9d258", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php84": "^1.30" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/dependency-injection": "<6.4", + "symfony/property-access": "<6.4.31|>=7.0,<7.4.2|>=8.0,<8.0.2", + "symfony/property-info": "<6.4.43", + "symfony/type-info": "<7.2.5", + "symfony/uid": "<6.4", + "symfony/validator": "<6.4", + "symfony/yaml": "<6.4" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "seld/jsonlint": "^1.10", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^7.2|^8.0", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/filesystem": "^6.4|^7.0|^8.0", + "symfony/form": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4.31|^7.4.2|^8.0.2", + "symfony/property-info": "^6.4.43|^7.4.15|^8.0.15", + "symfony/translation-contracts": "^2.5|^3", + "symfony/type-info": "^7.2.5|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\Serializer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + "source": "https://github.com/symfony/serializer/tree/v7.4.15" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2020-10-26T13:14:26+00:00" + "time": "2026-07-29T07:59:49+00:00" }, { - "name": "sebastian/recursion-context", - "version": "4.0.6", + "name": "symfony/service-contracts", + "version": "v3.7.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "539c6691e0623af6dc6f9c20384c120f963465a0" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0", - "reference": "539c6691e0623af6dc6f9c20384c120f963465a0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0", + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "conflict": { + "ext-psr": "<1.1|>=2" }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "3.7-dev" } }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Adam Harvey", - "email": "aharvey@php.net" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.1" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "url": "https://github.com/fabpot", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-08-10T06:57:39+00:00" + "time": "2026-06-16T09:55:08+00:00" }, { - "name": "sebastian/resource-operations", - "version": "3.0.4", + "name": "symfony/string", + "version": "v7.4.15", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" + "url": "https://github.com/symfony/string.git", + "reference": "e394af32256bf9e7bf80849d95e589167c10097b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "url": "https://api.github.com/repos/symfony/string/zipball/e394af32256bf9e7bf80849d95e589167c10097b", + "reference": "e394af32256bf9e7bf80849d95e589167c10097b", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.33", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "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/sebastianbergmann/resource-operations/tree/3.0.4" + "source": "https://github.com/symfony/string/tree/v7.4.15" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2024-03-14T16:00:52+00:00" + "time": "2026-07-28T07:33:02+00:00" }, { - "name": "sebastian/type", - "version": "3.2.1", + "name": "symfony/translation", + "version": "v7.4.14", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + "url": "https://github.com/symfony/translation.git", + "reference": "a1af4dacb24eb7ef4f1ca71b94da8ddbce572281" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "url": "https://api.github.com/repos/symfony/translation/zipball/a1af4dacb24eb7ef4f1ca71b94da8ddbce572281", + "reference": "a1af4dacb24eb7ef4f1ca71b94da8ddbce572281", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5.3|^3.3" + }, + "conflict": { + "nikic/php-parser": "<5.0", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "nikic/php-parser": "^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + "source": "https://github.com/symfony/translation/tree/v7.4.14" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2023-02-03T06:13:03+00:00" + "time": "2026-06-06T09:33:19+00:00" }, { - "name": "sebastian/version", - "version": "3.0.2", + "name": "symfony/translation-contracts", + "version": "v3.7.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/ccb206b98faccc511ebae8e5fad50f2dc0b30621", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.7-dev" } }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + "source": "https://github.com/symfony/translation-contracts/tree/v3.7.1" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2020-09-28T06:39:44+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { - "name": "sirbrillig/phpcs-variable-analysis", - "version": "v2.13.0", + "name": "symfony/type-info", + "version": "v7.4.9", "source": { "type": "git", - "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git", - "reference": "a15e970b8a0bf64cfa5e86d941f5e6b08855f369" + "url": "https://github.com/symfony/type-info.git", + "reference": "cafeedbf157b890e94ac5b83eaed85595106d5d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/a15e970b8a0bf64cfa5e86d941f5e6b08855f369", - "reference": "a15e970b8a0bf64cfa5e86d941f5e6b08855f369", + "url": "https://api.github.com/repos/symfony/type-info/zipball/cafeedbf157b890e94ac5b83eaed85595106d5d6", + "reference": "cafeedbf157b890e94ac5b83eaed85595106d5d6", "shasum": "" }, "require": { - "php": ">=5.4.0", - "squizlabs/php_codesniffer": "^3.5.7 || ^4.0.0" + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.30" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0", - "phpstan/phpstan": "^1.7 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.5 || ^7.0 || ^8.0 || ^9.0 || ^10.5.32 || ^11.3.3", - "vimeo/psalm": "^0.2 || ^0.3 || ^1.1 || ^4.24 || ^5.0 || ^6.0 || ^7.0" + "phpstan/phpdoc-parser": "^1.30|^2.0" }, - "type": "phpcodesniffer-standard", + "type": "library", "autoload": { "psr-4": { - "VariableAnalysis\\": "VariableAnalysis/" - } + "Symfony\\Component\\TypeInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-2-Clause" + "MIT" ], "authors": [ { - "name": "Sam Graham", - "email": "php-codesniffer-variableanalysis@illusori.co.uk" + "name": "Mathias Arlaud", + "email": "mathias.arlaud@gmail.com" }, { - "name": "Payton Swick", - "email": "payton@foolord.com" + "name": "Baptiste LEDUC", + "email": "baptiste.leduc@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "A PHPCS sniff to detect problems with variables.", + "description": "Extracts PHP types information.", + "homepage": "https://symfony.com", "keywords": [ - "phpcs", - "static analysis" + "PHPStan", + "phpdoc", + "symfony", + "type" ], "support": { - "issues": "https://github.com/sirbrillig/phpcs-variable-analysis/issues", - "source": "https://github.com/sirbrillig/phpcs-variable-analysis", - "wiki": "https://github.com/sirbrillig/phpcs-variable-analysis/wiki" + "source": "https://github.com/symfony/type-info/tree/v7.4.9" }, - "time": "2025-09-30T22:22:48+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-22T15:21:55+00:00" }, { - "name": "slevomat/coding-standard", - "version": "8.22.1", + "name": "symfony/var-dumper", + "version": "v7.4.15", "source": { "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "1dd80bf3b93692bedb21a6623c496887fad05fec" + "url": "https://github.com/symfony/var-dumper.git", + "reference": "04ba4add636a95ff437af3a5a9499bb1d6c6d4bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/1dd80bf3b93692bedb21a6623c496887fad05fec", - "reference": "1dd80bf3b93692bedb21a6623c496887fad05fec", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/04ba4add636a95ff437af3a5a9499bb1d6c6d4bd", + "reference": "04ba4add636a95ff437af3a5a9499bb1d6c6d4bd", "shasum": "" }, "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.1.2", - "php": "^7.4 || ^8.0", - "phpstan/phpdoc-parser": "^2.3.0", - "squizlabs/php_codesniffer": "^3.13.4" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" }, - "require-dev": { - "phing/phing": "3.0.1|3.1.0", - "php-parallel-lint/php-parallel-lint": "1.4.0", - "phpstan/phpstan": "2.1.24", - "phpstan/phpstan-deprecation-rules": "2.0.3", - "phpstan/phpstan-phpunit": "2.0.7", - "phpstan/phpstan-strict-rules": "2.0.6", - "phpunit/phpunit": "9.6.8|10.5.48|11.4.4|11.5.36|12.3.10" + "conflict": { + "symfony/console": "<6.4" }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "8.x-dev" - } + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12|^4.0" }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", "autoload": { + "files": [ + "Resources/functions/dump.php" + ], "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard/" - } + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", "keywords": [ - "dev", - "phpcs" + "debug", + "dump" ], "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.22.1" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.15" }, "funding": [ { - "url": "https://github.com/kukulich", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-09-13T08:53:30+00:00" + "time": "2026-07-21T15:13:06+00:00" }, { - "name": "squizlabs/php_codesniffer", - "version": "3.13.5", + "name": "symfony/yaml", + "version": "v7.4.15", "source": { "type": "git", - "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4" + "url": "https://github.com/symfony/yaml.git", + "reference": "e101850ded5d2c0d44bf32abb8996404afec2dec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4", - "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e101850ded5d2c0d44bf32abb8996404afec2dec", + "reference": "e101850ded5d2c0d44bf32abb8996404afec2dec", "shasum": "" }, "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<6.4" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + "symfony/console": "^6.4|^7.0|^8.0" }, "bin": [ - "bin/phpcbf", - "bin/phpcs" + "Resources/bin/yaml-lint" ], "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "Former lead" - }, + "authors": [ { - "name": "Juliette Reinders Folmer", - "role": "Current lead" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + "name": "Symfony Community", + "homepage": "https://symfony.com/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" - ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", "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" + "source": "https://github.com/symfony/yaml/tree/v7.4.15" }, "funding": [ { - "url": "https://github.com/PHPCSStandards", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://github.com/jrfnl", + "url": "https://github.com/fabpot", "type": "github" }, { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/phpcsstandards", - "type": "thanks_dev" + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2025-11-04T16:30:35+00:00" + "time": "2026-07-21T15:13:06+00:00" }, { "name": "szepeviktor/phpstan-wordpress", @@ -3992,25 +10355,84 @@ }, "time": "2025-09-14T02:58:22+00:00" }, + { + "name": "ta-tikoma/phpunit-architecture-test", + "version": "0.8.7", + "source": { + "type": "git", + "url": "https://github.com/ta-tikoma/phpunit-architecture-test.git", + "reference": "1248f3f506ca9641d4f68cebcd538fa489754db8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/1248f3f506ca9641d4f68cebcd538fa489754db8", + "reference": "1248f3f506ca9641d4f68cebcd538fa489754db8", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18.0 || ^5.0.0", + "php": "^8.1.0", + "phpdocumentor/reflection-docblock": "^5.3.0 || ^6.0.0", + "phpunit/phpunit": "^10.5.5 || ^11.0.0 || ^12.0.0 || ^13.0.0", + "symfony/finder": "^6.4.0 || ^7.0.0 || ^8.0.0" + }, + "require-dev": { + "laravel/pint": "^1.13.7", + "phpstan/phpstan": "^1.10.52" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPUnit\\Architecture\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ni Shi", + "email": "futik0ma011@gmail.com" + }, + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Methods for testing application architecture", + "keywords": [ + "architecture", + "phpunit", + "stucture", + "test", + "testing" + ], + "support": { + "issues": "https://github.com/ta-tikoma/phpunit-architecture-test/issues", + "source": "https://github.com/ta-tikoma/phpunit-architecture-test/tree/0.8.7" + }, + "time": "2026-02-17T17:25:14+00:00" + }, { "name": "theseer/tokenizer", - "version": "1.3.1", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/7989e43bf381af0eac72e4f0ca5bcbfa81658be4", + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4", "shasum": "" }, "require": { "ext-dom": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" + "php": "^8.1" }, "type": "library", "autoload": { @@ -4032,7 +10454,7 @@ "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.3.1" + "source": "https://github.com/theseer/tokenizer/tree/2.0.1" }, "funding": [ { @@ -4040,7 +10462,7 @@ "type": "github" } ], - "time": "2025-11-17T20:03:58+00:00" + "time": "2025-12-08T11:19:18+00:00" }, { "name": "tomasvotruba/type-coverage", @@ -4100,133 +10522,294 @@ "time": "2026-05-26T08:14:01+00:00" }, { - "name": "wp-coding-standards/wpcs", - "version": "3.4.1", + "name": "vlucas/phpdotenv", + "version": "v5.6.4", "source": { "type": "git", - "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", - "reference": "ec2ff942335f33683a5957a85d138753876a05cf" + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "416df702837983f8d5ff48c9c3fee4f5f57b980b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/ec2ff942335f33683a5957a85d138753876a05cf", - "reference": "ec2ff942335f33683a5957a85d138753876a05cf", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/416df702837983f8d5ff48c9c3fee4f5f57b980b", + "reference": "416df702837983f8d5ff48c9c3fee4f5f57b980b", "shasum": "" }, "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.4", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5", + "symfony/polyfill-ctype": "^1.26", + "symfony/polyfill-mbstring": "^1.26", + "symfony/polyfill-php80": "^1.26" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", "ext-filter": "*", - "ext-libxml": "*", - "ext-tokenizer": "*", - "ext-xmlreader": "*", - "php": ">=7.2", - "phpcsstandards/phpcsextra": "^1.5.1", - "phpcsstandards/phpcsutils": "^1.2.3", - "squizlabs/php_codesniffer": "^3.13.5" + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2026-07-06T19:11:50+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "8e1051fe39379367aecf014f41744ce7539a856f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/8e1051fe39379367aecf014f41744ce7539a856f", + "reference": "8e1051fe39379367aecf014f41744ce7539a856f", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" }, "require-dev": { - "php-parallel-lint/php-console-highlighter": "^1.0.0", - "php-parallel-lint/php-parallel-lint": "^1.4.0", - "phpcompatibility/php-compatibility": "^10.0.0@dev", - "phpcsstandards/phpcsdevtools": "^1.2.0", - "phpunit/phpunit": "^8.0 || ^9.0" + "phpunit/phpunit": "~8.5 || ~9.6 || ~10.5 || ~11.5" }, "suggest": { - "ext-iconv": "For improved results", - "ext-mbstring": "For improved results" + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } }, - "type": "phpcodesniffer-standard", "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { - "name": "Contributors", - "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" + "name": "Lars Moelleken", + "homepage": "https://www.moelleken.org/" } ], - "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", "keywords": [ - "phpcs", - "standards", - "static analysis", - "wordpress" + "ascii", + "clean", + "php" ], "support": { - "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", - "source": "https://github.com/WordPress/WordPress-Coding-Standards", - "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.1.1" }, "funding": [ { - "url": "https://opencollective.com/php_codesniffer", + "url": "https://www.paypal.me/moelleken", "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" } ], - "time": "2026-07-27T11:53:23+00:00" + "time": "2026-04-26T05:33:54+00:00" }, { - "name": "yoast/phpunit-polyfills", - "version": "2.0.5", + "name": "webmozart/assert", + "version": "2.4.1", "source": { "type": "git", - "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", - "reference": "1a6aecc9ebe4a9cea4e1047d0e6c496e52314c27" + "url": "https://github.com/webmozarts/assert.git", + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/1a6aecc9ebe4a9cea4e1047d0e6c496e52314c27", - "reference": "1a6aecc9ebe4a9cea4e1047d0e6c496e52314c27", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70", + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70", "shasum": "" }, "require": { - "php": ">=5.6", - "phpunit/phpunit": "^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0" + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" }, - "require-dev": { - "php-parallel-lint/php-console-highlighter": "^1.0.0", - "php-parallel-lint/php-parallel-lint": "^1.4.0", - "yoast/yoastcs": "^3.2.0" + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" }, "type": "library", "extra": { + "psalm": { + "pluginClass": "Webmozart\\Assert\\PsalmPlugin" + }, "branch-alias": { - "dev-main": "4.x-dev" + "dev-master": "2.0-dev", + "dev-feature/2-0": "2.0-dev" } }, "autoload": { - "files": [ - "phpunitpolyfills-autoload.php" - ] + "psr-4": { + "Webmozart\\Assert\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Team Yoast", - "email": "support@yoast.com", - "homepage": "https://yoast.com" + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" }, + { + "name": "Woody Gilk", + "email": "woody.gilk@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/2.4.1" + }, + "time": "2026-06-15T15:31:57+00:00" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "3.4.1", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", + "reference": "ec2ff942335f33683a5957a85d138753876a05cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/ec2ff942335f33683a5957a85d138753876a05cf", + "reference": "ec2ff942335f33683a5957a85d138753876a05cf", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-libxml": "*", + "ext-tokenizer": "*", + "ext-xmlreader": "*", + "php": ">=7.2", + "phpcsstandards/phpcsextra": "^1.5.1", + "phpcsstandards/phpcsutils": "^1.2.3", + "squizlabs/php_codesniffer": "^3.13.5" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcompatibility/php-compatibility": "^10.0.0@dev", + "phpcsstandards/phpcsdevtools": "^1.2.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "suggest": { + "ext-iconv": "For improved results", + "ext-mbstring": "For improved results" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { "name": "Contributors", - "homepage": "https://github.com/Yoast/PHPUnit-Polyfills/graphs/contributors" + "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" } ], - "description": "Set of polyfills for changed PHPUnit functionality to allow for creating PHPUnit cross-version compatible tests", - "homepage": "https://github.com/Yoast/PHPUnit-Polyfills", + "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", "keywords": [ - "phpunit", - "polyfill", - "testing" + "phpcs", + "standards", + "static analysis", + "wordpress" ], "support": { - "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", - "security": "https://github.com/Yoast/PHPUnit-Polyfills/security/policy", - "source": "https://github.com/Yoast/PHPUnit-Polyfills" + "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", + "source": "https://github.com/WordPress/WordPress-Coding-Standards", + "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" }, - "time": "2025-08-10T05:13:49+00:00" + "funding": [ + { + "url": "https://opencollective.com/php_codesniffer", + "type": "custom" + } + ], + "time": "2026-07-27T11:53:23+00:00" } ], "aliases": [], diff --git a/phpcs.xml b/phpcs.xml index 59bfa01..693f4d5 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -29,6 +29,15 @@ */tests/* + + + */tests/* + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index fe0829d..bd85503 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -4,25 +4,31 @@ bootstrap="tests/bootstrap.php" backupGlobals="false" colors="true" - convertErrorsToExceptions="true" - convertNoticesToExceptions="true" - convertWarningsToExceptions="true" - xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd" + xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" > - - - ./src/ - + + + ./tests/Unit/ + - - - - - + + ./tests/Integration/ + - - - ./tests/ + + ./tests/Arch/ + + + + + ./src/ + + diff --git a/tests/Arch/ConventionsTest.php b/tests/Arch/ConventionsTest.php new file mode 100644 index 0000000..542fa6e --- /dev/null +++ b/tests/Arch/ConventionsTest.php @@ -0,0 +1,24 @@ +expect( 'TenupFramework' ) + ->toUseStrictTypes(); + +arch( 'ships no debugging leftovers' ) + ->expect( [ 'var_dump', 'var_export', 'print_r', 'dd', 'dump', 'error_log' ] ) + ->not->toBeUsed() + // BlockRegistrar::register_blocks() logs deliberately when the block editor is missing, + // which is a real diagnostic rather than a leftover. Scoping the exception to that one + // class keeps the rule useful: a new error_log() anywhere else still fails. + ->ignoring( 'TenupFramework\BlockRegistrar' ); + +arch( 'never depends on test code' ) + ->expect( 'TenupFramework' ) + ->not->toUse( [ 'TenupFrameworkTests', 'TenupFrameworkTestClasses' ] ); diff --git a/tests/Assets/GetAssetInfoTest.php b/tests/Assets/GetAssetInfoTest.php deleted file mode 100644 index 415772f..0000000 --- a/tests/Assets/GetAssetInfoTest.php +++ /dev/null @@ -1,246 +0,0 @@ -setup_asset_vars( - dist_path: 'dist', - fallback_version: '1.0.0' - ); - - $this->assertEquals( 'dist/', $asset_info->dist_path ); - $this->assertEquals( '1.0.0', $asset_info->fallback_version ); - } - - /** - * Test get_asset_info returns an array with version and dependencies. - * - * @return void - */ - public function test_get_asset_info_returns_array_with_version_and_dependencies() { - $asset_info = new class() { - use GetAssetInfo; - }; - - $asset_info->setup_asset_vars( - dist_path: dirname( __DIR__, 2 ) . '/fixtures/assets/dist', - fallback_version: '1.0.0' - ); - - $asset = $asset_info->get_asset_info( - slug: 'test-script' - ); - $this->assertIsArray( $asset ); - $this->assertArrayHasKey( 'version', $asset ); - $this->assertArrayHasKey( 'dependencies', $asset ); - $vars = require dirname( __DIR__, 2 ) . '/fixtures/assets/dist/js/test-script.asset.php'; - $this->assertEquals( $vars, $asset ); - - $asset = $asset_info->get_asset_info( - slug: 'test-style' - ); - $this->assertArrayHasKey( 'version', $asset ); - $this->assertArrayHasKey( 'dependencies', $asset ); - $vars = require dirname( __DIR__, 2 ) . '/fixtures/assets/dist/css/test-style.asset.php'; - $this->assertEquals( $vars, $asset ); - - $asset = $asset_info->get_asset_info( - slug: 'non-existent' - ); - - $this->assertArrayHasKey( 'version', $asset ); - $this->assertArrayHasKey( 'dependencies', $asset ); - } - - /** - * Test get_asset_info returns a string when passed a specific dependency. - * - * @return void - */ - public function test_get_asset_info_returns_string_when_passed_specific_dependency() { - $asset_info = new class() { - use GetAssetInfo; - }; - - $asset_info->setup_asset_vars( - dist_path: dirname( __DIR__, 2 ) . '/fixtures/assets/dist', - fallback_version: '1.0.0' - ); - - $vars = require dirname( __DIR__, 2 ) . '/fixtures/assets/dist/js/test-script.asset.php'; - - $version = $asset_info->get_asset_info( - slug: 'test-script', - attribute: 'version' - ); - - $this->assertEquals( $vars['version'], $version ); - - $version = $asset_info->get_asset_info( - slug: 'test-script', - attribute: 'dependencies' - ); - - $this->assertEquals( $vars['dependencies'], $version ); - } - - /** - * Test get_asset_info throws and exception when get_asset_info is called without setting up the asset vars. - * - * @return void - */ - public function test_get_asset_info_throws_exception_when_called_without_setting_up_asset_vars() { - $asset_info = new class() { - use GetAssetInfo; - }; - - $this->expectException( \RuntimeException::class ); - $this->expectExceptionMessage( 'Asset variables not set. Please run setup_asset_vars() before calling get_asset_info().' ); - - $asset_info->get_asset_info( - slug: 'test-script' - ); - } - - /** - * Test get_asset_info with prefix-based slug handling (css/, js/, blocks/). - * - * @return void - */ - public function test_get_asset_info_with_prefix_based_slug() { - $asset_info = new class() { - use GetAssetInfo; - }; - - $asset_info->setup_asset_vars( - dist_path: dirname( __DIR__, 2 ) . '/fixtures/assets/dist', - fallback_version: '1.0.0' - ); - - // Test CSS prefix with existing fixture - $asset = $asset_info->get_asset_info( slug: 'css/test-style' ); - $this->assertIsArray( $asset ); - $this->assertArrayHasKey( 'version', $asset ); - $this->assertArrayHasKey( 'dependencies', $asset ); - $vars = require dirname( __DIR__, 2 ) . '/fixtures/assets/dist/css/test-style.asset.php'; - $this->assertEquals( $vars, $asset ); - - // Test JS prefix with existing fixture - $asset = $asset_info->get_asset_info( slug: 'js/test-script' ); - $this->assertIsArray( $asset ); - $this->assertArrayHasKey( 'version', $asset ); - $this->assertArrayHasKey( 'dependencies', $asset ); - $vars = require dirname( __DIR__, 2 ) . '/fixtures/assets/dist/js/test-script.asset.php'; - $this->assertEquals( $vars, $asset ); - - // Test blocks prefix with existing fixture - $asset = $asset_info->get_asset_info( slug: 'blocks/test-block' ); - $this->assertIsArray( $asset ); - $this->assertArrayHasKey( 'version', $asset ); - $this->assertArrayHasKey( 'dependencies', $asset ); - $vars = require dirname( __DIR__, 2 ) . '/fixtures/assets/dist/blocks/test-block.asset.php'; - $this->assertEquals( $vars, $asset ); - } - - /** - * Test get_asset_info priority order: prefix-based slugs take priority over fallback. - * - * @return void - */ - public function test_get_asset_info_priority_order_prefix_vs_fallback() { - $asset_info = new class() { - use GetAssetInfo; - }; - - $asset_info->setup_asset_vars( - dist_path: dirname( __DIR__, 2 ) . '/fixtures/assets/dist', - fallback_version: '1.0.0' - ); - - // Test that prefix-based slug works with existing fixtures - $asset = $asset_info->get_asset_info( slug: 'css/test-style' ); - $this->assertIsArray( $asset ); - $this->assertArrayHasKey( 'version', $asset ); - $this->assertArrayHasKey( 'dependencies', $asset ); - $vars = require dirname( __DIR__, 2 ) . '/fixtures/assets/dist/css/test-style.asset.php'; - $this->assertEquals( $vars, $asset ); - - // Test that fallback still works for non-prefixed slugs - $asset = $asset_info->get_asset_info( slug: 'test-script' ); - $this->assertIsArray( $asset ); - $this->assertArrayHasKey( 'version', $asset ); - $this->assertArrayHasKey( 'dependencies', $asset ); - $vars = require dirname( __DIR__, 2 ) . '/fixtures/assets/dist/js/test-script.asset.php'; - $this->assertEquals( $vars, $asset ); - } - - /** - * Test get_asset_info fallback behavior when direct file doesn't exist. - * - * @return void - */ - public function test_get_asset_info_fallback_when_direct_file_missing() { - $asset_info = new class() { - use GetAssetInfo; - }; - - $asset_info->setup_asset_vars( - dist_path: dirname( __DIR__, 2 ) . '/fixtures/assets/dist', - fallback_version: '1.0.0' - ); - - // Test that it falls back to JS directory first (priority order: js -> css -> blocks) - // Using existing fixture that exists in js/ directory - $asset = $asset_info->get_asset_info( slug: 'test-script' ); - $this->assertIsArray( $asset ); - $this->assertArrayHasKey( 'version', $asset ); - $this->assertArrayHasKey( 'dependencies', $asset ); - $vars = require dirname( __DIR__, 2 ) . '/fixtures/assets/dist/js/test-script.asset.php'; - $this->assertEquals( $vars, $asset ); - - // Test CSS fallback with existing fixture - $asset = $asset_info->get_asset_info( slug: 'test-style' ); - $this->assertIsArray( $asset ); - $this->assertArrayHasKey( 'version', $asset ); - $this->assertArrayHasKey( 'dependencies', $asset ); - $vars = require dirname( __DIR__, 2 ) . '/fixtures/assets/dist/css/test-style.asset.php'; - $this->assertEquals( $vars, $asset ); - - // Test blocks fallback with existing fixture - $asset = $asset_info->get_asset_info( slug: 'test-block' ); - $this->assertIsArray( $asset ); - $this->assertArrayHasKey( 'version', $asset ); - $this->assertArrayHasKey( 'dependencies', $asset ); - $vars = require dirname( __DIR__, 2 ) . '/fixtures/assets/dist/blocks/test-block.asset.php'; - $this->assertEquals( $vars, $asset ); - } -} diff --git a/tests/Bin/GenerateClassCacheTest.php b/tests/Bin/GenerateClassCacheTest.php deleted file mode 100644 index 81d10db..0000000 --- a/tests/Bin/GenerateClassCacheTest.php +++ /dev/null @@ -1,232 +0,0 @@ - - */ - private $temp_dirs = []; - - /** - * Remove any temporary directories created during the test. - * - * @return void - */ - protected function tearDown(): void { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid - foreach ( $this->temp_dirs as $dir ) { - $this->remove_dir( $dir ); - } - $this->temp_dirs = []; - - parent::tearDown(); - } - - /** - * Running the command against a directory writes a readable cache of its classes. - * - * @return void - */ - public function test_generates_a_cache_for_a_directory() { - $dir = $this->example_copy( 'plugin-inc' ); - - $result = $this->run_bin( [ $dir ] ); - - $this->assertSame( 0, $result['exit'], $result['stderr'] ); - $this->assertStringContainsString( 'Cached', $result['stdout'] ); - - $cache_file = $this->cache_file_path( $dir ); - $this->assertFileExists( $cache_file ); - - $cached = require $cache_file; - $this->assertContains( 'TenupFrameworkExamples\\Modules\\GreetingModule', $cached ); - $this->assertContains( 'TenupFrameworkExamples\\Support\\Formatter', $cached ); - } - - /** - * With no arguments the command prints usage to stderr and exits non-zero. - * - * @return void - */ - public function test_reports_usage_and_fails_without_arguments() { - $result = $this->run_bin( [] ); - - $this->assertSame( 1, $result['exit'] ); - $this->assertStringContainsString( 'Usage:', $result['stderr'] ); - } - - /** - * A missing directory fails that directory (non-zero exit, error on stderr) but the command - * still processes the directories that are valid. - * - * @return void - */ - public function test_missing_directory_fails_but_valid_directories_still_cache() { - $good = $this->example_copy( 'plugin-inc' ); - $missing = sys_get_temp_dir() . '/tenup_bin_missing_' . uniqid( '', true ); - - $result = $this->run_bin( [ $missing, $good ] ); - - $this->assertSame( 1, $result['exit'] ); - $this->assertStringContainsString( $missing, $result['stderr'] ); - $this->assertStringContainsString( 'Failed to generate cache', $result['stderr'] ); - - // The valid directory was still cached despite the earlier failure. - $this->assertFileExists( $this->cache_file_path( $good ) ); - } - - /** - * Several directories can be cached in a single invocation. - * - * @return void - */ - public function test_caches_multiple_directories() { - $first = $this->example_copy( 'plugin-inc' ); - $second = $this->example_copy( 'second-inc' ); - - $result = $this->run_bin( [ $first, $second ] ); - - $this->assertSame( 0, $result['exit'], $result['stderr'] ); - $this->assertFileExists( $this->cache_file_path( $first ) ); - $this->assertFileExists( $this->cache_file_path( $second ) ); - - $cached = require $this->cache_file_path( $second ); - $this->assertContains( 'TenupFrameworkExamples\\Widgets\\Card', $cached ); - } - - /** - * Run the bin script with the given arguments, returning its stdout, stderr and exit code. - * - * @param array $args The arguments to pass after the script name. - * - * @return array{stdout: string, stderr: string, exit: int} - */ - private function run_bin( array $args ): array { - $script = dirname( __DIR__, 2 ) . '/bin/tenup-framework-generate-class-cache'; - $command = array_map( 'escapeshellarg', array_merge( [ PHP_BINARY, $script ], $args ) ); - - $descriptors = [ - 1 => [ 'pipe', 'w' ], - 2 => [ 'pipe', 'w' ], - ]; - - $process = proc_open( implode( ' ', $command ), $descriptors, $pipes ); - $this->assertIsResource( $process ); - - $stdout = (string) stream_get_contents( $pipes[1] ); - $stderr = (string) stream_get_contents( $pipes[2] ); - fclose( $pipes[1] ); - fclose( $pipes[2] ); - - $exit = proc_close( $process ); - - return [ - 'stdout' => $stdout, - 'stderr' => $stderr, - 'exit' => $exit, - ]; - } - - /** - * Copy an example directory into a fresh temp directory so the command can write a cache - * into it without touching the committed examples. - * - * @param string $name The example directory name under tests/examples. - * - * @return string The path to the temp copy. - */ - private function example_copy( string $name ): string { - $source = __DIR__ . '/../examples/' . $name; - $target = sys_get_temp_dir() . '/tenup_bin_' . $name . '_' . uniqid( '', true ); - - $this->copy_dir( $source, $target ); - $this->temp_dirs[] = $target; - - return $target; - } - - /** - * The absolute path to the cache file the command writes for a directory. - * - * @param string $dir The discovery directory. - * - * @return string - */ - private function cache_file_path( string $dir ): string { - return $dir . '/' . ModuleInitialization::CACHE_DIR_NAME . '/' . ModuleInitialization::CACHE_FILENAME; - } - - /** - * Recursively copy a directory. - * - * @param string $source The source directory. - * @param string $target The target directory. - * - * @return void - */ - private function copy_dir( string $source, string $target ): void { - mkdir( $target, 0777, true ); - - $items = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator( $source, \FilesystemIterator::SKIP_DOTS ), - \RecursiveIteratorIterator::SELF_FIRST - ); - - foreach ( $items as $item ) { - $destination = $target . '/' . $items->getSubPathname(); - if ( $item->isDir() ) { - mkdir( $destination, 0777, true ); - } else { - copy( $item->getPathname(), $destination ); - } - } - } - - /** - * Recursively remove a directory. - * - * @param string $dir The directory to remove. - * - * @return void - */ - private function remove_dir( string $dir ): void { - if ( ! is_dir( $dir ) ) { - return; - } - - $items = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator( $dir, \FilesystemIterator::SKIP_DOTS ), - \RecursiveIteratorIterator::CHILD_FIRST - ); - - foreach ( $items as $item ) { - if ( $item->isDir() ) { - rmdir( $item->getPathname() ); - } else { - unlink( $item->getPathname() ); - } - } - - rmdir( $dir ); - } -} diff --git a/tests/BlockRegistrarTest.php b/tests/BlockRegistrarTest.php deleted file mode 100644 index 80cd0a0..0000000 --- a/tests/BlockRegistrarTest.php +++ /dev/null @@ -1,346 +0,0 @@ -assertInstanceOf( \TenupFramework\ModuleInterface::class, $block_registrar ); - $this->assertInstanceOf( \TenupFramework\BlockRegistrar::class, $block_registrar ); - } - - /** - * Test that can_register returns true by default. - * - * @return void - */ - public function test_can_register_returns_true() { - $block_registrar = new TestBlockRegistrar(); - - $this->assertTrue( $block_registrar->can_register() ); - } - - /** - * Test that get_blocks_directory is abstract and must be implemented. - * - * @return void - */ - public function test_get_blocks_directory_is_abstract() { - $this->expectException( \Error::class ); - new \TenupFramework\BlockRegistrar(); - } - - /** - * Test that register method calls parent register and adds hooks. - * - * @return void - */ - public function test_register_method_adds_hooks() { - // Create a concrete test class - $block_registrar = new TestBlockRegistrar(); - - // This should not throw an exception - $block_registrar->register(); - $this->assertTrue( true ); // If we get here, register() worked - } - - /** - * Test that get_blocks_directory returns an array. - * - * @return void - */ - public function test_get_blocks_directory_returns_array() { - $block_registrar = new TestBlockRegistrar(); - $directories = $block_registrar->get_blocks_directory(); - - $this->assertIsArray( $directories ); - $this->assertCount( 1, $directories ); - $this->assertEquals( '/test/blocks/', $directories[0] ); - } - - /** - * Test that multiple directories can be returned. - * - * @return void - */ - public function test_multiple_directories_support() { - $block_registrar = new TestMultiDirectoryBlockRegistrar(); - $directories = $block_registrar->get_blocks_directory(); - - $this->assertIsArray( $directories ); - $this->assertCount( 3, $directories ); - $this->assertEquals( '/test/blocks/', $directories[0] ); - $this->assertEquals( '/test/custom-blocks/', $directories[1] ); - $this->assertEquals( '/test/vendor-blocks/', $directories[2] ); - } - - /** - * Test that empty directory array is handled correctly. - * - * @return void - */ - public function test_empty_directory_array_support() { - $block_registrar = new TestEmptyDirectoryBlockRegistrar(); - $directories = $block_registrar->get_blocks_directory(); - - $this->assertIsArray( $directories ); - $this->assertEmpty( $directories ); - } - - /** - * Test register_blocks with non-existent directories. - * - * @return void - */ - public function test_register_blocks_with_non_existent_directories() { - $block_registrar = new TestBlockRegistrar(); - - // This test verifies the method exists and can be called - // In a real WordPress environment, it would handle non-existent directories gracefully - $this->assertTrue( method_exists( $block_registrar, 'register_blocks' ) ); - } - - /** - * Test register_blocks with empty directory array. - * - * @return void - */ - public function test_register_blocks_with_empty_directory_array() { - $block_registrar = new TestEmptyDirectoryBlockRegistrar(); - - // This test verifies the method exists and can be called - // In a real WordPress environment, it would handle empty directories gracefully - $this->assertTrue( method_exists( $block_registrar, 'register_blocks' ) ); - } - - /** - * Test get_block_options without markup.php file. - * - * @return void - */ - public function test_get_block_options_without_markup() { - $block_registrar = new TestBlockRegistrar(); - - // Use reflection to access protected method - $reflection = new \ReflectionClass( $block_registrar ); - $method = $reflection->getMethod( 'get_block_options' ); - $method->setAccessible( true ); - - $options = $method->invoke( $block_registrar, '/test/block-without-markup/' ); - - $this->assertIsArray( $options ); - $this->assertEmpty( $options ); - } - - /** - * Test get_block_options with markup.php file. - * - * @return void - */ - public function test_get_block_options_with_markup() { - $block_registrar = new TestBlockRegistrar(); - - // Use reflection to access protected method - $reflection = new \ReflectionClass( $block_registrar ); - $method = $reflection->getMethod( 'get_block_options' ); - $method->setAccessible( true ); - - // Mock file_exists to return true for markup.php - $original_file_exists = 'file_exists'; - if ( function_exists( 'file_exists' ) ) { - // In a real test environment, you'd mock this properly - // For now, we'll test the structure - $options = $method->invoke( $block_registrar, '/test/block-with-markup/' ); - - // The method should return an array (empty if file doesn't exist) - $this->assertIsArray( $options ); - } - } - - /** - * Test register_allowed_block_types method. - * - * @return void - */ - public function test_register_allowed_block_types() { - $block_registrar = new TestBlockRegistrar(); - - // Use reflection to access protected method - $reflection = new \ReflectionClass( $block_registrar ); - $method = $reflection->getMethod( 'register_allowed_block_types' ); - $method->setAccessible( true ); - - $block_names = [ 'test/block1', 'test/block2' ]; - - // This should not throw an exception - $method->invoke( $block_registrar, $block_names ); - $this->assertTrue( true ); // If we get here, no exception was thrown - } - - /** - * Test WordPress hook registration. - * - * @return void - */ - public function test_wordpress_hook_registration() { - $block_registrar = new TestBlockRegistrar(); - - // This should not throw an exception when registering hooks - $block_registrar->register(); - $this->assertTrue( true ); // If we get here, register() worked without throwing - } - - /** - * Test that multiple BlockRegistrar instances don't conflict. - * - * @return void - */ - public function test_multiple_instances_no_conflict() { - // Create two different instances - $theme_blocks = new TestBlockRegistrar(); - $plugin_blocks = new TestMultiDirectoryBlockRegistrar(); - - // Test that both instances can be created without conflicts - $this->assertInstanceOf( \TenupFramework\BlockRegistrar::class, $theme_blocks ); - $this->assertInstanceOf( \TenupFramework\BlockRegistrar::class, $plugin_blocks ); - $this->assertNotSame( $theme_blocks, $plugin_blocks ); - } - - /** - * Test static block name tracking. - * - * @return void - */ - public function test_static_block_name_tracking() { - // Use reflection to access static properties - $reflection = new \ReflectionClass( \TenupFramework\BlockRegistrar::class ); - - // Test that static properties exist - $this->assertTrue( $reflection->hasProperty( 'registered_block_names' ) ); - $this->assertTrue( $reflection->hasProperty( 'filter_registered' ) ); - $this->assertTrue( $reflection->hasProperty( 'block_sources' ) ); - - // Test that the static filter method exists - $this->assertTrue( $reflection->hasMethod( 'filter_allowed_block_types' ) ); - } - - /** - * Test block conflict detection methods. - * - * @return void - */ - public function test_block_conflict_detection() { - // Test conflict detection methods exist - $this->assertTrue( method_exists( \TenupFramework\BlockRegistrar::class, 'has_block_conflict' ) ); - $this->assertTrue( method_exists( \TenupFramework\BlockRegistrar::class, 'get_block_source' ) ); - $this->assertTrue( method_exists( \TenupFramework\BlockRegistrar::class, 'get_all_block_sources' ) ); - - // Test initial state - $this->assertFalse( \TenupFramework\BlockRegistrar::has_block_conflict( 'test/block' ) ); - $this->assertNull( \TenupFramework\BlockRegistrar::get_block_source( 'test/block' ) ); - $this->assertIsArray( \TenupFramework\BlockRegistrar::get_all_block_sources() ); - } - - /** - * Test block source tracking. - * - * @return void - */ - public function test_block_source_tracking() { - // Manually add a block source for testing - \TenupFramework\BlockRegistrar::$block_sources['test/block'] = 'TestClass'; - - // Test conflict detection - $this->assertTrue( \TenupFramework\BlockRegistrar::has_block_conflict( 'test/block' ) ); - $this->assertEquals( 'TestClass', \TenupFramework\BlockRegistrar::get_block_source( 'test/block' ) ); - - // Test getting all sources - $sources = \TenupFramework\BlockRegistrar::get_all_block_sources(); - $this->assertArrayHasKey( 'test/block', $sources ); - $this->assertEquals( 'TestClass', $sources['test/block'] ); - - // Clean up - unset( \TenupFramework\BlockRegistrar::$block_sources['test/block'] ); - } - - /** - * Test path validation edge cases. - * - * @return void - */ - public function test_path_validation_edge_cases() { - $block_registrar = new TestBlockRegistrar(); - - // Use reflection to access protected method - $reflection = new \ReflectionClass( $block_registrar ); - $method = $reflection->getMethod( 'validate_directory_path' ); - $method->setAccessible( true ); - - // Test invalid paths - $this->assertFalse( $method->invoke( $block_registrar, '' ) ); - $this->assertFalse( $method->invoke( $block_registrar, '../malicious' ) ); - $this->assertFalse( $method->invoke( $block_registrar, './relative' ) ); - $this->assertFalse( $method->invoke( $block_registrar, str_repeat( 'a', 1001 ) ) ); - - // Test valid paths - $this->assertEquals( '/valid/path/', $method->invoke( $block_registrar, '/valid/path' ) ); - $this->assertEquals( '/valid/path/', $method->invoke( $block_registrar, '/valid/path/' ) ); - $this->assertEquals( '/valid/path/', $method->invoke( $block_registrar, '\\valid\\path' ) ); - } - - /** - * Test block.json validation edge cases. - * - * @return void - */ - public function test_block_json_validation_edge_cases() { - $block_registrar = new TestBlockRegistrar(); - - // Use reflection to access protected method - $reflection = new \ReflectionClass( $block_registrar ); - $method = $reflection->getMethod( 'validate_block_json' ); - $method->setAccessible( true ); - - // Test invalid file paths - $this->assertFalse( $method->invoke( $block_registrar, '/non/existent/file.json' ) ); - - // Test invalid JSON (this would require creating actual files in tests) - // For now, just test that the method exists and handles errors - $this->assertTrue( method_exists( $block_registrar, 'validate_block_json' ) ); - } - - /** - * Test WordPress availability check. - * - * @return void - */ - public function test_wordpress_availability_check() { - $block_registrar = new TestBlockRegistrar(); - - // Test that the method exists and can be called - // In a real WordPress environment, it would check for function availability - $this->assertTrue( method_exists( $block_registrar, 'register_blocks' ) ); - } -} diff --git a/tests/Cache/ReadOnlyFileDiscoverCacheDriverTest.php b/tests/Cache/ReadOnlyFileDiscoverCacheDriverTest.php deleted file mode 100644 index 23fd831..0000000 --- a/tests/Cache/ReadOnlyFileDiscoverCacheDriverTest.php +++ /dev/null @@ -1,118 +0,0 @@ -dir = sys_get_temp_dir() . '/tenup_ro_driver_' . uniqid( '', true ); - mkdir( $this->dir ); - - return $this->dir; - } - - /** - * Remove the temporary directory after a test. - * - * @return void - */ - protected function tearDown(): void { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid - if ( '' !== $this->dir && is_dir( $this->dir ) ) { - $files = glob( $this->dir . '/*' ); - if ( false !== $files ) { - array_map( 'unlink', $files ); - } - rmdir( $this->dir ); - } - $this->dir = ''; - - parent::tearDown(); - } - - /** - * The constructor must not create the cache directory: the runtime never writes. - * - * @return void - */ - public function test_constructor_does_not_create_directory() { - $missing = sys_get_temp_dir() . '/tenup_ro_missing_' . uniqid( '', true ); - - new ReadOnlyFileDiscoverCacheDriver( $missing, false, 'cache.php' ); - - $this->assertDirectoryDoesNotExist( $missing ); - } - - /** - * put() is a no-op: nothing is written to disk. - * - * @return void - */ - public function test_put_writes_nothing() { - $dir = $this->make_dir(); - $driver = new ReadOnlyFileDiscoverCacheDriver( $dir, false, 'cache.php' ); - - $driver->put( 'id', [ 'Foo\\Bar' ] ); - - $this->assertFalse( $driver->has( 'id' ) ); - $this->assertFileDoesNotExist( $dir . '/cache.php' ); - } - - /** - * forget() is a no-op: an existing cache file is left untouched. - * - * @return void - */ - public function test_forget_deletes_nothing() { - $dir = $this->make_dir(); - file_put_contents( $dir . '/cache.php', 'forget( 'id' ); - - $this->assertFileExists( $dir . '/cache.php' ); - } - - /** - * has()/get() read an existing cache file produced with the same settings the - * build-time generator uses (serialize = false, an explicit filename). - * - * @return void - */ - public function test_has_and_get_read_an_existing_file() { - $dir = $this->make_dir(); - file_put_contents( $dir . '/cache.php', "assertTrue( $driver->has( 'id' ) ); - $this->assertSame( [ 'Foo\\Bar' ], $driver->get( 'id' ) ); - } -} diff --git a/tests/Core/EmojiTest.php b/tests/Core/EmojiTest.php deleted file mode 100644 index f9250f1..0000000 --- a/tests/Core/EmojiTest.php +++ /dev/null @@ -1,226 +0,0 @@ -assertInstanceOf( \TenupFramework\ModuleInterface::class, $emoji ); - } - - /** - * Test that Emoji can be registered. - * - * @return void - */ - public function test_can_register() { - $emoji = new Emoji(); - - $this->assertTrue( $emoji->can_register() ); - } - - /** - * Test that Emoji has correct load order. - * - * @return void - */ - public function test_load_order() { - $emoji = new Emoji(); - - $this->assertEquals( 5, $emoji->load_order() ); - } - - /** - * Test that register method can be called without errors. - * - * @return void - */ - public function test_register_can_be_called() { - $emoji = new Emoji(); - - // Mock WordPress functions to prevent actual function calls - \Brain\Monkey\Functions\when( 'remove_action' )->justReturn( true ); - \Brain\Monkey\Functions\when( 'remove_filter' )->justReturn( true ); - \Brain\Monkey\Functions\when( 'add_filter' )->justReturn( true ); - - // This should not throw any exceptions - $emoji->register(); - - // If we get here, the method executed successfully - $this->assertTrue( true ); - } - - /** - * Test that register method exists and is callable. - * - * @return void - */ - public function test_register_method_exists() { - $emoji = new Emoji(); - - $this->assertTrue( method_exists( $emoji, 'register' ) ); - $this->assertTrue( is_callable( [ $emoji, 'register' ] ) ); - } - - /** - * Test that Emoji has the expected WordPress function calls in register method. - * - * @return void - */ - public function test_register_method_contains_expected_calls() { - $reflection = new \ReflectionClass( Emoji::class ); - $method = $reflection->getMethod( 'register' ); - $filename = $method->getFileName(); - $start_line = $method->getStartLine(); - $end_line = $method->getEndLine(); - - // Read the method source code - $lines = file( $filename ); - $method_source = implode( '', array_slice( $lines, $start_line - 1, $end_line - $start_line + 1 ) ); - - // Verify the method contains the expected remove_action calls - $this->assertStringContainsString( "remove_action( 'wp_head', 'print_emoji_detection_script', 7 )", $method_source ); - $this->assertStringContainsString( "remove_action( 'admin_print_scripts', 'print_emoji_detection_script' )", $method_source ); - $this->assertStringContainsString( "remove_action( 'wp_print_styles', 'print_emoji_styles' )", $method_source ); - $this->assertStringContainsString( "remove_action( 'admin_print_styles', 'print_emoji_styles' )", $method_source ); - - // Verify the method contains the expected remove_filter calls - $this->assertStringContainsString( "remove_filter( 'the_content_feed', 'wp_staticize_emoji' )", $method_source ); - $this->assertStringContainsString( "remove_filter( 'comment_text_rss', 'wp_staticize_emoji' )", $method_source ); - $this->assertStringContainsString( "remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' )", $method_source ); - - // Verify the method contains the expected add_filter calls - $this->assertStringContainsString( "add_filter( 'tiny_mce_plugins', [ \$this, 'disable_emojis_tinymce' ] )", $method_source ); - $this->assertStringContainsString( "add_filter( 'wp_resource_hints', [ \$this, 'disable_emoji_dns_prefetch' ], 10, 2 )", $method_source ); - } - - /** - * Test that disable_emojis_tinymce method exists and is callable. - * - * @return void - */ - public function test_disable_emojis_tinymce_method_exists() { - $emoji = new Emoji(); - - $this->assertTrue( method_exists( $emoji, 'disable_emojis_tinymce' ) ); - $this->assertTrue( is_callable( [ $emoji, 'disable_emojis_tinymce' ] ) ); - } - - /** - * Test that disable_emoji_dns_prefetch method exists and is callable. - * - * @return void - */ - public function test_disable_emoji_dns_prefetch_method_exists() { - $emoji = new Emoji(); - - $this->assertTrue( method_exists( $emoji, 'disable_emoji_dns_prefetch' ) ); - $this->assertTrue( is_callable( [ $emoji, 'disable_emoji_dns_prefetch' ] ) ); - } - - /** - * Test disable_emojis_tinymce method functionality. - * - * @return void - */ - public function test_disable_emojis_tinymce_functionality() { - $emoji = new Emoji(); - - // Test with wpemoji plugin present - $plugins_with_emoji = [ 'wordpress', 'wpemoji', 'media' ]; - $result = $emoji->disable_emojis_tinymce( $plugins_with_emoji ); - - $this->assertNotContains( 'wpemoji', $result ); - $this->assertContains( 'wordpress', $result ); - $this->assertContains( 'media', $result ); - - // Test with wpemoji plugin not present - $plugins_without_emoji = [ 'wordpress', 'media' ]; - $result = $emoji->disable_emojis_tinymce( $plugins_without_emoji ); - - $this->assertEquals( $plugins_without_emoji, $result ); - } - - /** - * Test disable_emoji_dns_prefetch method functionality. - * - * @return void - */ - public function test_disable_emoji_dns_prefetch_functionality() { - $emoji = new Emoji(); - - // Mock apply_filters for emoji_svg_url - \Brain\Monkey\Filters\expectApplied( 'emoji_svg_url' ) - ->once() - ->andReturn( 'https://s.w.org/images/core/emoji/2/svg/' ); - - $urls = [ - 'https://fonts.googleapis.com', - 'https://s.w.org/images/core/emoji/2/svg/', - 'https://example.com', - ]; - - $result = $emoji->disable_emoji_dns_prefetch( $urls, 'dns-prefetch' ); - - $this->assertNotContains( 'https://s.w.org/images/core/emoji/2/svg/', $result ); - $this->assertContains( 'https://fonts.googleapis.com', $result ); - $this->assertContains( 'https://example.com', $result ); - - // Test with different relation type - $result = $emoji->disable_emoji_dns_prefetch( $urls, 'preconnect' ); - $this->assertEquals( $urls, $result ); - } - - /** - * Test that Emoji can be instantiated multiple times. - * - * @return void - */ - public function test_multiple_instances() { - $emoji_1 = new Emoji(); - $emoji_2 = new Emoji(); - - $this->assertInstanceOf( Emoji::class, $emoji_1 ); - $this->assertInstanceOf( Emoji::class, $emoji_2 ); - $this->assertNotSame( $emoji_1, $emoji_2 ); - } - - /** - * Test that Emoji uses the Module trait. - * - * @return void - */ - public function test_uses_module_trait() { - $emoji = new Emoji(); - - // Check that the class has the methods from the Module trait - $this->assertTrue( method_exists( $emoji, 'load_order' ) ); - $this->assertTrue( method_exists( $emoji, 'can_register' ) ); - $this->assertTrue( method_exists( $emoji, 'register' ) ); - } -} diff --git a/tests/Core/HeadOverridesTest.php b/tests/Core/HeadOverridesTest.php deleted file mode 100644 index b15d940..0000000 --- a/tests/Core/HeadOverridesTest.php +++ /dev/null @@ -1,137 +0,0 @@ -assertInstanceOf( \TenupFramework\ModuleInterface::class, $head_overrides ); - } - - /** - * Test that HeadOverrides can be registered. - * - * @return void - */ - public function test_can_register() { - $head_overrides = new HeadOverrides(); - - $this->assertTrue( $head_overrides->can_register() ); - } - - /** - * Test that HeadOverrides has correct load order. - * - * @return void - */ - public function test_load_order() { - $head_overrides = new HeadOverrides(); - - $this->assertEquals( 5, $head_overrides->load_order() ); - } - - /** - * Test that register method can be called without errors. - * - * @return void - */ - public function test_register_can_be_called() { - $head_overrides = new HeadOverrides(); - - // Mock remove_action to prevent actual WordPress function calls - \Brain\Monkey\Functions\when( 'remove_action' )->justReturn( true ); - - // This should not throw any exceptions - $head_overrides->register(); - - // If we get here, the method executed successfully - $this->assertTrue( true ); - } - - /** - * Test that register method exists and is callable. - * - * @return void - */ - public function test_register_method_exists() { - $head_overrides = new HeadOverrides(); - - $this->assertTrue( method_exists( $head_overrides, 'register' ) ); - $this->assertTrue( is_callable( [ $head_overrides, 'register' ] ) ); - } - - /** - * Test that HeadOverrides has the expected WordPress function calls in register method. - * - * @return void - */ - public function test_register_method_contains_expected_calls() { - $reflection = new \ReflectionClass( HeadOverrides::class ); - $method = $reflection->getMethod( 'register' ); - $filename = $method->getFileName(); - $start_line = $method->getStartLine(); - $end_line = $method->getEndLine(); - - // Read the method source code - $lines = file( $filename ); - $method_source = implode( '', array_slice( $lines, $start_line - 1, $end_line - $start_line + 1 ) ); - - // Verify the method contains the expected remove_action calls - $this->assertStringContainsString( "remove_action( 'wp_head', 'wp_generator' )", $method_source ); - $this->assertStringContainsString( "remove_action( 'wp_head', 'wlwmanifest_link' )", $method_source ); - $this->assertStringContainsString( "remove_action( 'wp_head', 'rsd_link' )", $method_source ); - } - - /** - * Test that HeadOverrides can be instantiated multiple times. - * - * @return void - */ - public function test_multiple_instances() { - $head_overrides_1 = new HeadOverrides(); - $head_overrides_2 = new HeadOverrides(); - - $this->assertInstanceOf( HeadOverrides::class, $head_overrides_1 ); - $this->assertInstanceOf( HeadOverrides::class, $head_overrides_2 ); - $this->assertNotSame( $head_overrides_1, $head_overrides_2 ); - } - - /** - * Test that HeadOverrides uses the Module trait. - * - * @return void - */ - public function test_uses_module_trait() { - $head_overrides = new HeadOverrides(); - - // Check that the class has the methods from the Module trait - $this->assertTrue( method_exists( $head_overrides, 'load_order' ) ); - $this->assertTrue( method_exists( $head_overrides, 'can_register' ) ); - $this->assertTrue( method_exists( $head_overrides, 'register' ) ); - } -} diff --git a/tests/Debug/LoaderDebugTest.php b/tests/Debug/LoaderDebugTest.php deleted file mode 100644 index 6eab4fd..0000000 --- a/tests/Debug/LoaderDebugTest.php +++ /dev/null @@ -1,522 +0,0 @@ - - */ - private function sample_record( string $directory = '/srv/site/wp-content/plugins/demo/inc' ): array { - return [ - 'directory' => $directory, - 'cache_file' => $directory . '/class-loader-cache/class-loader-cache-v2.php', - 'cache_exists' => false, - 'cache_used' => false, - 'cache_disabled' => false, - 'classes' => [ 'TenupTmp\\Widget' ], - 'version' => '1.3.0', - 'reference' => 'abcdef1234567890', - 'discovery_seconds' => 0.0123, - 'lookup_seconds' => 0.0456, - ]; - } - - /** - * Stub the functions record() needs, with the tooling enabled. - * - * @return void - */ - private function stub_enabled() { - when( 'add_action' )->justReturn( true ); - when( 'add_filter' )->justReturn( true ); - when( 'apply_filters' )->returnArg( 2 ); - } - - /** - * is_enabled() is false when the enable filter returns false. - * - * @return void - */ - public function test_is_enabled_false_when_filter_disables() { - when( 'add_action' )->justReturn( true ); - when( 'apply_filters' )->justReturn( false ); - - $this->assertFalse( LoaderDebug::is_enabled() ); - } - - /** - * is_enabled() is false when the disable constant is set. - * - * @runInSeparateProcess - * @preserveGlobalState disabled - * - * @return void - */ - public function test_is_enabled_false_when_constant_defined() { - when( 'add_action' )->justReturn( true ); - when( 'apply_filters' )->returnArg( 2 ); - - define( 'TENUP_FRAMEWORK_DISABLE_LOADER_DEBUG', true ); - - $this->assertFalse( LoaderDebug::is_enabled() ); - } - - /** - * record() stores the record when enabled. - * - * @return void - */ - public function test_record_stores_when_enabled() { - $this->stub_enabled(); - - LoaderDebug::record( $this->sample_record() ); - - $this->assertCount( 1, LoaderDebug::get_loaders() ); - } - - /** - * record() accumulates multiple records. - * - * @return void - */ - public function test_records_accumulate() { - $this->stub_enabled(); - - LoaderDebug::record( $this->sample_record( '/a/inc' ) ); - LoaderDebug::record( $this->sample_record( '/b/inc' ) ); - - $this->assertCount( 2, LoaderDebug::get_loaders() ); - } - - /** - * record() stores nothing when disabled. - * - * @return void - */ - public function test_record_skips_when_disabled() { - when( 'add_action' )->justReturn( true ); - when( 'apply_filters' )->justReturn( false ); - - LoaderDebug::record( $this->sample_record() ); - - $this->assertSame( [], LoaderDebug::get_loaders() ); - } - - /** - * The callback registered on the aggregation filter merges this copy's records into - * whatever other copies have already contributed. - * - * @return void - */ - public function test_aggregation_filter_merges_records() { - $captured = null; - - when( 'add_action' )->justReturn( true ); - when( 'apply_filters' )->returnArg( 2 ); - when( 'add_filter' )->alias( - static function ( $hook, $callback ) use ( &$captured ) { - if ( LoaderDebug::FILTER === $hook ) { - $captured = $callback; - } - return true; - } - ); - - LoaderDebug::record( $this->sample_record( '/b/inc' ) ); - - $this->assertIsCallable( $captured ); - - // A record contributed by another copy should be preserved alongside ours. - $existing = [ [ 'directory' => '/a/inc' ] ]; - $merged = $captured( $existing ); - - $this->assertCount( 2, $merged ); - $this->assertSame( '/a/inc', $merged[0]['directory'] ); - $this->assertSame( '/b/inc', $merged[1]['directory'] ); - } - - /** - * render_page() lists each loader and the classes it loaded. - * - * @return void - */ - public function test_render_page_lists_loaders_and_classes() { - $this->stub_render_environment(); - - LoaderDebug::record( $this->sample_record() ); - - $output = $this->capture_render(); - - $this->assertStringContainsString( 'WP Framework Loaders', $output ); - $this->assertStringContainsString( '/srv/site/wp-content/plugins/demo/inc', $output ); - $this->assertStringContainsString( 'TenupTmp\\Widget', $output ); - $this->assertStringContainsString( 'Check this cache for staleness', $output ); - - // The recorded discovery/lookup timings are surfaced on the page. - $this->assertStringContainsString( 'Discovery time', $output ); - $this->assertStringContainsString( 'Class lookup time', $output ); - $this->assertStringContainsString( '12.30 ms', $output ); // 0.0123s discovery. - $this->assertStringContainsString( '45.60 ms', $output ); // 0.0456s lookup. - } - - /** - * owner_label() derives a plugin name when the directory sits under the plugins root. - * - * @runInSeparateProcess - * @preserveGlobalState disabled - * - * @return void - */ - public function test_owner_label_derives_plugin_name() { - define( 'WP_PLUGIN_DIR', '/srv/site/wp-content/plugins' ); - - $method = ( new \ReflectionClass( LoaderDebug::class ) )->getMethod( 'owner_label' ); - $method->setAccessible( true ); - - $this->assertSame( - 'Plugin: demo', - $method->invoke( null, '/srv/site/wp-content/plugins/demo/inc' ) - ); - } - - /** - * render_page() reports drift when a staleness check is requested with a valid nonce. - * - * @return void - */ - public function test_render_page_reports_staleness_drift() { - $this->stub_render_environment(); - when( 'wp_verify_nonce' )->justReturn( true ); - - $dir = $this->make_temp_class_dir(); - - // The loaded list (in the record) is deliberately out of date versus what is on disk. - $record = $this->sample_record( $dir ); - $record['classes'] = [ 'TenupTmp\\Old' ]; - LoaderDebug::record( $record ); - - $_GET['check'] = md5( $dir ); - $_GET['_wpnonce'] = 'test'; - - $output = $this->capture_render(); - - unset( $_GET['check'], $_GET['_wpnonce'] ); - $this->remove_temp_dir( $dir ); - - $this->assertStringContainsString( 'Stale', $output ); - $this->assertStringContainsString( 'TenupTmp\\Widget', $output ); // On disk, missing from cache. - $this->assertStringContainsString( 'TenupTmp\\Old', $output ); // In cache, gone from disk. - // The drift notice also reports a real, positive live-discovery duration. - $this->assertMatchesRegularExpression( '/Live discovery took \d[\d.,]* (ms|s)\./', $output ); - } - - /** - * render_page() confirms an up-to-date cache and reports how long the live scan took when a - * staleness check is requested and the loaded list matches disk. - * - * @return void - */ - public function test_render_page_reports_up_to_date_and_timing() { - $this->stub_render_environment(); - when( 'wp_verify_nonce' )->justReturn( true ); - - $dir = $this->make_temp_class_dir(); - - // The loaded list matches what is actually on disk (the single Widget class). - $record = $this->sample_record( $dir ); - $record['classes'] = [ 'TenupTmp\\Widget' ]; - LoaderDebug::record( $record ); - - $_GET['check'] = md5( $dir ); - $_GET['_wpnonce'] = 'test'; - - $output = $this->capture_render(); - - unset( $_GET['check'], $_GET['_wpnonce'] ); - $this->remove_temp_dir( $dir ); - - $this->assertStringContainsString( 'Up to date', $output ); - // Require a real, positive duration — this must NOT match the "Live discovery took —." - // placeholder that format_duration() emits for a non-positive/absent value. - $this->assertMatchesRegularExpression( '/Live discovery took \d[\d.,]* (ms|s)\./', $output ); - } - - /** - * cache_state() maps each combination of the record flags to the expected severity and badge. - * - * @dataProvider cache_state_provider - * - * @param array $flags The cache_* flags to set on the record. - * @param string $expected_sev The expected severity. - * @param string $expected_snippet A substring expected in the badge. - * - * @return void - */ - public function test_cache_state_resolves_expected_states( array $flags, string $expected_sev, string $expected_snippet ) { - $state = $this->invoke_protected( 'cache_state', [ array_merge( $this->sample_record(), $flags ) ] ); - - $this->assertSame( $expected_sev, $state['severity'] ); - $this->assertStringContainsString( $expected_snippet, $state['badge'] ); - } - - /** - * Data for test_cache_state_resolves_expected_states. - * - * @return array, 1: string, 2: string}> - */ - public function cache_state_provider(): array { - return [ - 'disabled' => [ [ 'cache_disabled' => true ], 'warn', 'disabled' ], - 'uncached' => [ - [ - 'cache_exists' => false, - 'cache_used' => false, - ], - 'warn', - 'Uncached', - ], - 'present but unused' => [ - [ - 'cache_exists' => true, - 'cache_used' => false, - ], - 'error', - 'not used', - ], - 'failed to load' => [ - [ - 'cache_exists' => true, - 'cache_used' => false, - 'cache_failed' => true, - ], - 'error', - 'failed to load', - ], - 'in use' => [ - [ - 'cache_exists' => true, - 'cache_used' => true, - ], - 'ok', - 'in use', - ], - ]; - } - - /** - * legacy_files() reports files in the cache directory that are not the current cache file, - * and nothing when the directory is clean or absent. - * - * @return void - */ - public function test_legacy_files_detects_unexpected_files() { - $dir = $this->make_temp_class_dir(); - $cache_dir = $dir . '/class-loader-cache'; - mkdir( $cache_dir ); - - $current = $cache_dir . '/class-loader-cache-v2.php'; - file_put_contents( $current, 'assertSame( [], $this->invoke_protected( 'legacy_files', [ $current ] ) ); - - // A leftover file from an older version is reported. - file_put_contents( $cache_dir . '/discoverer-cache-TenupFramework', 'x' ); - $found = $this->invoke_protected( 'legacy_files', [ $current ] ); - $this->assertContains( 'discoverer-cache-TenupFramework', $found ); - $this->assertNotContains( 'class-loader-cache-v2.php', $found ); - - $this->remove_temp_dir( $dir ); - } - - /** - * legacy_files() is empty when the cache directory does not exist. - * - * @return void - */ - public function test_legacy_files_empty_when_directory_absent() { - $missing = sys_get_temp_dir() . '/tenup_missing_' . uniqid( '', true ) . '/class-loader-cache-v2.php'; - - $this->assertSame( [], $this->invoke_protected( 'legacy_files', [ $missing ] ) ); - } - - /** - * format_duration() picks a sensible unit and renders a placeholder for non-positive input. - * - * @dataProvider duration_provider - * - * @param mixed $seconds The duration in seconds. - * @param string $expected The expected rendered string. - * - * @return void - */ - public function test_format_duration( $seconds, string $expected ) { - $this->assertSame( $expected, $this->invoke_protected( 'format_duration', [ $seconds ] ) ); - } - - /** - * Data for test_format_duration. - * - * @return array - */ - public function duration_provider(): array { - return [ - 'zero' => [ 0.0, '—' ], - 'negative' => [ -0.005, '—' ], - 'non-numeric' => [ 'nope', '—' ], - 'not-a-number' => [ NAN, '—' ], - 'infinite' => [ INF, '—' ], - 'sub-milli' => [ 0.0004, '0.400 ms' ], - 'milliseconds' => [ 0.0123, '12.30 ms' ], - 'seconds' => [ 1.5, '1.50 s' ], - ]; - } - - /** - * cache_detail() renders "Built ago · · " with the build time in UTC. - * - * @return void - */ - public function test_cache_detail_shows_size_and_utc_build_time() { - when( 'human_time_diff' )->justReturn( '5 minutes' ); - when( 'size_format' )->alias( static fn( $bytes ) => $bytes . ' B' ); - - $dir = $this->make_temp_class_dir(); - $cache_dir = $dir . '/class-loader-cache'; - mkdir( $cache_dir ); - $cache_file = $cache_dir . '/class-loader-cache-v2.php'; - file_put_contents( $cache_file, 'invoke_protected( 'cache_detail', [ [ 'cache_file' => $cache_file ] ] ); - - $this->assertStringContainsString( 'Built 5 minutes ago', $detail ); - // The absolute build time is the file mtime rendered in UTC as the trailing segment. - $expected_utc = gmdate( 'Y-m-d H:i:s', (int) filemtime( $cache_file ) ) . ' UTC'; - $this->assertStringContainsString( '· ' . $expected_utc, $detail ); - $this->assertMatchesRegularExpression( '/·\s*\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} UTC$/', $detail ); - - $this->remove_temp_dir( $dir ); - } - - /** - * Invoke a protected static method on LoaderDebug via reflection. - * - * @param string $method The method name. - * @param array $args The arguments. - * - * @return mixed - */ - private function invoke_protected( string $method, array $args ) { - $reflection = ( new \ReflectionClass( LoaderDebug::class ) )->getMethod( $method ); - $reflection->setAccessible( true ); - - return $reflection->invokeArgs( null, $args ); - } - - /** - * Stub everything render_page() touches, with the tooling enabled and the current user - * capable. apply_filters returns this copy's records for the aggregation filter. - * - * @return void - */ - private function stub_render_environment() { - when( 'add_action' )->justReturn( true ); - when( 'add_filter' )->justReturn( true ); - when( 'current_user_can' )->justReturn( true ); - when( 'sanitize_text_field' )->returnArg( 1 ); - when( 'wp_unslash' )->returnArg( 1 ); - when( 'admin_url' )->alias( - static function ( $path = '' ) { - return 'http://example.test/wp-admin/' . $path; - } - ); - when( 'add_query_arg' )->alias( - static function ( $args, $url ) { - return $url . '?' . http_build_query( (array) $args ); - } - ); - when( 'wp_nonce_url' )->returnArg( 1 ); - when( 'apply_filters' )->alias( - static function ( $hook, $value = null ) { - if ( LoaderDebug::FILTER === $hook ) { - return LoaderDebug::get_loaders(); - } - return $value; - } - ); - } - - /** - * Capture the output of render_page(). - * - * @return string - */ - private function capture_render(): string { - ob_start(); - LoaderDebug::render_page(); - return (string) ob_get_clean(); - } - - /** - * Create a temporary directory containing a single discoverable class. - * - * @return string The created directory path. - */ - private function make_temp_class_dir(): string { - $dir = sys_get_temp_dir() . '/tenup_loader_debug_' . uniqid( '', true ); - mkdir( $dir ); - file_put_contents( $dir . '/Widget.php', "isDir() ) { - rmdir( $item->getPathname() ); - } else { - unlink( $item->getPathname() ); - } - } - - rmdir( $dir ); - } -} diff --git a/tests/Doubles/ConfigurableBlockRegistrar.php b/tests/Doubles/ConfigurableBlockRegistrar.php new file mode 100644 index 0000000..8dac7b3 --- /dev/null +++ b/tests/Doubles/ConfigurableBlockRegistrar.php @@ -0,0 +1,46 @@ + + */ + public array $directories; + + /** + * Constructor. + * + * @param array $directories The block directories to scan. + */ + public function __construct( array $directories = [] ) { + $this->directories = $directories; + } + + /** + * Get the blocks directory paths. + * + * @return array + */ + public function get_blocks_directory(): array { + return $this->directories; + } +} diff --git a/tests/Doubles/SecondBlockRegistrar.php b/tests/Doubles/SecondBlockRegistrar.php new file mode 100644 index 0000000..e6943b0 --- /dev/null +++ b/tests/Doubles/SecondBlockRegistrar.php @@ -0,0 +1,20 @@ + - */ - public static $registered_taxonomies = []; - - /** - * Registered post types. - * - * @var array - */ - public static $registered_post_types = []; - - /** - * Set up the test. - * - * @return void - */ - protected function setUp(): void { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid - parent::setUp(); - Monkey\setUp(); - - stubs( - [ - 'wp_get_environment_type' => 'local', - // Default to the front end so existing tests don't trigger admin-only debug - // recording; admin tests override this with their own stub. - 'is_admin' => false, - 'sanitize_title' => function ( $title ) { - return str_replace( ' ', '-', strtolower( $title ) ); - }, - 'register_post_type' => function ( $slug, $args ) { - self::$registered_post_types[ $slug ] = $args; - }, - 'register_taxonomy_for_object_type' => '__return_true', - 'register_taxonomy' => function ( $slug, $object_type, $args ) { - self::$registered_taxonomies[ $slug ] = $args; - }, - ] - ); - - stubEscapeFunctions(); - stubTranslationFunctions(); - - $this->reset_module_initialization(); - $this->reset_loader_debug(); - } - - /** - * Reset the ModuleInitialization singleton so its accumulated `$classes` do not leak between - * tests. The suite is not process-isolated (the trait-level annotation does not take effect), - * so without this a class registered in one test would be seen as "already initialized" in a - * later one. - * - * @return void - */ - protected function reset_module_initialization(): void { - if ( ! class_exists( \TenupFramework\ModuleInitialization::class ) ) { - return; - } - - $instance = ( new \ReflectionClass( \TenupFramework\ModuleInitialization::class ) )->getProperty( 'instance' ); - $instance->setAccessible( true ); - $instance->setValue( null, null ); - } - - /** - * Reset the static state of the LoaderDebug registry so each test starts clean, - * independent of test execution order or process isolation. - * - * @return void - */ - protected function reset_loader_debug(): void { - if ( ! class_exists( \TenupFramework\Debug\LoaderDebug::class ) ) { - return; - } - - $reflection = new \ReflectionClass( \TenupFramework\Debug\LoaderDebug::class ); - - $loaders = $reflection->getProperty( 'loaders' ); - $loaders->setAccessible( true ); - $loaders->setValue( null, [] ); - - $booted = $reflection->getProperty( 'booted' ); - $booted->setAccessible( true ); - $booted->setValue( null, false ); - - unset( $GLOBALS['tenup_framework_debug_page_registered'] ); - } - - /** - * Tear down the test. - * - * @return void - */ - protected function tearDown(): void { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid - self::$registered_taxonomies = []; - self::$registered_post_types = []; - - Monkey\tearDown(); - parent::tearDown(); - } -} diff --git a/tests/Helpers.php b/tests/Helpers.php new file mode 100644 index 0000000..90d7f48 --- /dev/null +++ b/tests/Helpers.php @@ -0,0 +1,363 @@ +getSubPathname(); + + if ( $item->isDir() ) { + mkdir( $destination, 0777, true ); + continue; + } + + copy( $item->getPathname(), $destination ); + } +} + +/** + * Recursively remove a directory. + * + * @param string $dir The directory to remove. + * + * @return void + */ +function tenup_remove_dir( string $dir ): void { + if ( ! is_dir( $dir ) ) { + return; + } + + $items = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( $dir, FilesystemIterator::SKIP_DOTS ), + RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ( $items as $item ) { + if ( $item->isDir() ) { + rmdir( $item->getPathname() ); + continue; + } + + unlink( $item->getPathname() ); + } + + rmdir( $dir ); +} + +/** + * Copy one of the committed example directories to a fresh temporary directory. + * + * @param string $name The example directory name under tests/examples. + * + * @return string The path to the temporary copy. + */ +function tenup_example_copy( string $name ): string { + $target = sys_get_temp_dir() . '/tenup_example_' . $name . '_' . uniqid( '', true ); + + tenup_copy_dir( dirname( __DIR__ ) . '/tests/examples/' . $name, $target ); + + return $target; +} + +/** + * The absolute path to the class cache file for a discovery directory. + * + * @param string $dir The discovery directory. + * + * @return string + */ +function tenup_cache_file_path( string $dir ): string { + return $dir . '/' . TenupFramework\ModuleInitialization::CACHE_DIR_NAME + . '/' . TenupFramework\ModuleInitialization::CACHE_FILENAME; +} + +/** + * Create a temporary directory containing a single discoverable class. + * + * @return string The created directory path. + */ +function tenup_temp_class_dir(): string { + $dir = sys_get_temp_dir() . '/tenup_framework_test_' . uniqid( '', true ); + + mkdir( $dir ); + file_put_contents( $dir . '/Widget.php', "setValue( null, null ); + + foreach ( [ + 'loaders' => [], + 'booted' => false, + ] as $name => $value ) { + $property = new ReflectionProperty( TenupFramework\Debug\LoaderDebug::class, $name ); + $property->setValue( null, $value ); + } + + // LoaderDebug registers its admin page once per request via this global. + unset( $GLOBALS['tenup_framework_debug_page_registered'] ); +} + +/** + * Reset BlockRegistrar's static registries between tests. + * + * They are process globals shared by every registrar instance, so without this a block + * registered in one test looks like a conflict in the next. + * + * @return void + */ +function tenup_reset_block_registrar(): void { + TenupFramework\BlockRegistrar::$registered_block_names = []; + TenupFramework\BlockRegistrar::$block_sources = []; + TenupFramework\BlockRegistrar::$filter_registered = false; +} + +/** + * The registry of temporary directories awaiting cleanup. + * + * A holder object rather than $this, because Pest's `test()` proxy cannot have array elements + * appended to a property. + * + * @return object{dirs: array} + */ +function tenup_temp_dir_registry(): object { + static $registry = null; + + if ( null === $registry ) { + $registry = new class() { + /** + * Tracked directories. + * + * @var array + */ + public array $dirs = []; + }; + } + + return $registry; +} + +/** + * Remove every tracked temporary directory and empty the registry. + * + * @return void + */ +function tenup_cleanup_temp_dirs(): void { + $registry = tenup_temp_dir_registry(); + + foreach ( $registry->dirs as $dir ) { + tenup_remove_dir( $dir ); + } + + $registry->dirs = []; +} + +/** + * Build a temporary blocks directory containing real block.json files. + * + * The directory is tracked for removal by tenup_cleanup_temp_dirs(). + * + * @param array $blocks Keyed by block name + * (namespace/name). `json` overrides the generated block.json; `markup` writes a + * markup.php alongside it. + * + * @return string The directory path, with a trailing slash (register_blocks() globs "*\/block.json"). + */ +function tenup_make_blocks_dir( array $blocks ): string { + $root = sys_get_temp_dir() . '/tenup_blocks_' . uniqid( '', true ) . '/'; + mkdir( $root, 0777, true ); + + tenup_temp_dir_registry()->dirs[] = $root; + + foreach ( $blocks as $name => $options ) { + $folder = $root . basename( $name ); + mkdir( $folder ); + + $json = $options['json'] ?? (string) json_encode( + [ + 'apiVersion' => 3, + 'name' => $name, + 'title' => ucfirst( basename( $name ) ), + 'category' => 'widgets', + ] + ); + + file_put_contents( $folder . '/block.json', $json ); + + if ( isset( $options['markup'] ) ) { + file_put_contents( $folder . '/markup.php', $options['markup'] ); + } + } + + return $root; +} + +/** + * Invoke a protected static method on LoaderDebug. + * + * Its rendering helpers are deliberately not public API, but they carry the formatting logic + * worth asserting directly rather than only through page output. + * + * @param string $method The method name. + * @param array $args Arguments to pass. + * + * @return mixed + */ +function tenup_invoke_loader_debug( string $method, array $args = [] ): mixed { + return ( new ReflectionMethod( TenupFramework\Debug\LoaderDebug::class, $method ) ) + ->invoke( null, ...$args ); +} + +/** + * A representative loader record, as ModuleInitialization would hand to LoaderDebug::record(). + * + * @param string $directory The loader directory. + * + * @return array + */ +function tenup_sample_loader_record( string $directory = '/srv/site/wp-content/plugins/demo/inc' ): array { + return [ + 'directory' => $directory, + 'cache_file' => $directory . '/class-loader-cache/class-loader-cache-v2.php', + 'cache_exists' => false, + 'cache_used' => false, + 'cache_disabled' => false, + 'cache_failed' => false, + 'classes' => [ 'TenupTmp\\Widget' ], + 'version' => '1.3.0', + 'reference' => 'abcdef1234567890', + 'discovery_seconds' => 0.0123, + 'lookup_seconds' => 0.0456, + ]; +} + +/** + * Create an administrator and switch to them. + * + * @throws RuntimeException If the user could not be created. + * + * @return int The new user ID. + */ +function tenup_acting_as_admin(): int { + $user_id = wp_insert_user( + [ + 'user_login' => 'tenup_admin_' . uniqid( '', false ), + 'user_pass' => wp_generate_password(), + 'role' => 'administrator', + ] + ); + + if ( is_wp_error( $user_id ) ) { + throw new RuntimeException( 'Could not create an administrator: ' . esc_html( $user_id->get_error_message() ) ); + } + + wp_set_current_user( $user_id ); + + return $user_id; +} + +/** + * Run a command, returning its stdout, stderr and exit code. + * + * @param array $command The command and its arguments, unescaped. + * + * @throws RuntimeException If the process could not be started. + * + * @return array{stdout: string, stderr: string, exit: int} + */ +function tenup_run_process( array $command ): array { + $descriptors = [ + 1 => [ 'pipe', 'w' ], + 2 => [ 'pipe', 'w' ], + ]; + + $process = proc_open( + implode( ' ', array_map( 'escapeshellarg', $command ) ), + $descriptors, + $pipes + ); + + if ( ! is_resource( $process ) ) { + throw new RuntimeException( 'Could not start process: ' . esc_html( implode( ' ', $command ) ) ); + } + + $stdout = (string) stream_get_contents( $pipes[1] ); + $stderr = (string) stream_get_contents( $pipes[2] ); + + fclose( $pipes[1] ); + fclose( $pipes[2] ); + + return [ + 'stdout' => $stdout, + 'stderr' => $stderr, + 'exit' => proc_close( $process ), + ]; +} + +/** + * Run the class-cache bin script, returning its stdout, stderr and exit code. + * + * Shelling out (rather than calling ModuleInitialization directly) is deliberate: it covers the + * script's own autoloader resolution, argument handling and exit codes, the way CI invokes it. + * + * @param array $args The arguments to pass after the script name. + * + * @return array{stdout: string, stderr: string, exit: int} + */ +function tenup_run_cache_bin( array $args ): array { + $script = dirname( __DIR__ ) . '/bin/tenup-framework-generate-class-cache'; + + return tenup_run_process( array_merge( [ PHP_BINARY, $script ], $args ) ); +} + +/** + * Run one of the tests/scripts/ helper scripts in a fresh PHP process. + * + * @param string $script The filename under tests/scripts/. + * @param array $args Arguments to pass to the script. + * + * @return array{stdout: string, stderr: string, exit: int} + */ +function tenup_run_script( string $script, array $args = [] ): array { + return tenup_run_process( + array_merge( [ PHP_BINARY, __DIR__ . '/scripts/' . $script ], $args ) + ); +} diff --git a/tests/Integration/BlockRegistrarTest.php b/tests/Integration/BlockRegistrarTest.php new file mode 100644 index 0000000..1ef7edc --- /dev/null +++ b/tests/Integration/BlockRegistrarTest.php @@ -0,0 +1,307 @@ +get_all_registered() ) as $name ) { + if ( str_starts_with( (string) $name, 'tenup-test/' ) ) { + unregister_block_type( (string) $name ); + } + } + + tenup_cleanup_temp_dirs(); + tenup_reset_block_registrar(); + } +); + +it( + 'is a module the loader will pick up', + function (): void { + $registrar = new ConfigurableBlockRegistrar(); + + expect( $registrar )->toBeInstanceOf( ModuleInterface::class ); + expect( $registrar->can_register() )->toBeTrue(); + } +); + +it( + 'defers block registration to the init action', + function (): void { + $registrar = new ConfigurableBlockRegistrar(); + + $registrar->register(); + + expect( has_action( 'init', [ $registrar, 'register_blocks' ] ) )->toBe( 10 ); + } +); + +it( + 'registers a block from a directory containing block.json', + function (): void { + $dir = tenup_make_blocks_dir( [ 'tenup-test/alpha' => [] ] ); + + ( new ConfigurableBlockRegistrar( [ $dir ] ) )->register_blocks(); + + $block = WP_Block_Type_Registry::get_instance()->get_registered( 'tenup-test/alpha' ); + + expect( $block )->not->toBeNull(); + expect( $block->title )->toBe( 'Alpha' ); + } +); + +it( + 'registers blocks found across several directories', + function (): void { + $first = tenup_make_blocks_dir( [ 'tenup-test/alpha' => [] ] ); + $second = tenup_make_blocks_dir( [ 'tenup-test/beta' => [] ] ); + + ( new ConfigurableBlockRegistrar( [ $first, $second ] ) )->register_blocks(); + + expect( WP_Block_Type_Registry::get_instance()->is_registered( 'tenup-test/alpha' ) )->toBeTrue(); + expect( WP_Block_Type_Registry::get_instance()->is_registered( 'tenup-test/beta' ) )->toBeTrue(); + } +); + +it( + 'registers nothing when given no directories', + function (): void { + ( new ConfigurableBlockRegistrar( [] ) )->register_blocks(); + + expect( BlockRegistrar::$registered_block_names )->toBe( [] ); + } +); + +it( + 'skips directories that do not exist', + function (): void { + $missing = sys_get_temp_dir() . '/tenup_blocks_missing_' . uniqid( '', true ) . '/'; + $real = tenup_make_blocks_dir( [ 'tenup-test/alpha' => [] ] ); + + ( new ConfigurableBlockRegistrar( [ $missing, $real ] ) )->register_blocks(); + + // The missing directory is passed first, to prove it does not abort the run. + expect( WP_Block_Type_Registry::get_instance()->is_registered( 'tenup-test/alpha' ) )->toBeTrue(); + } +); + +it( + 'skips a block whose block.json is malformed', + function (): void { + $dir = tenup_make_blocks_dir( + [ + 'tenup-test/broken' => [ 'json' => '{ not valid json' ], + 'tenup-test/alpha' => [], + ] + ); + + ( new ConfigurableBlockRegistrar( [ $dir ] ) )->register_blocks(); + + expect( WP_Block_Type_Registry::get_instance()->is_registered( 'tenup-test/broken' ) )->toBeFalse(); + // The valid sibling still registers. + expect( WP_Block_Type_Registry::get_instance()->is_registered( 'tenup-test/alpha' ) )->toBeTrue(); + } +); + +it( + 'skips a block.json with no name', + function (): void { + $dir = tenup_make_blocks_dir( + [ 'tenup-test/nameless' => [ 'json' => '{"apiVersion":3,"title":"Nameless"}' ] ] + ); + + ( new ConfigurableBlockRegistrar( [ $dir ] ) )->register_blocks(); + + expect( BlockRegistrar::$registered_block_names )->toBe( [] ); + } +); + +describe( + 'render callbacks', + function (): void { + it( + 'adds no render callback when the block has no markup.php', + function (): void { + $dir = tenup_make_blocks_dir( [ 'tenup-test/alpha' => [] ] ); + $registrar = new ConfigurableBlockRegistrar( [ $dir ] ); + + $options = ( new ReflectionMethod( $registrar, 'get_block_options' ) ) + ->invoke( $registrar, $dir . 'alpha' ); + + expect( $options )->toBe( [] ); + } + ); + + it( + 'adds a render callback when the block has markup.php', + function (): void { + $dir = tenup_make_blocks_dir( + [ 'tenup-test/alpha' => [ 'markup' => '' ] ] + ); + + $registrar = new ConfigurableBlockRegistrar( [ $dir ] ); + + $options = ( new ReflectionMethod( $registrar, 'get_block_options' ) ) + ->invoke( $registrar, $dir . 'alpha' ); + + expect( $options )->toHaveKey( 'render_callback' ); + expect( $options['render_callback'] )->toBeCallable(); + } + ); + + it( + 'renders the block through markup.php', + function (): void { + $dir = tenup_make_blocks_dir( + [ 'tenup-test/alpha' => [ 'markup' => '' ] ] + ); + + ( new ConfigurableBlockRegistrar( [ $dir ] ) )->register_blocks(); + + expect( + render_block( + [ + 'blockName' => 'tenup-test/alpha', + 'attrs' => [], + 'innerBlocks' => [], + 'innerHTML' => '', + 'innerContent' => [], + ] + ) + )->toContain( 'hello from markup' ); + } + ); + } +); + +describe( + 'source tracking and conflicts', + function (): void { + it( + 'records which class registered each block', + function (): void { + $dir = tenup_make_blocks_dir( [ 'tenup-test/alpha' => [] ] ); + + ( new ConfigurableBlockRegistrar( [ $dir ] ) )->register_blocks(); + + expect( BlockRegistrar::get_block_source( 'tenup-test/alpha' ) ) + ->toBe( ConfigurableBlockRegistrar::class ); + expect( BlockRegistrar::get_all_block_sources() )->toHaveKey( 'tenup-test/alpha' ); + } + ); + + it( + 'returns null for a block it never registered', + function (): void { + expect( BlockRegistrar::get_block_source( 'tenup-test/nope' ) )->toBeNull(); + } + ); + + it( + 'detects a conflict once a block name is taken', + function (): void { + $dir = tenup_make_blocks_dir( [ 'tenup-test/alpha' => [] ] ); + + expect( BlockRegistrar::has_block_conflict( 'tenup-test/alpha' ) )->toBeFalse(); + + ( new ConfigurableBlockRegistrar( [ $dir ] ) )->register_blocks(); + + expect( BlockRegistrar::has_block_conflict( 'tenup-test/alpha' ) )->toBeTrue(); + } + ); + + it( + 'keeps the original source when a second registrar claims the same name', + function (): void { + /* + * WordPress rejects the duplicate itself, via _doing_it_wrong from + * WP_Block_Type_Registry::register. Mantle escalates those to failures unless + * declared, so this states that the notice is the expected outcome. + * + * Worth noting: register_blocks() calls register_block_type_from_metadata() *before* + * checking self::$block_sources, and bails on a falsy return. So for two registrars + * competing in one request, WordPress's own rejection short-circuits the conflict + * branch and its log line never runs. The end state is still correct - first + * registrar wins, the name is recorded once - but the conflict logging is + * effectively unreachable here. + */ + $this->setExpectedIncorrectUsage( 'WP_Block_Type_Registry::register' ); + + $first = tenup_make_blocks_dir( [ 'tenup-test/alpha' => [] ] ); + $second = tenup_make_blocks_dir( [ 'tenup-test/alpha' => [] ] ); + + ( new ConfigurableBlockRegistrar( [ $first ] ) )->register_blocks(); + ( new SecondBlockRegistrar( [ $second ] ) )->register_blocks(); + + expect( BlockRegistrar::get_block_source( 'tenup-test/alpha' ) ) + ->toBe( ConfigurableBlockRegistrar::class ); + + // Recorded once, so the conflicting registrar cannot double-add it to allowed blocks. + expect( array_count_values( BlockRegistrar::$registered_block_names )['tenup-test/alpha'] )->toBe( 1 ); + } + ); + } +); + +describe( + 'allowed block types', + function (): void { + it( + 'adds registered blocks to the allowed list', + function (): void { + $dir = tenup_make_blocks_dir( [ 'tenup-test/alpha' => [] ] ); + + ( new ConfigurableBlockRegistrar( [ $dir ] ) )->register_blocks(); + + expect( BlockRegistrar::filter_allowed_block_types( [ 'core/paragraph' ] ) ) + ->toContain( 'core/paragraph' ) + ->toContain( 'tenup-test/alpha' ); + } + ); + + it( + 'passes a boolean through untouched', + function (): void { + // `true` means "all blocks allowed"; narrowing it to a list would be a regression. + expect( BlockRegistrar::filter_allowed_block_types( true ) )->toBeTrue(); + expect( BlockRegistrar::filter_allowed_block_types( false ) )->toBeFalse(); + } + ); + + it( + 'registers the allowed_block_types_all filter only once', + function (): void { + $first = tenup_make_blocks_dir( [ 'tenup-test/alpha' => [] ] ); + $second = tenup_make_blocks_dir( [ 'tenup-test/beta' => [] ] ); + + ( new ConfigurableBlockRegistrar( [ $first ] ) )->register_blocks(); + ( new SecondBlockRegistrar( [ $second ] ) )->register_blocks(); + + $callbacks = $GLOBALS['wp_filter']['allowed_block_types_all'][10] ?? []; + + expect( $callbacks )->toHaveCount( 1 ); + } + ); + } +); diff --git a/tests/Integration/Core/EmojiTest.php b/tests/Integration/Core/EmojiTest.php new file mode 100644 index 0000000..627d7e2 --- /dev/null +++ b/tests/Integration/Core/EmojiTest.php @@ -0,0 +1,170 @@ +emoji = new Emoji(); + } +); + +it( + 'is a module the loader will pick up', + function (): void { + expect( $this->emoji )->toBeInstanceOf( ModuleInterface::class ); + expect( $this->emoji->can_register() )->toBeTrue(); + expect( $this->emoji->load_order() )->toBe( 5 ); + } +); + +/* + * Asserted as whole sets rather than one test per hook. WordPress does not attach all of these + * in every version or request context — print_emoji_styles was deprecated out of core, and the + * admin_* hooks are not wired on a front-end request — so requiring each one to be attached up + * front makes the test fail on WordPress changes that are not regressions in this module. What + * matters is that none of them survive register(), plus a check that the set was not empty to + * begin with so the assertion cannot pass vacuously. + */ +const TENUP_EMOJI_ACTIONS = [ + [ 'wp_head', 'print_emoji_detection_script' ], + [ 'admin_print_scripts', 'print_emoji_detection_script' ], + [ 'wp_print_styles', 'print_emoji_styles' ], + [ 'admin_print_styles', 'print_emoji_styles' ], +]; + +const TENUP_EMOJI_FILTERS = [ + [ 'the_content_feed', 'wp_staticize_emoji' ], + [ 'comment_text_rss', 'wp_staticize_emoji' ], + [ 'wp_mail', 'wp_staticize_emoji_for_email' ], +]; + +it( + 'detaches every core emoji action it targets', + function (): void { + $attached_before = array_filter( + TENUP_EMOJI_ACTIONS, + static fn( array $hook ): bool => false !== has_action( $hook[0], $hook[1] ) + ); + + expect( $attached_before )->not->toBeEmpty(); + + $this->emoji->register(); + + foreach ( TENUP_EMOJI_ACTIONS as [ $hook, $callback ] ) { + expect( has_action( $hook, $callback ) )->toBeFalse(); + } + } +); + +it( + 'detaches every emoji staticize filter it targets', + function (): void { + $attached_before = array_filter( + TENUP_EMOJI_FILTERS, + static fn( array $hook ): bool => false !== has_filter( $hook[0], $hook[1] ) + ); + + expect( $attached_before )->not->toBeEmpty(); + + $this->emoji->register(); + + foreach ( TENUP_EMOJI_FILTERS as [ $hook, $callback ] ) { + expect( has_filter( $hook, $callback ) )->toBeFalse(); + } + } +); + +it( + 'attaches its own filters when registered', + function (): void { + $this->emoji->register(); + + expect( has_filter( 'tiny_mce_plugins', [ $this->emoji, 'disable_emojis_tinymce' ] ) )->not->toBeFalse(); + expect( has_filter( 'wp_resource_hints', [ $this->emoji, 'disable_emoji_dns_prefetch' ] ) )->not->toBeFalse(); + } +); + +it( + 'removes wpemoji from the TinyMCE plugin list', + function (): void { + $result = $this->emoji->disable_emojis_tinymce( [ 'wordpress', 'wpemoji', 'media' ] ); + + expect( $result )->not->toContain( 'wpemoji' ); + // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText -- A literal TinyMCE plugin handle, not prose. + expect( $result )->toContain( 'wordpress' )->toContain( 'media' ); + } +); + +it( + 'leaves the TinyMCE plugin list untouched when wpemoji is absent', + function (): void { + $plugins = [ 'wordpress', 'media' ]; + + expect( $this->emoji->disable_emojis_tinymce( $plugins ) )->toBe( $plugins ); + } +); + +it( + 'drops the emoji CDN host from dns-prefetch hints', + function (): void { + $result = $this->emoji->disable_emoji_dns_prefetch( + [ + 'https://fonts.googleapis.com', + 'https://s.w.org/images/core/emoji/2/svg/', + 'https://example.com', + ], + 'dns-prefetch' + ); + + expect( $result )->not->toContain( 'https://s.w.org/images/core/emoji/2/svg/' ); + expect( $result )->toContain( 'https://fonts.googleapis.com' )->toContain( 'https://example.com' ); + } +); + +it( + 'leaves hints for other relation types untouched', + function (): void { + $urls = [ 'https://s.w.org/images/core/emoji/2/svg/', 'https://example.com' ]; + + expect( $this->emoji->disable_emoji_dns_prefetch( $urls, 'preconnect' ) )->toBe( $urls ); + } +); + +it( + 'takes effect through the wp_resource_hints filter once registered', + function (): void { + $this->emoji->register(); + + $hints = apply_filters( + 'wp_resource_hints', + [ 'https://s.w.org/images/core/emoji/2/svg/', 'https://example.com' ], + 'dns-prefetch' + ); + + expect( $hints )->not->toContain( 'https://s.w.org/images/core/emoji/2/svg/' ); + } +); + +it( + 'removes the emoji detection script from the rendered head', + function (): void { + $this->emoji->register(); + + ob_start(); + do_action( 'wp_head' ); + $head = (string) ob_get_clean(); + + expect( $head )->not->toContain( '_wpemojiSettings' ); + } +); diff --git a/tests/Integration/Core/HeadOverridesTest.php b/tests/Integration/Core/HeadOverridesTest.php new file mode 100644 index 0000000..3136b27 --- /dev/null +++ b/tests/Integration/Core/HeadOverridesTest.php @@ -0,0 +1,86 @@ +head_overrides = new HeadOverrides(); + + // Record what was actually attached before we start, so the assertions hold regardless + // of which of these WordPress still ships (wlwmanifest_link went away in WP 6.3). + $this->attached_before = array_filter( + TENUP_HEAD_CALLBACKS, + static fn( string $callback ): bool => false !== has_action( 'wp_head', $callback ) + ); + } +); + +it( + 'is a module the loader will pick up', + function (): void { + expect( $this->head_overrides )->toBeInstanceOf( ModuleInterface::class ); + expect( $this->head_overrides->can_register() )->toBeTrue(); + // Ahead of taxonomies (9) and post types (10), since it only detaches core output. + expect( $this->head_overrides->load_order() )->toBe( 5 ); + } +); + +it( + 'detaches every wp_head callback it targets', + function (): void { + // Guard against a vacuous pass: if WordPress attached none of them, there is nothing to prove. + expect( $this->attached_before )->not->toBeEmpty(); + + $this->head_overrides->register(); + + foreach ( TENUP_HEAD_CALLBACKS as $callback ) { + expect( has_action( 'wp_head', $callback ) )->toBeFalse(); + } + } +); + +it( + 'removes the generator tag from the rendered head', + function (): void { + ob_start(); + do_action( 'wp_head' ); + $before = (string) ob_get_clean(); + + expect( $before )->toContain( 'head_overrides->register(); + + ob_start(); + do_action( 'wp_head' ); + $after = (string) ob_get_clean(); + + expect( $after )->not->toContain( 'head_overrides->register(); + + // A representative core callback it must not touch. + expect( has_action( 'wp_head', 'wp_robots' ) )->not->toBeFalse(); + } +); diff --git a/tests/Integration/Debug/LoaderDebugTest.php b/tests/Integration/Debug/LoaderDebugTest.php new file mode 100644 index 0000000..7d26ae4 --- /dev/null +++ b/tests/Integration/Debug/LoaderDebugTest.php @@ -0,0 +1,370 @@ +toBeTrue(); + } + ); + + it( + 'is disabled when the filter returns false', + function (): void { + add_filter( 'tenup_framework_enable_loader_debug', '__return_false' ); + + expect( LoaderDebug::is_enabled() )->toBeFalse(); + + remove_filter( 'tenup_framework_enable_loader_debug', '__return_false' ); + } + ); + + it( + 'stores nothing when disabled', + function (): void { + add_filter( 'tenup_framework_enable_loader_debug', '__return_false' ); + + LoaderDebug::record( tenup_sample_loader_record() ); + + expect( LoaderDebug::get_loaders() )->toBe( [] ); + + remove_filter( 'tenup_framework_enable_loader_debug', '__return_false' ); + } + ); + } +); + +describe( + 'recording', + function (): void { + it( + 'stores a record when enabled', + function (): void { + LoaderDebug::record( tenup_sample_loader_record() ); + + $loaders = LoaderDebug::get_loaders(); + + expect( $loaders )->toHaveCount( 1 ); + expect( $loaders[0]['directory'] )->toBe( '/srv/site/wp-content/plugins/demo/inc' ); + } + ); + + it( + 'accumulates records for different directories', + function (): void { + LoaderDebug::record( tenup_sample_loader_record( '/a/inc' ) ); + LoaderDebug::record( tenup_sample_loader_record( '/b/inc' ) ); + + expect( LoaderDebug::get_loaders() )->toHaveCount( 2 ); + } + ); + + it( + 'replaces rather than duplicates a repeat record for one directory', + function (): void { + LoaderDebug::record( tenup_sample_loader_record( '/a/inc' ) ); + LoaderDebug::record( tenup_sample_loader_record( '/a/inc' ) ); + + expect( LoaderDebug::get_loaders() )->toHaveCount( 1 ); + } + ); + + it( + 'contributes its records to the cross-copy aggregation filter', + function (): void { + LoaderDebug::record( tenup_sample_loader_record( '/b/inc' ) ); + + // Records from another framework copy must survive alongside ours, which is the whole + // reason aggregation happens over a fixed-string filter instead of class references. + $merged = apply_filters( LoaderDebug::FILTER, [ [ 'directory' => '/a/inc' ] ] ); + + expect( $merged )->toHaveCount( 2 ); + expect( $merged[0]['directory'] )->toBe( '/a/inc' ); + expect( $merged[1]['directory'] )->toBe( '/b/inc' ); + } + ); + } +); + +describe( + 'page rendering', + function (): void { + beforeEach( + function (): void { + tenup_acting_as_admin(); + } + ); + + it( + 'lists each loader, its classes and its timings', + function (): void { + LoaderDebug::record( tenup_sample_loader_record() ); + + ob_start(); + LoaderDebug::render_page(); + $output = (string) ob_get_clean(); + + expect( $output ) + ->toContain( 'WP Framework Loaders' ) + ->toContain( '/srv/site/wp-content/plugins/demo/inc' ) + ->toContain( 'TenupTmp\\Widget' ) + ->toContain( 'Check this cache for staleness' ) + ->toContain( 'Discovery time' ) + ->toContain( 'Class lookup time' ) + ->toContain( '12.30 ms' ) + ->toContain( '45.60 ms' ); + } + ); + + it( + 'says so when no loaders were recorded', + function (): void { + ob_start(); + LoaderDebug::render_page(); + $output = (string) ob_get_clean(); + + expect( $output )->toContain( 'No class loaders were recorded' ); + } + ); + + it( + 'reports an up-to-date cache when the live scan matches', + function (): void { + $dir = tenup_temp_class_dir(); + + $record = tenup_sample_loader_record( $dir ); + $record['classes'] = [ 'TenupTmp\\Widget' ]; + LoaderDebug::record( $record ); + + $_GET['check'] = tenup_invoke_loader_debug( 'token_for', [ $dir ] ); + $_GET['_wpnonce'] = wp_create_nonce( LoaderDebug::CHECK_NONCE ); + + ob_start(); + LoaderDebug::render_page(); + $output = (string) ob_get_clean(); + + expect( $output )->toContain( 'Up to date' ); + expect( $output )->toContain( 'Live discovery took' ); + + tenup_remove_dir( $dir ); + } + ); + + it( + 'reports drift when the recorded classes differ from a live scan', + function (): void { + $dir = tenup_temp_class_dir(); + + // A class the cache claims but which is not on disk, and a missing real one. + $record = tenup_sample_loader_record( $dir ); + $record['classes'] = [ 'TenupTmp\\Ghost' ]; + LoaderDebug::record( $record ); + + $_GET['check'] = tenup_invoke_loader_debug( 'token_for', [ $dir ] ); + $_GET['_wpnonce'] = wp_create_nonce( LoaderDebug::CHECK_NONCE ); + + ob_start(); + LoaderDebug::render_page(); + $output = (string) ob_get_clean(); + + expect( $output )->toContain( 'Stale' ); + expect( $output )->toContain( 'TenupTmp\\Ghost' ); + expect( $output )->toContain( 'TenupTmp\\Widget' ); + + tenup_remove_dir( $dir ); + } + ); + + it( + 'ignores a staleness check without a valid nonce', + function (): void { + $dir = tenup_temp_class_dir(); + + LoaderDebug::record( tenup_sample_loader_record( $dir ) ); + + $_GET['check'] = tenup_invoke_loader_debug( 'token_for', [ $dir ] ); + // No _wpnonce, so check_is_valid() must reject it. + + ob_start(); + LoaderDebug::render_page(); + $output = (string) ob_get_clean(); + + expect( $output )->toContain( 'Check this cache for staleness' ); + expect( $output )->not->toContain( 'Up to date' ); + + tenup_remove_dir( $dir ); + } + ); + } +); + +describe( + 'formatting helpers', + function (): void { + it( + 'derives a plugin name for a directory under the plugins root', + function (): void { + expect( tenup_invoke_loader_debug( 'owner_label', [ WP_PLUGIN_DIR . '/demo/inc' ] ) ) + ->toBe( 'Plugin: demo' ); + } + ); + + it( + 'falls back to the raw directory when it is outside any known root', + function (): void { + expect( tenup_invoke_loader_debug( 'owner_label', [ '/somewhere/else/inc' ] ) ) + ->toBe( '/somewhere/else/inc' ); + } + ); + + it( + 'resolves the headline cache state', + function ( array $flags, string $severity, string $snippet ): void { + $state = tenup_invoke_loader_debug( + 'cache_state', + [ array_merge( tenup_sample_loader_record(), $flags ) ] + ); + + expect( $state['severity'] )->toBe( $severity ); + expect( $state['badge'] )->toContain( $snippet ); + } + )->with( + [ + 'disabled' => [ [ 'cache_disabled' => true ], 'warn', 'disabled' ], + 'uncached' => [ + [ + 'cache_exists' => false, + 'cache_used' => false, + ], + 'warn', + 'Uncached', + ], + 'present but unused' => [ + [ + 'cache_exists' => true, + 'cache_used' => false, + ], + 'error', + 'not used', + ], + 'failed to load' => [ + [ + 'cache_exists' => true, + 'cache_used' => false, + 'cache_failed' => true, + ], + 'error', + 'failed to load', + ], + 'in use' => [ + [ + 'cache_exists' => true, + 'cache_used' => true, + ], + 'ok', + 'in use', + ], + ] + ); + + it( + 'formats a duration, rejecting anything not a positive finite number', + function ( mixed $seconds, string $expected ): void { + expect( tenup_invoke_loader_debug( 'format_duration', [ $seconds ] ) )->toBe( $expected ); + } + )->with( + [ + 'zero' => [ 0.0, '—' ], + 'negative' => [ -0.005, '—' ], + 'non-numeric' => [ 'nope', '—' ], + 'not-a-number' => [ NAN, '—' ], + 'infinite' => [ INF, '—' ], + 'sub-milli' => [ 0.0004, '0.400 ms' ], + 'milliseconds' => [ 0.0123, '12.30 ms' ], + 'seconds' => [ 1.5, '1.50 s' ], + ] + ); + + it( + 'reports unexpected files left in the cache directory', + function (): void { + $dir = tenup_temp_class_dir(); + $cache_dir = $dir . '/class-loader-cache'; + mkdir( $cache_dir ); + + $current = $cache_dir . '/class-loader-cache-v2.php'; + file_put_contents( $current, 'toBe( [ 'discoverer-cache-TenupFramework' ] ); + + tenup_remove_dir( $dir ); + } + ); + + it( + 'reports no unexpected files when the cache directory is absent', + function (): void { + expect( tenup_invoke_loader_debug( 'legacy_files', [ '/nope/class-loader-cache/x.php' ] ) ) + ->toBe( [] ); + } + ); + + it( + 'describes the cache file with its size and UTC build time', + function (): void { + $dir = tenup_temp_class_dir(); + $cache_dir = $dir . '/class-loader-cache'; + mkdir( $cache_dir ); + $cache_file = $cache_dir . '/class-loader-cache-v2.php'; + file_put_contents( $cache_file, ' $cache_file ] ] ); + + expect( $detail )->toContain( 'Built' ); + // The absolute build time is the file mtime, rendered in UTC as the trailing segment. + expect( $detail )->toContain( '· ' . gmdate( 'Y-m-d H:i:s', (int) filemtime( $cache_file ) ) . ' UTC' ); + expect( $detail )->toMatch( '/·\s*\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} UTC$/' ); + + tenup_remove_dir( $dir ); + } + ); + + it( + 'says there is no cache file when none is on disk', + function (): void { + expect( tenup_invoke_loader_debug( 'cache_detail', [ [ 'cache_file' => '' ] ] ) ) + ->toContain( 'No cache file on disk' ); + } + ); + } +); diff --git a/tests/Integration/ModuleInitialization/LoaderRecordingTest.php b/tests/Integration/ModuleInitialization/LoaderRecordingTest.php new file mode 100644 index 0000000..b15b6ed --- /dev/null +++ b/tests/Integration/ModuleInitialization/LoaderRecordingTest.php @@ -0,0 +1,105 @@ +dir = tenup_temp_class_dir(); + } +); + +afterEach( + function (): void { + tenup_remove_dir( $this->dir ); + tenup_reset_framework_state(); + } +); + +it( + 'records a loader for the directory it discovered', + function (): void { + ModuleInitialization::instance()->init_classes( $this->dir ); + + $loaders = LoaderDebug::get_loaders(); + + expect( $loaders )->toHaveCount( 1 ); + expect( $loaders[0]['directory'] )->toBe( $this->dir ); + expect( $loaders[0]['classes'] )->toContain( 'TenupTmp\\Widget' ); + } +); + +it( + 'records live discovery timing when no cache exists', + function (): void { + ModuleInitialization::instance()->init_classes( $this->dir ); + + $loader = LoaderDebug::get_loaders()[0]; + + expect( $loader['cache_used'] )->toBeFalse(); + // The never-wired default is 0.0, so strictly positive proves the instrumentation ran. + expect( $loader['discovery_seconds'] )->toBeFloat()->toBeGreaterThan( 0.0 ); + expect( $loader['lookup_seconds'] )->toBeFloat()->toBeGreaterThan( 0.0 ); + } +); + +it( + 'reports the cache as in use when one is present', + function (): void { + $module_init = ModuleInitialization::instance(); + $module_init->generate_cache( $this->dir ); + + $module_init->init_classes( $this->dir ); + + $loader = LoaderDebug::get_loaders()[0]; + + expect( $loader['cache_used'] )->toBeTrue(); + expect( $loader['discovery_seconds'] )->toBeGreaterThan( 0.0 ); + } +); + +it( + 'flags a corrupt cache as failed rather than in use', + function (): void { + $module_init = ModuleInitialization::instance(); + $module_init->generate_cache( $this->dir ); + file_put_contents( tenup_cache_file_path( $this->dir ), 'init_classes( $this->dir ); + + $loader = LoaderDebug::get_loaders()[0]; + + expect( $loader['cache_failed'] )->toBeTrue(); + expect( $loader['cache_used'] )->toBeFalse(); + // Still found the real class on disk, because it fell back to a live scan. + expect( $loader['classes'] )->toContain( 'TenupTmp\\Widget' ); + } +); + +it( + 'keeps one record per directory when init_classes() runs twice', + function (): void { + $module_init = ModuleInitialization::instance(); + + $module_init->init_classes( $this->dir ); + $module_init->init_classes( $this->dir ); + + expect( LoaderDebug::get_loaders() )->toHaveCount( 1 ); + } +); diff --git a/tests/Integration/ModuleInitializationTest.php b/tests/Integration/ModuleInitializationTest.php new file mode 100644 index 0000000..395d8f4 --- /dev/null +++ b/tests/Integration/ModuleInitializationTest.php @@ -0,0 +1,134 @@ +fixtures = dirname( __DIR__, 2 ) . '/fixtures/classes'; + } +); + +afterEach( + function (): void { + tenup_reset_framework_state(); + } +); + +it( + 'returns the same instance every time', + function (): void { + expect( ModuleInitialization::instance() )->toBe( ModuleInitialization::instance() ); + } +); + +it( + 'discovers the framework classes in a directory', + function (): void { + $classes = ModuleInitialization::instance()->get_classes( dirname( __DIR__, 2 ) . '/src/' ); + + expect( $classes ) + ->toContain( 'TenupFramework\PostTypes\AbstractPostType' ) + ->toContain( 'TenupFramework\PostTypes\AbstractCorePostType' ) + ->toContain( 'TenupFramework\Taxonomies\AbstractTaxonomy' ) + ->toContain( 'TenupFramework\ModuleInitialization' ); + } +); + +it( + 'registers only classes implementing ModuleInterface', + function (): void { + $module_init = ModuleInitialization::instance(); + $module_init->init_classes( $this->fixtures ); + + $registered = $module_init->get_all_classes(); + + expect( $registered )->not->toBeEmpty(); + + foreach ( $registered as $instance ) { + expect( $instance )->toBeInstanceOf( ModuleInterface::class ); + } + } +); + +it( + 'fires the per-module init action and skips non-modules', + function (): void { + ModuleInitialization::instance()->init_classes( $this->fixtures ); + + expect( did_action( 'tenup_framework_module_init__tenupframeworktestclasses-posttypes-demo' ) ) + ->toBeGreaterThan( 0 ); + + // Standalone implements no interface, so it must never be instantiated or announced. + expect( did_action( 'tenup_framework_module_init__tenupframeworktestclasses-standalone-standalone' ) ) + ->toBe( 0 ); + } +); + +it( + 'exposes registered modules through get_module()', + function (): void { + ModuleInitialization::instance()->init_classes( $this->fixtures ); + + expect( ModuleInitialization::get_module( Demo::class ) )->toBeInstanceOf( Demo::class ); + expect( ModuleInitialization::get_module( 'TenupFrameworkTestClasses\DoesntExist' ) )->toBeFalse(); + } +); + +it( + 'reflects loadable classes and rejects un-loadable ones', + function (): void { + $module_init = ModuleInitialization::instance(); + + expect( $module_init->get_fully_loadable_class( '\TenupFrameworkTestClasses\Loadable\BaseClass' ) ) + ->toBeInstanceOf( ReflectionClass::class ); + expect( $module_init->get_fully_loadable_class( '\TenupFrameworkTestClasses\Loadable\ChildClass' ) ) + ->toBeInstanceOf( ReflectionClass::class ); + // Extends a parent that does not exist, so reflection throws and false is returned. + expect( $module_init->get_fully_loadable_class( '\TenupFrameworkTestClasses\Loadable\InvalidChildClass' ) ) + ->toBeFalse(); + } +); + +it( + 'throws when the directory does not exist', + function (): void { + ModuleInitialization::instance()->init_classes( dirname( __DIR__, 2 ) . '/src/does-not-exist-1234567/' ); + } +)->throws( RuntimeException::class ); + +it( + 'throws when no directory is passed', + function (): void { + ModuleInitialization::instance()->init_classes(); + } +)->throws( RuntimeException::class ); + +// The admin-only counterpart lives in ModuleInitialization/LoaderRecordingTest.php, which +// applies Mantle's Admin_Screen trait to make is_admin() true for the whole file. +it( + 'records no loader debug data on the front end', + function (): void { + $dir = tenup_temp_class_dir(); + + ModuleInitialization::instance()->init_classes( $dir ); + + expect( LoaderDebug::get_loaders() )->toBe( [] ); + + tenup_remove_dir( $dir ); + } +); diff --git a/tests/Integration/PostTypes/AbstractPostTypeTest.php b/tests/Integration/PostTypes/AbstractPostTypeTest.php new file mode 100644 index 0000000..040a65f --- /dev/null +++ b/tests/Integration/PostTypes/AbstractPostTypeTest.php @@ -0,0 +1,154 @@ +post_type = new Demo(); + } +); + +afterEach( + function (): void { + // Post type and taxonomy registries are process globals, not database state, so the + // transaction rollback between tests does not clear them. + if ( post_type_exists( 'tenup-demo' ) ) { + unregister_post_type( 'tenup-demo' ); + } + + if ( taxonomy_exists( 'tenup-tax-demo' ) ) { + unregister_taxonomy( 'tenup-tax-demo' ); + } + } +); + +it( + 'registers the post type with WordPress', + function (): void { + expect( post_type_exists( 'tenup-demo' ) )->toBeFalse(); + + $this->post_type->register(); + + expect( post_type_exists( 'tenup-demo' ) )->toBeTrue(); + } +); + +it( + 'uses the plural label for name and the singular for singular_name', + function (): void { + $this->post_type->register(); + + $labels = get_post_type_object( 'tenup-demo' )->labels; + + expect( $labels->name )->toBe( 'Demos' ); + expect( $labels->singular_name )->toBe( 'Demo' ); + } +); + +it( + 'derives the remaining labels from the singular and plural labels', + function (): void { + $this->post_type->register(); + + $labels = get_post_type_object( 'tenup-demo' )->labels; + + expect( $labels->add_new_item )->toBe( 'Add New Demo' ); + expect( $labels->edit_item )->toBe( 'Edit Demo' ); + expect( $labels->view_items )->toBe( 'View Demos' ); + expect( $labels->all_items )->toBe( 'All Demos' ); + expect( $labels->archives )->toBe( 'Demo Archives' ); + // Lower-cased in the sentence-style labels. + expect( $labels->not_found )->toBe( 'No demos found.' ); + expect( $labels->insert_into_item )->toBe( 'Insert into demo' ); + } +); + +it( + 'applies the default options from get_options()', + function (): void { + $this->post_type->register(); + + $object = get_post_type_object( 'tenup-demo' ); + + expect( $object->public )->toBeTrue(); + expect( $object->has_archive )->toBeTrue(); + expect( $object->show_ui )->toBeTrue(); + expect( $object->show_in_menu )->toBeTrue(); + expect( $object->show_in_rest )->toBeTrue(); + // Deliberately false by default, unlike most of the flags above. + expect( $object->show_in_nav_menus )->toBeFalse(); + expect( $object->hierarchical )->toBeFalse(); + } +); + +it( + 'sets the menu icon declared by the subclass', + function (): void { + $this->post_type->register(); + + expect( get_post_type_object( 'tenup-demo' )->menu_icon )->toBe( 'dashicons-chart-pie' ); + } +); + +it( + 'registers the default editor supports', + function (): void { + $this->post_type->register(); + + foreach ( [ 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'revisions' ] as $feature ) { + expect( post_type_supports( 'tenup-demo', $feature ) )->toBeTrue(); + } + + expect( post_type_supports( 'tenup-demo', 'comments' ) )->toBeFalse(); + } +); + +it( + 'associates the taxonomies the post type declares', + function (): void { + // The taxonomy has to exist before register_taxonomy_for_object_type() will attach it, + // which is exactly why AbstractTaxonomy uses load order 9 and post types use 10. + ( new DemoTaxonomy() )->register(); + + $this->post_type->register(); + + expect( get_object_taxonomies( 'tenup-demo' ) )->toContain( 'tenup-tax-demo' ); + } +); + +it( + 'produces a post type that can actually store and retrieve a post', + function (): void { + $this->post_type->register(); + + $post_id = wp_insert_post( + [ + 'post_type' => 'tenup-demo', + 'post_title' => 'A demo item', + 'post_status' => 'publish', + ] + ); + + expect( $post_id )->toBeInt()->toBeGreaterThan( 0 ); + expect( get_post( $post_id )->post_type )->toBe( 'tenup-demo' ); + } +); + +it( + 'registers after taxonomies via its load order', + function (): void { + expect( $this->post_type->load_order() )->toBe( 10 ); + expect( ( new DemoTaxonomy() )->load_order() )->toBeLessThan( $this->post_type->load_order() ); + } +); diff --git a/tests/Integration/SmokeTest.php b/tests/Integration/SmokeTest.php new file mode 100644 index 0000000..5cf1977 --- /dev/null +++ b/tests/Integration/SmokeTest.php @@ -0,0 +1,27 @@ +toBeTrue(); + expect( function_exists( 'register_post_type' ) )->toBeTrue(); + expect( did_action( 'init' ) )->toBeGreaterThan( 0 ); + } +); + +it( + 'has a usable database', + function (): void { + global $wpdb; + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- Proving the test database is reachable is the point. + expect( $wpdb->get_var( 'SELECT 1' ) )->toEqual( 1 ); + } +); diff --git a/tests/Integration/Taxonomies/AbstractTaxonomyTest.php b/tests/Integration/Taxonomies/AbstractTaxonomyTest.php new file mode 100644 index 0000000..53b5e52 --- /dev/null +++ b/tests/Integration/Taxonomies/AbstractTaxonomyTest.php @@ -0,0 +1,110 @@ +taxonomy = new Demo(); + } +); + +afterEach( + function (): void { + if ( taxonomy_exists( 'tenup-tax-demo' ) ) { + unregister_taxonomy( 'tenup-tax-demo' ); + } + } +); + +it( + 'registers the taxonomy with WordPress', + function (): void { + expect( taxonomy_exists( 'tenup-tax-demo' ) )->toBeFalse(); + + $this->taxonomy->register(); + + expect( taxonomy_exists( 'tenup-tax-demo' ) )->toBeTrue(); + } +); + +it( + 'uses the plural label for name and the singular for singular_name', + function (): void { + $this->taxonomy->register(); + + $labels = get_taxonomy( 'tenup-tax-demo' )->labels; + + expect( $labels->name )->toBe( 'Demo Terms' ); + expect( $labels->singular_name )->toBe( 'Demo Term' ); + } +); + +it( + 'derives the remaining labels from the singular and plural labels', + function (): void { + $this->taxonomy->register(); + + $labels = get_taxonomy( 'tenup-tax-demo' )->labels; + + expect( $labels->search_items )->toBe( 'Search Demo Terms' ); + expect( $labels->add_new_item )->toBe( 'Add Demo Term' ); + expect( $labels->new_item_name )->toBe( 'New Demo Term Name' ); + expect( $labels->all_items )->toBe( 'All Demo Terms' ); + // Lower-cased in the sentence-style labels. + expect( $labels->separate_items_with_commas )->toBe( 'Separate demo terms with commas' ); + expect( $labels->not_found )->toBe( 'No demo terms found.' ); + } +); + +it( + 'applies the default options from get_options()', + function (): void { + $this->taxonomy->register(); + + $object = get_taxonomy( 'tenup-tax-demo' ); + + expect( $object->public )->toBeTrue(); + expect( $object->show_ui )->toBeTrue(); + expect( $object->show_admin_column )->toBeTrue(); + expect( $object->show_in_rest )->toBeTrue(); + expect( $object->hierarchical )->toBeFalse(); + expect( $object->query_var )->toBe( 'tenup-tax-demo' ); + } +); + +it( + 'attaches to no post types by default, leaving that to the post type classes', + function (): void { + expect( $this->taxonomy->get_post_types() )->toBe( [] ); + + $this->taxonomy->register(); + + expect( get_taxonomy( 'tenup-tax-demo' )->object_type )->toBe( [] ); + } +); + +it( + 'registers before post types via its load order', + function (): void { + expect( $this->taxonomy->load_order() )->toBe( 9 ); + } +); + +it( + 'produces a taxonomy that can actually store and retrieve a term', + function (): void { + $this->taxonomy->register(); + + $term = wp_insert_term( 'Example Term', 'tenup-tax-demo' ); + + expect( $term )->toBeArray(); + expect( get_term( $term['term_id'], 'tenup-tax-demo' )->name )->toBe( 'Example Term' ); + } +); diff --git a/tests/ModuleInitializationTest.php b/tests/ModuleInitializationTest.php deleted file mode 100644 index 5f054ff..0000000 --- a/tests/ModuleInitializationTest.php +++ /dev/null @@ -1,514 +0,0 @@ -get_classes( dirname( __DIR__, 1 ) . '/src/' ); - - // Check that we have the concrete classes we expect to see. - $this->assertContains( 'TenupFramework\PostTypes\AbstractPostType', $classes ); - $this->assertContains( 'TenupFramework\PostTypes\AbstractCorePostType', $classes ); - $this->assertContains( 'TenupFramework\Taxonomies\AbstractTaxonomy', $classes ); - $this->assertContains( 'TenupFramework\ModuleInitialization', $classes ); - } - - /** - * Ensure we can find the right classes. - * - * @return void - */ - public function test_it_can_find_classes_to_register() { - $class = \TenupFramework\ModuleInitialization::instance(); - $class->init_classes( dirname( __DIR__, 1 ) . '/fixtures/classes' ); - $classes = $class->get_all_classes(); - - // The registered set is non-empty and contains only ModuleInterface implementations. - $this->assertNotEmpty( $classes ); - foreach ( $classes as $registered ) { - $this->assertInstanceOf( \TenupFramework\ModuleInterface::class, $registered ); - } - } - - /** - * Ensure an exception is thrown when a directory does not exist. - * - * @return void - */ - public function test_that_an_exception_is_thrown_when_a_directory_does_not_exist() { - $class = \TenupFramework\ModuleInitialization::instance(); - $this->expectException( \RuntimeException::class ); - $class->init_classes( dirname( __DIR__, 1 ) . '/src/does-not-exist-1234567/' ); - } - - /** - * Ensure an exception is thrown when a directory is not passed. - * - * @return void - */ - public function test_that_an_exception_is_thrown_when_a_directory_is_not_passed() { - $class = \TenupFramework\ModuleInitialization::instance(); - $this->expectException( \RuntimeException::class ); - $class->init_classes(); - } - - /** - * Ensure the instance method returns the same instance. - * - * @return void - */ - public function test_instance_returns_same_instance() { - $instance1 = \TenupFramework\ModuleInitialization::instance(); - $instance2 = \TenupFramework\ModuleInitialization::instance(); - $this->assertSame( $instance1, $instance2 ); - } - - /** - * Ensure the instance method returns the same instance. - * - * @return void - */ - public function test_get_classes_returns_classes_from_directory() { - $module_init = \TenupFramework\ModuleInitialization::instance(); - $classes = $module_init->get_classes( dirname( __DIR__, 1 ) . '/fixtures/classes' ); - $this->assertIsArray( $classes ); - $this->assertNotEmpty( $classes ); - } - - /** - * Ensure the instance method returns the same instance. - * - * @return void - */ - public function test_init_classes_initializes_classes_in_correct_order() { - $module_init = \TenupFramework\ModuleInitialization::instance(); - $module_init->init_classes( dirname( __DIR__, 1 ) . '/fixtures/classes' ); - $classes = $module_init->get_all_classes(); - $this->assertNotEmpty( $classes ); - $this->assertNotContains( 'TenupFramework\Taxonomies\AbstractTaxonomy', $classes ); - $this->assertInstanceOf( \TenupFramework\ModuleInterface::class, reset( $classes ) ); - } - - /** - * Ensure that we can return an instantiated class vie get_module. - * - * @return void - */ - public function test_get_module_returns_instantiated_class() { - $module_init = \TenupFramework\ModuleInitialization::instance(); - $module_init->init_classes( dirname( __DIR__, 1 ) . '/fixtures/classes' ); - $module = \TenupFramework\ModuleInitialization::get_module( 'TenupFrameworkTestClasses\PostTypes\Demo' ); - $this->assertInstanceOf( \TenupFrameworkTestClasses\PostTypes\Demo::class, $module ); - - $module = \TenupFramework\ModuleInitialization::get_module( 'TenupFrameworkTestClasses\DoesntExist' ); - $this->assertFalse( $module ); - } - - /** - * Test that only classes implementing ModuleInterface are initialized. - * - * @return void - */ - public function test_only_classes_implementing_module_interface_are_initialized() { - $module_init = \TenupFramework\ModuleInitialization::instance(); - $module_init->init_classes( dirname( __DIR__, 1 ) . '/fixtures/classes' ); - - $this->assertTrue( did_action( 'tenup_framework_module_init__tenupframeworktestclasses-posttypes-demo' ) > 0, 'Demo was not initialized.' ); - $this->assertFalse( did_action( 'tenup_framework_module_init__tenupframeworktestclasses-standalone-standalone' ) > 0, 'Standalone class was initialized.' ); - } - - /** - * Validate if the classes are fully loadable. - * - * @return void - */ - public function testIsClassFullyLoadable() { - $module_init = \TenupFramework\ModuleInitialization::instance(); - - $this->assertInstanceOf( 'ReflectionClass', $module_init->get_fully_loadable_class( '\TenupFrameworkTestClasses\Loadable\BaseClass' ) ); - $this->assertInstanceOf( 'ReflectionClass', $module_init->get_fully_loadable_class( '\TenupFrameworkTestClasses\Loadable\ChildClass' ) ); - $this->assertFalse( $module_init->get_fully_loadable_class( '\TenupFrameworkTestClasses\Loadable\InvalidChildClass' ) ); - } - - - /** - * generate_cache() writes a readable cache file and returns the discovered classes. - * - * @return void - */ - public function test_generate_cache_writes_a_readable_cache_file() { - $dir = $this->make_temp_class_dir(); - - $module_init = \TenupFramework\ModuleInitialization::instance(); - $cached = $module_init->generate_cache( $dir ); - - $this->assertFileExists( $this->cache_file_path( $dir ) ); - $this->assertContains( 'TenupTmp\\Widget', $cached ); - - $this->remove_temp_dir( $dir ); - } - - /** - * The runtime read path uses the cache file when one is present. - * - * @return void - */ - public function test_get_classes_reads_the_cache_file_when_present() { - $dir = $this->make_temp_class_dir(); - - $module_init = \TenupFramework\ModuleInitialization::instance(); - $module_init->generate_cache( $dir ); - - // Tamper with the cache so we can prove the read path uses it rather than re-discovering. - $this->write_file( $this->cache_file_path( $dir ), "get_classes( $dir ); - - $this->assertSame( [ 'TenupTmp\\Sentinel' ], array_values( $read ) ); - - $this->remove_temp_dir( $dir ); - } - - /** - * With no cache present the runtime discovers live and writes nothing. - * - * @return void - */ - public function test_get_classes_creates_no_cache_when_none_exists() { - $dir = $this->make_temp_class_dir(); - - $module_init = \TenupFramework\ModuleInitialization::instance(); - $classes = $module_init->get_classes( $dir ); - - $this->assertContains( 'TenupTmp\\Widget', $classes ); - $this->assertDirectoryDoesNotExist( $dir . '/' . \TenupFramework\ModuleInitialization::CACHE_DIR_NAME ); - - $this->remove_temp_dir( $dir ); - } - - /** - * A cache written by an older framework version (a different filename) is ignored, - * so an upgraded site never serves a stale cache it cannot rewrite. - * - * @return void - */ - public function test_get_classes_ignores_legacy_cache_file() { - $dir = $this->make_temp_class_dir(); - $cache_dir = $dir . '/' . \TenupFramework\ModuleInitialization::CACHE_DIR_NAME; - mkdir( $cache_dir ); - - // The previous version wrote `discoverer-cache-{id}` as a serialized file. - $this->write_file( $cache_dir . '/discoverer-cache-TenupFramework', serialize( [ 'TenupTmp\\Legacy' ] ) ); - - $module_init = \TenupFramework\ModuleInitialization::instance(); - $read = $module_init->get_classes( $dir ); - - $this->assertNotContains( 'TenupTmp\\Legacy', $read ); - $this->assertContains( 'TenupTmp\\Widget', $read ); - - $this->remove_temp_dir( $dir ); - } - - /** - * A corrupt or truncated cache file does not fatal the request: get_classes() catches the - * error and falls back to a live discovery, so a bad cache degrades to uncached rather than - * taking the site down. - * - * @return void - */ - public function test_get_classes_falls_back_to_live_when_cache_is_corrupt() { - $dir = $this->make_temp_class_dir(); - $cache_dir = $dir . '/' . \TenupFramework\ModuleInitialization::CACHE_DIR_NAME; - mkdir( $cache_dir ); - - // A truncated / syntactically broken cache file — `require` on this throws a ParseError. - $this->write_file( - $cache_dir . '/' . \TenupFramework\ModuleInitialization::CACHE_FILENAME, - "get_classes( $dir ); - - // Fell back to a live scan and still found the real class on disk. - $this->assertContains( 'TenupTmp\\Widget', $read ); - - $this->remove_temp_dir( $dir ); - } - - /** - * Defining TENUP_FRAMEWORK_DISABLE_CLASS_CACHE forces live discovery even when a - * cache file is present. - * - * @return void - */ - public function test_disable_constant_forces_live_discovery() { - $dir = $this->make_temp_class_dir(); - - $module_init = \TenupFramework\ModuleInitialization::instance(); - $module_init->generate_cache( $dir ); - - // Tamper with the cache; with caching disabled this sentinel must not be read. - $this->write_file( $this->cache_file_path( $dir ), "get_classes( $dir ); - - $this->assertNotContains( 'TenupTmp\\Sentinel', $read ); - $this->assertContains( 'TenupTmp\\Widget', $read ); - - $this->remove_temp_dir( $dir ); - } - - /** - * In the admin, init_classes() hands a loader record to the debug registry. - * - * @return void - */ - public function test_init_classes_records_a_loader_in_admin() { - when( 'is_admin' )->justReturn( true ); - when( 'add_action' )->justReturn( true ); - when( 'add_filter' )->justReturn( true ); - when( 'apply_filters' )->returnArg( 2 ); - - $dir = $this->make_temp_class_dir(); - - \TenupFramework\ModuleInitialization::instance()->init_classes( $dir ); - - $loaders = \TenupFramework\Debug\LoaderDebug::get_loaders(); - $this->assertCount( 1, $loaders ); - $this->assertSame( $dir, $loaders[0]['directory'] ); - $this->assertContains( 'TenupTmp\\Widget', $loaders[0]['classes'] ); - - $this->remove_temp_dir( $dir ); - } - - /** - * With no cache present, init_classes() records the time of a genuine live (uncached) - * discovery — a real filesystem scan, so the recorded duration is strictly positive. - * - * @return void - */ - public function test_init_classes_records_live_discovery_timing_in_admin() { - when( 'is_admin' )->justReturn( true ); - when( 'add_action' )->justReturn( true ); - when( 'add_filter' )->justReturn( true ); - when( 'apply_filters' )->returnArg( 2 ); - - $dir = $this->make_temp_class_dir(); - - \TenupFramework\ModuleInitialization::instance()->init_classes( $dir ); - - $loaders = \TenupFramework\Debug\LoaderDebug::get_loaders(); - $this->assertCount( 1, $loaders ); - $this->assertFalse( $loaders[0]['cache_used'], 'No cache exists, so discovery must be live.' ); - $this->assertIsFloat( $loaders[0]['discovery_seconds'] ); - $this->assertIsFloat( $loaders[0]['lookup_seconds'] ); - // A live filesystem scan and the reflection loop both take measurable time; the never-wired - // default is 0.0, so asserting strictly-positive proves the instrumentation actually ran. - $this->assertGreaterThan( 0.0, $loaders[0]['discovery_seconds'] ); - $this->assertGreaterThan( 0.0, $loaders[0]['lookup_seconds'] ); - - $this->remove_temp_dir( $dir ); - } - - /** - * With a pre-built cache present, init_classes() reads it (cache_used) and still records a - * positive discovery duration — the cache-read cost rather than a live scan. - * - * @return void - */ - public function test_init_classes_records_cache_read_timing_in_admin() { - when( 'is_admin' )->justReturn( true ); - when( 'add_action' )->justReturn( true ); - when( 'add_filter' )->justReturn( true ); - when( 'apply_filters' )->returnArg( 2 ); - - $dir = $this->make_temp_class_dir(); - $module = \TenupFramework\ModuleInitialization::instance(); - $module->generate_cache( $dir ); - - $module->init_classes( $dir ); - - $loaders = \TenupFramework\Debug\LoaderDebug::get_loaders(); - $this->assertCount( 1, $loaders ); - $this->assertTrue( $loaders[0]['cache_used'], 'A cache file exists, so it should be used.' ); - $this->assertGreaterThan( 0.0, $loaders[0]['discovery_seconds'] ); - - $this->remove_temp_dir( $dir ); - } - - /** - * On the front end, init_classes() records nothing (the data is only viewable in the admin). - * - * @return void - */ - public function test_init_classes_records_nothing_on_the_front_end() { - when( 'is_admin' )->justReturn( false ); - - $dir = $this->make_temp_class_dir(); - - \TenupFramework\ModuleInitialization::instance()->init_classes( $dir ); - - $this->assertSame( [], \TenupFramework\Debug\LoaderDebug::get_loaders() ); - - $this->remove_temp_dir( $dir ); - } - - /** - * discover_live() ignores any cache file and returns the real on-disk classes. - * - * @return void - */ - public function test_discover_live_ignores_the_cache() { - $dir = $this->make_temp_class_dir(); - $module_init = \TenupFramework\ModuleInitialization::instance(); - $module_init->generate_cache( $dir ); - - // Tamper with the cache; discover_live() must not read it. - $this->write_file( $this->cache_file_path( $dir ), "discover_live( $dir ); - - $this->assertContains( 'TenupTmp\\Widget', $live ); - $this->assertNotContains( 'TenupTmp\\Sentinel', $live ); - - $this->remove_temp_dir( $dir ); - } - - /** - * A corrupt or truncated cache file falls back to a live scan instead of fataling. - * - * @return void - */ - public function test_get_classes_falls_back_when_the_cache_is_corrupt() { - $dir = $this->make_temp_class_dir(); - $module_init = \TenupFramework\ModuleInitialization::instance(); - $module_init->generate_cache( $dir ); - - // Truncated PHP: require() raises a ParseError, which the read path must catch. - $this->write_file( $this->cache_file_path( $dir ), 'get_classes( $dir ); - - $this->assertContains( 'TenupTmp\\Widget', $classes ); - - $this->remove_temp_dir( $dir ); - } - - /** - * When the read path falls back, the loader record flags the failure so the debug page can - * show it rather than reporting the cache as in use. - * - * @return void - */ - public function test_corrupt_cache_records_a_failed_state() { - when( 'is_admin' )->justReturn( true ); - when( 'add_action' )->justReturn( true ); - when( 'add_filter' )->justReturn( true ); - when( 'apply_filters' )->returnArg( 2 ); - - $dir = $this->make_temp_class_dir(); - $module_init = \TenupFramework\ModuleInitialization::instance(); - $module_init->generate_cache( $dir ); - $this->write_file( $this->cache_file_path( $dir ), 'init_classes( $dir ); - - $loaders = \TenupFramework\Debug\LoaderDebug::get_loaders(); - $this->assertNotEmpty( $loaders ); - $this->assertTrue( $loaders[0]['cache_failed'] ); - $this->assertFalse( $loaders[0]['cache_used'] ); - $this->assertContains( 'TenupTmp\\Widget', $loaders[0]['classes'] ); - - $this->remove_temp_dir( $dir ); - } - - /** - * Build the absolute path to the cache file for a discovery directory. - * - * @param string $dir The discovery directory. - * - * @return string - */ - private function cache_file_path( string $dir ): string { - return $dir . '/' . \TenupFramework\ModuleInitialization::CACHE_DIR_NAME - . '/' . \TenupFramework\ModuleInitialization::CACHE_FILENAME; - } - - /** - * Create a temporary directory containing a single discoverable class. - * - * @return string The created directory path. - */ - private function make_temp_class_dir(): string { - $dir = sys_get_temp_dir() . '/tenup_framework_test_' . uniqid( '', true ); - mkdir( $dir ); - $this->write_file( $dir . '/Widget.php', "assertNotFalse( file_put_contents( $path, $contents ) ); - } - - /** - * Recursively remove a temporary directory. - * - * @param string $dir The directory to remove. - * - * @return void - */ - private function remove_temp_dir( string $dir ): void { - if ( ! is_dir( $dir ) ) { - return; - } - - $items = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator( $dir, \FilesystemIterator::SKIP_DOTS ), - \RecursiveIteratorIterator::CHILD_FIRST - ); - - foreach ( $items as $item ) { - if ( $item->isDir() ) { - rmdir( $item->getPathname() ); - } else { - unlink( $item->getPathname() ); - } - } - - rmdir( $dir ); - } -} diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..3d6920a --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,21 @@ +extend( Integration_Test_Case::class )->in( 'Integration' ); +pest()->extend( Unit_Test_Case::class )->in( 'Unit' ); diff --git a/tests/PostTypes/AbstractPostTypeTest.php b/tests/PostTypes/AbstractPostTypeTest.php deleted file mode 100644 index 7cd0071..0000000 --- a/tests/PostTypes/AbstractPostTypeTest.php +++ /dev/null @@ -1,38 +0,0 @@ -register(); - - $this->assertArrayHasKey( $class->get_name(), self::$registered_post_types ); - $this->assertEquals( $class->get_plural_label(), self::$registered_post_types['tenup-demo']['labels']['name'] ); - $this->assertEquals( $class->get_singular_label(), self::$registered_post_types['tenup-demo']['labels']['singular_name'] ); - } -} diff --git a/tests/Taxonomies/AbstractTaxonomyTest.php b/tests/Taxonomies/AbstractTaxonomyTest.php deleted file mode 100644 index 5c6f4b0..0000000 --- a/tests/Taxonomies/AbstractTaxonomyTest.php +++ /dev/null @@ -1,38 +0,0 @@ -register(); - - $this->assertArrayHasKey( $class->get_name(), self::$registered_taxonomies ); - $this->assertEquals( $class->get_plural_label(), self::$registered_taxonomies['tenup-tax-demo']['labels']['name'] ); - $this->assertEquals( $class->get_singular_label(), self::$registered_taxonomies['tenup-tax-demo']['labels']['singular_name'] ); - } -} diff --git a/tests/TestBlockRegistrar.php b/tests/TestBlockRegistrar.php deleted file mode 100644 index 0167912..0000000 --- a/tests/TestBlockRegistrar.php +++ /dev/null @@ -1,26 +0,0 @@ -asset_info = tenup_asset_info_consumer(); + $this->asset_info->setup_asset_vars( + dist_path: tenup_asset_fixture_path(), + fallback_version: '1.0.0' + ); + } +); + +it( + 'normalises the dist path and stores the fallback version', + function (): void { + $asset_info = tenup_asset_info_consumer(); + + $asset_info->setup_asset_vars( dist_path: 'dist', fallback_version: '1.0.0' ); + + expect( $asset_info->dist_path )->toBe( 'dist/' ); + expect( $asset_info->fallback_version )->toBe( '1.0.0' ); + } +); + +it( + 'throws when get_asset_info() is called before setup_asset_vars()', + function (): void { + tenup_asset_info_consumer()->get_asset_info( slug: 'test-script' ); + } +)->throws( + RuntimeException::class, + 'Asset variables not set. Please run setup_asset_vars() before calling get_asset_info().' +); + +it( + 'resolves a bare slug by searching js/, then css/, then blocks/', + function ( string $slug, string $subdirectory ): void { + $expected = require tenup_asset_fixture_path() . "/{$subdirectory}/{$slug}.asset.php"; + + expect( $this->asset_info->get_asset_info( slug: $slug ) )->toBe( $expected ); + } +)->with( + [ + 'js sidecar' => [ 'test-script', 'js' ], + 'css sidecar' => [ 'test-style', 'css' ], + 'blocks sidecar' => [ 'test-block', 'blocks' ], + ] +); + +it( + 'resolves an explicitly prefixed slug', + function ( string $slug, string $subdirectory, string $file ): void { + $expected = require tenup_asset_fixture_path() . "/{$subdirectory}/{$file}.asset.php"; + + expect( $this->asset_info->get_asset_info( slug: $slug ) )->toBe( $expected ); + } +)->with( + [ + 'css prefix' => [ 'css/test-style', 'css', 'test-style' ], + 'js prefix' => [ 'js/test-script', 'js', 'test-script' ], + 'blocks prefix' => [ 'blocks/test-block', 'blocks', 'test-block' ], + ] +); + +it( + 'returns a single attribute when one is requested', + function (): void { + expect( $this->asset_info->get_asset_info( slug: 'test-script', attribute: 'version' ) ) + ->toBe( 'test-script-version' ); + + expect( $this->asset_info->get_asset_info( slug: 'test-script', attribute: 'dependencies' ) ) + ->toBe( [ 'test-script-deps' ] ); + } +); + +it( + 'falls back to the supplied version and no dependencies when no sidecar exists', + function (): void { + expect( $this->asset_info->get_asset_info( slug: 'non-existent' ) ) + ->toBe( + [ + 'version' => '1.0.0', + 'dependencies' => [], + ] + ); + } +); diff --git a/tests/Unit/Bin/GenerateClassCacheTest.php b/tests/Unit/Bin/GenerateClassCacheTest.php new file mode 100644 index 0000000..ac2e98c --- /dev/null +++ b/tests/Unit/Bin/GenerateClassCacheTest.php @@ -0,0 +1,94 @@ +temp_dirs = []; + } +); + +afterEach( + function (): void { + foreach ( $this->temp_dirs as $dir ) { + tenup_remove_dir( $dir ); + } + } +); + +it( + 'generates a readable cache of the classes in a directory', + function (): void { + $dir = tenup_example_copy( 'plugin-inc' ); + $this->temp_dirs[] = $dir; + + $result = tenup_run_cache_bin( [ $dir ] ); + + expect( $result['exit'] )->toBe( 0, $result['stderr'] ); + expect( $result['stdout'] )->toContain( 'Cached' ); + + $cache_file = tenup_cache_file_path( $dir ); + expect( file_exists( $cache_file ) )->toBeTrue(); + + $cached = require $cache_file; + expect( $cached ) + ->toContain( 'TenupFrameworkExamples\\Modules\\GreetingModule' ) + ->toContain( 'TenupFrameworkExamples\\Support\\Formatter' ); + } +); + +it( + 'reports usage and fails when given no arguments', + function (): void { + $result = tenup_run_cache_bin( [] ); + + expect( $result['exit'] )->toBe( 1 ); + expect( $result['stderr'] )->toContain( 'Usage:' ); + } +); + +it( + 'still caches the valid directories when one is missing', + function (): void { + $good = tenup_example_copy( 'plugin-inc' ); + $this->temp_dirs[] = $good; + $missing = sys_get_temp_dir() . '/tenup_bin_missing_' . uniqid( '', true ); + + // The missing directory is passed first, to prove a failure does not abort the run. + $result = tenup_run_cache_bin( [ $missing, $good ] ); + + expect( $result['exit'] )->toBe( 1 ); + expect( $result['stderr'] ) + ->toContain( $missing ) + ->toContain( 'Failed to generate cache' ); + + expect( file_exists( tenup_cache_file_path( $good ) ) )->toBeTrue(); + } +); + +it( + 'caches several directories in a single invocation', + function (): void { + $first = tenup_example_copy( 'plugin-inc' ); + $second = tenup_example_copy( 'second-inc' ); + $this->temp_dirs[] = $first; + $this->temp_dirs[] = $second; + + $result = tenup_run_cache_bin( [ $first, $second ] ); + + expect( $result['exit'] )->toBe( 0, $result['stderr'] ); + expect( file_exists( tenup_cache_file_path( $first ) ) )->toBeTrue(); + expect( file_exists( tenup_cache_file_path( $second ) ) )->toBeTrue(); + + $cached = require tenup_cache_file_path( $second ); + expect( $cached )->toContain( 'TenupFrameworkExamples\\Widgets\\Card' ); + } +); diff --git a/tests/Unit/Cache/ReadOnlyFileDiscoverCacheDriverTest.php b/tests/Unit/Cache/ReadOnlyFileDiscoverCacheDriverTest.php new file mode 100644 index 0000000..1d782a4 --- /dev/null +++ b/tests/Unit/Cache/ReadOnlyFileDiscoverCacheDriverTest.php @@ -0,0 +1,83 @@ +dir = sys_get_temp_dir() . '/tenup_ro_driver_' . uniqid( '', true ); + mkdir( $this->dir ); + } +); + +afterEach( + function (): void { + if ( ! is_dir( $this->dir ) ) { + return; + } + + $files = glob( $this->dir . '/*' ); + + if ( false !== $files ) { + array_map( 'unlink', $files ); + } + + rmdir( $this->dir ); + } +); + +it( + 'does not create the cache directory in the constructor', + function (): void { + $missing = sys_get_temp_dir() . '/tenup_ro_missing_' . uniqid( '', true ); + + new ReadOnlyFileDiscoverCacheDriver( $missing, false, 'cache.php' ); + + expect( is_dir( $missing ) )->toBeFalse(); + } +); + +it( + 'writes nothing when put() is called', + function (): void { + $driver = new ReadOnlyFileDiscoverCacheDriver( $this->dir, false, 'cache.php' ); + + $driver->put( 'id', [ 'Foo\\Bar' ] ); + + expect( $driver->has( 'id' ) )->toBeFalse(); + expect( file_exists( $this->dir . '/cache.php' ) )->toBeFalse(); + } +); + +it( + 'deletes nothing when forget() is called', + function (): void { + file_put_contents( $this->dir . '/cache.php', 'dir, false, 'cache.php' ); + $driver->forget( 'id' ); + + expect( file_exists( $this->dir . '/cache.php' ) )->toBeTrue(); + } +); + +it( + 'reads an existing cache file written with the generator settings', + function (): void { + // serialize = false and an explicit filename, matching generate_cache(). + file_put_contents( $this->dir . '/cache.php', "dir, false, 'cache.php' ); + + expect( $driver->has( 'id' ) )->toBeTrue(); + expect( $driver->get( 'id' ) )->toBe( [ 'Foo\\Bar' ] ); + } +); diff --git a/tests/Unit/ModuleInitialization/ClassCacheTest.php b/tests/Unit/ModuleInitialization/ClassCacheTest.php new file mode 100644 index 0000000..f9ca4e5 --- /dev/null +++ b/tests/Unit/ModuleInitialization/ClassCacheTest.php @@ -0,0 +1,119 @@ +dir = tenup_temp_class_dir(); + } +); + +afterEach( + function (): void { + tenup_remove_dir( $this->dir ); + } +); + +it( + 'writes a readable cache file and returns the discovered classes', + function (): void { + $cached = ModuleInitialization::instance()->generate_cache( $this->dir ); + + expect( file_exists( tenup_cache_file_path( $this->dir ) ) )->toBeTrue(); + expect( $cached )->toContain( 'TenupTmp\\Widget' ); + } +); + +it( + 'reads the cache file when one is present', + function (): void { + $module_init = ModuleInitialization::instance(); + $module_init->generate_cache( $this->dir ); + + // Tamper with the cache so a sentinel proves the read path used it rather than rescanning. + file_put_contents( tenup_cache_file_path( $this->dir ), "get_classes( $this->dir ) ) )->toBe( [ 'TenupTmp\\Sentinel' ] ); + } +); + +it( + 'writes no cache when none exists', + function (): void { + $classes = ModuleInitialization::instance()->get_classes( $this->dir ); + + expect( $classes )->toContain( 'TenupTmp\\Widget' ); + // The whole point of the read-only runtime: a server can never create a cache it must later invalidate. + expect( is_dir( $this->dir . '/' . ModuleInitialization::CACHE_DIR_NAME ) )->toBeFalse(); + } +); + +it( + 'ignores a cache written by an older framework version', + function (): void { + $cache_dir = $this->dir . '/' . ModuleInitialization::CACHE_DIR_NAME; + mkdir( $cache_dir ); + + // 1.x wrote `discoverer-cache-{id}` as a serialized file under a different name. + file_put_contents( $cache_dir . '/discoverer-cache-TenupFramework', serialize( [ 'TenupTmp\\Legacy' ] ) ); + + $read = ModuleInitialization::instance()->get_classes( $this->dir ); + + expect( $read )->not->toContain( 'TenupTmp\\Legacy' ); + expect( $read )->toContain( 'TenupTmp\\Widget' ); + } +); + +it( + 'falls back to a live scan when the cache is corrupt', + function ( string $contents ): void { + $cache_dir = $this->dir . '/' . ModuleInitialization::CACHE_DIR_NAME; + + if ( ! is_dir( $cache_dir ) ) { + mkdir( $cache_dir ); + } + + // require() on this raises a ParseError, which the read path must catch rather than fatal. + file_put_contents( tenup_cache_file_path( $this->dir ), $contents ); + + expect( ModuleInitialization::instance()->get_classes( $this->dir ) )->toContain( 'TenupTmp\\Widget' ); + } +)->with( + [ + 'unterminated array' => [ " [ 'generate_cache( $this->dir ); + + file_put_contents( tenup_cache_file_path( $this->dir ), "discover_live( $this->dir ); + + expect( $live )->toContain( 'TenupTmp\\Widget' ); + expect( $live )->not->toContain( 'TenupTmp\\Sentinel' ); + } +); + +it( + 'throws when the directory does not exist', + function (): void { + ModuleInitialization::instance()->get_classes( $this->dir . '/does-not-exist-1234567' ); + } +)->throws( RuntimeException::class ); diff --git a/tests/Unit/ModuleInitialization/DisableCacheConstantTest.php b/tests/Unit/ModuleInitialization/DisableCacheConstantTest.php new file mode 100644 index 0000000..89243bc --- /dev/null +++ b/tests/Unit/ModuleInitialization/DisableCacheConstantTest.php @@ -0,0 +1,33 @@ +toBe( 0, $result['stderr'] ); + + $classes = json_decode( $result['stdout'], true, 512, JSON_THROW_ON_ERROR ); + + // The tampered cache's sentinel proves whether the cache was read. + expect( $classes )->not->toContain( 'TenupTmp\\Sentinel' ); + expect( $classes )->toContain( 'TenupTmp\\Widget' ); + + tenup_remove_dir( $dir ); + } +); diff --git a/tests/Unit/SmokeTest.php b/tests/Unit/SmokeTest.php new file mode 100644 index 0000000..ed6d150 --- /dev/null +++ b/tests/Unit/SmokeTest.php @@ -0,0 +1,15 @@ +toBeTrue(); + } +); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 31479ab..baf6010 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,12 +1,77 @@ sys_get_temp_dir() . '/wp-framework-tests-wordpress', + 'WP_DB_NAME' => TENUP_FRAMEWORK_TEST_DB, + 'WP_DB_USER' => 'root', + 'WP_DB_PASSWORD' => '', + 'WP_DB_HOST' => '127.0.0.1', +]; + +foreach ( $tenup_framework_env_defaults as $tenup_framework_variable => $tenup_framework_default ) { + if ( false === getenv( $tenup_framework_variable ) ) { + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_putenv + putenv( "{$tenup_framework_variable}={$tenup_framework_default}" ); + } +} + +unset( $tenup_framework_env_defaults, $tenup_framework_variable, $tenup_framework_default ); + +/* + * Refuse to run against a database we were not pointed at. The installer is destructive, so + * a config that has drifted (a stale cached file, a stray WP_DB_NAME) must stop the suite + * rather than quietly rebuild someone else's tables. + */ +$tenup_framework_config = getenv( 'WP_CORE_DIR' ) . '/wp-tests-config.php'; + +if ( is_readable( $tenup_framework_config ) ) { + $tenup_framework_contents = (string) file_get_contents( $tenup_framework_config ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + $tenup_framework_expected = getenv( 'WP_DB_NAME' ); + + if ( ! str_contains( $tenup_framework_contents, "'DB_NAME', '{$tenup_framework_expected}'" ) ) { + fwrite( + STDERR, + PHP_EOL . 'Refusing to run: ' . $tenup_framework_config + . ' does not target the expected test database (' . $tenup_framework_expected . ').' + . PHP_EOL . 'Delete that file and re-run so it is regenerated.' . PHP_EOL + ); + exit( 1 ); + } + + unset( $tenup_framework_contents, $tenup_framework_expected ); +} -namespace TenupFrameworkTests; +unset( $tenup_framework_config ); -require dirname( __DIR__, 1 ) . '/vendor/autoload.php'; +\Mantle\Testing\install(); diff --git a/tests/scripts/disable-class-cache.php b/tests/scripts/disable-class-cache.php new file mode 100644 index 0000000..798ba21 --- /dev/null +++ b/tests/scripts/disable-class-cache.php @@ -0,0 +1,39 @@ + + * Prints the discovered class list as JSON. + * + * @package TenupFramework + */ + +declare( strict_types = 1 ); + +use TenupFramework\ModuleInitialization; + +require dirname( __DIR__, 2 ) . '/vendor/autoload.php'; + +if ( ! isset( $argv[1] ) ) { + fwrite( STDERR, 'Usage: disable-class-cache.php ' . PHP_EOL ); + exit( 1 ); +} + +$dir = $argv[1]; +$module_init = ModuleInitialization::instance(); + +// Build a real cache, then tamper with it: the sentinel is what a cache read would return. +$module_init->generate_cache( $dir ); + +file_put_contents( + $dir . '/' . ModuleInitialization::CACHE_DIR_NAME . '/' . ModuleInitialization::CACHE_FILENAME, + "get_classes( $dir ) ) ); From c43971a347af301b56631db11bafad64c6312c84 Mon Sep 17 00:00:00 2001 From: Daryll Doyle Date: Thu, 30 Jul 2026 18:29:24 +0100 Subject: [PATCH 2/2] fix: run PHP checks on pull requests against any base branch The pull_request trigger filtered on branches: [trunk, develop], and that filter matches the base branch of the PR. Every pull request opened against a release branch such as feature/2.0.0 was therefore ineligible, and the checks tab reported no checks for the commit rather than a failure. Drop the filter so all pull requests run, keep the push filter on the long-lived branches so PR commits do not run twice, and add workflow_dispatch for manual runs. --- .github/workflows/php.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 4a206f0..f213687 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -3,8 +3,12 @@ name: PHP Checks on: push: branches: ["trunk", "develop"] + # Deliberately unfiltered. A `branches` list here matches the PR's *base* branch, so + # restricting it to trunk/develop silently skipped every PR opened against a release branch + # such as feature/2.0.0 - the checks tab simply reported nothing for the commit. Pushes stay + # limited to the long-lived branches so PR commits do not run twice. pull_request: - branches: ["trunk", "develop"] + workflow_dispatch: permissions: contents: read