diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index d122ce7..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 @@ -55,9 +59,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 +97,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 ) ) );