diff --git a/.codex b/.codex
new file mode 100644
index 0000000..e69de29
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..8553979
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,66 @@
+name: Tests
+
+on:
+ pull_request:
+ push:
+ branches:
+ - master
+ - beta
+ - feat/**
+
+jobs:
+ phpunit:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.1'
+ coverage: none
+
+ - name: Install Composer dependencies
+ uses: ramsey/composer-install@v3
+
+ - name: Install WP-CLI if missing
+ run: |
+ if command -v wp >/dev/null 2>&1; then
+ wp --info
+ exit 0
+ fi
+
+ curl -fsSL -o /tmp/wp-cli.phar https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
+ chmod +x /tmp/wp-cli.phar
+ sudo mv /tmp/wp-cli.phar /usr/local/bin/wp
+ wp --info
+
+ - name: Start test services
+ run: composer test:env:start
+
+ - name: Install WordPress test environment
+ run: composer test:install
+
+ - name: Run PHPUnit
+ run: composer test
+
+ semantic-release-dry-run:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Semantic Release Dry Run
+ uses: cycjimmy/semantic-release-action@v4
+ with:
+ dry_run: true
+ extra_plugins: |
+ @semantic-release/github
+ @semantic-release/exec
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 22d0d82..23b345f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,4 @@
vendor
+.worktrees
+.cache
+.phpunit.result.cache
diff --git a/.releaserc b/.releaserc
index 9053ecc..c09df5f 100644
--- a/.releaserc
+++ b/.releaserc
@@ -20,7 +20,7 @@
[
"@semantic-release/exec",
{
- "prepareCmd": "zip -r '/tmp/release.zip' ./src README.md"
+ "prepareCmd": "zip -r '/tmp/release.zip' src lib composer.json README.md LICENSE"
}
],
[
diff --git a/README.md b/README.md
index 6bfdefe..e3bb658 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,123 @@
-# WC Data eXtender
+
+
+
WC Data Type
+Model-driven custom data objects for WooCommerce
+
+[](https://packagist.org/packages/x-wp/wc-data-type)
+
+
+
+[](https://github.com/x-wp/wc-data-type/actions/workflows/tests.yml)
+
+
+
+This library provides a standardized way to define and work with custom WooCommerce-style data objects. Define a model once, then reuse consistent property handling, factories, queries, and data-store behavior around it.
+
+## Key Features
+
+1. Model-driven setup: Define object shape with the `#[Model]` attribute.
+2. WooCommerce-style objects: Extend `XWC_Data` and keep a familiar CRUD workflow.
+3. Typed properties: Support for core props, meta props, and taxonomy props.
+4. Shared object helpers: Load single objects or collections through package utilities.
+5. Extensible architecture: Swap in custom data stores, factories, and meta stores when needed.
+6. WordPress-native integration: Designed for plugin code already built around WordPress and WooCommerce lifecycles.
+
+## Installation
+
+You can install this package via Composer:
+
+```bash
+composer require x-wp/wc-data-type
+```
+
+> [!TIP]
+> We recommend using `automattic/jetpack-autoloader` with this package to reduce autoloading conflicts in WordPress environments.
+
+## Usage
+
+Below is a simple example that defines a custom data object and loads it through the package helpers.
+
+### Defining a model
+
+```php
+ array(
+ 'default' => '',
+ 'type' => 'string',
+ ),
+ 'slug' => array(
+ 'default' => '',
+ 'type' => 'slug',
+ ),
+ 'price' => array(
+ 'default' => 0,
+ 'type' => 'float',
+ ),
+ ),
+)]
+final class Book extends XWC_Data {
+ protected $object_type = 'book';
+}
+```
+
+### Loading objects
+
+```php
+ 10,
+ 'orderby' => 'title',
+ 'order' => 'ASC',
+ ),
+);
+```
+
+Generated getters and setters follow the declared props, so classes like `Book` can expose methods such as `get_title()`, `set_title()`, `get_slug()`, and `set_price()`.
+
+## Testing
+
+The package ships with a PHPUnit suite that boots a WordPress test environment and exercises model definitions, object factories, queries, props, and runtime object behavior.
+
+Run the suite with:
+
+```bash
+composer test
+```
+
+To prepare the local WordPress test environment first:
+
+```bash
+composer test:install
+composer test
+```
+
+To clean the local test environment:
+
+```bash
+composer test:clean
+```
+
+## Documentation
+
+For package-specific usage, start with the public entrypoints used throughout the library:
+
+- `XWC\Data\Decorators\Model`
+- `XWC_Data`
+- `xwc_get_object()`
+- `xwc_get_objects()`
+
+Additional project information is available in the [repository](https://github.com/x-wp/wc-data-type).
+
+For maintainers preparing prereleases or stable tags, see [docs/release-process.md](docs/release-process.md).
diff --git a/bin/clean-wp-tests.sh b/bin/clean-wp-tests.sh
new file mode 100755
index 0000000..d9080c5
--- /dev/null
+++ b/bin/clean-wp-tests.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+
+rm -rf "${REPO_ROOT}/.cache/wp-tests"
diff --git a/bin/install-wp-tests.sh b/bin/install-wp-tests.sh
new file mode 100755
index 0000000..bf6a5d4
--- /dev/null
+++ b/bin/install-wp-tests.sh
@@ -0,0 +1,216 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+CACHE_DIR="${REPO_ROOT}/.cache"
+WP_CORE_DIR="${WP_CORE_DIR:-${CACHE_DIR}/wp-tests/wordpress}"
+WP_TESTS_DIR="${WP_TESTS_DIR:-${CACHE_DIR}/wp-tests/lib}"
+WP_DOWNLOADS_DIR="${CACHE_DIR}/wp-tests/downloads"
+WP_DEVELOP_DIR="${CACHE_DIR}/wp-tests/wordpress-develop"
+PLUGIN_SLUG="xwc-data-type-tests"
+PLUGIN_SOURCE_DIR="${REPO_ROOT}/tests/wp-plugin/${PLUGIN_SLUG}"
+PLUGIN_TARGET_DIR="${WP_CORE_DIR}/wp-content/plugins/${PLUGIN_SLUG}"
+
+DB_NAME="${DB_NAME:-wordpress_test}"
+DB_USER="${DB_USER:-wordpress}"
+DB_PASSWORD="${DB_PASSWORD:-wordpress}"
+DB_HOST="${DB_HOST:-127.0.0.1}"
+DB_PORT="${DB_PORT:-33067}"
+DB_ROOT_USER="${DB_ROOT_USER:-root}"
+DB_ROOT_PASSWORD="${DB_ROOT_PASSWORD:-root}"
+
+WP_VERSION="${WP_VERSION:-latest}"
+WC_VERSION="${WC_VERSION:-latest}"
+
+log() {
+ printf '[xwc-tests] %s\n' "$1" >&2
+}
+
+wp_cli() {
+ php -d memory_limit=512M "$(command -v wp)" "$@"
+}
+
+require_command() {
+ if ! command -v "$1" >/dev/null 2>&1; then
+ printf 'Missing required command: %s\n' "$1" >&2
+ exit 1
+ fi
+}
+
+wait_for_database() {
+ log "Waiting for MySQL at ${DB_HOST}:${DB_PORT}"
+
+ for _ in $(seq 1 30); do
+ if mysqladmin ping \
+ --protocol=tcp \
+ --host="${DB_HOST}" \
+ --port="${DB_PORT}" \
+ --user="${DB_ROOT_USER}" \
+ --password="${DB_ROOT_PASSWORD}" \
+ --silent >/dev/null 2>&1; then
+ return 0
+ fi
+
+ sleep 2
+ done
+
+ printf 'Timed out waiting for MySQL.\n' >&2
+ exit 1
+}
+
+ensure_database() {
+ mysql \
+ --protocol=tcp \
+ --host="${DB_HOST}" \
+ --port="${DB_PORT}" \
+ --user="${DB_ROOT_USER}" \
+ --password="${DB_ROOT_PASSWORD}" \
+ --execute="CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\`;"
+}
+
+ensure_wordpress() {
+ mkdir -p "${WP_CORE_DIR}" "${WP_DOWNLOADS_DIR}"
+
+ if [ ! -f "${WP_CORE_DIR}/wp-load.php" ]; then
+ log "Downloading WordPress ${WP_VERSION}"
+ wp_cli core download --version="${WP_VERSION}" --path="${WP_CORE_DIR}" --force
+ fi
+
+ if [ ! -f "${WP_CORE_DIR}/wp-config.php" ]; then
+ log 'Creating wp-config.php'
+ wp_cli config create \
+ --path="${WP_CORE_DIR}" \
+ --dbname="${DB_NAME}" \
+ --dbuser="${DB_USER}" \
+ --dbpass="${DB_PASSWORD}" \
+ --dbhost="${DB_HOST}:${DB_PORT}" \
+ --skip-check \
+ --force
+ fi
+
+ if ! wp_cli core is-installed --path="${WP_CORE_DIR}" >/dev/null 2>&1; then
+ log 'Installing WordPress site'
+ wp_cli core install \
+ --path="${WP_CORE_DIR}" \
+ --url="http://localhost:8080" \
+ --title="XWC Data Type Tests" \
+ --admin_user="admin" \
+ --admin_password="password" \
+ --admin_email="admin@example.org"
+ fi
+}
+
+download_wordpress_develop() {
+ local wp_core_version="$1"
+ local archive_path="${WP_DOWNLOADS_DIR}/wordpress-develop-${wp_core_version}.zip"
+ local extract_root="${WP_DOWNLOADS_DIR}/wordpress-develop-${wp_core_version}"
+ local source_root
+
+ if [ ! -d "${extract_root}" ]; then
+ mkdir -p "${WP_DOWNLOADS_DIR}"
+
+ if [ ! -f "${archive_path}" ]; then
+ log "Downloading wordpress-develop ${wp_core_version}"
+ if ! curl -fsSL -o "${archive_path}" "https://github.com/WordPress/wordpress-develop/archive/refs/tags/${wp_core_version}.zip"; then
+ log "Falling back to trunk for wordpress-develop ${wp_core_version}"
+ curl -fsSL -o "${archive_path}" "https://github.com/WordPress/wordpress-develop/archive/refs/heads/trunk.zip"
+ fi
+ fi
+
+ rm -rf "${extract_root}"
+ mkdir -p "${extract_root}"
+ unzip -q -o "${archive_path}" -d "${extract_root}"
+ fi
+
+ source_root="$(find "${extract_root}" -mindepth 1 -maxdepth 1 -type d | head -n 1)"
+
+ if [ -z "${source_root}" ]; then
+ printf 'Unable to locate extracted wordpress-develop files.\n' >&2
+ exit 1
+ fi
+
+ printf '%s\n' "${source_root}"
+}
+
+ensure_wordpress_tests_suite() {
+ local wp_core_version source_root
+
+ mkdir -p "${WP_TESTS_DIR}"
+
+ if [ -f "${WP_TESTS_DIR}/includes/bootstrap.php" ] && [ -f "${WP_TESTS_DIR}/wp-tests-config.php" ]; then
+ return 0
+ fi
+
+ wp_core_version="$(wp_cli core version --path="${WP_CORE_DIR}")"
+ source_root="$(download_wordpress_develop "${wp_core_version}")"
+
+ log "Preparing WordPress test suite for ${wp_core_version}"
+ rm -rf "${WP_TESTS_DIR}"
+ mkdir -p "${WP_TESTS_DIR}"
+ cp -R "${source_root}/tests/phpunit/." "${WP_TESTS_DIR}/"
+
+ cat > "${WP_TESTS_DIR}/wp-tests-config.php" </dev/null 2>&1 || true
+}
+
+main() {
+ require_command curl
+ require_command docker
+ require_command mysql
+ require_command mysqladmin
+ require_command php
+ require_command unzip
+ require_command wp
+
+ export WP_CLI_CACHE_DIR="${CACHE_DIR}/wp-cli"
+
+ wait_for_database
+ ensure_database
+ ensure_wordpress
+ ensure_wordpress_tests_suite
+ ensure_test_plugin
+ ensure_woocommerce
+
+ log 'WordPress test environment is ready'
+}
+
+main "$@"
diff --git a/composer.json b/composer.json
index c0e04e5..1c14cc2 100644
--- a/composer.json
+++ b/composer.json
@@ -21,17 +21,20 @@
"symfony/polyfill-php81": "^1.30",
"x-wp/helper-classes": "^1",
"x-wp/helper-functions": "^1",
- "x-wp/di-implementation": "^1"
+ "x-wp/di-implementation": "^1",
+ "symfony/polyfill-php83": "^1.32"
},
"require-dev": {
"x-wp/di": "^1.0 || ^2.0",
"oblak/wordpress-coding-standard": "^1",
"php-stubs/wordpress-stubs": "^6.5",
+ "phpunit/phpunit": "^9.6",
"phpstan/extension-installer": "^1.3",
"phpstan/phpstan": "^1.10",
"phpstan/phpstan-deprecation-rules": "^1.1",
"swissspidy/phpstan-no-private": "^0.2.0",
- "szepeviktor/phpstan-wordpress": "^1.3"
+ "szepeviktor/phpstan-wordpress": "^1.3",
+ "yoast/phpunit-polyfills": "^4.0"
},
"suggest": {
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
@@ -41,7 +44,8 @@
"XWC\\Data\\": "src/"
},
"classmap": [
- "src/Core/"
+ "src/Core/",
+ "src/Interfaces/"
],
"files": [
"lib/bootstrap.php",
@@ -58,5 +62,15 @@
"phpstan/extension-installer": true
},
"platform-check": false
+ },
+ "scripts": {
+ "test:env:start": "docker compose up -d mysql",
+ "test:env:stop": "docker compose down --remove-orphans",
+ "test:install": [
+ "@test:env:start",
+ "bash bin/install-wp-tests.sh"
+ ],
+ "test": "vendor/bin/phpunit",
+ "test:clean": "bash bin/clean-wp-tests.sh"
}
}
diff --git a/composer.lock b/composer.lock
index 547943b..0f88e95 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,29 +4,30 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "ba617a63daade3fbd8e55a7b67456bda",
+ "content-hash": "9c80341551de30f4febf11a40e3223d9",
"packages": [
{
"name": "automattic/jetpack-constants",
- "version": "v2.0.5",
+ "version": "v3.0.8",
"source": {
"type": "git",
"url": "https://github.com/Automattic/jetpack-constants.git",
- "reference": "0c2644d642b06ae2a31c561f5bfc6f74a4abc8f1"
+ "reference": "f9bf00ab48956b8326209e7c0baf247a0ed721c4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Automattic/jetpack-constants/zipball/0c2644d642b06ae2a31c561f5bfc6f74a4abc8f1",
- "reference": "0c2644d642b06ae2a31c561f5bfc6f74a4abc8f1",
+ "url": "https://api.github.com/repos/Automattic/jetpack-constants/zipball/f9bf00ab48956b8326209e7c0baf247a0ed721c4",
+ "reference": "f9bf00ab48956b8326209e7c0baf247a0ed721c4",
"shasum": ""
},
"require": {
- "php": ">=7.0"
+ "php": ">=7.2"
},
"require-dev": {
- "automattic/jetpack-changelogger": "^4.2.8",
- "brain/monkey": "2.6.1",
- "yoast/phpunit-polyfills": "^1.1.1"
+ "automattic/jetpack-changelogger": "^6.0.5",
+ "automattic/phpunit-select-config": "^1.0.3",
+ "brain/monkey": "^2.6.2",
+ "yoast/phpunit-polyfills": "^4.0.0"
},
"suggest": {
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
@@ -36,7 +37,7 @@
"autotagger": true,
"mirror-repo": "Automattic/jetpack-constants",
"branch-alias": {
- "dev-trunk": "2.0.x-dev"
+ "dev-trunk": "3.0.x-dev"
},
"changelogger": {
"link-template": "https://github.com/Automattic/jetpack-constants/compare/v${old}...v${new}"
@@ -53,22 +54,22 @@
],
"description": "A wrapper for defining constants in a more testable way.",
"support": {
- "source": "https://github.com/Automattic/jetpack-constants/tree/v2.0.5"
+ "source": "https://github.com/Automattic/jetpack-constants/tree/v3.0.8"
},
- "time": "2024-11-04T09:23:35+00:00"
+ "time": "2025-04-28T15:12:45+00:00"
},
{
"name": "laravel/serializable-closure",
- "version": "v2.0.2",
+ "version": "v2.0.4",
"source": {
"type": "git",
"url": "https://github.com/laravel/serializable-closure.git",
- "reference": "2e1a362527783bcab6c316aad51bf36c5513ae44"
+ "reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/2e1a362527783bcab6c316aad51bf36c5513ae44",
- "reference": "2e1a362527783bcab6c316aad51bf36c5513ae44",
+ "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/b352cf0534aa1ae6b4d825d1e762e35d43f8a841",
+ "reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841",
"shasum": ""
},
"require": {
@@ -116,7 +117,7 @@
"issues": "https://github.com/laravel/serializable-closure/issues",
"source": "https://github.com/laravel/serializable-closure"
},
- "time": "2025-01-24T15:42:37+00:00"
+ "time": "2025-03-19T13:51:03+00:00"
},
{
"name": "php-di/invoker",
@@ -175,16 +176,16 @@
},
{
"name": "php-di/php-di",
- "version": "7.0.8",
+ "version": "7.0.11",
"source": {
"type": "git",
"url": "https://github.com/PHP-DI/PHP-DI.git",
- "reference": "98ddc81f8f768a2ad39e4cbe737285eaeabe577a"
+ "reference": "32f111a6d214564520a57831d397263e8946c1d2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/98ddc81f8f768a2ad39e4cbe737285eaeabe577a",
- "reference": "98ddc81f8f768a2ad39e4cbe737285eaeabe577a",
+ "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/32f111a6d214564520a57831d397263e8946c1d2",
+ "reference": "32f111a6d214564520a57831d397263e8946c1d2",
"shasum": ""
},
"require": {
@@ -200,8 +201,8 @@
"friendsofphp/php-cs-fixer": "^3",
"friendsofphp/proxy-manager-lts": "^1",
"mnapoli/phpunit-easymock": "^1.3",
- "phpunit/phpunit": "^9.6",
- "vimeo/psalm": "^4.6"
+ "phpunit/phpunit": "^9.6 || ^10 || ^11",
+ "vimeo/psalm": "^5|^6"
},
"suggest": {
"friendsofphp/proxy-manager-lts": "Install it if you want to use lazy injection (version ^1)"
@@ -232,7 +233,7 @@
],
"support": {
"issues": "https://github.com/PHP-DI/PHP-DI/issues",
- "source": "https://github.com/PHP-DI/PHP-DI/tree/7.0.8"
+ "source": "https://github.com/PHP-DI/PHP-DI/tree/7.0.11"
},
"funding": [
{
@@ -244,7 +245,7 @@
"type": "tidelift"
}
],
- "time": "2025-01-28T21:02:46+00:00"
+ "time": "2025-06-03T07:45:57+00:00"
},
{
"name": "psr/container",
@@ -301,7 +302,7 @@
},
{
"name": "symfony/polyfill-php81",
- "version": "v1.31.0",
+ "version": "v1.32.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
@@ -357,7 +358,83 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0"
+ "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php83",
+ "version": "v1.32.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php83.git",
+ "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491",
+ "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php83\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0"
},
"funding": [
{
@@ -377,20 +454,20 @@
},
{
"name": "x-wp/di",
- "version": "v1.7.4",
+ "version": "v1.8.1",
"source": {
"type": "git",
"url": "https://github.com/x-wp/di.git",
- "reference": "21a26e76e405842460ab68f32dd4aedef0deef8a"
+ "reference": "ab0eed96c4c594c0e6c832f54ebdcbb3ebe74caa"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/x-wp/di/zipball/21a26e76e405842460ab68f32dd4aedef0deef8a",
- "reference": "21a26e76e405842460ab68f32dd4aedef0deef8a",
+ "url": "https://api.github.com/repos/x-wp/di/zipball/ab0eed96c4c594c0e6c832f54ebdcbb3ebe74caa",
+ "reference": "ab0eed96c4c594c0e6c832f54ebdcbb3ebe74caa",
"shasum": ""
},
"require": {
- "automattic/jetpack-constants": "^2",
+ "automattic/jetpack-constants": "^2 || ^3",
"php": ">=8.0",
"php-di/php-di": "^7",
"symfony/polyfill-php81": "^1.31",
@@ -401,19 +478,23 @@
"oblak/wp-hook-di": "*"
},
"provide": {
- "psr/container-implementation": "^1.0",
+ "psr/container-implementation": "1.1 || 2.0",
"x-wp/di-implementation": "self.version"
},
"require-dev": {
+ "automattic/jetpack-autoloader": "*",
"oblak/wordpress-coding-standard": "^1.1",
+ "php-stubs/woocommerce-stubs": "^9.5",
"php-stubs/wordpress-stubs": "^6.6",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "^1.12",
"phpstan/phpstan-deprecation-rules": "^1.2",
"swissspidy/phpstan-no-private": "^0.2",
"symfony/polyfill-php82": "^1.31",
+ "symfony/var-dumper": "^5.4",
"szepeviktor/phpstan-wordpress": "^1.3",
- "wp-cli/wp-cli": "^2.11"
+ "wp-cli/wp-cli": "^2.11",
+ "x-wp/whoops": "^1.1"
},
"suggest": {
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
@@ -452,22 +533,28 @@
],
"support": {
"issues": "https://github.com/x-wp/hook-manager/issues",
- "source": "https://github.com/x-wp/di/tree/v1.7.4"
+ "source": "https://github.com/x-wp/di/tree/v1.8.1"
},
- "time": "2025-02-05T17:03:52+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/seebeen",
+ "type": "github"
+ }
+ ],
+ "time": "2025-07-06T19:03:40+00:00"
},
{
"name": "x-wp/helper-classes",
- "version": "v1.19.3",
+ "version": "v1.21.0",
"source": {
"type": "git",
"url": "https://github.com/x-wp/helper-classes.git",
- "reference": "a8d3424b875696c87d5dc26e73d66dbbb9992d74"
+ "reference": "9d0e4611a41846e5407c61548d0de2f7da9c7478"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/x-wp/helper-classes/zipball/a8d3424b875696c87d5dc26e73d66dbbb9992d74",
- "reference": "a8d3424b875696c87d5dc26e73d66dbbb9992d74",
+ "url": "https://api.github.com/repos/x-wp/helper-classes/zipball/9d0e4611a41846e5407c61548d0de2f7da9c7478",
+ "reference": "9d0e4611a41846e5407c61548d0de2f7da9c7478",
"shasum": ""
},
"require": {
@@ -508,22 +595,28 @@
],
"support": {
"issues": "https://github.com/x-wp/helper-classes/issues",
- "source": "https://github.com/x-wp/helper-classes/tree/v1.19.3"
+ "source": "https://github.com/x-wp/helper-classes/tree/v1.21.0"
},
- "time": "2025-02-05T15:40:56+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/seebeen",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-11T18:24:14+00:00"
},
{
"name": "x-wp/helper-functions",
- "version": "v1.19.3",
+ "version": "v1.21.0",
"source": {
"type": "git",
"url": "https://github.com/x-wp/helper-functions.git",
- "reference": "0430cff023ec47d99ed527501e5189ca799681c5"
+ "reference": "b6696bc39b68e3df1b9da77c7d4226347861045c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/x-wp/helper-functions/zipball/0430cff023ec47d99ed527501e5189ca799681c5",
- "reference": "0430cff023ec47d99ed527501e5189ca799681c5",
+ "url": "https://api.github.com/repos/x-wp/helper-functions/zipball/b6696bc39b68e3df1b9da77c7d4226347861045c",
+ "reference": "b6696bc39b68e3df1b9da77c7d4226347861045c",
"shasum": ""
},
"require": {
@@ -536,6 +629,7 @@
"autoload": {
"files": [
"xwp-helper-fns-arr.php",
+ "xwp-helper-fns-meta.php",
"xwp-helper-fns-num.php",
"xwp-helper-fns-req.php",
"xwp-helper-fns.php"
@@ -566,22 +660,28 @@
],
"support": {
"issues": "https://github.com/x-wp/helper-functions/issues",
- "source": "https://github.com/x-wp/helper-functions/tree/v1.19.3"
+ "source": "https://github.com/x-wp/helper-functions/tree/v1.21.0"
},
- "time": "2025-02-01T19:59:36+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/seebeen",
+ "type": "github"
+ }
+ ],
+ "time": "2025-06-08T13:14:33+00:00"
},
{
"name": "x-wp/helper-traits",
- "version": "v1.19.3",
+ "version": "v1.21.0",
"source": {
"type": "git",
"url": "https://github.com/x-wp/helper-traits.git",
- "reference": "0367d136d6ba36e2ae0fe1854584ef760ea7cae9"
+ "reference": "c341b8ad27de9b1a73e2d53dc8702897a0cfba18"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/x-wp/helper-traits/zipball/0367d136d6ba36e2ae0fe1854584ef760ea7cae9",
- "reference": "0367d136d6ba36e2ae0fe1854584ef760ea7cae9",
+ "url": "https://api.github.com/repos/x-wp/helper-traits/zipball/c341b8ad27de9b1a73e2d53dc8702897a0cfba18",
+ "reference": "c341b8ad27de9b1a73e2d53dc8702897a0cfba18",
"shasum": ""
},
"require": {
@@ -618,36 +718,42 @@
],
"support": {
"issues": "https://github.com/x-wp/helper-traits/issues",
- "source": "https://github.com/x-wp/helper-traits/tree/v1.19.3"
+ "source": "https://github.com/x-wp/helper-traits/tree/v1.21.0"
},
- "time": "2024-09-18T12:43:44+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/seebeen",
+ "type": "github"
+ }
+ ],
+ "time": "2025-06-08T10:17:19+00:00"
}
],
"packages-dev": [
{
"name": "dealerdirect/phpcodesniffer-composer-installer",
- "version": "v1.0.0",
+ "version": "v1.1.1",
"source": {
"type": "git",
"url": "https://github.com/PHPCSStandards/composer-installer.git",
- "reference": "4be43904336affa5c2f70744a348312336afd0da"
+ "reference": "6e0fa428497bf560152ee73ffbb8af5c6a56b0dd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da",
- "reference": "4be43904336affa5c2f70744a348312336afd0da",
+ "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/6e0fa428497bf560152ee73ffbb8af5c6a56b0dd",
+ "reference": "6e0fa428497bf560152ee73ffbb8af5c6a56b0dd",
"shasum": ""
},
"require": {
- "composer-plugin-api": "^1.0 || ^2.0",
+ "composer-plugin-api": "^2.2",
"php": ">=5.4",
"squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0"
},
"require-dev": {
- "composer/composer": "*",
+ "composer/composer": "^2.2",
"ext-json": "*",
"ext-zip": "*",
- "php-parallel-lint/php-parallel-lint": "^1.3.1",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
"phpcompatibility/php-compatibility": "^9.0",
"yoast/phpunit-polyfills": "^1.0"
},
@@ -667,9 +773,9 @@
"authors": [
{
"name": "Franck Nijhof",
- "email": "franck.nijhof@dealerdirect.com",
- "homepage": "http://www.frenck.nl",
- "role": "Developer / IT Manager"
+ "email": "opensource@frenck.dev",
+ "homepage": "https://frenck.dev",
+ "role": "Open source developer"
},
{
"name": "Contributors",
@@ -677,7 +783,6 @@
}
],
"description": "PHP_CodeSniffer Standards Composer Installer Plugin",
- "homepage": "http://www.dealerdirect.com",
"keywords": [
"PHPCodeSniffer",
"PHP_CodeSniffer",
@@ -698,201 +803,529 @@
],
"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"
},
- "time": "2023-01-05T11:28:13+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/PHPCSStandards",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/jrfnl",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "open_collective"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/phpcsstandards",
+ "type": "thanks_dev"
+ }
+ ],
+ "time": "2025-06-27T17:24:01+00:00"
},
{
- "name": "oblak/wordpress-coding-standard",
- "version": "v1.3.0",
+ "name": "doctrine/instantiator",
+ "version": "2.0.0",
"source": {
"type": "git",
- "url": "https://github.com/oblakstudio/wordpress-coding-standards.git",
- "reference": "b9c6fd5c58edccc464927c9b9f239b0ee0692daf"
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/oblakstudio/wordpress-coding-standards/zipball/b9c6fd5c58edccc464927c9b9f239b0ee0692daf",
- "reference": "b9c6fd5c58edccc464927c9b9f239b0ee0692daf",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0",
+ "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0",
"shasum": ""
},
"require": {
- "ext-filter": "*",
- "php": ">=7.4",
- "phpcompatibility/php-compatibility": "^9",
- "phpcompatibility/phpcompatibility-wp": "^2.1",
- "phpcsstandards/phpcsextra": "^1.1",
- "phpcsstandards/phpcsutils": "^1.0",
- "slevomat/coding-standard": "^8.14",
- "squizlabs/php_codesniffer": "^3.8",
- "wp-coding-standards/wpcs": "^3"
+ "php": "^8.1"
},
"require-dev": {
- "phpcsstandards/phpcsdevtools": "^1.2.0"
+ "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"
},
- "suggest": {
- "dealerdirect/phpcodesniffer-composer-installer": "^1.0"
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+ }
},
- "type": "phpcodesniffer-standard",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
- "name": "Contributors",
- "homepage": "https://github.com/oblakstudio/wordpress-cofing-standard/graphs/contributors"
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "homepage": "https://ocramius.github.io/"
}
],
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+ "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
"keywords": [
- "phpcs",
- "standards",
- "static analysis",
- "wordpress"
+ "constructor",
+ "instantiate"
],
"support": {
- "issues": "https://github.com/oblakstudio/wordpress-coding-standards/issues",
- "source": "https://github.com/oblakstudio/wordpress-coding-standards/tree/v1.3.0"
+ "issues": "https://github.com/doctrine/instantiator/issues",
+ "source": "https://github.com/doctrine/instantiator/tree/2.0.0"
},
"funding": [
{
- "url": "https://github.com/seebeen",
- "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%2Finstantiator",
+ "type": "tidelift"
}
],
- "time": "2025-04-25T15:40:25+00:00"
+ "time": "2022-12-30T00:23:10+00:00"
},
{
- "name": "php-stubs/wordpress-stubs",
- "version": "v6.7.1",
+ "name": "myclabs/deep-copy",
+ "version": "1.13.4",
"source": {
"type": "git",
- "url": "https://github.com/php-stubs/wordpress-stubs.git",
- "reference": "83448e918bf06d1ed3d67ceb6a985fc266a02fd1"
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/83448e918bf06d1ed3d67ceb6a985fc266a02fd1",
- "reference": "83448e918bf06d1ed3d67ceb6a985fc266a02fd1",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
+ "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
"shasum": ""
},
- "require-dev": {
- "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
- "nikic/php-parser": "^4.13",
- "php": "^7.4 || ^8.0",
- "php-stubs/generator": "^0.8.3",
- "phpdocumentor/reflection-docblock": "^5.4.1",
- "phpstan/phpstan": "^1.11",
- "phpunit/phpunit": "^9.5",
- "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.1.1",
- "wp-coding-standards/wpcs": "3.1.0 as 2.3.0"
+ "require": {
+ "php": "^7.1 || ^8.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"
+ "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": "WordPress function and class declaration stubs for static analysis.",
- "homepage": "https://github.com/php-stubs/wordpress-stubs",
+ "description": "Create deep copies (clones) of your objects",
"keywords": [
- "PHPStan",
- "static analysis",
- "wordpress"
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
],
"support": {
- "issues": "https://github.com/php-stubs/wordpress-stubs/issues",
- "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.7.1"
+ "issues": "https://github.com/myclabs/DeepCopy/issues",
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
},
- "time": "2024-11-24T03:57:09+00:00"
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-01T08:46:24+00:00"
},
{
- "name": "phpcompatibility/php-compatibility",
- "version": "9.3.5",
+ "name": "nikic/php-parser",
+ "version": "v5.7.0",
"source": {
"type": "git",
- "url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
- "reference": "9fb324479acf6f39452e0655d2429cc0d3914243"
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243",
- "reference": "9fb324479acf6f39452e0655d2429cc0d3914243",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82",
+ "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82",
"shasum": ""
},
"require": {
- "php": ">=5.3",
- "squizlabs/php_codesniffer": "^2.3 || ^3.0.2"
- },
- "conflict": {
- "squizlabs/php_codesniffer": "2.6.2"
+ "ext-ctype": "*",
+ "ext-json": "*",
+ "ext-tokenizer": "*",
+ "php": ">=7.4"
},
"require-dev": {
- "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
+ "ircmaxell/php-yacc": "^0.0.7",
+ "phpunit/phpunit": "^9.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."
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
},
- "type": "phpcodesniffer-standard",
"notification-url": "https://packagist.org/downloads/",
"license": [
- "LGPL-3.0-or-later"
+ "BSD-3-Clause"
],
"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"
+ "name": "Nikita Popov"
}
],
- "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.",
- "homepage": "http://techblog.wimgodden.be/tag/codesniffer/",
+ "description": "A PHP parser written in PHP",
"keywords": [
- "compatibility",
- "phpcs",
- "standards"
+ "parser",
+ "php"
],
"support": {
- "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues",
- "source": "https://github.com/PHPCompatibility/PHPCompatibility"
+ "issues": "https://github.com/nikic/PHP-Parser/issues",
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0"
},
- "time": "2019-12-27T09:44:58+00:00"
+ "time": "2025-12-06T11:56:16+00:00"
},
{
- "name": "phpcompatibility/phpcompatibility-paragonie",
- "version": "1.3.3",
+ "name": "oblak/wordpress-coding-standard",
+ "version": "v1.3.0",
"source": {
"type": "git",
- "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git",
- "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac"
+ "url": "https://github.com/oblakstudio/wordpress-coding-standards.git",
+ "reference": "b9c6fd5c58edccc464927c9b9f239b0ee0692daf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/293975b465e0e709b571cbf0c957c6c0a7b9a2ac",
- "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac",
+ "url": "https://api.github.com/repos/oblakstudio/wordpress-coding-standards/zipball/b9c6fd5c58edccc464927c9b9f239b0ee0692daf",
+ "reference": "b9c6fd5c58edccc464927c9b9f239b0ee0692daf",
"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"
+ "ext-filter": "*",
+ "php": ">=7.4",
+ "phpcompatibility/php-compatibility": "^9",
+ "phpcompatibility/phpcompatibility-wp": "^2.1",
+ "phpcsstandards/phpcsextra": "^1.1",
+ "phpcsstandards/phpcsutils": "^1.0",
+ "slevomat/coding-standard": "^8.14",
+ "squizlabs/php_codesniffer": "^3.8",
+ "wp-coding-standards/wpcs": "^3"
+ },
+ "require-dev": {
+ "phpcsstandards/phpcsdevtools": "^1.2.0"
+ },
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0"
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/oblakstudio/wordpress-cofing-standard/graphs/contributors"
+ }
+ ],
+ "keywords": [
+ "phpcs",
+ "standards",
+ "static analysis",
+ "wordpress"
+ ],
+ "support": {
+ "issues": "https://github.com/oblakstudio/wordpress-coding-standards/issues",
+ "source": "https://github.com/oblakstudio/wordpress-coding-standards/tree/v1.3.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/seebeen",
+ "type": "github"
+ }
+ ],
+ "time": "2025-04-25T15:40:25+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.8.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-stubs/wordpress-stubs.git",
+ "reference": "92e444847d94f7c30f88c60004648f507688acd5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/92e444847d94f7c30f88c60004648f507688acd5",
+ "reference": "92e444847d94f7c30f88c60004648f507688acd5",
+ "shasum": ""
+ },
+ "conflict": {
+ "phpdocumentor/reflection-docblock": "5.6.1"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "nikic/php-parser": "^5.4",
+ "php": "^7.4 || ^8.0",
+ "php-stubs/generator": "^0.8.3",
+ "phpdocumentor/reflection-docblock": "^5.4.1",
+ "phpstan/phpstan": "^2.1",
+ "phpunit/phpunit": "^9.5",
+ "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.8.1"
+ },
+ "time": "2025-05-02T12:33:34+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.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git",
+ "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/293975b465e0e709b571cbf0c957c6c0a7b9a2ac",
+ "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac",
+ "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.",
@@ -1021,29 +1454,29 @@
},
{
"name": "phpcsstandards/phpcsextra",
- "version": "1.3.0",
+ "version": "1.4.0",
"source": {
"type": "git",
"url": "https://github.com/PHPCSStandards/PHPCSExtra.git",
- "reference": "46d08eb86eec622b96c466adec3063adfed280dd"
+ "reference": "fa4b8d051e278072928e32d817456a7fdb57b6ca"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/46d08eb86eec622b96c466adec3063adfed280dd",
- "reference": "46d08eb86eec622b96c466adec3063adfed280dd",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/fa4b8d051e278072928e32d817456a7fdb57b6ca",
+ "reference": "fa4b8d051e278072928e32d817456a7fdb57b6ca",
"shasum": ""
},
"require": {
"php": ">=5.4",
- "phpcsstandards/phpcsutils": "^1.0.9",
- "squizlabs/php_codesniffer": "^3.12.1"
+ "phpcsstandards/phpcsutils": "^1.1.0",
+ "squizlabs/php_codesniffer": "^3.13.0 || ^4.0"
},
"require-dev": {
"php-parallel-lint/php-console-highlighter": "^1.0",
- "php-parallel-lint/php-parallel-lint": "^1.3.2",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
"phpcsstandards/phpcsdevcs": "^1.1.6",
"phpcsstandards/phpcsdevtools": "^1.2.1",
- "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0"
+ "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4"
},
"type": "phpcodesniffer-standard",
"extra": {
@@ -1099,33 +1532,33 @@
"type": "thanks_dev"
}
],
- "time": "2025-04-20T23:35:32+00:00"
+ "time": "2025-06-14T07:40:39+00:00"
},
{
"name": "phpcsstandards/phpcsutils",
- "version": "1.0.12",
+ "version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/PHPCSStandards/PHPCSUtils.git",
- "reference": "87b233b00daf83fb70f40c9a28692be017ea7c6c"
+ "reference": "65355670ac17c34cd235cf9d3ceae1b9252c4dad"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/87b233b00daf83fb70f40c9a28692be017ea7c6c",
- "reference": "87b233b00daf83fb70f40c9a28692be017ea7c6c",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/65355670ac17c34cd235cf9d3ceae1b9252c4dad",
+ "reference": "65355670ac17c34cd235cf9d3ceae1b9252c4dad",
"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.10.0 || 4.0.x-dev@dev"
+ "squizlabs/php_codesniffer": "^3.13.0 || ^4.0"
},
"require-dev": {
"ext-filter": "*",
"php-parallel-lint/php-console-highlighter": "^1.0",
- "php-parallel-lint/php-parallel-lint": "^1.3.2",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
"phpcsstandards/phpcsdevcs": "^1.1.6",
- "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0"
+ "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0 || ^3.0.0"
},
"type": "phpcodesniffer-standard",
"extra": {
@@ -1162,6 +1595,7 @@
"phpcodesniffer-standard",
"phpcs",
"phpcs3",
+ "phpcs4",
"standards",
"static analysis",
"tokens",
@@ -1179,244 +1613,1689 @@
"type": "github"
},
{
- "url": "https://github.com/jrfnl",
+ "url": "https://github.com/jrfnl",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "open_collective"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/phpcsstandards",
+ "type": "thanks_dev"
+ }
+ ],
+ "time": "2025-06-12T04:32:33+00:00"
+ },
+ {
+ "name": "phpstan/extension-installer",
+ "version": "1.4.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/extension-installer.git",
+ "reference": "85e90b3942d06b2326fba0403ec24fe912372936"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936",
+ "reference": "85e90b3942d06b2326fba0403ec24fe912372936",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^2.0",
+ "php": "^7.2 || ^8.0",
+ "phpstan/phpstan": "^1.9.0 || ^2.0"
+ },
+ "require-dev": {
+ "composer/composer": "^2.0",
+ "php-parallel-lint/php-parallel-lint": "^1.2.0",
+ "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "PHPStan\\ExtensionInstaller\\Plugin"
+ },
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\ExtensionInstaller\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Composer plugin for automatic installation of PHPStan extensions",
+ "keywords": [
+ "dev",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/phpstan/extension-installer/issues",
+ "source": "https://github.com/phpstan/extension-installer/tree/1.4.3"
+ },
+ "time": "2024-09-04T20:21:43+00:00"
+ },
+ {
+ "name": "phpstan/phpdoc-parser",
+ "version": "2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpdoc-parser.git",
+ "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68",
+ "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68",
+ "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.1.0"
+ },
+ "time": "2025-02-19T13:28:12+00:00"
+ },
+ {
+ "name": "phpstan/phpstan",
+ "version": "1.12.27",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan.git",
+ "reference": "3a6e423c076ab39dfedc307e2ac627ef579db162"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3a6e423c076ab39dfedc307e2ac627ef579db162",
+ "reference": "3a6e423c076ab39dfedc307e2ac627ef579db162",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2|^8.0"
+ },
+ "conflict": {
+ "phpstan/phpstan-shim": "*"
+ },
+ "bin": [
+ "phpstan",
+ "phpstan.phar"
+ ],
+ "type": "library",
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPStan - PHP Static Analysis Tool",
+ "keywords": [
+ "dev",
+ "static analysis"
+ ],
+ "support": {
+ "docs": "https://phpstan.org/user-guide/getting-started",
+ "forum": "https://github.com/phpstan/phpstan/discussions",
+ "issues": "https://github.com/phpstan/phpstan/issues",
+ "security": "https://github.com/phpstan/phpstan/security/policy",
+ "source": "https://github.com/phpstan/phpstan-src"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/ondrejmirtes",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/phpstan",
+ "type": "github"
+ }
+ ],
+ "time": "2025-05-21T20:51:45+00:00"
+ },
+ {
+ "name": "phpstan/phpstan-deprecation-rules",
+ "version": "1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan-deprecation-rules.git",
+ "reference": "f94d246cc143ec5a23da868f8f7e1393b50eaa82"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/f94d246cc143ec5a23da868f8f7e1393b50eaa82",
+ "reference": "f94d246cc143ec5a23da868f8f7e1393b50eaa82",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0",
+ "phpstan/phpstan": "^1.12"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-parallel-lint": "^1.2",
+ "phpstan/phpstan-phpunit": "^1.0",
+ "phpunit/phpunit": "^9.5"
+ },
+ "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.",
+ "support": {
+ "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues",
+ "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.2.1"
+ },
+ "time": "2024-09-11T15:52:35+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "9.2.32",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5",
+ "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5",
+ "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"
+ },
+ "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"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "9.2.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/9.2.32"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-08-22T04:23:01+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "3.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
+ "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "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",
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2021-12-02T12:48:52+00:00"
+ },
+ {
+ "name": "phpunit/php-invoker",
+ "version": "3.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-invoker.git",
+ "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+ "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "ext-pcntl": "*",
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-pcntl": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Invoke callables with a timeout",
+ "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+ "keywords": [
+ "process"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+ "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T05:58:55+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+ "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.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",
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T05:33:50+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "5.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+ "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "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": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:16:10+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "9.6.34",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "b36f02317466907a230d3aa1d34467041271ef4a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b36f02317466907a230d3aa1d34467041271ef4a",
+ "reference": "b36f02317466907a230d3aa1d34467041271ef4a",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.5.0 || ^2",
+ "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": ">=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"
+ },
+ "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"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "9.6-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/9.6.34"
+ },
+ "funding": [
+ {
+ "url": "https://phpunit.de/sponsors.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-01-27T05:45:00+00:00"
+ },
+ {
+ "name": "sebastian/cli-parser",
+ "version": "1.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b",
+ "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for parsing CLI options",
+ "homepage": "https://github.com/sebastianbergmann/cli-parser",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-02T06:27:43+00:00"
+ },
+ {
+ "name": "sebastian/code-unit",
+ "version": "1.0.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit.git",
+ "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120",
+ "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/code-unit",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit/issues",
+ "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:08:54+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+ "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
+ "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T05:30:19+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "4.0.10",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d",
+ "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/diff": "^4.0",
+ "sebastian/exporter": "^4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.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": "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",
+ "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.10"
+ },
+ "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-01-24T09:22:56+00:00"
+ },
+ {
+ "name": "sebastian/complexity",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/complexity.git",
+ "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a",
+ "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^4.18 || ^5.0",
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.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",
+ "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-12-22T06:19:30+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "4.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc",
+ "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3",
+ "symfony/process": "^4.2 || ^5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.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",
+ "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-02T06:30:58+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "5.1.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
+ "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.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": "http://www.github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/environment/issues",
+ "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:03:51+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "4.0.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c",
+ "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/recursion-context": "^4.0"
+ },
+ "require-dev": {
+ "ext-mbstring": "*",
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.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",
+ "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.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/exporter",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-09-24T06:03:27+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "5.0.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6",
+ "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/object-reflector": "^2.0",
+ "sebastian/recursion-context": "^4.0"
+ },
+ "require-dev": {
+ "ext-dom": "*",
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-uopz": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "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": "Snapshotting of global state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/global-state/issues",
+ "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.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/global-state",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-10T07:10:35+00:00"
+ },
+ {
+ "name": "sebastian/lines-of-code",
+ "version": "1.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+ "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5",
+ "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^4.18 || ^5.0",
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.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",
+ "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-12-22T06:20:34+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "4.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "5c9eeac41b290a3712d88851518825ad78f45c71"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71",
+ "reference": "5c9eeac41b290a3712d88851518825ad78f45c71",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/object-reflector": "^2.0",
+ "sebastian/recursion-context": "^4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "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",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:12:34+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+ "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.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",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
"type": "github"
- },
- {
- "url": "https://opencollective.com/php_codesniffer",
- "type": "open_collective"
}
],
- "time": "2024-05-20T13:34:27+00:00"
+ "time": "2020-10-26T13:14:26+00:00"
},
{
- "name": "phpstan/extension-installer",
- "version": "1.4.3",
+ "name": "sebastian/recursion-context",
+ "version": "4.0.6",
"source": {
"type": "git",
- "url": "https://github.com/phpstan/extension-installer.git",
- "reference": "85e90b3942d06b2326fba0403ec24fe912372936"
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "539c6691e0623af6dc6f9c20384c120f963465a0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936",
- "reference": "85e90b3942d06b2326fba0403ec24fe912372936",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0",
+ "reference": "539c6691e0623af6dc6f9c20384c120f963465a0",
"shasum": ""
},
"require": {
- "composer-plugin-api": "^2.0",
- "php": "^7.2 || ^8.0",
- "phpstan/phpstan": "^1.9.0 || ^2.0"
+ "php": ">=7.3"
},
"require-dev": {
- "composer/composer": "^2.0",
- "php-parallel-lint/php-parallel-lint": "^1.2.0",
- "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0"
+ "phpunit/phpunit": "^9.3"
},
- "type": "composer-plugin",
+ "type": "library",
"extra": {
- "class": "PHPStan\\ExtensionInstaller\\Plugin"
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
},
"autoload": {
- "psr-4": {
- "PHPStan\\ExtensionInstaller\\": "src/"
- }
+ "classmap": [
+ "src/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
- "description": "Composer plugin for automatic installation of PHPStan extensions",
- "keywords": [
- "dev",
- "static analysis"
+ "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/phpstan/extension-installer/issues",
- "source": "https://github.com/phpstan/extension-installer/tree/1.4.3"
+ "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6"
},
- "time": "2024-09-04T20:21:43+00:00"
+ "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-10T06:57:39+00:00"
},
{
- "name": "phpstan/phpdoc-parser",
- "version": "2.1.0",
+ "name": "sebastian/resource-operations",
+ "version": "3.0.4",
"source": {
"type": "git",
- "url": "https://github.com/phpstan/phpdoc-parser.git",
- "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68"
+ "url": "https://github.com/sebastianbergmann/resource-operations.git",
+ "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68",
- "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68",
+ "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e",
+ "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e",
"shasum": ""
},
"require": {
- "php": "^7.4 || ^8.0"
+ "php": ">=7.3"
},
"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"
+ "phpunit/phpunit": "^9.0"
},
"type": "library",
- "autoload": {
- "psr-4": {
- "PHPStan\\PhpDocParser\\": [
- "src/"
- ]
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
}
},
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
- "description": "PHPDoc parser with support for nullable, intersection and generic types",
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides a list of PHP built-in functions that operate on resources",
+ "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
"support": {
- "issues": "https://github.com/phpstan/phpdoc-parser/issues",
- "source": "https://github.com/phpstan/phpdoc-parser/tree/2.1.0"
+ "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4"
},
- "time": "2025-02-19T13:28:12+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-14T16:00:52+00:00"
},
{
- "name": "phpstan/phpstan",
- "version": "1.12.16",
+ "name": "sebastian/type",
+ "version": "3.2.1",
"source": {
"type": "git",
- "url": "https://github.com/phpstan/phpstan.git",
- "reference": "e0bb5cb78545aae631220735aa706eac633a6be9"
+ "url": "https://github.com/sebastianbergmann/type.git",
+ "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e0bb5cb78545aae631220735aa706eac633a6be9",
- "reference": "e0bb5cb78545aae631220735aa706eac633a6be9",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
+ "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
"shasum": ""
},
"require": {
- "php": "^7.2|^8.0"
+ "php": ">=7.3"
},
- "conflict": {
- "phpstan/phpstan-shim": "*"
+ "require-dev": {
+ "phpunit/phpunit": "^9.5"
},
- "bin": [
- "phpstan",
- "phpstan.phar"
- ],
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.2-dev"
+ }
+ },
"autoload": {
- "files": [
- "bootstrap.php"
+ "classmap": [
+ "src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
- "description": "PHPStan - PHP Static Analysis Tool",
- "keywords": [
- "dev",
- "static analysis"
+ "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": {
- "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"
+ "issues": "https://github.com/sebastianbergmann/type/issues",
+ "source": "https://github.com/sebastianbergmann/type/tree/3.2.1"
},
"funding": [
{
- "url": "https://github.com/ondrejmirtes",
- "type": "github"
- },
- {
- "url": "https://github.com/phpstan",
+ "url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
- "time": "2025-01-21T14:50:05+00:00"
+ "time": "2023-02-03T06:13:03+00:00"
},
{
- "name": "phpstan/phpstan-deprecation-rules",
- "version": "1.2.1",
+ "name": "sebastian/version",
+ "version": "3.0.2",
"source": {
"type": "git",
- "url": "https://github.com/phpstan/phpstan-deprecation-rules.git",
- "reference": "f94d246cc143ec5a23da868f8f7e1393b50eaa82"
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "c6c1022351a901512170118436c764e473f6de8c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/f94d246cc143ec5a23da868f8f7e1393b50eaa82",
- "reference": "f94d246cc143ec5a23da868f8f7e1393b50eaa82",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c",
+ "reference": "c6c1022351a901512170118436c764e473f6de8c",
"shasum": ""
},
"require": {
- "php": "^7.2 || ^8.0",
- "phpstan/phpstan": "^1.12"
- },
- "require-dev": {
- "php-parallel-lint/php-parallel-lint": "^1.2",
- "phpstan/phpstan-phpunit": "^1.0",
- "phpunit/phpunit": "^9.5"
+ "php": ">=7.3"
},
- "type": "phpstan-extension",
+ "type": "library",
"extra": {
- "phpstan": {
- "includes": [
- "rules.neon"
- ]
+ "branch-alias": {
+ "dev-master": "3.0-dev"
}
},
"autoload": {
- "psr-4": {
- "PHPStan\\": "src/"
- }
+ "classmap": [
+ "src/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
- "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.",
+ "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/phpstan/phpstan-deprecation-rules/issues",
- "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.2.1"
+ "issues": "https://github.com/sebastianbergmann/version/issues",
+ "source": "https://github.com/sebastianbergmann/version/tree/3.0.2"
},
- "time": "2024-09-11T15:52:35+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T06:39:44+00:00"
},
{
"name": "slevomat/coding-standard",
- "version": "8.18.0",
+ "version": "8.19.1",
"source": {
"type": "git",
"url": "https://github.com/slevomat/coding-standard.git",
- "reference": "f3b23cb9b26301b8c3c7bb03035a1bee23974593"
+ "reference": "458d665acd49009efebd7e0cb385d71ae9ac3220"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/f3b23cb9b26301b8c3c7bb03035a1bee23974593",
- "reference": "f3b23cb9b26301b8c3c7bb03035a1bee23974593",
+ "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/458d665acd49009efebd7e0cb385d71ae9ac3220",
+ "reference": "458d665acd49009efebd7e0cb385d71ae9ac3220",
"shasum": ""
},
"require": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0",
"php": "^7.4 || ^8.0",
"phpstan/phpdoc-parser": "^2.1.0",
- "squizlabs/php_codesniffer": "^3.12.2"
+ "squizlabs/php_codesniffer": "^3.13.0"
},
"require-dev": {
"phing/phing": "3.0.1",
"php-parallel-lint/php-parallel-lint": "1.4.0",
- "phpstan/phpstan": "2.1.13",
- "phpstan/phpstan-deprecation-rules": "2.0.2",
+ "phpstan/phpstan": "2.1.17",
+ "phpstan/phpstan-deprecation-rules": "2.0.3",
"phpstan/phpstan-phpunit": "2.0.6",
"phpstan/phpstan-strict-rules": "2.0.4",
- "phpunit/phpunit": "9.6.8|10.5.45|11.4.4|11.5.17|12.1.3"
+ "phpunit/phpunit": "9.6.8|10.5.45|11.4.4|11.5.21|12.1.3"
},
"type": "phpcodesniffer-standard",
"extra": {
@@ -1440,7 +3319,7 @@
],
"support": {
"issues": "https://github.com/slevomat/coding-standard/issues",
- "source": "https://github.com/slevomat/coding-standard/tree/8.18.0"
+ "source": "https://github.com/slevomat/coding-standard/tree/8.19.1"
},
"funding": [
{
@@ -1452,20 +3331,20 @@
"type": "tidelift"
}
],
- "time": "2025-05-01T09:40:50+00:00"
+ "time": "2025-06-09T17:53:57+00:00"
},
{
"name": "squizlabs/php_codesniffer",
- "version": "3.13.0",
+ "version": "3.13.2",
"source": {
"type": "git",
"url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
- "reference": "65ff2489553b83b4597e89c3b8b721487011d186"
+ "reference": "5b5e3821314f947dd040c70f7992a64eac89025c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/65ff2489553b83b4597e89c3b8b721487011d186",
- "reference": "65ff2489553b83b4597e89c3b8b721487011d186",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5b5e3821314f947dd040c70f7992a64eac89025c",
+ "reference": "5b5e3821314f947dd040c70f7992a64eac89025c",
"shasum": ""
},
"require": {
@@ -1536,7 +3415,7 @@
"type": "thanks_dev"
}
],
- "time": "2025-05-11T03:36:00+00:00"
+ "time": "2025-06-17T22:17:01+00:00"
},
{
"name": "swissspidy/phpstan-no-private",
@@ -1591,7 +3470,7 @@
},
{
"name": "symfony/polyfill-php73",
- "version": "v1.31.0",
+ "version": "v1.32.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php73.git",
@@ -1647,7 +3526,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0"
+ "source": "https://github.com/symfony/polyfill-php73/tree/v1.32.0"
},
"funding": [
{
@@ -1728,6 +3607,56 @@
},
"time": "2024-06-28T22:27:19+00:00"
},
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "b7489ce515e168639d17feec34b8847c326b0b3c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c",
+ "reference": "b7489ce515e168639d17feec34b8847c326b0b3c",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "support": {
+ "issues": "https://github.com/theseer/tokenizer/issues",
+ "source": "https://github.com/theseer/tokenizer/tree/1.3.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2025-11-17T20:03:58+00:00"
+ },
{
"name": "wp-coding-standards/wpcs",
"version": "3.1.0",
@@ -1793,16 +3722,79 @@
}
],
"time": "2024-03-25T16:39:00+00:00"
+ },
+ {
+ "name": "yoast/phpunit-polyfills",
+ "version": "4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Yoast/PHPUnit-Polyfills.git",
+ "reference": "134921bfca9b02d8f374c48381451da1d98402f9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/134921bfca9b02d8f374c48381451da1d98402f9",
+ "reference": "134921bfca9b02d8f374c48381451da1d98402f9",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1",
+ "phpunit/phpunit": "^7.5 || ^8.0 || ^9.0 || ^11.0 || ^12.0"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-console-highlighter": "^1.0.0",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
+ "yoast/yoastcs": "^3.1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.x-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "phpunitpolyfills-autoload.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Team Yoast",
+ "email": "support@yoast.com",
+ "homepage": "https://yoast.com"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/Yoast/PHPUnit-Polyfills/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",
+ "keywords": [
+ "phpunit",
+ "polyfill",
+ "testing"
+ ],
+ "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"
+ },
+ "time": "2025-02-09T18:58:54+00:00"
}
],
"aliases": [],
"minimum-stability": "alpha",
- "stability-flags": [],
+ "stability-flags": {},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
"php": ">=8.0"
},
- "platform-dev": [],
- "plugin-api-version": "2.6.0"
+ "platform-dev": {},
+ "plugin-api-version": "2.9.0"
}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..bac6592
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,18 @@
+services:
+ mysql:
+ image: mysql:8.0
+ environment:
+ MYSQL_DATABASE: wordpress_test
+ MYSQL_USER: wordpress
+ MYSQL_PASSWORD: wordpress
+ MYSQL_ROOT_PASSWORD: root
+ ports:
+ - "33067:3306"
+ healthcheck:
+ test:
+ - CMD-SHELL
+ - mysqladmin ping -h 127.0.0.1 -proot --silent
+ interval: 5s
+ timeout: 5s
+ retries: 12
+ start_period: 10s
diff --git a/docs/release-process.md b/docs/release-process.md
new file mode 100644
index 0000000..edfa798
--- /dev/null
+++ b/docs/release-process.md
@@ -0,0 +1,26 @@
+# Release Process
+
+## Branches
+
+- `beta` is the prerelease branch. Pushes here may publish prerelease builds only.
+- `master` is the stable release branch. Stable tags are cut from commits that land here.
+
+## v2.0.0 Trigger
+
+Semantic Release will not infer a major version from test, fix, refactor, or chore commits alone. To cut `v2.0.0`, the merge commit that lands on `master` must include an explicit breaking-change signal:
+
+- Use a conventional commit subject with `!`, such as `feat!: ship stateful data API`.
+- Or include a `BREAKING CHANGE:` footer in the commit body that explains the incompatible change.
+
+## Release Checklist
+
+Before merging a stable release to `master`:
+
+1. Run `composer test` and confirm the full suite passes.
+2. Confirm the semantic-release dry-run job passes in GitHub Actions.
+3. Verify the release artifact contains `src/`, `lib/`, `composer.json`, `README.md`, and `LICENSE`.
+4. Summarize the breaking changes and migration notes in the release notes.
+
+## Notes
+
+The GitHub release artifact is intended to be usable outside a Packagist install, so it must include both the runtime source tree and the autoload metadata declared in `composer.json`.
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..53eacbe
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,19 @@
+
+
+
+
+ tests
+
+
+
+
+
+
+
+
diff --git a/src/Core/XWC_Data.php b/src/Core/XWC_Data.php
index f67c073..9ff9434 100644
--- a/src/Core/XWC_Data.php
+++ b/src/Core/XWC_Data.php
@@ -47,6 +47,7 @@ abstract class XWC_Data extends WC_Data implements XWC_Data_Definition {
* Data store object.
*
* @var XWC_Data_Store_XT
+ * @phpstan-ignore property.phpDocType
*/
protected $data_store;
@@ -74,6 +75,25 @@ public function __construct( int|array|stdClass|XWC_Data $data = 0 ) {
->do_actions( $data );
}
+ /**
+ * Get the debug information for this object.
+ *
+ * @return array
+ */
+ public function __debugInfo() {
+ return array(
+ 'changes' => $this->changes,
+ 'data' => $this->data,
+ 'id' => $this->get_id(),
+ 'meta_data' => wp_list_pluck(
+ $this->get_meta_data(),
+ 'value',
+ 'key',
+ ),
+ 'read' => $this->get_object_read(),
+ );
+ }
+
/**
* Universal prop getter / setter
*
@@ -91,6 +111,36 @@ public function __call( string $name, array $args ): mixed {
: $this->set_prop( $prop, $args[0] );
}
+ /**
+ * Serialize the object.
+ *
+ * @return array{id: int}
+ */
+ public function __serialize(): array {
+ return array( 'id' => $this->get_id() );
+ }
+
+ /**
+ * Unserialize the object.
+ *
+ * @param array{id?: int} $data Data to unserialize.
+ */
+ public function __unserialize( array $data ): void {
+ $this
+ ->load_data_store()
+ ->load_object_args()
+ ->load_data( $data['id'] ?? 0 )
+ ->do_actions( $data['id'] ?? 0 );
+ }
+
+ public function jsonSerialize(): mixed {
+ $data = $this->get_data();
+
+ unset( $data['meta_data'] );
+
+ return $data;
+ }
+
/**
* Load the data for this object from the database.
*
@@ -127,12 +177,32 @@ public function get_core_data_read(): bool {
return (bool) $this->core_read;
}
+ /**
+ * Take the changes made to the meta props and apply them to the data.
+ *
+ * @return void
+ */
+ public function apply_changes() {
+ $meta_changes = array_intersect(
+ array_keys( $this->changes ),
+ array_keys( array_diff_key( $this->data, $this->core_data, $this->extra_data, $this->tax_data ) ),
+ );
+
+ foreach ( $meta_changes as $meta_prop ) {
+ $this->data[ $meta_prop ] = $this->changes[ $meta_prop ];
+ unset( $this->changes[ $meta_prop ] );
+ }
+
+ parent::apply_changes();
+ }
+
public function save() {
$args = $this->get_id() > 0
? array( 'updated', 'changes' )
: array( 'created', null );
return $this
+ ->maybe_set_object()
->maybe_set_date( ...$args )
->save_wc_data();
}
@@ -142,7 +212,7 @@ public function save() {
*
* @param string $name Method name.
* @param array $args Method arguments.
- * @return array{0: string, 1: string, 2: string}}
+ * @return array{0: string, 1: string, 2: string}
*/
final protected function parse_method_name( string $name, array $args ): array {
\preg_match( '/^([gs]et)_(.+)$/', $name, $m );
@@ -151,7 +221,7 @@ final protected function parse_method_name( string $name, array $args ): array {
$type = $m[1] ?? '';
$prop = $m[2] ?? '';
- if ( ! $method || ! $type || ! $prop || ( 'set' === $type && ! isset( $args[0] ) ) ) {
+ if ( ! $method || ! $type || ! $prop || ( 'set' === $type && count( $args ) < 1 ) ) {
$this->error( 'bmc', \sprintf( 'BMC: %s, %s', static::class, $name ) );
}
@@ -328,6 +398,39 @@ protected function is_internal_meta_key( $key ) {
return $parent_check;
}
+ protected function maybe_set_object(): static {
+ if ( ! $this->has_prop_type( 'object' ) ) {
+ return $this;
+ }
+
+ $changed = array_diff(
+ (array) $this->get_prop_by_type( 'object' ),
+ array_keys( parent::get_changes() ),
+ );
+
+ foreach ( $changed as $prop ) {
+ $obj = $this->{"get_{$prop}"}();
+
+ if ( ! ( $obj?->changed() ?? false ) ) {
+ continue;
+ }
+
+ $this->changes[ $prop ] = $obj;
+ }
+
+ return $this;
+ }
+
+ protected function has_prop_type( string $type ): bool {
+ foreach ( $this->get_prop_types() as $t ) {
+ if ( $t === $type || str_starts_with( $t, $type . '|' ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
/**
* Maybe set the created or updated date on save.
*
@@ -338,7 +441,7 @@ protected function is_internal_meta_key( $key ) {
protected function maybe_set_date( string $type, ?string $key = null ): static {
$prop = $this->get_prop_by_type( "date_{$type}" );
- if ( ! $prop ) {
+ if ( ! $prop || \is_array( $prop ) ) {
return $this;
}
diff --git a/src/Core/XWC_Data_Store_XT.php b/src/Core/XWC_Data_Store_XT.php
index e718f5b..fe8d8b1 100644
--- a/src/Core/XWC_Data_Store_XT.php
+++ b/src/Core/XWC_Data_Store_XT.php
@@ -52,7 +52,7 @@ class XWC_Data_Store_XT extends WC_Data_Store_WP implements WC_Object_Data_Store
* core_data: array,
* data: array,
* tax_data: array,
- * prop_types: array,
+ * prop_types: array,
* unique_data: array,
* required_data: array,
* }
@@ -116,7 +116,7 @@ public function get_object_type(): string {
* core_data: array,
* data: array,
* tax_data: array,
- * prop_types: array,
+ * prop_types: array,
* unique_data: array,
* required_data: array,
* }
@@ -365,14 +365,14 @@ public function is_value_unique( mixed $value, string $prop, int $obj_id ): bool
public function unique_entity_slug( string $slug, string $prop, int $obj_id ): string {
global $wpdb;
- $prop = $this->get_cols_to_props()[ $prop ] ?? $prop;
+ $col = $this->get_cols_to_props()[ $prop ] ?? $prop;
$check = $wpdb->get_var(
$wpdb->prepare(
'SELECT %i FROM %i WHERE %i = %s AND %i != %d LIMIT 1',
- $prop,
+ $col,
$this->get_table(),
- $prop,
+ $col,
$slug,
$this->get_id_field(),
$obj_id,
@@ -390,7 +390,8 @@ public function unique_entity_slug( string $slug, string $prop, int $obj_id ): s
? \preg_replace( '/-(\d+)$/', "-$suffix", $slug )
: "{$slug}-1";
- return $this->unique_entity_slug( $slug, $prop, $obj_id );
+ // Pass the already-remapped column name to avoid double-remapping on recursion.
+ return $this->unique_entity_slug( (string) $slug, $col, $obj_id );
}
/**
@@ -467,8 +468,9 @@ protected function get_data_row( int $id ): array {
$data_row = $wpdb->get_row(
$wpdb->prepare(
- "SELECT * FROM %i WHERE {$this->get_id_field()} = %d",
+ 'SELECT * FROM %i WHERE %i = %d',
$this->get_table(),
+ $this->get_id_field(),
$id,
),
ARRAY_A,
diff --git a/src/Core/XWC_Object_Factory.php b/src/Core/XWC_Object_Factory.php
index d02bd1d..95b1285 100644
--- a/src/Core/XWC_Object_Factory.php
+++ b/src/Core/XWC_Object_Factory.php
@@ -1,6 +1,8 @@
|false get_object_classname(int $id, string $type) Get the object class name.
- * @method static int|false get_object_id(mixed $id, string $type) Get the object ID.
- *
- * Object factory.
+ * @implements Object_Factory
*/
-class XWC_Object_Factory {
- use Singleton_Ex;
-
+class XWC_Object_Factory implements Object_Factory {
/**
- * Array of entity names with their class names.
+ * Entity type.
*
- * @var array>
+ * @var string
*/
- protected array $models = array();
+ protected string $type;
/**
- * Constructor
+ * Data object class name.
+ *
+ * @var class-string
*/
- protected function __construct() {
- $this->init();
- }
+ protected string $classname;
/**
- * Handles dynamic method calls for getting object data.
+ * Initialize the object factory with an entity.
*
- * @param string $name Method name.
- * @param mixed $args Method arguments.
+ * @template D of XWC_Data_Store_XT
+ * @template M of XWC_Meta_Store
*
- * @return mixed
+ * @param Entity $e Entity instance.
+ * @return static
*/
- public function __call( string $name, $args ) {
- \preg_match( '/^get_(.*?)(?:_id|_classname)?$/', $name, $matches );
+ public function initialize( Entity $e ): static {
+ $this->type = $e->name;
+ $this->classname = $e->model;
- $type = $matches[1] ?? '';
- $method = \str_replace( $type, 'object', $name );
+ return $this;
+ }
- if ( ! \method_exists( $this, $method ) ) {
- return false;
+ public function make_object( mixed $id ): XWC_Data {
+ $obj = $this->get_object( $id );
+
+ if ( $obj ) {
+ return $obj;
}
- $args[] = $type;
+ $classname = $this->get_classname( 0 );
- return $this->{"$method"}( ...$args );
- }
+ if ( ! $classname ) {
+ throw new \RuntimeException(
+ \esc_html( "Cannot resolve a concrete class for entity type '{$this->type}'." ),
+ );
+ }
- /**
- * Handles dynamic static method calls for getting object data
- *
- * @param string $name Method name.
- * @param mixed $args Method arguments.
- *
- * @return mixed
- */
- public static function __callStatic( $name, $args ) {
- return static::instance()->__call( $name, $args );
+ return new $classname( 0 );
}
- /**
- * Get a data object
- *
- * @param mixed $id Object ID.
- * @param string $type Object type.
- * @return T|false
- */
- public function get_object( mixed $id, string $type = '' ): XWC_Data|bool {
- $id = $this->{"get_{$type}_id"}( $id );
+ public function get_object( mixed $id ): ?XWC_Data {
+ $id = $this->get_id( $id );
if ( ! $id ) {
- return false;
+ return null;
}
- /**
- * Filters the class name of a data object.
- *
- * @var class-string $classname
- */
- $classname = $this->{"get_{$type}_classname"}( $id );
+ $classname = $this->get_classname( $id );
try {
return new $classname( $id );
} catch ( \Exception ) {
- return false;
- }
- }
-
- /**
- *
- * Initialize the data types.
- *
- * @global Entity_Manager $xwc_entities
- */
- protected function init(): void {
- /**
- * Global entity manager.
- *
- * @var Entity_Manager $xwc_entities
- */
- global $xwc_entities;
-
- foreach ( $xwc_entities->get_entity() as $type => $dto ) {
- $this->models[ $type ] = $dto->model;
+ return null;
}
}
- /**
- * Get the object ID.
- *
- * @param mixed $id Object ID.
- * @param string $type Object type.
- * @return int|false
- */
- protected function get_object_id( mixed $id, string $type ): int|bool {
- $obj = $GLOBALS[ $type ] ?? null;
+ public function get_id( mixed $id ): int|bool {
+ $obj = $GLOBALS[ $this->type ] ?? null;
+ // @phpstan-ignore return.type
return match ( true ) {
- default => false,
\is_numeric( $id ) => (int) $id,
- $obj instanceof XWC_Data => $obj->get_id(),
$id instanceof XWC_Data => $id->get_id(),
+ $obj instanceof XWC_Data => $obj->get_id(),
+ default => false,
};
}
- /**
- * Get the object class name.
- *
- * @param int $id Object ID.
- * @param string $type Object type.
- * @return class-string|false
- */
- protected function get_object_classname( int $id, string $type ): string|bool {
+ public function get_classname( int $id ): bool|string {
/**
* Filters the class name of a data object.
*
* @var class-string|false $classname
*/
// Documented in WooCommerce.
- $classname = \apply_filters( "xwc_{$type}_class", $this->models[ $type ] ?? false, $id );
+ $classname = \apply_filters( "xwc_{$this->type}_class", $this->classname, $id );
if ( ! $classname || ! \class_exists( $classname ) ) {
return false;
diff --git a/src/Core/XWC_Object_Query.php b/src/Core/XWC_Object_Query.php
index 0f755d1..7cea3f8 100644
--- a/src/Core/XWC_Object_Query.php
+++ b/src/Core/XWC_Object_Query.php
@@ -100,10 +100,9 @@ public function count( ?array $query = null ): int {
$query['fields'] = 'ids';
$this->query( $query );
- $this->reset();
}
- return $this->total;
+ return $this->total ?? 0;
}
/**
@@ -120,6 +119,7 @@ protected function parse( array $q ): void {
'order' => 'DESC',
'orderby' => $this->id_field,
'page' => 1,
+ 'per_page' => 20,
);
$q = \wp_parse_args( $q, $d );
@@ -262,12 +262,10 @@ protected function init_terms( array &$c, array $q ): void {
* @param array $q Query variables.
*/
protected function init_orderby( array &$c, array $q ): void {
- $c['orderby'] = 'ORDER BY ';
-
$c['orderby'] = match ( $q['orderby'] ) {
- 'rand' => 'RAND()',
+ 'rand' => 'RAND()',
$this->id_field => "{$this->table}.{$this->id_field} {$q['order']}",
- default => "{$this->table}.{$q['orderby']} {$q['order']}",
+ default => "{$this->table}.{$q['orderby']} {$q['order']}",
};
}
diff --git a/src/Core/XWC_Prop.php b/src/Core/XWC_Prop.php
new file mode 100644
index 0000000..a9a9472
--- /dev/null
+++ b/src/Core/XWC_Prop.php
@@ -0,0 +1,331 @@
+
+ */
+class XWC_Prop implements ArrayAccess, JsonSerializable {
+ /**
+ * Traversible data array.
+ *
+ * @var array
+ */
+ protected array $data = array();
+
+ /**
+ * The hash of the data.
+ *
+ * This is used to track changes to the data.
+ *
+ * @var string
+ */
+ protected string $hash;
+
+ /**
+ * Did we read the object?
+ *
+ * @var bool
+ */
+ protected bool $read = false;
+
+ protected bool $changed = false;
+
+ /**
+ * Gets the default JSON representation of the object.
+ *
+ * @return static
+ */
+ public static function default(): static {
+ // @phpstan-ignore new.static
+ return new static();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $data Data and hash.
+ * }
+ */
+ public function __construct( array $data = array() ) {
+ $this->data = $this->default_data();
+ $this->hash = $this->hash_data();
+
+ $this->load_data( $data );
+ }
+
+ /**
+ * Serializes the object to an array.
+ *
+ * @return array
+ */
+ public function __serialize(): array {
+ return $this->get_data();
+ }
+
+ /**
+ * Unserializes the object from an array.
+ *
+ * @param array $data Data to unserialize.
+ */
+ public function __unserialize( array $data ): void {
+ $this->load_data( $data );
+ }
+
+ /**
+ * Return data needed for JSON serialization.
+ *
+ * @return ?array
+ */
+ public function jsonSerialize(): mixed {
+ $data = $this->get_data();
+ $defl = $this->default_data();
+
+ $this->sort( $data );
+ $this->sort( $defl );
+ return $data === $defl
+ ? null
+ : $data;
+ }
+
+ /**
+ * Sets the value at the specified offset.
+ *
+ * @param TKey $offset The offset to set.
+ * @param TValue $value The value to set.
+ * @return static
+ */
+ public function set( string $offset, mixed $value ): static {
+ $old = $this->get( $offset );
+
+ $this->data[ $offset ] = $value;
+
+ if ( $this->get_read() && $old !== $value ) {
+ $this->changed = true;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Loads data into the property.
+ *
+ * @param array $data The data to load.
+ * @return static
+ */
+ public function set_data( array $data ): static {
+ $old = $this->data;
+ $this->data = $data;
+
+ if ( $this->get_read() && $old !== $data ) {
+ $this->changed = true;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Mark the object as read.
+ *
+ * @param bool $read Whether the object has been read.
+ * @return static
+ */
+ public function set_read( bool $read = true ): static {
+ $this->read = $read;
+
+ return $this;
+ }
+
+ /**
+ * Sets the data for the property.
+ *
+ * @template TData of XWC_Prop
+ *
+ * @param TData|array $data The data to set.
+ * @return ($data is array ? static : TData)
+ */
+ public function with_data( array|XWC_Prop $data ): XWC_Prop {
+ if ( is_array( $data ) ) {
+ return $this->set_data( $data );
+ }
+
+ $cname = $data::class;
+
+ return new $cname( $data->get_data() );
+ }
+
+ /**
+ * Gets the value at the specified offset.
+ *
+ * @param TKey $offset The offset to retrieve.
+ * @return TValue|null Can return any type or null if not set.
+ */
+ public function get( string $offset ): mixed {
+ return $this->data[ $offset ] ?? null;
+ }
+
+ /**
+ * Gets the data array.
+ *
+ * @return array
+ */
+ public function get_data(): array {
+ return $this->data;
+ }
+
+ /**
+ * Gets the hash of the data.
+ *
+ * @return ?string
+ */
+ public function get_hash(): ?string {
+ return $this->hash ?? null;
+ }
+
+ /**
+ * Checks if the object has been read.
+ *
+ * @return bool
+ */
+ public function get_read(): bool {
+ return $this->read;
+ }
+
+ public function changed(): bool {
+ if ( ! $this->get_read() ) {
+ return false;
+ }
+
+ return $this->changed;
+ }
+
+ /**
+ * Assigns a value to the specified offset.
+ *
+ * Used by the ArrayAccess interface.
+ *
+ * @param TKey $offset The offset to assign the value to.
+ * @param TValue $value The value to set.
+ * @return void
+ */
+ public function offsetSet( $offset, $value ): void {
+ throw new \BadMethodCallException( 'Do not use this method directly. Use the setter method instead.' );
+ }
+
+ /**
+ * Returns the value at the specified offset.
+ *
+ * Used by the ArrayAccess interface.
+ *
+ * @param TKey $offset The offset to retrieve.
+ * @return TValue|array Can return any type.
+ */
+ public function &offsetGet( $offset ): mixed {
+ return $this->data[ $offset ] ?? array();
+ }
+
+ /**
+ * Checks if the specified offset exists.
+ *
+ * Used by the ArrayAccess interface.
+ *
+ * @param TKey $offset The offset to check.
+ * @return bool
+ */
+ public function offsetExists( $offset ): bool {
+ return isset( $this->data[ $offset ] );
+ }
+
+ /**
+ * Unsets the value at the specified offset.
+ *
+ * Used by the ArrayAccess interface.
+ *
+ * @param TKey $offset The offset to unset.
+ * @return void
+ */
+ public function offsetUnset( $offset ): void {
+ throw new \BadMethodCallException( 'Do not use this method directly. Use the setter method instead.' );
+ }
+
+ /**
+ * Sets the hash of the data.
+ *
+ * @param string $hash The hash to set.
+ * @return static
+ */
+ protected function set_hash( string $hash ): static {
+ $this->hash = $hash;
+
+ return $this;
+ }
+
+ /**
+ * Returns the default data for this property.
+ *
+ * @return array
+ */
+ protected function default_data(): array {
+ return array();
+ }
+
+ /**
+ * Hashes the data.
+ *
+ * This method sorts the data and then hashes it using md5.
+ *
+ * @param null|array $data Optional data to hash. If not provided, uses the current data.
+ * @return string The hash of the data.
+ */
+ protected function hash_data( ?array $data = null ): string {
+ $data ??= $this->data;
+
+ $this->sort( $data );
+
+ return hash( 'md5', (string) wp_json_encode( $data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ) );
+ }
+
+ /**
+ * Sorts the data array recursively.
+ *
+ * This method sorts the array in place, preserving the keys and ensuring
+ * that nested arrays are also sorted.
+ *
+ * @param array $arr The array to sort.
+ * @return void
+ */
+ protected function sort( array &$arr ): void {
+ array_is_list( $arr )
+ ? sort( $arr, SORT_NATURAL | SORT_FLAG_CASE )
+ : ksort( $arr, SORT_NATURAL | SORT_FLAG_CASE );
+
+ foreach ( $arr as &$value ) {
+ if ( ! is_array( $value ) ) {
+ continue;
+ }
+
+ $this->sort( $value );
+ }
+ }
+
+ /**
+ * Load the data.
+ *
+ * @param array $data The data to load.
+ * }
+ */
+ protected function load_data( array $data = array() ): void {
+ if ( ! $data ) {
+ $this->set_read( true );
+ return;
+ }
+
+ $this
+ ->set_data( $data )
+ ->set_hash( $this->hash_data() )
+ ->set_read( true );
+ }
+}
diff --git a/src/Decorators/Model.php b/src/Decorators/Model.php
index 655e4fb..fd0c893 100644
--- a/src/Decorators/Model.php
+++ b/src/Decorators/Model.php
@@ -40,14 +40,14 @@ class Model {
public string $table;
public string $data_store;
- public string $factory;
+ public ?string $factory;
/**
* Core properties.
*
* @var array $core_props Array of core properties.
* @param array|null $factory Object factory class name.
- * @return ($factory is null ? class-string> : class-string)
+ * @return class-string|null
*/
- protected function set_factory( ?string $factory ): string {
+ protected function set_factory( ?string $factory ): ?string {
if ( \is_null( $factory ) ) {
- return XWC_Object_Factory::class;
+ return null;
}
if ( ! \class_exists( $factory ) ) {
@@ -312,7 +312,11 @@ protected function set_meta_store( ?string $store ): ?string {
return null;
}
- $store ??= XWC_Meta_Store::class;
+ if ( \is_null( $store ) ) {
+ throw new \InvalidArgumentException(
+ \esc_html( "A concrete meta store class must be provided when meta props are defined for '{$this->name}'." ),
+ );
+ }
if ( ! \class_exists( $store ) ) {
throw new \InvalidArgumentException( \esc_html( "Meta store class $store does not exist." ) );
@@ -410,7 +414,11 @@ private function parse_tax_arg( array $args ): array {
),
);
- $args['field'] = \preg_replace( '/^id$/', 'term_id', \ltrim( $args['field'], 'term_' ) );
+ $field = $args['field'];
+ $field = \str_starts_with( $field, 'term_' ) ? \substr( $field, 5 ) : $field;
+ $field = 'id' === $field ? 'term_id' : $field;
+
+ $args['field'] = $field;
$args['default'] = 'array' === $args['return'] ? (array) $args['default'] : $args['default'];
$args['type'] = \sprintf( 'term_%s|%s|%s', $args['return'], $args['field'], $args['taxonomy'] );
diff --git a/src/Decorators/Model_Modifier.php b/src/Decorators/Model_Modifier.php
index b893c7a..78d3c4d 100644
--- a/src/Decorators/Model_Modifier.php
+++ b/src/Decorators/Model_Modifier.php
@@ -28,7 +28,7 @@ class Model_Modifier extends Model {
* @param class-string|null $data_store Data store class name.
* @param arrayget_definers() as $prop => $setter ) {
- $this->$prop = $args[ $prop ] || \is_null( $args[ $prop ] )
- ? $this->$setter( $args[ $prop ] )
- : $args[ $prop ];
+ if ( ! \array_key_exists( $prop, $args ) ) {
+ continue;
+ }
+
+ $this->$prop = $this->$setter( $args[ $prop ] );
}
}
}
diff --git a/src/Entity.php b/src/Entity.php
index c10d762..23eea1a 100644
--- a/src/Entity.php
+++ b/src/Entity.php
@@ -87,6 +87,13 @@ class Entity {
'meta_obj_field',
);
+ /**
+ * Properties to be set.
+ *
+ * @var array
+ */
+ private static array $props;
+
/**
* Object factories.
*
@@ -133,7 +140,7 @@ class Entity {
*/
protected string $container;
- protected string $factory;
+ protected ?string $factory;
/**
* Core properties.
@@ -185,26 +192,26 @@ class Entity {
*/
protected ContainerInterface $ctr;
+ /**
+ * Default values for properties.
+ *
+ * @var array
+ */
+ private array $defaults = array(
+ 'meta_table' => '',
+ );
+
/**
* Constructor.
*
* @param Model ...$defs Model definitions.
*/
- public function __construct(
- Model ...$defs,
- ) {
- $vars = \array_keys( \get_class_vars( $this::class ) );
- $vars = \array_diff(
- $vars,
- array( 'args', 'factories', 'stores', 'hooked', 'defaults', 'ctr', 'container' ),
- );
-
- foreach ( $vars as $var ) {
-
- $this->$var = $this->set_prop( $var, $defs );
+ public function __construct( Model ...$defs ) {
+ foreach ( $this->get_props() as $prop ) {
+ $this->$prop = $this->set_prop( $prop, $defs );
}
- static::$stores[ $this->name ] = null;
+ static::$stores[ $this->name ] ??= null; // @phpstan-ignore assign.propertyType
}
/**
@@ -242,7 +249,7 @@ public function add_hooks(): void {
* Prime the data store with the entity name.
*
* @param array $stores Data stores.
- * @return array
+ * @return array
*/
public function prime_data_store( array $stores ): array {
$to_add = \array_keys( static::$stores );
@@ -260,7 +267,19 @@ public function prime_data_store( array $stores ): array {
* @return mixed
*/
protected function set_prop( string $prop, array $defs ): mixed {
- $defined = \wp_list_pluck( \wp_list_filter( $defs, array( $prop => null ), 'NOT' ), $prop );
+ $defined = array();
+
+ foreach ( $defs as $def ) {
+ if ( ! isset( $def->$prop ) ) {
+ continue;
+ }
+
+ $defined[] = $def->$prop;
+ }
+
+ if ( ! \count( $defined ) ) {
+ return $this->defaults[ $prop ] ?? null;
+ }
if ( 1 === \count( $defined ) ) {
return \current( $defined );
@@ -272,9 +291,7 @@ protected function set_prop( string $prop, array $defs ): mixed {
return \array_merge( $base, ...$defined );
}
- $final = \end( $defined );
-
- return $final ? $final : $base;
+ return \end( $defined );
}
/**
@@ -292,7 +309,7 @@ protected function get_core_data(): array {
* @return array
*/
protected function get_data(): array {
- return \wp_list_pluck( $this->meta_props, 'default' );
+ return \wp_list_pluck( $this->meta_props ?? array(), 'default' );
}
/**
@@ -301,7 +318,7 @@ protected function get_data(): array {
* @return array
*/
protected function get_tax_data(): array {
- return \wp_list_pluck( $this->tax_props, 'default' );
+ return \wp_list_pluck( $this->tax_props ?? array(), 'default' );
}
/**
@@ -312,8 +329,8 @@ protected function get_tax_data(): array {
protected function get_prop_types(): array {
return \array_merge(
\wp_list_pluck( $this->core_props, 'type' ),
- \wp_list_pluck( $this->meta_props, 'type' ),
- \wp_list_pluck( $this->tax_props, 'type' ),
+ \wp_list_pluck( $this->meta_props ?? array(), 'type' ),
+ \wp_list_pluck( $this->tax_props ?? array(), 'type' ),
);
}
@@ -346,7 +363,7 @@ protected function get_required_data(): array {
* @return array
*/
protected function get_meta_to_props(): array {
- return \array_flip( \wp_list_pluck( $this->meta_props, 'name' ) );
+ return \array_flip( \wp_list_pluck( $this->meta_props ?? array(), 'name' ) );
}
/**
@@ -364,7 +381,7 @@ protected function get_cols_to_props(): array {
* @return array
*/
protected function get_tax_to_props(): array {
- return \array_flip( \wp_list_pluck( $this->tax_props, 'taxonomy' ) );
+ return \array_flip( \wp_list_pluck( $this->tax_props ?? array(), 'taxonomy' ) );
}
/**
@@ -373,7 +390,7 @@ protected function get_tax_to_props(): array {
* @return array
*/
protected function get_tax_fields(): array {
- return \wp_list_pluck( $this->tax_props, 'field', 'taxonomy' );
+ return \wp_list_pluck( $this->tax_props ?? array(), 'field', 'taxonomy' );
}
/**
@@ -382,7 +399,14 @@ protected function get_tax_fields(): array {
* @return TFact
*/
protected function get_factory(): XWC_Object_Factory {
- return static::$factories[ $this->name ] ??= $this->factory::instance();
+ /**
+ * Variable override.
+ *
+ * @var null|class-string $factory
+ */
+ $factory = $this->factory ?? XWC_Object_Factory::class;
+
+ return static::$factories[ $this->name ] ??= $this->make( $factory )->initialize( $this );
}
/**
@@ -406,7 +430,7 @@ protected function get_meta_store(): ?XWC_Meta_Store {
}
protected function get_has_meta(): bool {
- return '' !== $this->meta_table && array() !== $this->meta_props;
+ return '' !== $this->meta_table && ! empty( $this->meta_props );
}
/**
@@ -429,10 +453,22 @@ protected function get_repo(): XWC_Data_Store_XT {
return $this->get_data_store();
}
+ /**
+ * Get the properties of the entity.
+ *
+ * @return array
+ */
+ private function get_props(): array {
+ return self::$props ??= \array_diff(
+ \array_keys( \get_class_vars( $this::class ) ),
+ array( 'props', 'defaults', 'args', 'factories', 'stores', 'hooked', 'ctr', 'container' ),
+ );
+ }
+
/**
* Make an instance of a class.
*
- * @template TObj of TDstr|TMeta
+ * @template TObj of TDstr|TMeta|TFact
* @param class-string $cname Class name.
* @return TObj
*/
diff --git a/src/Entity_Manager.php b/src/Entity_Manager.php
index 2ead7ca..2cb755b 100644
--- a/src/Entity_Manager.php
+++ b/src/Entity_Manager.php
@@ -115,8 +115,13 @@ protected function do_register( string $classname, ?ContainerInterface $containe
* @return array,XWC_Object_Factory,XWC_Meta_Store>>
*/
protected function get_models( string $target ): array {
- $defs = array();
+ /**
+ * Get the inheritance chain for the target class.
+ *
+ * @var array> $chain
+ */
$chain = Reflection::get_inheritance_chain( $target, true );
+ $defs = array();
foreach ( $chain as $classname ) {
$defs[] = Reflection::get_decorator( $classname, Model::class )?->set_model( $classname );
diff --git a/src/Interfaces/Object_Factory.php b/src/Interfaces/Object_Factory.php
new file mode 100644
index 0000000..98fa9a8
--- /dev/null
+++ b/src/Interfaces/Object_Factory.php
@@ -0,0 +1,44 @@
+
+ */
+ public function get_id( mixed $id ): int|bool;
+
+ /**
+ * Get the class name of a data object by ID.
+ *
+ * @param int $id Object ID.
+ * @return false|class-string
+ */
+ public function get_classname( int $id ): bool|string;
+}
diff --git a/src/Interfaces/XWC_Data_Definition.php b/src/Interfaces/XWC_Data_Definition.php
index 98950bb..0f2fbab 100644
--- a/src/Interfaces/XWC_Data_Definition.php
+++ b/src/Interfaces/XWC_Data_Definition.php
@@ -1,4 +1,4 @@
+ */
+ abstract protected function get_valid_statuses(): array;
+
+ abstract protected function get_default_status(): string;
+
+ abstract protected function get_status_prefix(): string;
+
+ /**
+ * Set the object status.
+ *
+ * @param string $to New status.
+ * @return array{from: string, to: string}
+ */
+ public function set_status( string $to ): array {
+ $from = $this->get_status();
+ $to = $this->strip_object_status( $to );
+
+ if ( ! $this->object_read ) {
+ $this->set_prop( 'status', $to );
+
+ return \compact( 'from', 'to' );
+ }
+
+ if ( ! $this->is_valid_status( $to ) ) {
+ $to = $this->get_default_status();
+ }
+
+ if ( 'draft' !== $from && ! $this->is_valid_status( $from ) ) {
+ $from = $this->get_default_status();
+ }
+
+ $this->set_prop( 'status', $to );
+
+ return \compact( 'from', 'to' );
+ }
+
+ /**
+ * Get the invoice status.
+ *
+ * @param string $context Context.
+ * @return string
+ */
+ public function get_status( string $context = 'view' ): string {
+ $status = $this->get_prop( 'status', $context );
+
+ if ( '' === $status && 'view' === $context ) {
+ $status = $this->get_default_status();
+ }
+
+ return $status;
+ }
+
+ /**
+ * Get invoice status prop for the database.
+ *
+ * @param string $status Status to format.
+ * @return string
+ */
+ protected function get_object_status_prop( string $status ) {
+ if ( '' === $status ) {
+ $status = $this->get_default_status();
+ }
+
+ $status = $this->format_object_status( $status );
+
+ return $status;
+ }
+
+ protected function strip_object_status( string $status ): string {
+ $prefix = $this->get_status_prefix();
+
+ return \str_starts_with( $status, $prefix )
+ ? \substr( $status, \strlen( $prefix ) )
+ : $status;
+ }
+
+ /**
+ * Format the object status with a prefix.
+ *
+ * @param string $status Status to format.
+ * @return string
+ */
+ protected function format_object_status( string $status ): string {
+ $prefix = $this->get_status_prefix();
+
+ return ! \str_starts_with( $status, $prefix )
+ ? $prefix . $status
+ : $status;
+ }
+
+ /**
+ * Check if a status is valid.
+ *
+ * @param string $status Status to check.
+ * @return bool
+ */
+ protected function is_valid_status( string $status ): bool {
+ return \in_array( $this->format_object_status( $status ), $this->get_valid_statuses(), true );
+ }
+}
diff --git a/src/Mixins/Status_Transition_Methods.php b/src/Mixins/Status_Transition_Methods.php
new file mode 100644
index 0000000..bd88137
--- /dev/null
+++ b/src/Mixins/Status_Transition_Methods.php
@@ -0,0 +1,213 @@
+status_transition()->get_id();
+ }
+
+ /**
+ * Check if the object has a specific status.
+ *
+ * @param string ...$status Status.
+ * @return bool
+ */
+ public function has_status( string ...$status ): bool {
+ return \in_array( $this->get_status(), $status, true );
+ }
+
+ /**
+ * Set the status of the object.
+ *
+ * @param string $new_status New status to set.
+ * @param string $note Note to add to the transition.
+ * @param bool $manual Whether the status change is manual or not.
+ * @return array{from: string, to: string}
+ */
+ public function set_status( string $new_status, string $note = '', bool $manual = false ): array {
+ [ 'from' => $from, 'to' => $to ] = $this->set_status_base( $new_status );
+
+ if ( ! $this->object_read || '' === $from || $from === $to ) {
+ return array(
+ 'from' => $from,
+ 'to' => $to,
+ );
+ }
+
+ $from = $this->transition['from'] ?? $from;
+
+ $this->transition = array(
+ 'from' => $from,
+ 'manual' => $manual,
+ 'note' => $note,
+ 'to' => $to,
+ );
+
+ if ( $manual ) {
+ $tag = $this->get_tag_base( 'edit', 'status' );
+
+ /**
+ * Fires when the status of an object is manually changed.
+ *
+ * @param int $id Object ID.
+ * @param string $to New status.
+ *
+ * @since 1.0.0
+ */
+ \do_action( $tag, $this->get_id(), $to );
+ }
+
+ return array(
+ 'from' => $from,
+ 'to' => $to,
+ );
+ }
+
+ /**
+ * Update the status of the object.
+ *
+ * Same as `set_status`, but also saves the object.
+ *
+ * @param string $new_status New status to set.
+ * @param string $note Note to add to the transition.
+ * @param bool $manual Whether the status change is manual or not.
+ * @return bool
+ */
+ public function update_status( string $new_status, string $note = '', bool $manual = false ): bool {
+ if ( ! $this->can_update_status() ) {
+ return false;
+ }
+
+ try {
+ $this->set_status( $new_status, $note, $manual );
+ $this->save();
+ } catch ( \Throwable ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ protected function can_update_status(): bool {
+ return $this->get_id() > 0;
+ }
+
+ /**
+ * Get the state transition for a specific property.
+ *
+ * @return false|array{
+ * from: string,
+ * to: string,
+ * note: string,
+ * manual: bool
+ * }
+ */
+ protected function get_transition(): bool|array {
+ $transition = $this->transition;
+
+ $this->transition = false;
+
+ return $transition;
+ }
+
+ protected function status_transition(): static {
+ $transition = $this->get_transition();
+
+ if ( false === $transition ) {
+ return $this;
+ }
+
+ $base = $this->get_tag_base();
+
+ try {
+
+ [ 'from' => $from, 'to' => $to ] = $transition;
+
+ /**
+ * Fires for specific status transition.
+ *
+ * @param int $id Object ID.
+ * @param XWC_Stateful_Data $object Object instance.
+ * @param array $transition Transition data.
+ *
+ * @since 1.0.0
+ */
+ \do_action( "{$base}_{$to}", $this->get_id(), $this, $transition );
+
+ /**
+ * Fires for status transition from one status to another.
+ *
+ * @param int $id Object ID.
+ * @param XWC_Stateful_Data $object Object instance.
+ *
+ * @since 1.0.0
+ */
+ \do_action( "{$base}_{$from}_to_{$to}", $this->get_id(), $this );
+
+ /**
+ * Fires for status change.
+ *
+ * @param int $id Object ID.
+ * @param string $from Previous status.
+ * @param string $to New status.
+ * @param XWC_Stateful_Data $object Object instance.
+ *
+ * @since 1.0.0
+ */
+ \do_action( "{$base}_changed", $this->get_id(), $from, $to, $this );
+
+ } catch ( \Exception $e ) {
+ if ( \function_exists( 'wc_get_logger' ) ) {
+ $logger = \wc_get_logger();
+
+ $logger->error(
+ \sprintf(
+ 'Status transition of %s #%d errored!',
+ $this->object_type,
+ $this->get_id(),
+ ),
+ array(
+ $this->object_type => $this,
+ 'error' => $e,
+ ),
+ );
+ }
+ } finally {
+ return $this;
+ }
+ }
+
+ protected function get_tag_base( string ...$tags ): string {
+ $tags = $tags ?: array( 'status' );
+ \array_unshift( $tags, 'xwc', $this->object_type );
+
+ return \implode( '_', $tags );
+ }
+}
diff --git a/src/Model/Prop_Getters.php b/src/Model/Prop_Getters.php
index 532cd53..576f73a 100644
--- a/src/Model/Prop_Getters.php
+++ b/src/Model/Prop_Getters.php
@@ -2,6 +2,8 @@
namespace XWC\Data\Model;
+use JsonSerializable;
+use Stringable;
use XWC_Data;
/**
@@ -29,7 +31,7 @@ trait Prop_Getters {
/**
* Array linking props to their types.
*
- * @var array
+ * @var array
*/
protected array $prop_types = array();
@@ -48,10 +50,12 @@ trait Prop_Getters {
protected array $required_data = array();
public function get_prop_group( string $prop ): string {
+ $meta_props = \array_diff_key( $this->data, $this->core_data, $this->extra_data, $this->tax_data );
+
return match ( true ) {
isset( $this->core_data[ $prop ] ) => 'core',
isset( $this->extra_data[ $prop ] ) => 'extra',
- isset( $this->meta_data[ $prop ] ) => 'meta',
+ isset( $meta_props[ $prop ] ) => 'meta',
default => 'none',
};
}
@@ -103,7 +107,7 @@ public function get_core_data( string $context = 'db', bool $include_id = false,
*/
public function get_core_changes(): array {
$changed = array();
- $props = \array_intersect( $this->get_core_keys(), \array_keys( $this->changes ) );
+ $props = \array_intersect( $this->get_core_keys(), \array_keys( $this->get_changes() ) );
if ( 0 === \count( $props ) ) {
return $changed;
@@ -133,12 +137,25 @@ public function get_data() {
return $data;
}
+ /**
+ * Get all changes for this object.
+ *
+ * This includes core data, extra data, and meta data.
+ *
+ * @return array
+ */
+ public function get_changes() {
+ return $this
+ ->maybe_set_object()
+ ->get_wc_data_changes();
+ }
+
/**
* Get the type of a prop.
*
* @param string $prop Name of prop to get type for.
* @return array{
- * 0: 'date_created'|'date_updated'|'date'|'bool'|'bool_int'|'term_single'|'term_array'|'array_assoc'|'array'|'binary'|'base64_string'|'json_obj'|'json'|'int'|'float'|'slug'|'string'|'other',
+ * 0: 'date_created'|'date_updated'|'date'|'bool'|'bool_int'|'enum'|'term_single'|'term_array'|'array_assoc'|'array_set'|'array'|'binary'|'base64_string'|'json_obj'|'json'|'int'|'float'|'slug'|'other'|string|class-string,
* 1: array
* } | array{0: 'enum', 1: array{0: class-string}}
*/
@@ -148,7 +165,7 @@ protected function get_prop_type( string $prop ): array {
/**
* Variable narrowing for prop types.
*
- * @var 'date_created'|'date_updated'|'date'|'bool'|'bool_int'|'enum'|'term_single'|'term_array'|'array_assoc'|'array'|'binary'|'base64_string'|'json_obj'|'json'|'int'|'float'|'slug'|'string'|'other' $type
+ * @var 'date_created'|'date_updated'|'date'|'bool'|'bool_int'|'enum'|'term_single'|'term_array'|'array_assoc'|'array_set'|'array'|'binary'|'base64_string'|'json_obj'|'json'|'int'|'float'|'slug'|'other'|string|class-string $type
*/
$type = \array_shift( $types );
@@ -177,7 +194,11 @@ protected function is_base64_string( ?string $value ): bool {
* @return ($type is 'date_created' ? null|string : ($type is 'date_updated' ? null|string : null|string|array))
*/
protected function get_prop_by_type( string $type ): null|string|array {
- $types = \array_filter( $this->get_prop_types(), static fn( $t ) => $t === $type );
+ $types = \array_filter(
+ $this->get_prop_types(),
+ fn( $t ) => $this->filter_prop( $t, $type ),
+ );
+
$types = \array_keys( $types );
return match ( \count( $types ) ) {
@@ -215,6 +236,7 @@ protected function get_prop( $prop, $context = 'view' ) {
'bool_int' => $this->get_bool_prop( $value, 'int' ),
'array_assoc' => $this->get_array_prop( $value, 'assoc' ),
'array' => $this->get_array_prop( $value, 'normal' ),
+ 'array_set' => $this->get_array_prop( $value, 'set' ),
'term_single' => $this->get_term_prop( $value, ...$sub ),
'term_array' => $this->get_term_prop( $value, ...$sub ),
'enum' => $this->get_enum_prop( $value ),
@@ -222,10 +244,23 @@ protected function get_prop( $prop, $context = 'view' ) {
'json_obj' => $this->get_json_prop( $value, \JSON_FORCE_OBJECT ),
'binary' => $this->get_binary_prop( $value ),
'base64_string' => $this->get_base64_string_prop( $value ),
+ 'object' => $this->get_object_prop( $value, ...$sub ),
default => $this->get_unknown_prop( $type, $prop, $value ),
};
}
+ /**
+ * Get WC data changes.
+ *
+ * This is a wrapper for the parent method to ensure that the WC_Data
+ * changes are returned in the correct format.
+ *
+ * @return array
+ */
+ protected function get_wc_data_changes(): array {
+ return parent::get_changes();
+ }
+
protected function get_wc_data_prop( string $prop, string $context = 'view' ): mixed {
return parent::get_prop( $prop, $context );
}
@@ -235,7 +270,7 @@ protected function get_date_prop( ?\WC_DateTime $value ): ?string {
return null;
}
- return \gmdate( 'Y-m-d H:i:s', $value->getOffsetTimestamp() );
+ return \gmdate( 'Y-m-d H:i:s', $value->getTimestamp() );
}
/**
@@ -256,7 +291,7 @@ protected function get_bool_prop( mixed $value, string $format = 'string' ): int
* Get array prop value.
*
* @param mixed $value Value to convert to a string.
- * @param 'assoc'|'normal' $format Format of the array.
+ * @param 'assoc'|'normal'|'set' $format Format of the array.
* @return string
*/
protected function get_array_prop( mixed $value, string $format = 'assoc' ): string {
@@ -264,6 +299,7 @@ protected function get_array_prop( mixed $value, string $format = 'assoc' ): str
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
'assoc' => \serialize( $value ),
'normal' => \implode( ',', $value ),
+ 'set' => \implode( ',', \array_unique( (array) $value ) ),
};
}
@@ -287,11 +323,11 @@ protected function get_term_prop( mixed $value, string $field, string $taxonomy
/**
* Get enum prop value.
*
- * @param \BackedEnum $enum_val Enum value.
- * @return string|int
+ * @param \BackedEnum|null $enum_val Enum value.
+ * @return null|string|int
*/
- protected function get_enum_prop( $enum_val ): string|int {
- return $enum_val->value;
+ protected function get_enum_prop( $enum_val ): null|string|int {
+ return $enum_val?->value ?? null;
}
/**
@@ -312,13 +348,45 @@ protected function get_json_prop( mixed $value, int $flags = 0 ): string {
* @return string
*/
protected function get_binary_prop( mixed $value ): string {
- return ! $this->is_binary_string( $value ) ? \hex2bin( $value ) : $value;
+ if ( $this->is_binary_string( $value ) ) {
+ return $value;
+ }
+
+ $decoded = \hex2bin( (string) $value );
+
+ return false !== $decoded ? $decoded : (string) $value;
}
- protected function get_base64_string_prop( ?string $value ): string {
+ protected function get_base64_string_prop( ?string $value ): ?string {
+ if ( null === $value ) {
+ return null;
+ }
+
return ! $this->is_base64_string( $value ) ? \base64_encode( $value ) : $value;
}
+ /**
+ * Get an object prop value.
+ *
+ * This is used for objects that implement JsonSerializable or Stringable.
+ *
+ * @param mixed $value Value to convert to a string.
+ * @param string $cname Class name of the prop, defaults to XWC_Prop.
+ * @return ?string
+ */
+ protected function get_object_prop( mixed $value, string $cname = \XWC_Prop::class ): ?string {
+ $iof = static fn( $t ) => $t instanceof JsonSerializable || $t instanceof Stringable;
+ $enc = JSON_UNESCAPED_UNICODE;
+
+ return match ( true ) {
+ $iof( $value ) => $this->get_json_prop( $value, $enc ),
+ $value === $cname => $this->get_json_prop( new $cname(), $enc ),
+ \class_exists( (string) $value ) => null,
+ '' === (string) $value => null,
+ default => null,
+ };
+ }
+
protected function get_unknown_prop( string $type, string $prop, mixed $value ): mixed {
if ( \method_exists( $this, "get_{$type}_prop" ) ) {
$value = $this->{"get_{$type}_prop"}( $value, $prop );
@@ -336,4 +404,10 @@ protected function get_unknown_prop( string $type, string $prop, mixed $value ):
*/
return \apply_filters( "xwc_data_get_{$type}_prop", $value, $prop );
}
+
+ private function filter_prop( string $type, string $find ): bool {
+ $regex = '/^' . \preg_quote( $find, '/' ) . '(?:$|\|.+$)/';
+
+ return 1 === \preg_match( $regex, $type );
+ }
}
diff --git a/src/Model/Prop_Setters.php b/src/Model/Prop_Setters.php
index 4c99a77..a754115 100644
--- a/src/Model/Prop_Setters.php
+++ b/src/Model/Prop_Setters.php
@@ -3,9 +3,11 @@
namespace XWC\Data\Model;
use BackedEnum;
+use WC_Data_Exception;
use XWC_Data;
use XWC_Data_Store_XT;
use XWC_Meta_Store;
+use XWC_Prop;
/**
* Prop setters trait.
@@ -17,20 +19,30 @@
* @phpstan-require-extends XWC_Data
*/
trait Prop_Setters {
+ /**
+ * Whether we are currently inside a set_time_prop → parent::set_date_prop recursion.
+ *
+ * Using an instance property instead of a static variable so that concurrent
+ * calls on different props (or different object instances) cannot interfere.
+ *
+ * @var bool
+ */
+ private bool $time_prop_loop = false;
+
/**
* Get the type of a prop.
*
* @param string $prop Name of prop to get type for.
* @return array{
- * 0: 'date_created'|'date_updated'|'date'|'bool'|'bool_int'|'term_single'|'term_array'|'array_assoc'|'array'|'binary'|'base64_string'|'json_obj'|'json'|'int'|'float'|'slug'|'string'|'other',
+ * 0: 'date_created'|'date_updated'|'date'|'bool'|'bool_int'|'enum'|'term_single'|'term_array'|'array_assoc'|'array_set'|'array'|'binary'|'base64_string'|'json_obj'|'json'|'int'|'float'|'slug'|'other'|string|class-string,
* 1: array
* } | array{0: 'enum', 1: array{0: class-string}}
*/
abstract protected function get_prop_type( string $prop ): array;
- abstract protected function is_binary_string( string $value ): bool;
+ abstract protected function is_binary_string( ?string $value ): bool;
- abstract protected function is_base64_string( string $value ): bool;
+ abstract protected function is_base64_string( ?string $value ): bool;
/**
* Set a collection of props in one go, collect any errors, and return the result.
@@ -50,22 +62,15 @@ public function set_props( $props, $context = 'set' ) {
return $prop_res;
}
- $save_res = null;
-
try {
$save_res = $this->save();
} catch ( \Throwable $e ) {
- $save_res = new \WP_Error( 'save_error', $e->getMessage() );
- } finally {
- return match ( true ) {
- 0 === $save_res => new \WP_Error(
- 'save_error',
- 'An unknown error occurred while saving.',
- ),
- \is_wp_error( $save_res ) => $save_res,
- default => $this,
- };
+ return new \WP_Error( 'save_error', $e->getMessage() );
}
+
+ return 0 === $save_res
+ ? new \WP_Error( 'save_error', 'An unknown error occurred while saving.' )
+ : $this;
}
/**
@@ -93,9 +98,9 @@ protected function set_prop( $prop, $value ): static {
[ $type, $sub ] = $this->get_prop_type( $prop );
match ( $type ) {
- 'date_created' => $this->set_date_prop( $prop, $value ),
- 'date_updated' => $this->set_date_prop( $prop, $value ),
- 'date' => $this->set_date_prop( $prop, $value ),
+ 'date_created' => $this->set_time_prop( $prop, $value ),
+ 'date_updated' => $this->set_time_prop( $prop, $value ),
+ 'date' => $this->set_time_prop( $prop, $value ),
'bool' => $this->set_bool_prop( $prop, $value ),
'bool_int' => $this->set_bool_prop( $prop, $value ),
'enum' => $this->set_enum_prop( $prop, $value, ...$sub ),
@@ -103,6 +108,7 @@ protected function set_prop( $prop, $value ): static {
'term_array' => $this->set_array_term_prop( $prop, $value, ...$sub ),
'array_assoc' => $this->set_assoc_arr_prop( $prop, $value ),
'array' => $this->set_normal_arr_prop( $prop, $value ),
+ 'array_set' => $this->set_unique_arr_prop( $prop, $value ),
'binary' => $this->set_binary_prop( $prop, $value ),
'base64_string' => $this->set_base64_string_prop( $prop, $value ),
'json_obj' => $this->set_json_prop( $prop, $value, false ),
@@ -111,6 +117,7 @@ protected function set_prop( $prop, $value ): static {
'float' => $this->set_float_prop( $prop, $value ),
'slug' => $this->set_slug_prop( $prop, $value ),
'string' => $this->set_wc_data_prop( $prop, $value ),
+ 'object' => $this->set_object_prop( $prop, $value, ...$sub ),
default => $this->set_unknown_prop( $type, $prop, $value ),
};
@@ -150,17 +157,18 @@ protected function set_wc_data_prop( $prop, $value ) {
* @param mixed $value Property value.
* @return void
*/
- protected function set_date_prop( $prop, $value ) {
- static $loop;
+ protected function set_time_prop( $prop, $value ) {
+ if ( ! $this->time_prop_loop ) {
+ if ( \is_string( $value ) && \preg_match( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $value ) ) {
+ $value = \wc_string_to_timestamp( $value );
+ }
- if ( ! $loop ) {
- $loop = true;
+ $this->time_prop_loop = true;
parent::set_date_prop( $prop, $value );
+ $this->time_prop_loop = false;
return;
}
- $loop = false;
-
$this->set_wc_data_prop( $prop, $value );
}
@@ -190,6 +198,11 @@ protected function set_bool_prop( string $prop, $value ) {
* @return void
*/
protected function set_enum_prop( string $prop, mixed $val, null|string|BackedEnum $type = null ) {
+ if ( null === $type ) {
+ $this->set_wc_data_prop( $prop, $val );
+ return;
+ }
+
if ( $val instanceof $type ) {
$this->set_wc_data_prop( $prop, $val );
return;
@@ -255,6 +268,19 @@ protected function set_normal_arr_prop( string $prop, $value ) {
$this->set_wc_data_prop( $prop, \wc_string_to_array( $value ) );
}
+ /**
+ * Set an array prop with unique values
+ *
+ * @param string $prop Property name.
+ * @param mixed $value Property value.
+ * @return void
+ */
+ protected function set_unique_arr_prop( string $prop, $value ) {
+ $value = \array_values( \array_unique( \wc_string_to_array( $value ) ) );
+
+ $this->set_wc_data_prop( $prop, $value );
+ }
+
/**
* Set an associative array prop
*
@@ -309,13 +335,41 @@ protected function set_base64_string_prop( string $prop, mixed $value ) {
* @return void
*/
protected function set_json_prop( string $prop, string|array $value, bool $assoc = true ) {
- \error_log( 'set_json_prop called with value: ' . \print_r( $value, true ) );
if ( ! \is_array( $value ) ) {
$value = \json_decode( $value, $assoc );
}
$this->set_wc_data_prop( $prop, $value );
}
+ /**
+ * Set an object prop
+ *
+ * @template TObj of XWC_Prop
+ *
+ * @param string $prop
+ * @param mixed $value
+ * @param class-string $cname Class name to parse the value into.
+ */
+ protected function set_object_prop( string $prop, mixed $value, string $cname = XWC_Prop::class ): void {
+ $data = match ( true ) {
+ \is_array( $value ) => $value,
+ \is_string( $value ) => \json_decode( $value, true ) ?? array(),
+ \is_a( $value, XWC_Prop::class ) => $value,
+ default => array(),
+ };
+
+ /**
+ * If the object is not read, we need to get the prop from the data store.
+ *
+ * @var TObj $obj
+ */
+ $obj = $this->get_object_read()
+ ? $this->get_prop( $prop )?->with_data( $data ) ?? new $cname( $data )
+ : new $cname( $data );
+
+ $this->set_wc_data_prop( $prop, $obj );
+ }
+
/**
* Set an int prop
*
diff --git a/src/Repo/Query_Handler.php b/src/Repo/Query_Handler.php
index e5135aa..d422b10 100644
--- a/src/Repo/Query_Handler.php
+++ b/src/Repo/Query_Handler.php
@@ -181,15 +181,6 @@ protected function get_date_query_args( array $vars, array $dates ): array {
* @return array
*/
protected function get_meta_query_args( array $vars ): array {
- $keys = array(
- 'parent',
- 'parent_exclude',
- 'exclude',
- 'limit',
- 'type',
- 'return',
- );
-
return parent::get_wp_query_args( $vars );
}
diff --git a/src/Utils/xwc-data-utils-object.php b/src/Utils/xwc-data-utils-object.php
index 721bc15..5c81a8c 100644
--- a/src/Utils/xwc-data-utils-object.php
+++ b/src/Utils/xwc-data-utils-object.php
@@ -14,8 +14,14 @@
* @return T
*/
function xwc_ds( string $name, string $cn = XWC_Data_Store_XT::class ): XWC_Data_Store_XT {
+ $entity = xwc_get_entity( $name );
+
+ if ( null === $entity ) {
+ throw new \RuntimeException( \esc_html( "Entity '{$name}' is not registered." ) );
+ }
+
// @phpstan-ignore return.type
- return xwc_get_entity( $name )->repo;
+ return $entity->repo;
}
/**
@@ -35,7 +41,13 @@ function xwc_data_store( string $name ): WC_Data_Store {
* @return XWC_Object_Factory
*/
function xwc_get_object_factory( string $name ): XWC_Object_Factory {
- return xwc_get_entity( $name )->factory;
+ $entity = xwc_get_entity( $name );
+
+ if ( null === $entity ) {
+ throw new \RuntimeException( \esc_html( "Entity '{$name}' is not registered." ) );
+ }
+
+ return $entity->factory;
}
/**
@@ -70,18 +82,19 @@ function xwc_get_object( mixed $id, string $name, int|bool|null $def = false ):
return $def;
}
- return xwc_get_object_factory( $name )->{"get_$name"}( $id ) ?: $def;
+ // @phpstan-ignore return.type
+ return xwc_get_object_factory( $name )->get_object( $id ) ?: $def;
}
/**
* Get the class name of a data object by ID and type.
*
* @param int $id Object ID.
- * @param string $name Object type.
+ * @param string $type Object type.
* @return class-string
*/
-function xwc_get_object_classname( int $id, string $name ): string {
- return xwc_get_object_factory( $name )->{"get_{$name}_classname"}( $id );
+function xwc_get_object_classname( int $id, string $type ): string {
+ return xwc_get_object_factory( $type )->get_classname( $id ) ?: XWC_Data::class;
}
/**
@@ -92,9 +105,7 @@ function xwc_get_object_classname( int $id, string $name ): string {
* @return XWC_Data
*/
function xwc_get_object_instance( int $id, string $type ): XWC_Data {
- $classname = xwc_get_object_classname( $id, $type );
-
- return new $classname( $id );
+ return xwc_get_object_factory( $type )->make_object( $id );
}
/**
diff --git a/tests/DataObjectTest.php b/tests/DataObjectTest.php
new file mode 100644
index 0000000..982dfa3
--- /dev/null
+++ b/tests/DataObjectTest.php
@@ -0,0 +1,109 @@
+set_name('Alpha');
+ $item->set_slug('alpha-item');
+ $item->set_score('42');
+
+ $this->assertSame('Alpha', $item->get_name());
+ $this->assertSame('alpha-item', $item->get_slug());
+ $this->assertSame(42, $item->get_score());
+ }
+
+ public function test_hydrated_item_reports_core_data_and_core_changes(): void {
+ xwc_test_seed_custom_entity_rows(
+ array(
+ array( 'name' => 'Alpha', 'slug' => 'alpha', 'score' => 10 ),
+ ),
+ );
+
+ $item = new XWC_Test_Item(1);
+ $item->set_score(15);
+
+ $this->assertSame(
+ array(
+ 'name' => 'Alpha',
+ 'slug' => 'alpha',
+ 'score' => 15,
+ 'id' => 1,
+ ),
+ $item->get_core_data('view', true),
+ );
+ $this->assertSame(
+ array(
+ 'score' => 15,
+ ),
+ $item->get_core_changes(),
+ );
+ }
+
+ public function test_json_serialize_returns_object_data_without_meta_data_key(): void {
+ xwc_test_seed_custom_entity_rows(
+ array(
+ array( 'name' => 'Alpha', 'slug' => 'alpha', 'score' => 10 ),
+ ),
+ );
+
+ $item = new XWC_Test_Item(1);
+ $data = $item->jsonSerialize();
+
+ $this->assertSame(1, $data['id']);
+ $this->assertSame('Alpha', $data['name']);
+ $this->assertArrayNotHasKey('meta_data', $data);
+ }
+
+ public function test_serialization_round_trip_restores_object_identity(): void {
+ xwc_test_seed_custom_entity_rows(
+ array(
+ array( 'name' => 'Alpha', 'slug' => 'alpha', 'score' => 10 ),
+ ),
+ );
+
+ /** @var XWC_Test_Item $restored */
+ $restored = unserialize(serialize(new XWC_Test_Item(1)));
+
+ $this->assertInstanceOf(XWC_Test_Item::class, $restored);
+ $this->assertSame(1, $restored->get_id());
+ $this->assertSame('alpha', $restored->get_slug());
+ }
+
+ public function test_object_helpers_return_defaults_and_paged_results(): void {
+ xwc_test_seed_custom_entity_rows(
+ array(
+ array( 'name' => 'Alpha', 'slug' => 'alpha', 'score' => 10 ),
+ array( 'name' => 'Beta', 'slug' => 'beta', 'score' => 20 ),
+ array( 'name' => 'Gamma', 'slug' => 'gamma', 'score' => 30 ),
+ ),
+ );
+
+ $missing = xwc_get_object(999, xwc_test_custom_entity_name(), null);
+ $paged = xwc_get_objects(
+ xwc_test_custom_entity_name(),
+ array(
+ 'limit' => 2,
+ 'order' => 'ASC',
+ 'orderby' => 'score',
+ 'paginate' => true,
+ 'return' => 'ids',
+ ),
+ );
+
+ $this->assertNull($missing);
+ $this->assertSame(array(1, 2), $paged['objects']);
+ $this->assertSame(2, $paged['pages']);
+ $this->assertSame(3, $paged['total']);
+ }
+}
diff --git a/tests/ModelDefinitionTest.php b/tests/ModelDefinitionTest.php
new file mode 100644
index 0000000..f71da86
--- /dev/null
+++ b/tests/ModelDefinitionTest.php
@@ -0,0 +1,78 @@
+expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('A concrete meta store class must be provided when meta props are defined');
+
+ new Model(
+ 'fixture',
+ '{{PREFIX}}fixture_items',
+ array(
+ 'name' => array(
+ 'default' => '',
+ 'type' => 'string',
+ ),
+ ),
+ array(
+ 'color' => array(
+ 'default' => '',
+ 'type' => 'string',
+ ),
+ ),
+ );
+ }
+
+ public function test_model_normalizes_tax_field_id_to_term_id(): void {
+ $model = new Model(
+ 'fixture',
+ '{{PREFIX}}fixture_items',
+ array(
+ 'name' => array(
+ 'default' => '',
+ 'type' => 'string',
+ ),
+ ),
+ array(),
+ array(
+ 'category' => array(
+ 'field' => 'id',
+ 'return' => 'single',
+ 'taxonomy' => 'category',
+ ),
+ ),
+ );
+
+ $this->assertSame('term_single|term_id|category', $model->tax_props['category']['type']);
+ $this->assertSame('term_id', $model->tax_props['category']['field']);
+ }
+
+ public function test_model_keeps_term_id_tax_field_stable(): void {
+ $model = new Model(
+ 'fixture',
+ '{{PREFIX}}fixture_items',
+ array(
+ 'name' => array(
+ 'default' => '',
+ 'type' => 'string',
+ ),
+ ),
+ array(),
+ array(
+ 'category' => array(
+ 'field' => 'term_id',
+ 'return' => 'single',
+ 'taxonomy' => 'category',
+ ),
+ ),
+ );
+
+ $this->assertSame('term_single|term_id|category', $model->tax_props['category']['type']);
+ $this->assertSame('term_id', $model->tax_props['category']['field']);
+ }
+}
diff --git a/tests/ObjectFactoryTest.php b/tests/ObjectFactoryTest.php
new file mode 100644
index 0000000..ae262e3
--- /dev/null
+++ b/tests/ObjectFactoryTest.php
@@ -0,0 +1,82 @@
+assertSame(25, $factory->get_id(25));
+ $this->assertSame(25, $factory->get_id('25'));
+ }
+
+ public function test_factory_get_id_accepts_data_objects_and_globals(): void {
+ xwc_test_seed_custom_entity_rows(
+ array(
+ array( 'name' => 'Alpha', 'slug' => 'alpha', 'score' => 10 ),
+ ),
+ );
+
+ $factory = xwc_get_object_factory(xwc_test_custom_entity_name());
+ $object = new XWC_Test_Item(1);
+
+ $GLOBALS[xwc_test_custom_entity_name()] = $object;
+
+ $this->assertSame(1, $factory->get_id($object));
+ $this->assertSame(1, $factory->get_id(null));
+ }
+
+ public function test_factory_get_object_returns_null_for_missing_ids(): void {
+ $factory = xwc_get_object_factory(xwc_test_custom_entity_name());
+
+ $this->assertNull($factory->get_object(false));
+ }
+
+ public function test_factory_make_object_returns_concrete_object_even_for_zero_id(): void {
+ $factory = xwc_get_object_factory(xwc_test_custom_entity_name());
+ $object = $factory->make_object(0);
+
+ $this->assertInstanceOf(XWC_Test_Item::class, $object);
+ $this->assertSame(0, $object->get_id());
+ }
+
+ public function test_object_classname_falls_back_to_base_class_when_filter_returns_invalid_class(): void {
+ add_filter(
+ 'xwc_' . xwc_test_custom_entity_name() . '_class',
+ static fn (): string => 'Missing_Class',
+ );
+
+ try {
+ $this->assertSame(XWC_Data::class, xwc_get_object_classname(1, xwc_test_custom_entity_name()));
+ } finally {
+ remove_all_filters('xwc_' . xwc_test_custom_entity_name() . '_class');
+ }
+ }
+
+ public function test_get_object_instance_returns_hydrated_object(): void {
+ xwc_test_seed_custom_entity_rows(
+ array(
+ array( 'name' => 'Alpha', 'slug' => 'alpha', 'score' => 10 ),
+ ),
+ );
+
+ $object = xwc_get_object_instance(1, xwc_test_custom_entity_name());
+
+ $this->assertInstanceOf(XWC_Test_Item::class, $object);
+ $this->assertSame('alpha', $object->get_slug());
+ }
+}
diff --git a/tests/PropTest.php b/tests/PropTest.php
new file mode 100644
index 0000000..b64d88a
--- /dev/null
+++ b/tests/PropTest.php
@@ -0,0 +1,75 @@
+assertNull($prop->jsonSerialize());
+ }
+
+ public function test_set_marks_prop_as_changed_after_read(): void {
+ $prop = new XWC_Test_Prop();
+
+ $prop->set('alpha', 'b');
+
+ $this->assertTrue($prop->changed());
+ $this->assertSame('b', $prop->get('alpha'));
+ }
+
+ public function test_set_data_marks_prop_as_changed_after_read(): void {
+ $prop = new XWC_Test_Prop();
+
+ $prop->set_data(
+ array(
+ 'alpha' => 'z',
+ 'items' => array(1, 2, 3),
+ ),
+ );
+
+ $this->assertTrue($prop->changed());
+ }
+
+ public function test_with_data_clones_from_other_prop_instances(): void {
+ $source = new XWC_Test_Prop(
+ array(
+ 'alpha' => 'source',
+ 'items' => array(4, 5),
+ ),
+ );
+ $clone = XWC_Test_Prop::default()->with_data($source);
+
+ $this->assertInstanceOf(XWC_Test_Prop::class, $clone);
+ $this->assertNotSame($source, $clone);
+ $this->assertSame($source->get_data(), $clone->get_data());
+ }
+
+ public function test_serialization_round_trip_preserves_data(): void {
+ $prop = new XWC_Test_Prop(
+ array(
+ 'alpha' => 'serialized',
+ 'items' => array(9, 7),
+ ),
+ );
+
+ /** @var XWC_Test_Prop $roundTrip */
+ $roundTrip = unserialize(serialize($prop));
+
+ $this->assertInstanceOf(XWC_Test_Prop::class, $roundTrip);
+ $this->assertSame($prop->get_data(), $roundTrip->get_data());
+ }
+
+ public function test_array_access_mutators_throw(): void {
+ $prop = new XWC_Test_Prop();
+
+ $this->expectException(BadMethodCallException::class);
+ $this->expectExceptionMessage('Do not use this method directly.');
+
+ $prop['alpha'] = 'c';
+ }
+}
diff --git a/tests/QueryTest.php b/tests/QueryTest.php
new file mode 100644
index 0000000..d6609b5
--- /dev/null
+++ b/tests/QueryTest.php
@@ -0,0 +1,192 @@
+ 1,
+ 'name' => 'Alpha',
+ 'slug' => 'alpha',
+ 'score' => 10,
+ ),
+ array(
+ 'id' => 2,
+ 'name' => 'Beta',
+ 'slug' => 'beta',
+ 'score' => 20,
+ ),
+ array(
+ 'id' => 3,
+ 'name' => 'Gamma',
+ 'slug' => 'gamma',
+ 'score' => 30,
+ ),
+ ),
+ );
+
+ $query = new XWC_Object_Query(
+ xwc_test_custom_entity_table_name(),
+ 'id',
+ );
+
+ $this->assertSame(
+ 3,
+ $query->count(
+ array(
+ 'fields' => 'ids',
+ 'page' => 1,
+ 'per_page' => 2,
+ ),
+ ),
+ );
+ $this->assertSame(3, $query->total);
+ }
+
+ public function test_custom_query_can_filter_rows_without_paging(): void {
+ xwc_test_seed_custom_entity_rows(
+ array(
+ array( 'name' => 'Alpha', 'slug' => 'alpha', 'score' => 10 ),
+ array( 'name' => 'Beta', 'slug' => 'beta', 'score' => 20 ),
+ array( 'name' => 'Gamma', 'slug' => 'gamma', 'score' => 20 ),
+ ),
+ );
+
+ $query = new XWC_Object_Query(
+ xwc_test_custom_entity_table_name(),
+ 'id',
+ );
+
+ $this->assertSame(
+ array(2, 3),
+ $query->query(
+ array(
+ 'col_query' => array(
+ 'score' => 20,
+ ),
+ 'fields' => 'ids',
+ 'order' => 'ASC',
+ 'orderby' => 'id',
+ 'per_page' => 0,
+ ),
+ ),
+ );
+ $this->assertSame(2, $query->total);
+ $this->assertSame(1, $query->pages);
+ }
+
+ public function test_xwc_ds_returns_registered_repo_instance(): void {
+ $repo = xwc_ds(xwc_test_custom_entity_name());
+
+ $this->assertInstanceOf(XWC_Data_Store_XT::class, $repo);
+ $this->assertSame(xwc_test_custom_entity_table_name(), $repo->get_table());
+ }
+
+ public function test_repo_query_paginates_and_returns_ids(): void {
+ xwc_test_seed_custom_entity_rows(
+ array(
+ array( 'name' => 'Alpha', 'slug' => 'alpha', 'score' => 10 ),
+ array( 'name' => 'Beta', 'slug' => 'beta', 'score' => 20 ),
+ array( 'name' => 'Gamma', 'slug' => 'gamma', 'score' => 30 ),
+ ),
+ );
+
+ $results = xwc_ds(xwc_test_custom_entity_name())->query(
+ array(
+ 'limit' => 2,
+ 'order' => 'ASC',
+ 'orderby' => 'score',
+ 'paginate' => true,
+ 'return' => 'ids',
+ ),
+ );
+
+ $this->assertSame(2, $results['pages']);
+ $this->assertSame(3, $results['total']);
+ $this->assertCount(2, $results['objects']);
+ $this->assertSame(array(1, 2), $results['objects']);
+ }
+
+ public function test_repo_query_can_return_hydrated_objects(): void {
+ xwc_test_seed_custom_entity_rows(
+ array(
+ array( 'name' => 'Alpha', 'slug' => 'alpha', 'score' => 10 ),
+ array( 'name' => 'Beta', 'slug' => 'beta', 'score' => 20 ),
+ ),
+ );
+
+ $results = xwc_ds(xwc_test_custom_entity_name())->query(
+ array(
+ 'order' => 'ASC',
+ 'orderby' => 'score',
+ 'return' => 'objects',
+ ),
+ );
+
+ $this->assertCount(2, $results);
+ $this->assertInstanceOf(XWC_Test_Item::class, $results[0]);
+ $this->assertSame('alpha', $results[0]->get_slug());
+ $this->assertSame(20, $results[1]->get_score());
+ }
+
+ public function test_repo_count_applies_core_column_filters(): void {
+ xwc_test_seed_custom_entity_rows(
+ array(
+ array( 'name' => 'Alpha', 'slug' => 'alpha', 'score' => 10 ),
+ array( 'name' => 'Beta', 'slug' => 'beta', 'score' => 20 ),
+ array( 'name' => 'Gamma', 'slug' => 'gamma', 'score' => 20 ),
+ ),
+ );
+
+ $count = xwc_ds(xwc_test_custom_entity_name())->count(
+ array(
+ 'score' => 20,
+ ),
+ );
+
+ $this->assertSame(2, $count);
+ }
+
+ public function test_repo_find_returns_first_matching_object(): void {
+ xwc_test_seed_custom_entity_rows(
+ array(
+ array( 'name' => 'Alpha', 'slug' => 'alpha', 'score' => 10 ),
+ array( 'name' => 'Beta', 'slug' => 'beta', 'score' => 20 ),
+ ),
+ );
+
+ $found = xwc_ds(xwc_test_custom_entity_name())->find(
+ array(
+ 'slug' => 'beta',
+ ),
+ );
+
+ $this->assertInstanceOf(XWC_Test_Item::class, $found);
+ $this->assertSame(2, $found->get_id());
+ $this->assertSame('Beta', $found->get_name());
+ }
+
+ public function test_xwc_ds_throws_for_unknown_entities(): void {
+ $this->expectException(RuntimeException::class);
+ $this->expectExceptionMessage('missing-entity');
+
+ xwc_ds('missing-entity');
+ }
+
+ public function test_xwc_get_object_factory_throws_for_unknown_entities(): void {
+ $this->expectException(RuntimeException::class);
+ $this->expectExceptionMessage('missing-entity');
+
+ xwc_get_object_factory('missing-entity');
+ }
+}
diff --git a/tests/ReleaseConfigTest.php b/tests/ReleaseConfigTest.php
new file mode 100644
index 0000000..caaad8f
--- /dev/null
+++ b/tests/ReleaseConfigTest.php
@@ -0,0 +1,41 @@
+assertIsArray($exec_plugin);
+ $this->assertArrayHasKey('prepareCmd', $exec_plugin);
+ $this->assertStringContainsString('src', $exec_plugin['prepareCmd']);
+ $this->assertStringContainsString('lib', $exec_plugin['prepareCmd']);
+ $this->assertStringContainsString('composer.json', $exec_plugin['prepareCmd']);
+ $this->assertStringContainsString('README.md', $exec_plugin['prepareCmd']);
+ $this->assertStringContainsString('LICENSE', $exec_plugin['prepareCmd']);
+ }
+
+ public function test_release_config_keeps_beta_as_prerelease_branch(): void {
+ $config = json_decode((string) file_get_contents(dirname(__DIR__) . '/.releaserc'), true, 512, JSON_THROW_ON_ERROR);
+
+ $this->assertSame('master', $config['branches'][0]);
+ $this->assertContains(
+ array(
+ 'name' => 'beta',
+ 'prerelease' => true,
+ ),
+ $config['branches']
+ );
+ }
+}
diff --git a/tests/SmokeTest.php b/tests/SmokeTest.php
new file mode 100644
index 0000000..2b1e684
--- /dev/null
+++ b/tests/SmokeTest.php
@@ -0,0 +1,18 @@
+assertTrue(function_exists('add_action'));
+ $this->assertTrue(class_exists('WooCommerce'));
+ $this->assertTrue(class_exists(Entity_Manager::class));
+
+ $manager = Entity_Manager::instance();
+
+ $this->assertInstanceOf(Entity_Manager::class, $manager);
+ }
+}
diff --git a/tests/Support/TestItem.php b/tests/Support/TestItem.php
new file mode 100644
index 0000000..73c3db1
--- /dev/null
+++ b/tests/Support/TestItem.php
@@ -0,0 +1,27 @@
+ array(
+ 'default' => '',
+ 'type' => 'string',
+ ),
+ 'slug' => array(
+ 'default' => '',
+ 'type' => 'slug',
+ ),
+ 'score' => array(
+ 'default' => 0,
+ 'type' => 'int',
+ ),
+ ),
+)]
+final class XWC_Test_Item extends XWC_Data {
+ protected $object_type = 'xwc_test_item';
+}
diff --git a/tests/Support/TestProp.php b/tests/Support/TestProp.php
new file mode 100644
index 0000000..2706c1d
--- /dev/null
+++ b/tests/Support/TestProp.php
@@ -0,0 +1,12 @@
+ 'a',
+ 'items' => array(3, 1, 2),
+ );
+ }
+}
diff --git a/tests/Support/fixtures.php b/tests/Support/fixtures.php
new file mode 100644
index 0000000..565f48b
--- /dev/null
+++ b/tests/Support/fixtures.php
@@ -0,0 +1,70 @@
+prefix . 'xwc_test_items';
+}
+
+function xwc_test_install_custom_entity(): void {
+ global $wpdb;
+
+ if (! function_exists('dbDelta')) {
+ require_once ABSPATH . 'wp-admin/includes/upgrade.php';
+ }
+
+ $table = xwc_test_custom_entity_table_name();
+ $charset = $wpdb->get_charset_collate();
+
+ dbDelta(
+ "CREATE TABLE {$table} (
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(255) NOT NULL DEFAULT '',
+ slug VARCHAR(200) NOT NULL DEFAULT '',
+ score BIGINT NOT NULL DEFAULT 0,
+ PRIMARY KEY (id),
+ KEY slug (slug),
+ KEY score (score)
+ ) {$charset};"
+ );
+
+ if (! xwc_entity_exists(xwc_test_custom_entity_name())) {
+ xwc_register_entity(XWC_Test_Item::class);
+ }
+}
+
+function xwc_test_reset_custom_entity_table(): void {
+ global $wpdb;
+
+ xwc_test_install_custom_entity();
+ $wpdb->query('TRUNCATE TABLE ' . xwc_test_custom_entity_table_name()); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+}
+
+/**
+ * @param array $rows
+ */
+function xwc_test_seed_custom_entity_rows(array $rows): void {
+ global $wpdb;
+
+ xwc_test_install_custom_entity();
+
+ foreach ($rows as $row) {
+ $wpdb->insert(
+ xwc_test_custom_entity_table_name(),
+ wp_parse_args(
+ $row,
+ array(
+ 'name' => '',
+ 'score' => 0,
+ 'slug' => '',
+ )
+ )
+ );
+ }
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..98e1b13
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,47 @@
+wpdb_table_fix();
+ WC_Install::create_tables();
+ update_option('woocommerce_version', WC_VERSION);
+ update_option('woocommerce_db_version', WC()->db_version);
+ }
+ },
+ 0
+ );
+ }
+);
+
+require_once $_tests_dir . '/includes/bootstrap.php';
diff --git a/tests/wp-plugin/xwc-data-type-tests/xwc-data-type-tests.php b/tests/wp-plugin/xwc-data-type-tests/xwc-data-type-tests.php
new file mode 100644
index 0000000..ab4732b
--- /dev/null
+++ b/tests/wp-plugin/xwc-data-type-tests/xwc-data-type-tests.php
@@ -0,0 +1,13 @@
+