diff --git a/.ci/mysql_fixtures.sh b/.ci/mysql_fixtures.sh new file mode 100644 index 0000000..aa14663 --- /dev/null +++ b/.ci/mysql_fixtures.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +echo "Configure MySQL test database" + +mysql --user=root --password=Password123 -e "create database laminasdb_test;" diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 2f02e70..4fe1f16 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -7,5 +7,41 @@ on: tags: jobs: - ci: - uses: laminas/workflow-continuous-integration/.github/workflows/continuous-integration.yml@1.x \ No newline at end of file + matrix: + name: Generate job matrix + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.matrix.outputs.matrix }} + steps: + - name: Gather CI configuration + id: matrix + uses: laminas/laminas-ci-matrix-action@v1 + + qa: + name: QA Checks + needs: [matrix] + runs-on: ${{ matrix.operatingSystem }} + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.matrix.outputs.matrix) }} + steps: + - name: ${{ matrix.name }} + uses: laminas/laminas-continuous-integration-action@v1 + with: + job: ${{ matrix.job }} + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ROOT_PASSWORD: 'password' + MYSQL_ROOT_HOST: '%' + MYSQL_USER: 'gha' + MYSQL_PASSWORD: 'password' + MYSQL_DATABASE: 'laminasdb_test' + options: >- + --health-cmd="mysqladmin ping" + --health-interval 10s + --health-timeout 5s + --health-retries 3 + ports: + - 3306 \ No newline at end of file diff --git a/.laminas-ci/mysql_permissions.sql b/.laminas-ci/mysql_permissions.sql new file mode 100644 index 0000000..59c1c60 --- /dev/null +++ b/.laminas-ci/mysql_permissions.sql @@ -0,0 +1,3 @@ +CREATE USER 'gha'@'%' IDENTIFIED WITH mysql_native_password BY 'password'; +GRANT ALL PRIVILEGES ON *.* TO 'gha'@'%'; +FLUSH PRIVILEGES; diff --git a/.laminas-ci/phpunit.xml b/.laminas-ci/phpunit.xml new file mode 100644 index 0000000..1c8f2bf --- /dev/null +++ b/.laminas-ci/phpunit.xml @@ -0,0 +1,29 @@ + + + + + + + + + ./test/unit + + + ./test/integration + + + + + + + + + + + + diff --git a/.laminas-ci/pre-install.sh b/.laminas-ci/pre-install.sh new file mode 100644 index 0000000..a3c153e --- /dev/null +++ b/.laminas-ci/pre-install.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +WORKING_DIRECTORY=$2 +JOB=$3 +PHP_VERSION=$(echo "${JOB}" | jq -r '.php') + + +if [ ! -z "$GITHUB_BASE_REF" ] && [[ "$GITHUB_BASE_REF" =~ ^[0-9]+\.[0-9] ]]; then + readarray -td. TARGET_BRANCH_VERSION_PARTS <<<"${GITHUB_BASE_REF}."; + unset 'TARGET_BRANCH_VERSION_PARTS[-1]'; + declare -a TARGET_BRANCH_VERSION_PARTS + MAJOR_OF_TARGET_BRANCH=${TARGET_BRANCH_VERSION_PARTS[0]} + MINOR_OF_TARGET_BRANCH=${TARGET_BRANCH_VERSION_PARTS[1]} + + export COMPOSER_ROOT_VERISON="${MAJOR_OF_TARGET_BRANCH}.${MINOR_OF_TARGET_BRANCH}.99" + echo "Exported COMPOSER_ROOT_VERISON as ${COMPOSER_ROOT_VERISON}" +fi diff --git a/.laminas-ci/pre-run.sh b/.laminas-ci/pre-run.sh new file mode 100644 index 0000000..660082a --- /dev/null +++ b/.laminas-ci/pre-run.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -e + +TEST_USER=$1 +WORKSPACE=$2 +JOB=$3 + +COMMAND=$(echo "${JOB}" | jq -r '.command') + +if [[ ! ${COMMAND} =~ phpunit ]]; then + exit 0 +fi + +PHP_VERSION=$(echo "${JOB}" | jq -r '.php') + +# Install CI version of phpunit config +cp .laminas-ci/phpunit.xml phpunit.xml + +# Install lsof (used in integration tests) +apt update -qq +apt install -yqq lsof diff --git a/bin/install-deps.sh b/bin/install-deps.sh new file mode 100644 index 0000000..df3c3a4 --- /dev/null +++ b/bin/install-deps.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +/usr/bin/composer validate && \ +/usr/bin/composer --ignore-platform-reqs install \ + --no-ansi --no-progress --no-scripts \ + --classmap-authoritative --no-interaction \ + --quiet \ No newline at end of file diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..0bb7326 --- /dev/null +++ b/compose.yml @@ -0,0 +1,31 @@ +services: + php: + build: + context: ./ + dockerfile: docker/php/Dockerfile + args: + - PHP_VERSION=${PHP_VERSION:-8.3.19} + volumes: + - ./:/var/www/html + depends_on: + - mysql + + mysql: + build: + context: ./ + dockerfile: docker/databases/mysql/Dockerfile + args: + - VERSION=${MYSQL_VERSION:-8.0.41} + ports: + - "3306:3306" + environment: + - MYSQL_DATABASE=${MYSQL_DATABASE:-laminasdb_test} + - MYSQL_USER=${MYSQL_USER:-user} + - MYSQL_PASSWORD=${MYSQL_PASSWORD:-password} + - MYSQL_RANDOM_ROOT_PASSWORD=${MYSQL_RANDOM_ROOT_PASSWORD:-yes} + volumes: + - ./test/integration/TestFixtures/mysql.sql:/docker-entrypoint-initdb.d/mysql.sql + healthcheck: + test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] + timeout: 20s + retries: 10 diff --git a/composer.json b/composer.json index e649f4c..7eeabce 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "laminas/laminas-db-mysql-adapter", + "name": "laminas/laminas-db-adapter-mysql", "description": "MySQL support for laminas-db", "license": "BSD-3-Clause", "keywords": [ @@ -7,18 +7,18 @@ "mysql", "db" ], - "homepage": "https://github.com/axleus/laminas-db-mysql/discussions", + "homepage": "https://github.com/axleus/laminas-db-adapter-mysql/discussions", "support": { - "issues": "https://github.com/alxeus/laminas-db-mysql/issues", - "source": "https://github.com/axleus/laminas-db-mysql", - "forum": "https://github.com/axleus/laminas-db-mysql/discussions" + "issues": "https://github.com/alxeus/laminas-db-adapter-mysql/issues", + "source": "https://github.com/axleus/laminas-db-adapter-mysql", + "forum": "https://github.com/axleus/laminas-db-adapter-mysql/discussions" }, "minimum-stability": "dev", "prefer-stable": true, "config": { "sort-packages": true, "platform": { - "php": "8.1.99" + "php": "8.2.99" }, "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true @@ -26,39 +26,42 @@ }, "extra": { "laminas": { - "component": "Laminas\\Db\\Mysql", - "config-provider": "Laminas\\Db\\Mysql\\ConfigProvider" + "component": "Laminas\\Db\\Adapter\\Mysql", + "config-provider": "Laminas\\Db\\Adapter\\Mysql\\ConfigProvider" } }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/axleus/laminas-db" + } + ], "require": { "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", - "laminas/laminas-db": "3.0.x" + "laminas/laminas-db": "dev-adapter-migration-mysql" }, "require-dev": { "ext-mysqli": "*", "ext-pdo_mysql": "*", "laminas/laminas-coding-standard": "^3.0.1", - "laminas/laminas-eventmanager": "^3.6.0", - "laminas/laminas-hydrator": "^4.7", - "laminas/laminas-servicemanager": "^3.19.0", - "phpunit/phpunit": "^9.5.25", + "phpunit/phpunit": "^11.5.15", "psalm/plugin-phpunit": "^0.19.2", "vimeo/psalm": "^6.8.8" }, "suggest": { "ext-mysqli": "Required for MySQLi support", "ext-pdo_mysql": "Required for PDO MySQL support", - "laminas/laminas-servicemanager": "Laminas\\ServiceManager component" + "laminas/laminas-eventmanager": "Required for TableGateway events support" }, "autoload": { "psr-4": { - "Laminas\\Db\\Mysql\\": "src/" + "Laminas\\Db\\Adapter\\Mysql\\": "src/" } }, "autoload-dev": { "psr-4": { - "LaminasTest\\Db\\Mysql\\": "test/unit/", - "LaminasIntegrationTest\\Db\\Mysql\\": "test/integration/" + "LaminasTest\\Db\\Adapter\\Mysql\\": "test/unit/", + "LaminasIntegrationTest\\Db\\Adapter\\Mysql\\": "test/integration/" } }, "scripts": { @@ -73,8 +76,5 @@ "test-integration": "phpunit --colors=always --testsuite \"integration test\"", "static-analysis": "psalm --shepherd --stats", "upload-coverage": "coveralls -v" - }, - "conflict": { - "laminas/laminas-db": "*" } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..403f5f1 --- /dev/null +++ b/composer.lock @@ -0,0 +1,5568 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "3601be926e7985151a1ac381838f662d", + "packages": [ + { + "name": "brick/varexporter", + "version": "0.5.0", + "source": { + "type": "git", + "url": "https://github.com/brick/varexporter.git", + "reference": "84b2a7a91f69aa5d079aec5a0a7256ebf2dceb6b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/varexporter/zipball/84b2a7a91f69aa5d079aec5a0a7256ebf2dceb6b", + "reference": "84b2a7a91f69aa5d079aec5a0a7256ebf2dceb6b", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^9.3", + "psalm/phar": "5.21.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\VarExporter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A powerful alternative to var_export(), which can export closures and objects without __set_state()", + "keywords": [ + "var_export" + ], + "support": { + "issues": "https://github.com/brick/varexporter/issues", + "source": "https://github.com/brick/varexporter/tree/0.5.0" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2024-05-10T17:15:19+00:00" + }, + { + "name": "laminas/laminas-db", + "version": "dev-adapter-migration-mysql", + "source": { + "type": "git", + "url": "https://github.com/axleus/laminas-db.git", + "reference": "6088adf2d39c4fae6d86dee69461e8622a499c34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/axleus/laminas-db/zipball/6088adf2d39c4fae6d86dee69461e8622a499c34", + "reference": "6088adf2d39c4fae6d86dee69461e8622a499c34", + "shasum": "" + }, + "require": { + "laminas/laminas-servicemanager": "^4.0.0", + "laminas/laminas-stdlib": "^3.20.0", + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "conflict": { + "zendframework/zend-db": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "^3.0.1", + "laminas/laminas-eventmanager": "^3.14.0", + "laminas/laminas-servicemanager": "^4.0.0", + "phpunit/phpunit": "^11.5.12", + "psalm/plugin-phpunit": "^0.19.2", + "rector/rector": "^2.0", + "vimeo/psalm": "^6.8.8" + }, + "suggest": { + "laminas/laminas-eventmanager": "Laminas\\EventManager component", + "laminas/laminas-hydrator": "(^5.0.0) Laminas\\Hydrator component for using HydratingResultSets", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Db\\": "src/", + "CustomRule\\PHPUnit\\": "rector/" + } + }, + "autoload-dev": { + "psr-4": { + "LaminasTest\\Db\\": "test/unit/", + "LaminasIntegrationTest\\Db\\": "test/integration/" + } + }, + "scripts": { + "check": [ + "@cs-check", + "@test" + ], + "cs-check": [ + "phpcs" + ], + "cs-fix": [ + "phpcbf" + ], + "test": [ + "phpunit --colors=always --testsuite \"unit test\"" + ], + "test-coverage": [ + "phpunit --colors=always --coverage-clover clover.xml" + ], + "test-integration": [ + "phpunit --colors=always --testsuite \"integration test\"" + ], + "static-analysis": [ + "psalm --shepherd --stats" + ], + "upload-coverage": [ + "coveralls -v" + ] + }, + "license": [ + "BSD-3-Clause" + ], + "description": "Database abstraction layer, SQL abstraction, result set abstraction, and RowDataGateway and TableDataGateway implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "db", + "laminas" + ], + "support": { + "docs": "https://docs.laminas.dev/laminas-db/", + "issues": "https://github.com/laminas/laminas-db/issues", + "source": "https://github.com/laminas/laminas-db", + "rss": "https://github.com/laminas/laminas-db/releases.atom", + "chat": "https://laminas.dev/chat", + "forum": "https://discourse.laminas.dev" + }, + "funding": [ + { + "type": "github", + "url": "https://github.com/AXLEUS" + } + ], + "time": "2025-05-16T06:09:34+00:00" + }, + { + "name": "laminas/laminas-servicemanager", + "version": "4.4.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-servicemanager.git", + "reference": "74da44d07e493b834347123242d0047976fb9932" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/74da44d07e493b834347123242d0047976fb9932", + "reference": "74da44d07e493b834347123242d0047976fb9932", + "shasum": "" + }, + "require": { + "brick/varexporter": "^0.3.8 || ^0.4.0 || ^0.5.0", + "laminas/laminas-stdlib": "^3.19", + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", + "psr/container": "^1.1 || ^2.0" + }, + "conflict": { + "laminas/laminas-code": "<4.10.0", + "zendframework/zend-code": "<3.3.1" + }, + "provide": { + "psr/container-implementation": "^1.0 || ^2.0" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.11.99.5", + "friendsofphp/proxy-manager-lts": "^1.0.18", + "laminas/laminas-cli": "^1.11", + "laminas/laminas-coding-standard": "~3.0.1", + "laminas/laminas-container-config-test": "^1.0", + "mikey179/vfsstream": "^1.6.12", + "phpbench/phpbench": "^1.4.0", + "phpunit/phpunit": "^10.5.44", + "psalm/plugin-phpunit": "^0.19.2", + "symfony/console": "^6.4.17 || ^7.0", + "vimeo/psalm": "^6.2.0" + }, + "suggest": { + "friendsofphp/proxy-manager-lts": "To handle lazy initialization of services", + "laminas/laminas-cli": "To consume CLI commands provided by this component" + }, + "type": "library", + "extra": { + "laminas": { + "module": "Laminas\\ServiceManager", + "config-provider": "Laminas\\ServiceManager\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Laminas\\ServiceManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Factory-Driven Dependency Injection Container", + "homepage": "https://laminas.dev", + "keywords": [ + "PSR-11", + "dependency-injection", + "di", + "dic", + "laminas", + "service-manager", + "servicemanager" + ], + "support": { + "chat": "https://laminas.dev/chat", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-servicemanager/issues", + "source": "https://github.com/laminas/laminas-servicemanager/tree/4.4.0" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2025-02-04T06:13:50+00:00" + }, + { + "name": "laminas/laminas-stdlib", + "version": "3.20.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-stdlib.git", + "reference": "8974a1213be42c3e2f70b2c27b17f910291ab2f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/8974a1213be42c3e2f70b2c27b17f910291ab2f4", + "reference": "8974a1213be42c3e2f70b2c27b17f910291ab2f4", + "shasum": "" + }, + "require": { + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "conflict": { + "zendframework/zend-stdlib": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "^3.0", + "phpbench/phpbench": "^1.3.1", + "phpunit/phpunit": "^10.5.38", + "psalm/plugin-phpunit": "^0.19.0", + "vimeo/psalm": "^5.26.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Stdlib\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "SPL extensions, array utilities, error handlers, and more", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "stdlib" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-stdlib/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-stdlib/issues", + "rss": "https://github.com/laminas/laminas-stdlib/releases.atom", + "source": "https://github.com/laminas/laminas-stdlib" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2024-10-29T13:46:07+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.4.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", + "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" + }, + "time": "2024-12-30T11:07:19+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + } + ], + "packages-dev": [ + { + "name": "amphp/amp", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/amp.git", + "reference": "7cf7fef3d667bfe4b2560bc87e67d5387a7bcde9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/amp/zipball/7cf7fef3d667bfe4b2560bc87e67d5387a7bcde9", + "reference": "7cf7fef3d667bfe4b2560bc87e67d5387a7bcde9", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "5.23.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Future/functions.php", + "src/Internal/functions.php" + ], + "psr-4": { + "Amp\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "https://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v3.1.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-01-26T16:07:39+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v2.1.2", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/55a6bd071aec26fa2a3e002618c20c35e3df1b46", + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/parser": "^1.1", + "amphp/pipeline": "^1", + "amphp/serialization": "^1", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2.3" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.22.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Internal/functions.php" + ], + "psr-4": { + "Amp\\ByteStream\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "https://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v2.1.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-03-16T17:10:27+00:00" + }, + { + "name": "amphp/cache", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/cache.git", + "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/cache/zipball/46912e387e6aa94933b61ea1ead9cf7540b7797c", + "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/serialization": "^1", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Cache\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + } + ], + "description": "A fiber-aware cache API based on Amp and Revolt.", + "homepage": "https://amphp.org/cache", + "support": { + "issues": "https://github.com/amphp/cache/issues", + "source": "https://github.com/amphp/cache/tree/v2.0.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-19T03:38:06+00:00" + }, + { + "name": "amphp/dns", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/dns.git", + "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/dns/zipball/78eb3db5fc69bf2fc0cb503c4fcba667bc223c71", + "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/cache": "^2", + "amphp/parser": "^1", + "amphp/process": "^2", + "daverandom/libdns": "^2.0.2", + "ext-filter": "*", + "ext-json": "*", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.20" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Dns\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Wright", + "email": "addr@daverandom.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + } + ], + "description": "Async DNS resolution for Amp.", + "homepage": "https://github.com/amphp/dns", + "keywords": [ + "amp", + "amphp", + "async", + "client", + "dns", + "resolve" + ], + "support": { + "issues": "https://github.com/amphp/dns/issues", + "source": "https://github.com/amphp/dns/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-01-19T15:43:40+00:00" + }, + { + "name": "amphp/parallel", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/parallel.git", + "reference": "5113111de02796a782f5d90767455e7391cca190" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/parallel/zipball/5113111de02796a782f5d90767455e7391cca190", + "reference": "5113111de02796a782f5d90767455e7391cca190", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/cache": "^2", + "amphp/parser": "^1", + "amphp/pipeline": "^1", + "amphp/process": "^2", + "amphp/serialization": "^1", + "amphp/socket": "^2", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.18" + }, + "type": "library", + "autoload": { + "files": [ + "src/Context/functions.php", + "src/Context/Internal/functions.php", + "src/Ipc/functions.php", + "src/Worker/functions.php" + ], + "psr-4": { + "Amp\\Parallel\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Stephen Coakley", + "email": "me@stephencoakley.com" + } + ], + "description": "Parallel processing component for Amp.", + "homepage": "https://github.com/amphp/parallel", + "keywords": [ + "async", + "asynchronous", + "concurrent", + "multi-processing", + "multi-threading" + ], + "support": { + "issues": "https://github.com/amphp/parallel/issues", + "source": "https://github.com/amphp/parallel/tree/v2.3.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-12-21T01:56:09+00:00" + }, + { + "name": "amphp/parser", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/parser.git", + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/parser/zipball/3cf1f8b32a0171d4b1bed93d25617637a77cded7", + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7", + "shasum": "" + }, + "require": { + "php": ">=7.4" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Parser\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A generator parser to make streaming parsers simple.", + "homepage": "https://github.com/amphp/parser", + "keywords": [ + "async", + "non-blocking", + "parser", + "stream" + ], + "support": { + "issues": "https://github.com/amphp/parser/issues", + "source": "https://github.com/amphp/parser/tree/v1.1.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-03-21T19:16:53+00:00" + }, + { + "name": "amphp/pipeline", + "version": "v1.2.3", + "source": { + "type": "git", + "url": "https://github.com/amphp/pipeline.git", + "reference": "7b52598c2e9105ebcddf247fc523161581930367" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/pipeline/zipball/7b52598c2e9105ebcddf247fc523161581930367", + "reference": "7b52598c2e9105ebcddf247fc523161581930367", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "php": ">=8.1", + "revolt/event-loop": "^1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.18" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Pipeline\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Asynchronous iterators and operators.", + "homepage": "https://amphp.org/pipeline", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "iterator", + "non-blocking" + ], + "support": { + "issues": "https://github.com/amphp/pipeline/issues", + "source": "https://github.com/amphp/pipeline/tree/v1.2.3" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-03-16T16:33:53+00:00" + }, + { + "name": "amphp/process", + "version": "v2.0.3", + "source": { + "type": "git", + "url": "https://github.com/amphp/process.git", + "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/process/zipball/52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", + "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Process\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A fiber-aware process manager based on Amp and Revolt.", + "homepage": "https://amphp.org/process", + "support": { + "issues": "https://github.com/amphp/process/issues", + "source": "https://github.com/amphp/process/tree/v2.0.3" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-19T03:13:44+00:00" + }, + { + "name": "amphp/serialization", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/serialization.git", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/serialization/zipball/693e77b2fb0b266c3c7d622317f881de44ae94a1", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "phpunit/phpunit": "^9 || ^8 || ^7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Serialization\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Serialization tools for IPC and data storage in PHP.", + "homepage": "https://github.com/amphp/serialization", + "keywords": [ + "async", + "asynchronous", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/amphp/serialization/issues", + "source": "https://github.com/amphp/serialization/tree/master" + }, + "time": "2020-03-25T21:39:07+00:00" + }, + { + "name": "amphp/socket", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/socket.git", + "reference": "58e0422221825b79681b72c50c47a930be7bf1e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/socket/zipball/58e0422221825b79681b72c50c47a930be7bf1e1", + "reference": "58e0422221825b79681b72c50c47a930be7bf1e1", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/dns": "^2", + "ext-openssl": "*", + "kelunik/certificate": "^1.1", + "league/uri": "^6.5 | ^7", + "league/uri-interfaces": "^2.3 | ^7", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "amphp/process": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "5.20" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Internal/functions.php", + "src/SocketAddress/functions.php" + ], + "psr-4": { + "Amp\\Socket\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@gmail.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Non-blocking socket connection / server implementations based on Amp and Revolt.", + "homepage": "https://github.com/amphp/socket", + "keywords": [ + "amp", + "async", + "encryption", + "non-blocking", + "sockets", + "tcp", + "tls" + ], + "support": { + "issues": "https://github.com/amphp/socket/issues", + "source": "https://github.com/amphp/socket/tree/v2.3.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-21T14:33:03+00:00" + }, + { + "name": "amphp/sync", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/sync.git", + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/sync/zipball/217097b785130d77cfcc58ff583cf26cd1770bf1", + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/pipeline": "^1", + "amphp/serialization": "^1", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.23" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Sync\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Stephen Coakley", + "email": "me@stephencoakley.com" + } + ], + "description": "Non-blocking synchronization primitives for PHP based on Amp and Revolt.", + "homepage": "https://github.com/amphp/sync", + "keywords": [ + "async", + "asynchronous", + "mutex", + "semaphore", + "synchronization" + ], + "support": { + "issues": "https://github.com/amphp/sync/issues", + "source": "https://github.com/amphp/sync/tree/v2.3.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-08-03T19:31:26+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-09-19T14:15:21+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "danog/advanced-json-rpc", + "version": "v3.2.2", + "source": { + "type": "git", + "url": "https://github.com/danog/php-advanced-json-rpc.git", + "reference": "aadb1c4068a88c3d0530cfe324b067920661efcb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/danog/php-advanced-json-rpc/zipball/aadb1c4068a88c3d0530cfe324b067920661efcb", + "reference": "aadb1c4068a88c3d0530cfe324b067920661efcb", + "shasum": "" + }, + "require": { + "netresearch/jsonmapper": "^5", + "php": ">=8.1", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" + }, + "replace": { + "felixfbecker/php-advanced-json-rpc": "^3" + }, + "require-dev": { + "phpunit/phpunit": "^9" + }, + "type": "library", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + }, + { + "name": "Daniil Gentili", + "email": "daniil@daniil.it" + } + ], + "description": "A more advanced JSONRPC implementation", + "support": { + "issues": "https://github.com/danog/php-advanced-json-rpc/issues", + "source": "https://github.com/danog/php-advanced-json-rpc/tree/v3.2.2" + }, + "time": "2025-02-14T10:55:15+00:00" + }, + { + "name": "daverandom/libdns", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/DaveRandom/LibDNS.git", + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DaveRandom/LibDNS/zipball/b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "Required for IDN support" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "LibDNS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "DNS protocol implementation written in pure PHP", + "keywords": [ + "dns" + ], + "support": { + "issues": "https://github.com/DaveRandom/LibDNS/issues", + "source": "https://github.com/DaveRandom/LibDNS/tree/v2.1.0" + }, + "time": "2024-04-12T12:12:48+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "4be43904336affa5c2f70744a348312336afd0da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", + "reference": "4be43904336affa5c2f70744a348312336afd0da", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "time": "2023-01-05T11:28:13+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" + }, + "time": "2025-04-07T20:06:18+00:00" + }, + { + "name": "felixfbecker/language-server-protocol", + "version": "v1.5.3", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server-protocol.git", + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/a9e113dbc7d849e35b8776da39edaf4313b7b6c9", + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "*", + "squizlabs/php_codesniffer": "^3.1", + "vimeo/psalm": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "LanguageServerProtocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "PHP classes for the Language Server Protocol", + "keywords": [ + "language", + "microsoft", + "php", + "server" + ], + "support": { + "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.3" + }, + "time": "2024-04-30T00:40:11+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "8520451a140d3f46ac33042715115e290cf5785f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", + "reference": "8520451a140d3f46ac33042715115e290cf5785f", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2024-08-06T10:04:20+00:00" + }, + { + "name": "kelunik/certificate", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/kelunik/certificate.git", + "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kelunik/certificate/zipball/7e00d498c264d5eb4f78c69f41c8bd6719c0199e", + "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "php": ">=7.0" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^6 | 7 | ^8 | ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Kelunik\\Certificate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Access certificate details and transform between different formats.", + "keywords": [ + "DER", + "certificate", + "certificates", + "openssl", + "pem", + "x509" + ], + "support": { + "issues": "https://github.com/kelunik/certificate/issues", + "source": "https://github.com/kelunik/certificate/tree/v1.1.3" + }, + "time": "2023-02-03T21:26:53+00:00" + }, + { + "name": "laminas/laminas-coding-standard", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-coding-standard.git", + "reference": "d4412caba9ed16c93cdcf301759f5ee71f9d9aea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-coding-standard/zipball/d4412caba9ed16c93cdcf301759f5ee71f9d9aea", + "reference": "d4412caba9ed16c93cdcf301759f5ee71f9d9aea", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0", + "php": "^7.4 || ^8.0", + "slevomat/coding-standard": "^8.15.0", + "squizlabs/php_codesniffer": "^3.10", + "webimpress/coding-standard": "^1.3" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "LaminasCodingStandard\\": "src/LaminasCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Laminas Coding Standard", + "homepage": "https://laminas.dev", + "keywords": [ + "Coding Standard", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-coding-standard/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-coding-standard/issues", + "rss": "https://github.com/laminas/laminas-coding-standard/releases.atom", + "source": "https://github.com/laminas/laminas-coding-standard" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2025-05-13T08:37:04+00:00" + }, + { + "name": "league/uri", + "version": "7.5.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri.git", + "reference": "81fb5145d2644324614cc532b28efd0215bda430" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/81fb5145d2644324614cc532b28efd0215bda430", + "reference": "81fb5145d2644324614cc532b28efd0215bda430", + "shasum": "" + }, + "require": { + "league/uri-interfaces": "^7.5", + "php": "^8.1" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", + "league/uri-components": "Needed to easily manipulate URI objects components", + "php-64bit": "to improve IPV4 host parsing", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri/tree/7.5.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2024-12-08T08:40:02+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.5.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1", + "psr/http-factory": "^1", + "psr/http-message": "^1.1 || ^2.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "php-64bit": "to improve IPV4 host parsing", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common interfaces and classes for URI representation and interaction", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.5.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2024-12-08T08:18:47+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-04-29T12:36:36+00:00" + }, + { + "name": "netresearch/jsonmapper", + "version": "v5.0.0", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c", + "reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/v5.0.0" + }, + "time": "2024-09-08T10:20:00+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.6.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/92dde6a5919e34835c506ac8c523ef095a95ed62", + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.2" + }, + "time": "2025-04-13T19:20:35+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" + }, + "time": "2024-11-09T15:12:26+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.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": "phpunit/php-code-coverage", + "version": "11.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/14d63fbcca18457e49c6f8bebaa91a87e8e188d7", + "reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.4.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.0", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.2" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.9" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-25T13:26:39+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-27T05:02:59+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:07:44+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:08:43+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:09:35+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "11.5.20", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "e6bdea63ecb7a8287d2cdab25bdde3126e0cfe6f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e6bdea63ecb7a8287d2cdab25bdde3126e0cfe6f", + "reference": "e6bdea63ecb7a8287d2cdab25bdde3126e0cfe6f", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.1", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.9", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.1", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.0", + "sebastian/exporter": "^6.3.0", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.1.2", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.20" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2025-05-11T06:39:52+00:00" + }, + { + "name": "psalm/plugin-phpunit", + "version": "0.19.5", + "source": { + "type": "git", + "url": "https://github.com/psalm/psalm-plugin-phpunit.git", + "reference": "143f9d5e049fffcdbc0da3fbb99f6149f9d3e2dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/psalm/psalm-plugin-phpunit/zipball/143f9d5e049fffcdbc0da3fbb99f6149f9d3e2dc", + "reference": "143f9d5e049fffcdbc0da3fbb99f6149f9d3e2dc", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "php": ">=8.1", + "vimeo/psalm": "dev-master || ^6.10.0" + }, + "conflict": { + "phpspec/prophecy": "<1.20.0", + "phpspec/prophecy-phpunit": "<2.3.0", + "phpunit/phpunit": "<8.5.1" + }, + "require-dev": { + "php": "^7.3 || ^8.0", + "phpunit/phpunit": "^10.0 || ^11.0 || ^12.0", + "squizlabs/php_codesniffer": "^3.3.1", + "weirdan/prophecy-shim": "^1.0 || ^2.0" + }, + "type": "psalm-plugin", + "extra": { + "psalm": { + "pluginClass": "Psalm\\PhpUnitPlugin\\Plugin" + } + }, + "autoload": { + "psr-4": { + "Psalm\\PhpUnitPlugin\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Brown", + "email": "github@muglug.com" + } + ], + "description": "Psalm plugin for PHPUnit", + "support": { + "issues": "https://github.com/psalm/psalm-plugin-phpunit/issues", + "source": "https://github.com/psalm/psalm-plugin-phpunit/tree/0.19.5" + }, + "time": "2025-03-31T18:49:55+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "revolt/event-loop", + "version": "v1.0.7", + "source": { + "type": "git", + "url": "https://github.com/revoltphp/event-loop.git", + "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/09bf1bf7f7f574453efe43044b06fafe12216eb3", + "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.15" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Revolt\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "ceesjank@gmail.com" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Rock-solid event loop for concurrent PHP applications.", + "keywords": [ + "async", + "asynchronous", + "concurrency", + "event", + "event-loop", + "non-blocking", + "scheduler" + ], + "support": { + "issues": "https://github.com/revoltphp/event-loop/issues", + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.7" + }, + "time": "2025-01-25T19:27:39+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:41:36+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-19T07:56:08+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:45:54+00:00" + }, + { + "name": "sebastian/comparator", + "version": "6.3.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/24b8fbc2c8e201bb1308e7b05148d6ab393b6959", + "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.4" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-07T06:57:01+00:00" + }, + { + "name": "sebastian/complexity", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:49:50+00:00" + }, + { + "name": "sebastian/diff", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:53:05+00:00" + }, + { + "name": "sebastian/environment", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", + "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:54:44+00:00" + }, + { + "name": "sebastian/exporter", + "version": "6.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3", + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-12-05T09:17:50+00:00" + }, + { + "name": "sebastian/global-state", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:57:36+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:58:38+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:00:13+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:01:32+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:10:34+00:00" + }, + { + "name": "sebastian/type", + "version": "5.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", + "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-18T13:35:50+00:00" + }, + { + "name": "sebastian/version", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-09T05:16:32+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "8.18.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "f3b23cb9b26301b8c3c7bb03035a1bee23974593" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/f3b23cb9b26301b8c3c7bb03035a1bee23974593", + "reference": "f3b23cb9b26301b8c3c7bb03035a1bee23974593", + "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" + }, + "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-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" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "keywords": [ + "dev", + "phpcs" + ], + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/8.18.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2025-05-01T09:40:50+00:00" + }, + { + "name": "spatie/array-to-xml", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/array-to-xml.git", + "reference": "7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67", + "reference": "7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": "^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.2", + "pestphp/pest": "^1.21", + "spatie/pest-plugin-snapshots": "^1.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Spatie\\ArrayToXml\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://freek.dev", + "role": "Developer" + } + ], + "description": "Convert an array to xml", + "homepage": "https://github.com/spatie/array-to-xml", + "keywords": [ + "array", + "convert", + "xml" + ], + "support": { + "source": "https://github.com/spatie/array-to-xml/tree/3.4.0" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2024-12-16T12:45:15+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.13.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "65ff2489553b83b4597e89c3b8b721487011d186" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/65ff2489553b83b4597e89c3b8b721487011d186", + "reference": "65ff2489553b83b4597e89c3b8b721487011d186", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-05-11T03:36:00+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "symfony/console", + "version": "v7.2.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "0e2e3f38c192e93e622e41ec37f4ca70cfedf218" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/0e2e3f38c192e93e622e41ec37f4ca70cfedf218", + "reference": "0e2e3f38c192e93e622e41ec37f4ca70cfedf218", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.2.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-07T19:09:28+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-25T15:15:23+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "000df7860439609837bbe28670b0be15783b7fbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/000df7860439609837bbe28670b0be15783b7fbf", + "reference": "000df7860439609837bbe28670b0be15783b7fbf", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-02-20T12:04:08+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/string", + "version": "v7.2.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/a214fe7d62bd4df2a76447c67c6b26e1d5e74931", + "reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.2.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-20T20:18:16+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + }, + { + "name": "vimeo/psalm", + "version": "6.10.3", + "source": { + "type": "git", + "url": "https://github.com/vimeo/psalm.git", + "reference": "90b5b9f5e7c8e441b191d3c82c58214753d7c7c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/90b5b9f5e7c8e441b191d3c82c58214753d7c7c1", + "reference": "90b5b9f5e7c8e441b191d3c82c58214753d7c7c1", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/parallel": "^2.3", + "composer-runtime-api": "^2", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^2.0 || ^3.0", + "danog/advanced-json-rpc": "^3.1", + "dnoegel/php-xdg-base-dir": "^0.1.1", + "ext-ctype": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "felixfbecker/language-server-protocol": "^1.5.3", + "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", + "netresearch/jsonmapper": "^5.0", + "nikic/php-parser": "^5.0.0", + "php": "~8.1.31 || ~8.2.27 || ~8.3.16 || ~8.4.3", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0 || ^7.0", + "spatie/array-to-xml": "^2.17.0 || ^3.0", + "symfony/console": "^6.0 || ^7.0", + "symfony/filesystem": "~6.3.12 || ~6.4.3 || ^7.0.3", + "symfony/polyfill-php84": "^1.31.0" + }, + "provide": { + "psalm/psalm": "self.version" + }, + "require-dev": { + "amphp/phpunit-util": "^3", + "bamarni/composer-bin-plugin": "^1.4", + "brianium/paratest": "^6.9", + "danog/class-finder": "^0.4.8", + "dg/bypass-finals": "^1.5", + "ext-curl": "*", + "mockery/mockery": "^1.5", + "nunomaduro/mock-final-classes": "^1.1", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpdoc-parser": "^1.6", + "phpunit/phpunit": "^9.6", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.19", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.6", + "symfony/process": "^6.0 || ^7.0" + }, + "suggest": { + "ext-curl": "In order to send data to shepherd", + "ext-igbinary": "^2.0.5 is required, used to serialize caching data" + }, + "bin": [ + "psalm", + "psalm-language-server", + "psalm-plugin", + "psalm-refactor", + "psalm-review", + "psalter" + ], + "type": "project", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev", + "dev-3.x": "3.x-dev", + "dev-4.x": "4.x-dev", + "dev-5.x": "5.x-dev", + "dev-6.x": "6.x-dev", + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psalm\\": "src/Psalm/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Brown" + }, + { + "name": "Daniil Gentili", + "email": "daniil@daniil.it" + } + ], + "description": "A static analysis tool for finding errors in PHP applications", + "keywords": [ + "code", + "inspection", + "php", + "static analysis" + ], + "support": { + "docs": "https://psalm.dev/docs", + "issues": "https://github.com/vimeo/psalm/issues", + "source": "https://github.com/vimeo/psalm" + }, + "time": "2025-05-05T18:23:39+00:00" + }, + { + "name": "webimpress/coding-standard", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/webimpress/coding-standard.git", + "reference": "6f6a1a90bd9e18fc8bee0660dd1d1ce68cf9fc53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webimpress/coding-standard/zipball/6f6a1a90bd9e18fc8bee0660dd1d1ce68cf9fc53", + "reference": "6f6a1a90bd9e18fc8bee0660dd1d1ce68cf9fc53", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0", + "squizlabs/php_codesniffer": "^3.10.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.6.15" + }, + "type": "phpcodesniffer-standard", + "extra": { + "dev-master": "1.2.x-dev", + "dev-develop": "1.3.x-dev" + }, + "autoload": { + "psr-4": { + "WebimpressCodingStandard\\": "src/WebimpressCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "description": "Webimpress Coding Standard", + "keywords": [ + "Coding Standard", + "PSR-2", + "phpcs", + "psr-12", + "webimpress" + ], + "support": { + "issues": "https://github.com/webimpress/coding-standard/issues", + "source": "https://github.com/webimpress/coding-standard/tree/1.4.0" + }, + "funding": [ + { + "url": "https://github.com/michalbundyra", + "type": "github" + } + ], + "time": "2024-10-16T06:55:17+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": { + "laminas/laminas-db": 20 + }, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "platform-dev": { + "ext-mysqli": "*", + "ext-pdo_mysql": "*" + }, + "platform-overrides": { + "php": "8.2.99" + }, + "plugin-api-version": "2.6.0" +} diff --git a/docker/databases/mysql/Dockerfile b/docker/databases/mysql/Dockerfile new file mode 100644 index 0000000..69c3f20 --- /dev/null +++ b/docker/databases/mysql/Dockerfile @@ -0,0 +1,3 @@ +ARG VERSION=8.4.5 + +FROM mysql:${VERSION}-bookworm AS base \ No newline at end of file diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile new file mode 100644 index 0000000..eaaff2e --- /dev/null +++ b/docker/php/Dockerfile @@ -0,0 +1,26 @@ +ARG PHP_VERSION=8.3.19 + +FROM php:${PHP_VERSION}-apache-bookworm AS base + +# Copy Composer from the official Docker Hub repository to the local filesystem +COPY --from=composer:2.8.6 /usr/bin/composer /usr/bin/ + +# Install the database extensions for MySQL, PostgreSQL, and SQLite, their +# dependencies, and any other tools that are required for the environment to be +# used fully and completely. +RUN apt-get update \ + && apt-get install -y git \ + && docker-php-ext-install mysqli pdo pdo_mysql \ + && apt-get clean + +# Allow the www-data login so that it can run Composer instead of using root +RUN usermod -s /usr/bin/bash www-data + +# Copy all of the files from the context to the current directory setting the +# correct owner +COPY --chown=www-data:www-data . . + +RUN chmod +x bin/install-deps.sh + +# Validate and install PHP's dependencies +RUN su --preserve-environment www-data --command "bin/install-deps.sh" \ No newline at end of file diff --git a/docker/php/conf.d/error_reporting.ini b/docker/php/conf.d/error_reporting.ini new file mode 100644 index 0000000..d040e65 --- /dev/null +++ b/docker/php/conf.d/error_reporting.ini @@ -0,0 +1 @@ +error_reporting=E_ALL \ No newline at end of file diff --git a/docker/php/conf.d/xdebug.ini b/docker/php/conf.d/xdebug.ini new file mode 100644 index 0000000..e29c8bd --- /dev/null +++ b/docker/php/conf.d/xdebug.ini @@ -0,0 +1,6 @@ +zend_extension=xdebug + +[xdebug] +xdebug.mode=develop,debug +xdebug.client_host=host.docker.internal +xdebug.start_with_request=yes \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c79ac12..739dbf3 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -13,23 +13,24 @@ failOnNotice="true" failOnDeprecation="true" failOnWarning="true"> + + + + - - test/unit + + ./test/unit - - test/integration + + ./test/integration - - - - - src - - - test/config - test/resource - - + + + + + + + + \ No newline at end of file diff --git a/psalm-baseline.xml b/psalm-baseline.xml new file mode 100644 index 0000000..0a173f4 --- /dev/null +++ b/psalm-baseline.xml @@ -0,0 +1,1063 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + has(ProfilerInterface::class) ? $container->get(ProfilerInterface::class) : null]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + resource : $resultResource]]> + + + + resource->insert_id]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + resource->connect_error]]> + + + + + + + + + + + + + resource instanceof \mysqli]]> + resource instanceof \mysqli]]> + + + + resource->connect_error]]> + + + + + + + + + + + + + + + + + + + connection->getResource()]]> + + + + + + + + + + + + + + + + + + + + + + + + + + get('config')['db']]]> + + + + + + + + + + + + + + + + resource->affected_rows]]> + resource->num_rows]]> + resource->num_rows]]> + + + + + + + + + + + + + resource->error]]> + statementBindValues['keys']]]> + + + statementBindValues['keys'][$i]]]> + statementBindValues['values'][$i]]]> + + + currentData[$this->statementBindValues['keys'][$i]]]]> + + + currentData[$this->statementBindValues['keys'][$i]]]]> + + + + + + + + + + + + name]]> + + + resource->num_rows]]> + + + + + + + + + + + + + + + resource->error]]> + resource->num_rows]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + profiler]]> + + + profiler]]> + + + + + + + + + driver]]> + + + + + + + + + + + + + + + + + + + + + + connectionParameters]]> + connectionParameters]]> + + + + + + + + + driver->createStatement($sql)]]> + + + + + + + + + resource->getAttribute(\PDO::ATTR_DRIVER_NAME)]]> + resource->getAttribute(\PDO::ATTR_DRIVER_NAME)]]> + + + + + + + + dsn]]> + + + + + + + fetchColumn()]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + resource]]> + + + + + + + + + + + + + + + + + + + + connection->getResource()]]> + + + + + + + + + + + + + + + + + + + + features[$name]]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + get('config')['db']]]> + + + get('config')['db']]]> + + + + + + + + rowCount instanceof Closure]]> + rowCount)]]> + + + + resource->rowCount()]]> + rowCount)]]> + + + + + + + + + + + + + + + + rowCount;]]> + + + + + + + + + + resource->rowCount()]]> + + + + + + + + + + + resource]]> + + + + + + driver->createResult($this->resource, $this)]]> + + + + + + + + + + + + + + + resource->errorInfo()]]> + + + + + + + + + + + + + + + + + + + + + + + + profiler]]> + + + parameterContainer]]> + profiler]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + processInfo['paramPrefix']]]> + processInfo['paramPrefix']]]> + + + limit]]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + variables]]> + variables]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + adapter]]> + adapter]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + adapter]]> + + + + + + + + + + + + + + + + + + + + + current()]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $key]]> + $key]]> + id]]> + name]]> + value]]> + + + + + + adapter]]> + adapter]]> + adapter]]> + adapter]]> + adapter]]> + adapter]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixtureFile)]]> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/psalm.xml.dist b/psalm.xml.dist new file mode 100644 index 0000000..86d649b --- /dev/null +++ b/psalm.xml.dist @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/psr-container.php.stub b/psr-container.php.stub new file mode 100644 index 0000000..1a59597 --- /dev/null +++ b/psr-container.php.stub @@ -0,0 +1,23 @@ + $id + * @psalm-return ($id is class-string ? T : mixed) + */ + public function get(string $id): mixed; + } +} \ No newline at end of file diff --git a/src/Adapter.php b/src/Adapter.php index f4c7957..d57022f 100644 --- a/src/Adapter.php +++ b/src/Adapter.php @@ -2,59 +2,86 @@ declare(strict_types=1); -namespace Laminas\Db\Mysql; +namespace Laminas\Db\Adapter\Mysql; use Laminas\Db\Adapter\AbstractAdapter; -use Laminas\Db\Adapter\Profiler; use Laminas\Db\Adapter\Driver\DriverInterface; -use Laminas\Db\Adapter\Platform; -use Laminas\Db\Exception; -use Laminas\Db\ResultSet; +use Laminas\Db\Adapter\Exception; +use Laminas\Db\Adapter\Platform\PlatformInterface; + +use function is_string; +use function str_starts_with; +use function strtolower; /** * @property DriverInterface $driver - * @property Platform\PlatformInterface $platform + * @property PlatformInterface $platform */ class Adapter extends AbstractAdapter { - /** @var DriverInterface */ - protected $driver; - - /** @var Platform\PlatformInterface */ - protected $platform; - - /** - * @deprecated - * - * @var Driver\StatementInterface - */ - protected $lastPreparedStatement; - /** - * @param DriverInterface|array $driver - * @throws Exception\InvalidArgumentException - */ - public function __construct( - array $parameters, - DriverInterface $driver, - Platform\PlatformInterface $platform, - ?ResultSet\ResultSetInterface $queryResultPrototype = null, - ?Profiler\ProfilerInterface $profiler = null - ) { - - if ($parameters !== []) { - if ($profiler === null && isset($parameters['profiler'])) { - $profiler = $this->createProfiler($parameters); - } + public function getCurrentSchema(): string + { + return $this->driver->getConnection()->getCurrentSchema(); + } + + protected function createDriver(array $parameters): DriverInterface + { + if (! isset($parameters['driver'])) { + throw new Exception\InvalidArgumentException( + __FUNCTION__ . ' expects a "driver" key to be present inside the parameters' + ); + } + + if ($parameters['driver'] instanceof Driver\Mysqli\Mysqli || $parameters['driver'] instanceof Driver\Pdo\Pdo) { + return $parameters['driver']; + } + + if (! is_string($parameters['driver'])) { + throw new Exception\InvalidArgumentException( + __FUNCTION__ + . ' expects a "driver" to be a string or instance of ' + . Driver\Mysqli\Mysqli::class + . ' or ' . Driver\Pdo\Pdo::class + ); } - $driver->checkEnvironment(); - $this->driver = $driver; + $options = []; + if (isset($parameters['options'])) { + $options = (array) $parameters['options']; + unset($parameters['options']); + } - $this->platform = $platform; - $this->queryResultSetPrototype = $queryResultPrototype ?: new ResultSet\ResultSet(); + $driverName = strtolower($parameters['driver']); + switch ($driverName) { + case 'mysqli': + $driver = new Driver\Mysqli\Mysqli($parameters, null, null, $options); + break; + case 'pdo': + default: + if ($driverName === 'pdo' || str_starts_with($driverName, 'pdo')) { + $driver = new Driver\Pdo\Pdo($parameters); + } + } - if ($profiler) { - $this->setProfiler($profiler); + if (! isset($driver) || ! $driver instanceof DriverInterface) { + throw new Exception\InvalidArgumentException('DriverInterface expected'); } + + return $driver; + } + + protected function createPlatform(array $parameters): PlatformInterface + { + // currently only supported by the IbmDb2 & Oracle concrete implementations + // todo: check recent versions of mysqli and pdo to see if they support this + $options = $parameters['platform_options'] ?? []; + // mysqli or pdo_mysql driver + if ($this->driver instanceof Driver\Mysqli\Mysqli || $this->driver instanceof Driver\Pdo\Pdo) { + $driver = $this->driver; + } else { + $driver = null; + } + + return new Platform\Mysql($driver); } } diff --git a/src/AdapterServiceFactory.php b/src/AdapterServiceFactory.php deleted file mode 100644 index 963b85f..0000000 --- a/src/AdapterServiceFactory.php +++ /dev/null @@ -1,33 +0,0 @@ -get('config'); - - return new Adapter( - $config['db'], - $container->get(DriverInterface::class), - $container->get(PlatformInterface::class), - $container->has(ResultSetInterface::class) ? $container->get(ResultSetInterface::class) : null, - $container->has(ProfilerInterface::class) ? $container->get(ProfilerInterface::class) : null - ); - } -} diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index 8c3d700..28b2f72 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -1,12 +1,12 @@ [ - PlatformInterface::class => Platform\Mysql::class, + 'abstract_factories' => [ + AdapterAbstractServiceFactory::class, ], - 'factories' => [ - AdapterInterface::class => AdapterServiceFactory::class, - //DriverInterface::class => Driver\Mysqli\DriverFactory::class, - DriverInterface::class => Driver\Pdo\DriverFactory::class, - Platform\Mysql::class => InvokableFactory::class, + 'aliases' => [ + AdapterInterface::class => Adapter::class, + MetadataInterface::class => Metadata\Source\MysqlMetadata::class, + ], + 'factories' => [ + Adapter::class => AdapterServiceFactory::class, + Metadata\Source\MysqlMetadata::class => MetadataFactory::class, ], ]; } diff --git a/src/DatabasePlatformNameTrait.php b/src/DatabasePlatformNameTrait.php new file mode 100644 index 0000000..63ff670 --- /dev/null +++ b/src/DatabasePlatformNameTrait.php @@ -0,0 +1,25 @@ +driver = $driver; return $this; } - /** - * {@inheritDoc} - */ - public function getCurrentSchema() + /** @inheritDoc */ + #[Override] + public function getCurrentSchema(): string|bool { if (! $this->isConnected()) { $this->connect(); @@ -81,10 +80,9 @@ public function setResource(\mysqli $resource) return $this; } - /** - * {@inheritDoc} - */ - public function connect() + /** @inheritDoc */ + #[Override] + public function connect(): static { if ($this->resource instanceof \mysqli) { return $this; @@ -145,8 +143,7 @@ public function connect() $this->resource->ssl_set($clientKey, $clientCert, $caCert, $caPath, $cipher); //MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT is not valid option, needs to be set as flag if ( - isset($p['driver_options']) - && isset($p['driver_options'][MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT]) + isset($p['driver_options'][MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT]) ) { $flags |= MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; } @@ -156,7 +153,7 @@ public function connect() $flags === null ? $this->resource->real_connect($hostname, $username, $password, $database, $port, $socket) : $this->resource->real_connect($hostname, $username, $password, $database, $port, $socket, $flags); - } catch (GenericException $e) { + } catch (GenericException) { throw new Exception\RuntimeException( 'Connection error', $this->resource->connect_errno, @@ -179,29 +176,26 @@ public function connect() return $this; } - /** - * {@inheritDoc} - */ - public function isConnected() + /** @inheritDoc */ + public function isConnected(): bool { return $this->resource instanceof \mysqli; } - /** - * {@inheritDoc} - */ - public function disconnect() + /** @inheritDoc */ + #[Override] + public function disconnect(): static { if ($this->resource instanceof \mysqli) { $this->resource->close(); } $this->resource = null; + return $this; } - /** - * {@inheritDoc} - */ - public function beginTransaction() + /** @inheritDoc */ + #[Override] + public function beginTransaction(): static { if (! $this->isConnected()) { $this->connect(); @@ -213,10 +207,9 @@ public function beginTransaction() return $this; } - /** - * {@inheritDoc} - */ - public function commit() + /** @inheritDoc */ + #[Override] + public function commit(): static { if (! $this->isConnected()) { $this->connect(); @@ -229,10 +222,9 @@ public function commit() return $this; } - /** - * {@inheritDoc} - */ - public function rollback() + /** @inheritDoc */ + #[Override] + public function rollback(): static { if (! $this->isConnected()) { throw new Exception\RuntimeException('Must be connected before you can rollback.'); @@ -250,25 +242,22 @@ public function rollback() } /** - * {@inheritDoc} + * @inheritDoc * * @throws Exception\InvalidQueryException */ - public function execute($sql) + #[Override] + public function execute($sql): ResultInterface { if (! $this->isConnected()) { $this->connect(); } - if ($this->profiler) { - $this->profiler->profilerStart($sql); - } + $this->profiler?->profilerStart($sql); $resultResource = $this->resource->query($sql); - if ($this->profiler) { - $this->profiler->profilerFinish($sql); - } + $this->profiler?->profilerFinish($sql); // if the returnValue is something other than a mysqli_result, bypass wrapping it if ($resultResource === false) { @@ -278,10 +267,9 @@ public function execute($sql) return $this->driver->createResult($resultResource === true ? $this->resource : $resultResource); } - /** - * {@inheritDoc} - */ - public function getLastGeneratedValue($name = null) + /** @inheritDoc */ + #[Override] + public function getLastGeneratedValue($name = null): string|int|bool|null { return $this->resource->insert_id; } @@ -289,6 +277,8 @@ public function getLastGeneratedValue($name = null) /** * Create a new mysqli resource * + * todo: why do we have this random method here? + * * @return \mysqli */ protected function createResource() diff --git a/src/Driver/Mysqli/DriverFactory.php b/src/Driver/Mysqli/DriverFactory.php deleted file mode 100644 index e3adb9d..0000000 --- a/src/Driver/Mysqli/DriverFactory.php +++ /dev/null @@ -1,24 +0,0 @@ -get('config')['db']; - $options = []; - if (isset($dbConfig['options'])) { - $options = (array) $dbConfig['options']; - unset($dbConfig['options']); - } - return new Driver($dbConfig, null, null, $options); - } -} diff --git a/src/Driver/Mysqli/Driver.php b/src/Driver/Mysqli/Mysqli.php similarity index 90% rename from src/Driver/Mysqli/Driver.php rename to src/Driver/Mysqli/Mysqli.php index 7d3c8de..35b35b9 100644 --- a/src/Driver/Mysqli/Driver.php +++ b/src/Driver/Mysqli/Mysqli.php @@ -1,11 +1,12 @@ resultPrototype; } + /** + * Get database platform name + * + * @param string $nameFormat + * @return string + */ + public function getDatabasePlatformName($nameFormat = self::NAME_FORMAT_CAMELCASE) + { + if ($nameFormat === self::NAME_FORMAT_CAMELCASE) { + return 'Mysql'; + } + + return 'MySQL'; + } + /** * Check environment * @@ -164,8 +177,8 @@ public function createStatement($sqlOrResource = null) * @todo Resource tracking * if (is_resource($sqlOrResource) && !in_array($sqlOrResource, $this->resources, true)) { * $this->resources[] = $sqlOrResource; - * } - */ + *} + */ $statement = clone $this->statementPrototype; if ($sqlOrResource instanceof mysqli_stmt) { @@ -213,7 +226,7 @@ public function getPrepareType() * @param mixed $type * @return string */ - public function formatParameterName($name, $type = null) + public function formatParameterName($name, $type = null): string { return '?'; } diff --git a/src/Driver/Mysqli/Result.php b/src/Driver/Mysqli/Result.php index 19c513c..5d78553 100644 --- a/src/Driver/Mysqli/Result.php +++ b/src/Driver/Mysqli/Result.php @@ -1,6 +1,8 @@ sql = $sql; return $this; @@ -141,9 +143,9 @@ public function setResource(mysqli_stmt $mysqliStatement) /** * Get sql * - * @return string + * @return string|null */ - public function getSql() + public function getSql(): ?string { return $this->sql; } @@ -171,12 +173,10 @@ public function isPrepared() /** * Prepare * - * @param string $sql - * @return $this Provides a fluent interface - * @throws Exception\InvalidQueryException - * @throws Exception\RuntimeException + * @param string|null $sql + * @return Statement|null Provides a fluent interface */ - public function prepare($sql = null) + public function prepare(?string $sql = null): ?static { if ($this->isPrepared) { throw new Exception\RuntimeException('This statement has already been prepared'); @@ -229,15 +229,11 @@ public function execute($parameters = null) } /** END Standard ParameterContainer Merging Block */ - if ($this->profiler) { - $this->profiler->profilerStart($this); - } + $this->profiler?->profilerStart($this); $return = $this->resource->execute(); - if ($this->profiler) { - $this->profiler->profilerFinish(); - } + $this->profiler?->profilerFinish(); if ($return === false) { throw new Exception\RuntimeException($this->resource->error); diff --git a/src/Driver/Pdo/Connection.php b/src/Driver/Pdo/Connection.php index ab2992a..6718842 100644 --- a/src/Driver/Pdo/Connection.php +++ b/src/Driver/Pdo/Connection.php @@ -1,129 +1,34 @@ setConnectionParameters($connectionParameters); - } elseif ($connectionParameters instanceof \PDO) { - $this->setResource($connectionParameters); - } elseif (null !== $connectionParameters) { - throw new Exception\InvalidArgumentException( - '$connection must be an array of parameters, a PDO object or null' - ); - } - } - /** - * Set driver - * - * @return $this Provides a fluent interface + * @inheritDoc */ - public function setDriver(DriverInterface $driver) - { - $this->driver = $driver; - - return $this; - } - - /** - * {@inheritDoc} - */ - public function setConnectionParameters(array $connectionParameters) - { - $this->connectionParameters = $connectionParameters; - if (isset($connectionParameters['dsn'])) { - $this->driverName = substr( - $connectionParameters['dsn'], - 0, - strpos($connectionParameters['dsn'], ':') - ); - } elseif (isset($connectionParameters['pdodriver'])) { - $this->driverName = strtolower($connectionParameters['pdodriver']); - } elseif (isset($connectionParameters['driver'])) { - $this->driverName = strtolower(substr( - str_replace(['-', '_', ' '], '', $connectionParameters['driver']), - 3 - )); - } - } - - /** - * Get the dsn string for this connection - * - * @throws RunTimeException - * @return string - */ - public function getDsn() - { - if (! $this->dsn) { - throw new Exception\RuntimeException( - 'The DSN has not been set or constructed from parameters in connect() for this Connection' - ); - } - - return $this->dsn; - } - - /** - * {@inheritDoc} - */ - public function getCurrentSchema() + #[Override] + public function getCurrentSchema(): string|bool { if (! $this->isConnected()) { $this->connect(); } - // switch ($this->driverName) { - // case 'mysql': - // $sql = 'SELECT DATABASE()'; - // break; - // case 'sqlite': - // return 'main'; - // case 'sqlsrv': - // case 'dblib': - // $sql = 'SELECT SCHEMA_NAME()'; - // break; - // case 'pgsql': - // default: - // $sql = 'SELECT CURRENT_SCHEMA'; - // break; - // } - /** @var PDOStatement $result */ $result = $this->resource->query('SELECT DATABASE()'); if ($result instanceof PDOStatement) { @@ -134,25 +39,13 @@ public function getCurrentSchema() } /** - * Set resource - * - * @return $this Provides a fluent interface - */ - public function setResource(\PDO $resource) - { - $this->resource = $resource; - $this->driverName = strtolower($this->resource->getAttribute(\PDO::ATTR_DRIVER_NAME)); - - return $this; - } - - /** - * {@inheritDoc} + * @inheritDoc * * @throws Exception\InvalidConnectionParametersException * @throws Exception\RuntimeException */ - public function connect() + #[Override] + public function connect(): static { if ($this->resource) { return $this; @@ -167,7 +60,7 @@ public function connect() break; case 'driver': $value = strtolower((string) $value); - if (strpos($value, 'pdo') === 0) { + if (str_starts_with($value, 'pdo')) { $pdoDriver = str_replace(['-', '_', ' '], '', $value); $pdoDriver = substr($pdoDriver, 3) ?: ''; } @@ -269,9 +162,6 @@ public function connect() try { $this->resource = new \PDO($dsn, $username, $password, $options); $this->resource->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); - if (isset($charset) && $pdoDriver === 'pgsql') { - $this->resource->exec('SET NAMES ' . $this->resource->quote($charset)); - } $this->driverName = strtolower($this->resource->getAttribute(\PDO::ATTR_DRIVER_NAME)); } catch (PDOException $e) { $code = $e->getCode(); @@ -285,142 +175,16 @@ public function connect() } /** - * {@inheritDoc} - */ - public function isConnected() - { - return $this->resource instanceof \PDO; - } - - /** - * {@inheritDoc} - */ - public function beginTransaction() - { - if (! $this->isConnected()) { - $this->connect(); - } - - if (0 === $this->nestedTransactionsCount) { - $this->resource->beginTransaction(); - $this->inTransaction = true; - } - - $this->nestedTransactionsCount++; - - return $this; - } - - /** - * {@inheritDoc} - */ - public function commit() - { - if (! $this->isConnected()) { - $this->connect(); - } - - if ($this->inTransaction) { - $this->nestedTransactionsCount -= 1; - } - - /* - * This shouldn't check for being in a transaction since - * after issuing a SET autocommit=0; we have to commit too. - */ - if (0 === $this->nestedTransactionsCount) { - $this->resource->commit(); - $this->inTransaction = false; - } - - return $this; - } - - /** - * {@inheritDoc} + * @inheritDoc * - * @throws Exception\RuntimeException + * @param string $name */ - public function rollback() + #[Override] + public function getLastGeneratedValue($name = null): string|int|bool|null { - if (! $this->isConnected()) { - throw new Exception\RuntimeException('Must be connected before you can rollback'); - } - - if (! $this->inTransaction()) { - throw new Exception\RuntimeException('Must call beginTransaction() before you can rollback'); - } - - $this->resource->rollBack(); - - $this->inTransaction = false; - $this->nestedTransactionsCount = 0; - - return $this; - } - - /** - * {@inheritDoc} - * - * @throws Exception\InvalidQueryException - */ - public function execute($sql) - { - if (! $this->isConnected()) { - $this->connect(); - } - - if ($this->profiler) { - $this->profiler->profilerStart($sql); - } - - $resultResource = $this->resource->query($sql); - - if ($this->profiler) { - $this->profiler->profilerFinish($sql); - } - - if ($resultResource === false) { - $errorInfo = $this->resource->errorInfo(); - throw new Exception\InvalidQueryException($errorInfo[2]); - } - - return $this->driver->createResult($resultResource, $sql); - } - - /** - * Prepare - * - * @param string $sql - * @return Statement - */ - public function prepare($sql) - { - if (! $this->isConnected()) { - $this->connect(); - } - - return $this->driver->createStatement($sql); - } - - /** - * {@inheritDoc} - * - * @param string $name - * @return string|null|false - */ - public function getLastGeneratedValue($name = null) - { - if ( - $name === null - && ($this->driverName === 'pgsql' || $this->driverName === 'firebird') - ) { - return; - } - try { return $this->resource->lastInsertId($name); - } catch (\Exception $e) { + } catch (\Exception) { // do nothing } diff --git a/src/Driver/Pdo/Driver.php b/src/Driver/Pdo/Driver.php deleted file mode 100644 index 2236e0e..0000000 --- a/src/Driver/Pdo/Driver.php +++ /dev/null @@ -1,302 +0,0 @@ -registerConnection($connection); - $this->registerStatementPrototype($statementPrototype ?: new Statement()); - $this->registerResultPrototype($resultPrototype ?: new Result()); - if (is_array($features)) { - foreach ($features as $name => $feature) { - $this->addFeature($name, $feature); - } - } elseif ($features instanceof AbstractFeature) { - $this->addFeature($features->getName(), $features); - } elseif ($features === self::FEATURES_DEFAULT) { - $this->setupDefaultFeatures(); - } - } - - /** - * @return $this Provides a fluent interface - */ - public function setProfiler(Profiler\ProfilerInterface $profiler) - { - $this->profiler = $profiler; - if ($this->connection instanceof Profiler\ProfilerAwareInterface) { - $this->connection->setProfiler($profiler); - } - if ($this->statementPrototype instanceof Profiler\ProfilerAwareInterface) { - $this->statementPrototype->setProfiler($profiler); - } - return $this; - } - - /** - * @return null|Profiler\ProfilerInterface - */ - public function getProfiler() - { - return $this->profiler; - } - - /** - * Register connection - * - * @return $this Provides a fluent interface - */ - public function registerConnection(Connection $connection) - { - $this->connection = $connection; - $this->connection->setDriver($this); - return $this; - } - - /** - * Register statement prototype - */ - public function registerStatementPrototype(Statement $statementPrototype) - { - $this->statementPrototype = $statementPrototype; - $this->statementPrototype->setDriver($this); - } - - /** - * Register result prototype - */ - public function registerResultPrototype(Result $resultPrototype) - { - $this->resultPrototype = $resultPrototype; - } - - /** - * Add feature - * - * @param string $name - * @param AbstractFeature $feature - * @return $this Provides a fluent interface - */ - public function addFeature($name, $feature) - { - if ($feature instanceof AbstractFeature) { - $name = $feature->getName(); // overwrite the name, just in case - $feature->setDriver($this); - } - $this->features[$name] = $feature; - return $this; - } - - /** - * Setup the default features for Pdo - * - * @return $this Provides a fluent interface - */ - public function setupDefaultFeatures() - { - $driverName = $this->connection->getDriverName(); - if ($driverName === 'sqlite') { - $this->addFeature(null, new Feature\SqliteRowCounter()); - return $this; - } - - if ($driverName === 'oci') { - $this->addFeature(null, new Feature\OracleRowCounter()); - return $this; - } - - return $this; - } - - /** - * Get feature - * - * @param string $name - * @return AbstractFeature|false - */ - public function getFeature($name) - { - if (isset($this->features[$name])) { - return $this->features[$name]; - } - return false; - } - - /** - * Check environment - */ - public function checkEnvironment() - { - if (! extension_loaded('PDO')) { - throw new Exception\RuntimeException( - 'The PDO extension is required for this adapter but the extension is not loaded' - ); - } - } - - /** - * @return Connection - */ - public function getConnection() - { - return $this->connection; - } - - /** - * @param string|PDOStatement $sqlOrResource - * @return Statement - */ - public function createStatement($sqlOrResource = null) - { - $statement = clone $this->statementPrototype; - if ($sqlOrResource instanceof PDOStatement) { - $statement->setResource($sqlOrResource); - } else { - if (is_string($sqlOrResource)) { - $statement->setSql($sqlOrResource); - } - if (! $this->connection->isConnected()) { - $this->connection->connect(); - } - $statement->initialize($this->connection->getResource()); - } - return $statement; - } - - /** - * @param resource $resource - * @param mixed $context - * @return Result - */ - public function createResult($resource, $context = null) - { - $result = clone $this->resultPrototype; - $rowCount = null; - - // special feature, sqlite PDO counter - // if ( - // $this->connection->getDriverName() === 'sqlite' - // && ($sqliteRowCounter = $this->getFeature('SqliteRowCounter')) - // && $resource->columnCount() > 0 - // ) { - // $rowCount = $sqliteRowCounter->getRowCountClosure($context); - // } - - // special feature, oracle PDO counter - // if ( - // $this->connection->getDriverName() === 'oci' - // && ($oracleRowCounter = $this->getFeature('OracleRowCounter')) - // && $resource->columnCount() > 0 - // ) { - // $rowCount = $oracleRowCounter->getRowCountClosure($context); - // } - - $result->initialize($resource, $this->connection->getLastGeneratedValue(), $rowCount); - return $result; - } - - /** - * @return Result - */ - public function getResultPrototype() - { - return $this->resultPrototype; - } - - /** - * @return string - */ - public function getPrepareType() - { - return self::PARAMETERIZATION_NAMED; - } - - /** - * @param string $name - * @param string|null $type - * @return string - */ - public function formatParameterName($name, $type = null) - { - if ($type === null && ! is_numeric($name) || $type === self::PARAMETERIZATION_NAMED) { - $name = ltrim($name, ':'); - // @see https://bugs.php.net/bug.php?id=43130 - if (preg_match('/[^a-zA-Z0-9_]/', $name)) { - throw new Exception\RuntimeException(sprintf( - 'The PDO param %s contains invalid characters.' - . ' Only alphabetic characters, digits, and underscores (_)' - . ' are allowed.', - $name - )); - } - return ':' . $name; - } - - return '?'; - } - - /** - * @param string|null $name - * @return string|null|false - */ - public function getLastGeneratedValue($name = null) - { - return $this->connection->getLastGeneratedValue($name); - } -} diff --git a/src/Driver/Pdo/DriverFactory.php b/src/Driver/Pdo/DriverFactory.php deleted file mode 100644 index d15903b..0000000 --- a/src/Driver/Pdo/DriverFactory.php +++ /dev/null @@ -1,17 +0,0 @@ -get('config')['db']); - } -} diff --git a/src/Driver/Pdo/Pdo.php b/src/Driver/Pdo/Pdo.php new file mode 100644 index 0000000..500890d --- /dev/null +++ b/src/Driver/Pdo/Pdo.php @@ -0,0 +1,131 @@ +registerConnection($connection); + $this->registerStatementPrototype($statementPrototype ?: new Statement()); + $this->registerResultPrototype($resultPrototype ?: new Result()); + if (is_array($features)) { + foreach ($features as $name => $feature) { + $this->addFeature($name, $feature); + } + } elseif ($features instanceof AbstractFeature) { + $this->addFeature($features->getName(), $features); + } elseif ($features === self::FEATURES_DEFAULT) { + $this->setupDefaultFeatures(); + } + } + + /** + * @return $this Provides a fluent interface + */ + public function setProfiler(Profiler\ProfilerInterface $profiler) + { + $this->profiler = $profiler; + if ($this->connection instanceof Profiler\ProfilerAwareInterface) { + $this->connection->setProfiler($profiler); + } + if ($this->statementPrototype instanceof Profiler\ProfilerAwareInterface) { + $this->statementPrototype->setProfiler($profiler); + } + return $this; + } + + /** + * @return null|Profiler\ProfilerInterface + */ + public function getProfiler() + { + return $this->profiler; + } + + /** + * Register connection + * + * @return $this Provides a fluent interface + */ + public function registerConnection(Connection $connection) + { + $this->connection = $connection; + $this->connection->setDriver($this); + return $this; + } + + /** + * Setup the default features for Pdo + * + * @return $this Provides a fluent interface + */ + public function setupDefaultFeatures() + { + return $this; + } + + /** + * Get feature + * + * @param string $name + * @return AbstractFeature|false + */ + public function getFeature($name) + { + if (isset($this->features[$name])) { + return $this->features[$name]; + } + return false; + } + + /** + * Get database platform name + * + * @param string $nameFormat + * @return string + */ + public function getDatabasePlatformName($nameFormat = self::NAME_FORMAT_CAMELCASE): string + { + $name = $this->getConnection()->getDriverName(); + + if ($nameFormat === self::NAME_FORMAT_CAMELCASE) { + return ucfirst($name); + } + + if ($nameFormat === self::NAME_FORMAT_NATURAL) { + return match ($name) { + 'mysql' => 'MySQL', + default => ucfirst($name), + }; + } + + throw new Exception\InvalidArgumentException( + 'Invalid name format provided. Must be one of: ' . self::NAME_FORMAT_CAMELCASE . ', ' . self::NAME_FORMAT_NATURAL + ); + } +} diff --git a/src/Driver/Pdo/Result.php b/src/Driver/Pdo/Result.php deleted file mode 100644 index 04b53c4..0000000 --- a/src/Driver/Pdo/Result.php +++ /dev/null @@ -1,276 +0,0 @@ -resource = $resource; - $this->generatedValue = $generatedValue; - $this->rowCount = $rowCount; - - return $this; - } - - /** - * @return void - */ - public function buffer() - { - } - - /** - * @return bool|null - */ - public function isBuffered() - { - return false; - } - - /** - * @param int $fetchMode - * @throws Exception\InvalidArgumentException On invalid fetch mode. - */ - public function setFetchMode($fetchMode) - { - if (! in_array($fetchMode, self::VALID_FETCH_MODES, true)) { - throw new Exception\InvalidArgumentException( - 'The fetch mode must be one of the PDO::FETCH_* constants.' - ); - } - - $this->fetchMode = (int) $fetchMode; - } - - /** - * @return int - */ - public function getFetchMode() - { - return $this->fetchMode; - } - - /** - * Get resource - * - * @return mixed - */ - public function getResource() - { - return $this->resource; - } - - /** - * Get the data - * - * @return mixed - */ - #[ReturnTypeWillChange] - public function current() - { - if ($this->currentComplete) { - return $this->currentData; - } - - $this->currentData = $this->resource->fetch($this->fetchMode); - $this->currentComplete = true; - return $this->currentData; - } - - /** - * Next - * - * @return mixed - */ - #[ReturnTypeWillChange] - public function next() - { - $this->currentData = $this->resource->fetch($this->fetchMode); - $this->currentComplete = true; - $this->position++; - return $this->currentData; - } - - /** - * Key - * - * @return mixed - */ - #[ReturnTypeWillChange] - public function key() - { - return $this->position; - } - - /** - * @throws Exception\RuntimeException - * @return void - */ - #[ReturnTypeWillChange] - public function rewind() - { - if ($this->statementMode === self::STATEMENT_MODE_FORWARD && $this->position > 0) { - throw new Exception\RuntimeException( - 'This result is a forward only result set, calling rewind() after moving forward is not supported' - ); - } - if (! $this->currentComplete) { - $this->currentData = $this->resource->fetch($this->fetchMode); - $this->currentComplete = true; - } - $this->position = 0; - } - - /** - * Valid - * - * @return bool - */ - #[ReturnTypeWillChange] - public function valid() - { - return $this->currentData !== false; - } - - /** - * Count - * - * @return int - */ - #[ReturnTypeWillChange] - public function count() - { - if (is_int($this->rowCount)) { - return $this->rowCount; - } - if ($this->rowCount instanceof Closure) { - $this->rowCount = (int) call_user_func($this->rowCount); - } else { - $this->rowCount = (int) $this->resource->rowCount(); - } - return $this->rowCount; - } - - /** - * @return int - */ - public function getFieldCount() - { - return $this->resource->columnCount(); - } - - /** - * Is query result - * - * @return bool - */ - public function isQueryResult() - { - return $this->resource->columnCount() > 0; - } - - /** - * Get affected rows - * - * @return int - */ - public function getAffectedRows() - { - return $this->resource->rowCount(); - } - - /** - * @return mixed|null - */ - public function getGeneratedValue() - { - return $this->generatedValue; - } -} diff --git a/src/Driver/Pdo/Statement.php b/src/Driver/Pdo/Statement.php deleted file mode 100644 index 50968f0..0000000 --- a/src/Driver/Pdo/Statement.php +++ /dev/null @@ -1,290 +0,0 @@ -driver = $driver; - return $this; - } - - /** - * @return $this Provides a fluent interface - */ - public function setProfiler(Profiler\ProfilerInterface $profiler) - { - $this->profiler = $profiler; - return $this; - } - - /** - * @return null|Profiler\ProfilerInterface - */ - public function getProfiler() - { - return $this->profiler; - } - - /** - * Initialize - * - * @return $this Provides a fluent interface - */ - public function initialize(\PDO $connectionResource) - { - $this->pdo = $connectionResource; - return $this; - } - - /** - * Set resource - * - * @return $this Provides a fluent interface - */ - public function setResource(PDOStatement $pdoStatement) - { - $this->resource = $pdoStatement; - return $this; - } - - /** - * Get resource - * - * @return mixed - */ - public function getResource() - { - return $this->resource; - } - - /** - * Set sql - * - * @param string $sql - * @return $this Provides a fluent interface - */ - public function setSql($sql) - { - $this->sql = $sql; - return $this; - } - - /** - * Get sql - * - * @return string - */ - public function getSql() - { - return $this->sql; - } - - /** - * @return $this Provides a fluent interface - */ - public function setParameterContainer(ParameterContainer $parameterContainer) - { - $this->parameterContainer = $parameterContainer; - return $this; - } - - /** - * @return ParameterContainer - */ - public function getParameterContainer() - { - return $this->parameterContainer; - } - - /** - * @param string $sql - * @throws Exception\RuntimeException - */ - public function prepare($sql = null) - { - if ($this->isPrepared) { - throw new Exception\RuntimeException('This statement has been prepared already'); - } - - if ($sql === null) { - $sql = $this->sql; - } - - $this->resource = $this->pdo->prepare($sql); - - if ($this->resource === false) { - $error = $this->pdo->errorInfo(); - throw new Exception\RuntimeException($error[2]); - } - - $this->isPrepared = true; - } - - /** - * @return bool - */ - public function isPrepared() - { - return $this->isPrepared; - } - - /** - * @param null|array|ParameterContainer $parameters - * @throws Exception\InvalidQueryException - * @return Result - */ - public function execute($parameters = null) - { - if (! $this->isPrepared) { - $this->prepare(); - } - - /** START Standard ParameterContainer Merging Block */ - if (! $this->parameterContainer instanceof ParameterContainer) { - if ($parameters instanceof ParameterContainer) { - $this->parameterContainer = $parameters; - $parameters = null; - } else { - $this->parameterContainer = new ParameterContainer(); - } - } - - if (is_array($parameters)) { - $this->parameterContainer->setFromArray($parameters); - } - - if ($this->parameterContainer->count() > 0) { - $this->bindParametersFromContainer(); - } - /** END Standard ParameterContainer Merging Block */ - - if ($this->profiler) { - $this->profiler->profilerStart($this); - } - - try { - $this->resource->execute(); - } catch (PDOException $e) { - if ($this->profiler) { - $this->profiler->profilerFinish(); - } - - $code = $e->getCode(); - if (! is_int($code)) { - $code = 0; - } - - throw new Exception\InvalidQueryException( - 'Statement could not be executed (' . implode(' - ', $this->resource->errorInfo()) . ')', - $code, - $e - ); - } - - if ($this->profiler) { - $this->profiler->profilerFinish(); - } - - return $this->driver->createResult($this->resource, $this); - } - - /** - * Bind parameters from container - */ - protected function bindParametersFromContainer() - { - if ($this->parametersBound) { - return; - } - - $parameters = $this->parameterContainer->getNamedArray(); - foreach ($parameters as $name => &$value) { - if (is_bool($value)) { - $type = \PDO::PARAM_BOOL; - } elseif (is_int($value)) { - $type = \PDO::PARAM_INT; - } else { - $type = \PDO::PARAM_STR; - } - if ($this->parameterContainer->offsetHasErrata($name)) { - switch ($this->parameterContainer->offsetGetErrata($name)) { - case ParameterContainer::TYPE_INTEGER: - $type = \PDO::PARAM_INT; - break; - case ParameterContainer::TYPE_NULL: - $type = \PDO::PARAM_NULL; - break; - case ParameterContainer::TYPE_LOB: - $type = \PDO::PARAM_LOB; - break; - } - } - - // parameter is named or positional, value is reference - $parameter = is_int($name) ? $name + 1 : $this->driver->formatParameterName($name); - $this->resource->bindParam($parameter, $value, $type); - } - } - - /** - * Perform a deep clone - * - * @return void - */ - public function __clone() - { - $this->isPrepared = false; - $this->parametersBound = false; - $this->resource = null; - if ($this->parameterContainer) { - $this->parameterContainer = clone $this->parameterContainer; - } - } -} diff --git a/src/Metadata/Source/MysqlMetadata.php b/src/Metadata/Source/MysqlMetadata.php new file mode 100644 index 0000000..13f2043 --- /dev/null +++ b/src/Metadata/Source/MysqlMetadata.php @@ -0,0 +1,547 @@ +data['schemas'])) { + return; + } + $this->prepareDataHierarchy('schemas'); + + $p = $this->adapter->getPlatform(); + + $sql = 'SELECT ' . $p->quoteIdentifier('SCHEMA_NAME') + . ' FROM ' . $p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'SCHEMATA']) + . ' WHERE ' . $p->quoteIdentifier('SCHEMA_NAME') + . ' != \'INFORMATION_SCHEMA\''; + + $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); + + $schemas = []; + foreach ($results->toArray() as $row) { + $schemas[] = $row['SCHEMA_NAME']; + } + + $this->data['schemas'] = $schemas; + } + + /** + * @param string $schema + * @return void + */ + protected function loadTableNameData($schema): void + { + if (isset($this->data['table_names'][$schema])) { + return; + } + $this->prepareDataHierarchy('table_names', $schema); + + $p = $this->adapter->getPlatform(); + + $isColumns = [ + ['T', 'TABLE_NAME'], + ['T', 'TABLE_TYPE'], + ['V', 'VIEW_DEFINITION'], + ['V', 'CHECK_OPTION'], + ['V', 'IS_UPDATABLE'], + ]; + + array_walk($isColumns, function (&$c) use ($p) { + $c = $p->quoteIdentifierChain($c); + }); + + $sql = 'SELECT ' . implode(', ', $isColumns) + . ' FROM ' . $p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'TABLES']) . 'T' + + . ' LEFT JOIN ' . $p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'VIEWS']) . ' V' + . ' ON ' . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) + . ' = ' . $p->quoteIdentifierChain(['V', 'TABLE_SCHEMA']) + . ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_NAME']) + . ' = ' . $p->quoteIdentifierChain(['V', 'TABLE_NAME']) + + . ' WHERE ' . $p->quoteIdentifierChain(['T', 'TABLE_TYPE']) + . ' IN (\'BASE TABLE\', \'VIEW\')'; + + if ($schema !== self::DEFAULT_SCHEMA) { + $sql .= ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) + . ' = ' . $p->quoteTrustedValue($schema); + } else { + $sql .= ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) + . ' != \'INFORMATION_SCHEMA\''; + } + + $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); + + $tables = []; + foreach ($results->toArray() as $row) { + $tables[$row['TABLE_NAME']] = [ + 'table_type' => $row['TABLE_TYPE'], + 'view_definition' => $row['VIEW_DEFINITION'], + 'check_option' => $row['CHECK_OPTION'], + 'is_updatable' => 'YES' === $row['IS_UPDATABLE'], + ]; + } + + $this->data['table_names'][$schema] = $tables; + } + + /** + * @param string $table + * @param string $schema + * @return void + */ + protected function loadColumnData($table, $schema): void + { + if (isset($this->data['columns'][$schema][$table])) { + return; + } + $this->prepareDataHierarchy('columns', $schema, $table); + $p = $this->adapter->getPlatform(); + + $isColumns = [ + ['C', 'ORDINAL_POSITION'], + ['C', 'COLUMN_DEFAULT'], + ['C', 'IS_NULLABLE'], + ['C', 'DATA_TYPE'], + ['C', 'CHARACTER_MAXIMUM_LENGTH'], + ['C', 'CHARACTER_OCTET_LENGTH'], + ['C', 'NUMERIC_PRECISION'], + ['C', 'NUMERIC_SCALE'], + ['C', 'COLUMN_NAME'], + ['C', 'COLUMN_TYPE'], + ]; + + array_walk($isColumns, function (&$c) use ($p) { + $c = $p->quoteIdentifierChain($c); + }); + + $sql = 'SELECT ' . implode(', ', $isColumns) + . ' FROM ' . $p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'TABLES']) . 'T' + . ' INNER JOIN ' . $p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'COLUMNS']) . 'C' + . ' ON ' . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) + . ' = ' . $p->quoteIdentifierChain(['C', 'TABLE_SCHEMA']) + . ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_NAME']) + . ' = ' . $p->quoteIdentifierChain(['C', 'TABLE_NAME']) + . ' WHERE ' . $p->quoteIdentifierChain(['T', 'TABLE_TYPE']) + . ' IN (\'BASE TABLE\', \'VIEW\')' + . ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_NAME']) + . ' = ' . $p->quoteTrustedValue($table); + + if ($schema !== self::DEFAULT_SCHEMA) { + $sql .= ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) + . ' = ' . $p->quoteTrustedValue($schema); + } else { + $sql .= ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) + . ' != \'INFORMATION_SCHEMA\''; + } + + $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); + $columns = []; + foreach ($results->toArray() as $row) { + $erratas = []; + $matches = []; + if (preg_match('/^(?:enum|set)\((.+)\)$/i', $row['COLUMN_TYPE'], $matches)) { + $permittedValues = $matches[1]; + if ( + preg_match_all( + "/\\s*'((?:[^']++|'')*+)'\\s*(?:,|\$)/", + $permittedValues, + $matches, + PREG_PATTERN_ORDER + ) + ) { + $permittedValues = str_replace("''", "'", $matches[1]); + } else { + $permittedValues = [$permittedValues]; + } + $erratas['permitted_values'] = $permittedValues; + } + $columns[$row['COLUMN_NAME']] = [ + 'ordinal_position' => $row['ORDINAL_POSITION'], + 'column_default' => $row['COLUMN_DEFAULT'], + 'is_nullable' => 'YES' === $row['IS_NULLABLE'], + 'data_type' => $row['DATA_TYPE'], + 'character_maximum_length' => $row['CHARACTER_MAXIMUM_LENGTH'], + 'character_octet_length' => $row['CHARACTER_OCTET_LENGTH'], + 'numeric_precision' => $row['NUMERIC_PRECISION'], + 'numeric_scale' => $row['NUMERIC_SCALE'], + 'numeric_unsigned' => str_contains($row['COLUMN_TYPE'], 'unsigned'), + 'erratas' => $erratas, + ]; + } + + $this->data['columns'][$schema][$table] = $columns; + } + + /** + * @param string $table + * @param string $schema + * @return void + */ + protected function loadConstraintData($table, $schema) + { + // phpcs:disable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps + if (isset($this->data['constraints'][$schema][$table])) { + return; + } + + $this->prepareDataHierarchy('constraints', $schema, $table); + + $isColumns = [ + ['T', 'TABLE_NAME'], + ['TC', 'CONSTRAINT_NAME'], + ['TC', 'CONSTRAINT_TYPE'], + ['KCU', 'COLUMN_NAME'], + ['RC', 'MATCH_OPTION'], + ['RC', 'UPDATE_RULE'], + ['RC', 'DELETE_RULE'], + ['KCU', 'REFERENCED_TABLE_SCHEMA'], + ['KCU', 'REFERENCED_TABLE_NAME'], + ['KCU', 'REFERENCED_COLUMN_NAME'], + ]; + + $p = $this->adapter->getPlatform(); + + array_walk($isColumns, function (&$c) use ($p) { + $c = $p->quoteIdentifierChain($c); + }); + + $sql = 'SELECT ' . implode(', ', $isColumns) + . ' FROM ' . $p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'TABLES']) . ' T' + + . ' INNER JOIN ' . $p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'TABLE_CONSTRAINTS']) . ' TC' + . ' ON ' . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) + . ' = ' . $p->quoteIdentifierChain(['TC', 'TABLE_SCHEMA']) + . ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_NAME']) + . ' = ' . $p->quoteIdentifierChain(['TC', 'TABLE_NAME']) + + . ' LEFT JOIN ' . $p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'KEY_COLUMN_USAGE']) . ' KCU' + . ' ON ' . $p->quoteIdentifierChain(['TC', 'TABLE_SCHEMA']) + . ' = ' . $p->quoteIdentifierChain(['KCU', 'TABLE_SCHEMA']) + . ' AND ' . $p->quoteIdentifierChain(['TC', 'TABLE_NAME']) + . ' = ' . $p->quoteIdentifierChain(['KCU', 'TABLE_NAME']) + . ' AND ' . $p->quoteIdentifierChain(['TC', 'CONSTRAINT_NAME']) + . ' = ' . $p->quoteIdentifierChain(['KCU', 'CONSTRAINT_NAME']) + + . ' LEFT JOIN ' . $p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'REFERENTIAL_CONSTRAINTS']) . ' RC' + . ' ON ' . $p->quoteIdentifierChain(['TC', 'CONSTRAINT_SCHEMA']) + . ' = ' . $p->quoteIdentifierChain(['RC', 'CONSTRAINT_SCHEMA']) + . ' AND ' . $p->quoteIdentifierChain(['TC', 'CONSTRAINT_NAME']) + . ' = ' . $p->quoteIdentifierChain(['RC', 'CONSTRAINT_NAME']) + + . ' WHERE ' . $p->quoteIdentifierChain(['T', 'TABLE_NAME']) + . ' = ' . $p->quoteTrustedValue($table) + . ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_TYPE']) + . ' IN (\'BASE TABLE\', \'VIEW\')'; + + if ($schema !== self::DEFAULT_SCHEMA) { + $sql .= ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) + . ' = ' . $p->quoteTrustedValue($schema); + } else { + $sql .= ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) + . ' != \'INFORMATION_SCHEMA\''; + } + + $sql .= ' ORDER BY CASE ' . $p->quoteIdentifierChain(['TC', 'CONSTRAINT_TYPE']) + . " WHEN 'PRIMARY KEY' THEN 1" + . " WHEN 'UNIQUE' THEN 2" + . " WHEN 'FOREIGN KEY' THEN 3" + . " ELSE 4 END" + + . ', ' . $p->quoteIdentifierChain(['TC', 'CONSTRAINT_NAME']) + . ', ' . $p->quoteIdentifierChain(['KCU', 'ORDINAL_POSITION']); + + $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); + + $realName = null; + $constraints = []; + foreach ($results->toArray() as $row) { + if ($row['CONSTRAINT_NAME'] !== $realName) { + $realName = $row['CONSTRAINT_NAME']; + $isFK = 'FOREIGN KEY' === $row['CONSTRAINT_TYPE']; + if ($isFK) { + $name = $realName; + } else { + $name = '_laminas_' . $row['TABLE_NAME'] . '_' . $realName; + } + $constraints[$name] = [ + 'constraint_name' => $name, + 'constraint_type' => $row['CONSTRAINT_TYPE'], + 'table_name' => $row['TABLE_NAME'], + 'columns' => [], + ]; + if ($isFK) { + $constraints[$name]['referenced_table_schema'] = $row['REFERENCED_TABLE_SCHEMA']; + $constraints[$name]['referenced_table_name'] = $row['REFERENCED_TABLE_NAME']; + $constraints[$name]['referenced_columns'] = []; + $constraints[$name]['match_option'] = $row['MATCH_OPTION']; + $constraints[$name]['update_rule'] = $row['UPDATE_RULE']; + $constraints[$name]['delete_rule'] = $row['DELETE_RULE']; + } + } + $constraints[$name]['columns'][] = $row['COLUMN_NAME']; + if ($isFK) { + $constraints[$name]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME']; + } + } + + $this->data['constraints'][$schema][$table] = $constraints; + // phpcs:enable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps + } + + /** + * @param string $schema + * @return void + */ + protected function loadConstraintDataNames($schema) + { + if (isset($this->data['constraint_names'][$schema])) { + return; + } + + $this->prepareDataHierarchy('constraint_names', $schema); + + $p = $this->adapter->getPlatform(); + + $isColumns = [ + ['TC', 'TABLE_NAME'], + ['TC', 'CONSTRAINT_NAME'], + ['TC', 'CONSTRAINT_TYPE'], + ]; + + array_walk($isColumns, function (&$c) use ($p) { + $c = $p->quoteIdentifierChain($c); + }); + + $sql = 'SELECT ' . implode(', ', $isColumns) + . ' FROM ' . $p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'TABLES']) . 'T' + . ' INNER JOIN ' . $p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'TABLE_CONSTRAINTS']) . 'TC' + . ' ON ' . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) + . ' = ' . $p->quoteIdentifierChain(['TC', 'TABLE_SCHEMA']) + . ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_NAME']) + . ' = ' . $p->quoteIdentifierChain(['TC', 'TABLE_NAME']) + . ' WHERE ' . $p->quoteIdentifierChain(['T', 'TABLE_TYPE']) + . ' IN (\'BASE TABLE\', \'VIEW\')'; + + if ($schema !== self::DEFAULT_SCHEMA) { + $sql .= ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) + . ' = ' . $p->quoteTrustedValue($schema); + } else { + $sql .= ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) + . ' != \'INFORMATION_SCHEMA\''; + } + + $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); + + $data = []; + foreach ($results->toArray() as $row) { + $data[] = array_change_key_case($row, CASE_LOWER); + } + + $this->data['constraint_names'][$schema] = $data; + } + + /** + * @param string $schema + * @return void + */ + protected function loadConstraintDataKeys($schema) + { + if (isset($this->data['constraint_keys'][$schema])) { + return; + } + + $this->prepareDataHierarchy('constraint_keys', $schema); + + $p = $this->adapter->getPlatform(); + + $isColumns = [ + ['T', 'TABLE_NAME'], + ['KCU', 'CONSTRAINT_NAME'], + ['KCU', 'COLUMN_NAME'], + ['KCU', 'ORDINAL_POSITION'], + ]; + + array_walk($isColumns, function (&$c) use ($p) { + $c = $p->quoteIdentifierChain($c); + }); + + $sql = 'SELECT ' . implode(', ', $isColumns) + . ' FROM ' . $p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'TABLES']) . 'T' + + . ' INNER JOIN ' . $p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'KEY_COLUMN_USAGE']) . 'KCU' + . ' ON ' . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) + . ' = ' . $p->quoteIdentifierChain(['KCU', 'TABLE_SCHEMA']) + . ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_NAME']) + . ' = ' . $p->quoteIdentifierChain(['KCU', 'TABLE_NAME']) + + . ' WHERE ' . $p->quoteIdentifierChain(['T', 'TABLE_TYPE']) + . ' IN (\'BASE TABLE\', \'VIEW\')'; + + if ($schema !== self::DEFAULT_SCHEMA) { + $sql .= ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) + . ' = ' . $p->quoteTrustedValue($schema); + } else { + $sql .= ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) + . ' != \'INFORMATION_SCHEMA\''; + } + + $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); + + $data = []; + foreach ($results->toArray() as $row) { + $data[] = array_change_key_case($row, CASE_LOWER); + } + + $this->data['constraint_keys'][$schema] = $data; + } + + /** + * @param string $table + * @param string $schema + * @return void + */ + protected function loadConstraintReferences($table, $schema) + { + parent::loadConstraintReferences($table, $schema); + + $p = $this->adapter->getPlatform(); + + $isColumns = [ + ['RC', 'TABLE_NAME'], + ['RC', 'CONSTRAINT_NAME'], + ['RC', 'UPDATE_RULE'], + ['RC', 'DELETE_RULE'], + ['KCU', 'REFERENCED_TABLE_SCHEMA'], + ['KCU', 'REFERENCED_TABLE_NAME'], + ['KCU', 'REFERENCED_COLUMN_NAME'], + ]; + + array_walk($isColumns, function (&$c) use ($p) { + $c = $p->quoteIdentifierChain($c); + }); + + $sql = 'SELECT ' . implode(', ', $isColumns) + . 'FROM ' . $p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'TABLES']) . 'T' + + . ' INNER JOIN ' . $p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'REFERENTIAL_CONSTRAINTS']) . 'RC' + . ' ON ' . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) + . ' = ' . $p->quoteIdentifierChain(['RC', 'CONSTRAINT_SCHEMA']) + . ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_NAME']) + . ' = ' . $p->quoteIdentifierChain(['RC', 'TABLE_NAME']) + + . ' INNER JOIN ' . $p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'KEY_COLUMN_USAGE']) . 'KCU' + . ' ON ' . $p->quoteIdentifierChain(['RC', 'CONSTRAINT_SCHEMA']) + . ' = ' . $p->quoteIdentifierChain(['KCU', 'TABLE_SCHEMA']) + . ' AND ' . $p->quoteIdentifierChain(['RC', 'TABLE_NAME']) + . ' = ' . $p->quoteIdentifierChain(['KCU', 'TABLE_NAME']) + . ' AND ' . $p->quoteIdentifierChain(['RC', 'CONSTRAINT_NAME']) + . ' = ' . $p->quoteIdentifierChain(['KCU', 'CONSTRAINT_NAME']) + + . 'WHERE ' . $p->quoteIdentifierChain(['T', 'TABLE_TYPE']) + . ' IN (\'BASE TABLE\', \'VIEW\')'; + + if ($schema !== self::DEFAULT_SCHEMA) { + $sql .= ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) + . ' = ' . $p->quoteTrustedValue($schema); + } else { + $sql .= ' AND ' . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) + . ' != \'INFORMATION_SCHEMA\''; + } + + $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); + + $data = []; + foreach ($results->toArray() as $row) { + $data[] = array_change_key_case($row, CASE_LOWER); + } + + $this->data['constraint_references'][$schema] = $data; + } + + /** + * @param string $schema + * @return void + */ + protected function loadTriggerData($schema) + { + if (isset($this->data['triggers'][$schema])) { + return; + } + + $this->prepareDataHierarchy('triggers', $schema); + + $p = $this->adapter->getPlatform(); + + $isColumns = [ + // 'TRIGGER_CATALOG', + // 'TRIGGER_SCHEMA', + 'TRIGGER_NAME', + 'EVENT_MANIPULATION', + 'EVENT_OBJECT_CATALOG', + 'EVENT_OBJECT_SCHEMA', + 'EVENT_OBJECT_TABLE', + 'ACTION_ORDER', + 'ACTION_CONDITION', + 'ACTION_STATEMENT', + 'ACTION_ORIENTATION', + 'ACTION_TIMING', + 'ACTION_REFERENCE_OLD_TABLE', + 'ACTION_REFERENCE_NEW_TABLE', + 'ACTION_REFERENCE_OLD_ROW', + 'ACTION_REFERENCE_NEW_ROW', + 'CREATED', + ]; + + array_walk($isColumns, function (&$c) use ($p) { + $c = $p->quoteIdentifier($c); + }); + + $sql = 'SELECT ' . implode(', ', $isColumns) + . ' FROM ' . $p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'TRIGGERS']) + . ' WHERE '; + + if ($schema !== self::DEFAULT_SCHEMA) { + $sql .= $p->quoteIdentifier('TRIGGER_SCHEMA') + . ' = ' . $p->quoteTrustedValue($schema); + } else { + $sql .= $p->quoteIdentifier('TRIGGER_SCHEMA') + . ' != \'INFORMATION_SCHEMA\''; + } + + $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); + + $data = []; + foreach ($results->toArray() as $row) { + $row = array_change_key_case($row, CASE_LOWER); + if (null !== $row['created']) { + $row['created'] = new DateTime($row['created']); + } + $data[$row['trigger_name']] = $row; + } + + $this->data['triggers'][$schema] = $data; + } +} diff --git a/src/Module.php b/src/Module.php index 57b4f71..07de966 100644 --- a/src/Module.php +++ b/src/Module.php @@ -2,14 +2,14 @@ declare(strict_types=1); -namespace Laminas\Db\Mysql; +namespace Laminas\Db\Adapter\Mysql; final class Module { public function getConfig(): array { return [ - 'service_manager' => (new ConfigProvider())->getDependencyConfig(), + 'service_manager' => (new ConfigProvider())->getDependencies(), ]; } } diff --git a/src/Platform/Mysql.php b/src/Platform/Mysql.php index 6967a50..aa14855 100644 --- a/src/Platform/Mysql.php +++ b/src/Platform/Mysql.php @@ -1,13 +1,17 @@ setDriver($driver); } @@ -60,12 +68,11 @@ public function setDriver($driver) } throw new Exception\InvalidArgumentException( - '$driver must be a Mysqli or Mysql PDO Laminas\Db\Adapter\Driver, Mysqli instance or MySQL PDO instance' + '$driver must be a Laminas\Db\Adapter\Mysql\Driver\*, Mysqli\Mysqli or Pdo\Pdo instance' ); } /** - * todo: if needed return Backed Enum * {@inheritDoc} */ public function getName() @@ -73,6 +80,11 @@ public function getName() return 'MySQL'; } + public function getSqlPlatformDecorator(): PlatformDecoratorInterface + { + return new SqlPlatform(); + } + /** * {@inheritDoc} */ @@ -108,6 +120,7 @@ public function quoteTrustedValue($value) protected function quoteViaDriver($value) { if ($this->driver instanceof DriverInterface) { + // todo: verify this can not return a PDOStatement instance $resource = $this->driver->getConnection()->getResource(); } else { $resource = $this->driver; diff --git a/src/Platform/PlatformFactory.php b/src/Platform/PlatformFactory.php deleted file mode 100644 index 9606bc6..0000000 --- a/src/Platform/PlatformFactory.php +++ /dev/null @@ -1,17 +0,0 @@ -variables); + $connection->connect(); + + self::assertTrue($connection->isConnected()); + $connection->disconnect(); + self::assertFalse($connection->isConnected()); + } +} diff --git a/test/integration/Driver/Mysqli/TableGatewayTest.php b/test/integration/Driver/Mysqli/TableGatewayTest.php new file mode 100644 index 0000000..84745e3 --- /dev/null +++ b/test/integration/Driver/Mysqli/TableGatewayTest.php @@ -0,0 +1,65 @@ + 'mysqli', + 'database' => $this->variables['database'], + 'hostname' => $this->variables['hostname'], + 'username' => $this->variables['username'], + 'password' => $this->variables['password'], + 'options' => ['buffer_results' => true], + ]); + $tableGateway = new TableGateway('test', $adapter); + $rowset = $tableGateway->select('id = 0'); + $this->assertEquals(true, $rowset->isBuffered()); + + $this->assertNull($rowset->current()); + + $adapter->getDriver()->getConnection()->disconnect(); + } + + /** + * @see https://github.com/zendframework/zend-db/issues/330 + */ + public function testSelectWithEmptyCurrentWithoutBufferResult(): void + { + $adapter = new Adapter([ + 'driver' => 'mysqli', + 'database' => $this->variables['database'], + 'hostname' => $this->variables['hostname'], + 'username' => $this->variables['username'], + 'password' => $this->variables['password'], + 'options' => ['buffer_results' => false], + ]); + $tableGateway = new TableGateway('test', $adapter); + $rowset = $tableGateway->select('id = 0'); + $this->assertEquals(false, $rowset->isBuffered()); + + /** @todo Have resultset implememt Iterator */ + /** @psalm-suppress UndefinedInterfaceMethod */ + $this->assertNull($rowset->current()); + + $adapter->getDriver()->getConnection()->disconnect(); + } +} diff --git a/test/integration/Driver/Mysqli/TraitSetup.php b/test/integration/Driver/Mysqli/TraitSetup.php new file mode 100644 index 0000000..15cfc3d --- /dev/null +++ b/test/integration/Driver/Mysqli/TraitSetup.php @@ -0,0 +1,64 @@ + */ + protected array $variables = [ + 'hostname' => 'TESTS_LAMINAS_DB_ADAPTER_MYSQL_HOSTNAME', + 'username' => 'TESTS_LAMINAS_DB_ADAPTER_MYSQL_USERNAME', + 'password' => 'TESTS_LAMINAS_DB_ADAPTER_MYSQL_PASSWORD', + 'database' => 'TESTS_LAMINAS_DB_ADAPTER_MYSQL_DATABASE', + ]; + + /** @var array */ + protected array $optional = [ + 'port' => 'TESTS_LAMINAS_DB_ADAPTER_MYSQL_PORT', + ]; + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + #[RequiresPhpExtension('mysqli')] + #[Override] + protected function setUp(): void + { + // $testEnabled = (string) getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_ENABLED'); + // if (strtolower($testEnabled) !== 'true') { + // $this->markTestSkipped('Mysqli integration test disabled'); + // } + + // if (! extension_loaded('mysqli')) { + // $this->fail('The phpunit group integration-mysqli was enabled, but the extension is not loaded.'); + // } + + foreach ($this->variables as $name => $value) { + if (! is_string(getenv($value)) || '' === getenv($value)) { + $this->markTestSkipped(sprintf( + 'Missing required variable %s $this->mockUpdate phpunit.xml for this integration test', + $value + )); + } else { + $this->variables[$name] = (string) getenv($value); + } + } + + foreach ($this->optional as $name => $value) { + if (is_string(getenv($value)) && '' === getenv($value)) { + $this->variables[$name] = (string) getenv($value); + } + } + } +} diff --git a/test/integration/Driver/Pdo/AbstractAdapterTestCase.php b/test/integration/Driver/Pdo/AbstractAdapterTestCase.php new file mode 100644 index 0000000..4969497 --- /dev/null +++ b/test/integration/Driver/Pdo/AbstractAdapterTestCase.php @@ -0,0 +1,79 @@ +assertInstanceOf(AdapterInterface::class, $this->adapter); + } + + public function testDriverDisconnectAfterQuoteWithPlatform(): void + { + $isTcpConnection = $this->isTcpConnection(); + + $this->getAdapter()->getDriver()->getConnection()->connect(); + + self::assertTrue($this->getAdapter()->getDriver()->getConnection()->isConnected()); + if ($isTcpConnection) { + self::assertTrue($this->isConnectedTcp()); + } + + $this->getAdapter()->getDriver()->getConnection()->disconnect(); + + self::assertFalse($this->getAdapter()->getDriver()->getConnection()->isConnected()); + if ($isTcpConnection) { + self::assertFalse($this->isConnectedTcp()); + } + + $this->getAdapter()->getDriver()->getConnection()->connect(); + + self::assertTrue($this->getAdapter()->getDriver()->getConnection()->isConnected()); + if ($isTcpConnection) { + self::assertTrue($this->isConnectedTcp()); + } + + $this->getAdapter()->getPlatform()->quoteValue('test'); + + $this->getAdapter()->getDriver()->getConnection()->disconnect(); + + self::assertFalse($this->getAdapter()->getDriver()->getConnection()->isConnected()); + if ($isTcpConnection) { + self::assertFalse($this->isConnectedTcp()); + } + } + + protected function isConnectedTcp(): bool + { + $mypid = getmypid(); + $dbPort = (string) $this->port; + /** @psalm-suppress ForbiddenCode - running lsof */ + $lsof = shell_exec("lsof -i -P -n | grep $dbPort | grep $mypid"); + + return $lsof !== null; + } + + protected function isTcpConnection(): bool + { + return $this->getHostname() !== 'localhost'; + } +} diff --git a/test/integration/Driver/Pdo/AdapterTrait.php b/test/integration/Driver/Pdo/AdapterTrait.php new file mode 100644 index 0000000..feba2b7 --- /dev/null +++ b/test/integration/Driver/Pdo/AdapterTrait.php @@ -0,0 +1,27 @@ +adapter === null) { + $this->fail('Adapter not initialized'); + } + + return $this->adapter; + } + + protected function getHostname(): ?string + { + return $this->hostname; + } +} diff --git a/test/integration/Driver/Pdo/Mysql/AdapterTest.php b/test/integration/Driver/Pdo/Mysql/AdapterTest.php new file mode 100644 index 0000000..f14138f --- /dev/null +++ b/test/integration/Driver/Pdo/Mysql/AdapterTest.php @@ -0,0 +1,18 @@ +markTestSkipped('pdo_mysql integration tests are not enabled!'); + } + + $this->adapter = new Adapter([ + 'driver' => 'pdo_mysql', + 'database' => (string) getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_DATABASE'), + 'hostname' => (string) getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_HOSTNAME'), + 'username' => (string) getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_USERNAME'), + 'password' => (string) getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_PASSWORD'), + ]); + + $this->hostname = (string) getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_HOSTNAME'); + } +} diff --git a/test/integration/Driver/Pdo/Mysql/QueryTest.php b/test/integration/Driver/Pdo/Mysql/QueryTest.php new file mode 100644 index 0000000..31bdcb8 --- /dev/null +++ b/test/integration/Driver/Pdo/Mysql/QueryTest.php @@ -0,0 +1,130 @@ +, + * 2: array + * }> + */ + public static function getQueriesWithRowResult(): array + { + return [ + ['SELECT * FROM test WHERE id = ?', [1], ['id' => 1, 'name' => 'foo', 'value' => 'bar']], + ['SELECT * FROM test WHERE id = :id', [':id' => 1], ['id' => 1, 'name' => 'foo', 'value' => 'bar']], + ['SELECT * FROM test WHERE id = :id', ['id' => 1], ['id' => 1, 'name' => 'foo', 'value' => 'bar']], + ['SELECT * FROM test WHERE name = ?', ['123'], ['id' => '4', 'name' => '123', 'value' => 'bar']], + [ + // name is string, but given parameter is int, can lead to unexpected result + 'SELECT * FROM test WHERE name = ?', + [123], + ['id' => '3', 'name' => '123a', 'value' => 'bar'], + ], + ]; + } + + /** + * @throws Exception + */ + #[DataProvider('getQueriesWithRowResult')] + public function testQuery(string $query, array $params, array $expected): void + { + /** @todo Have AdapterInterface implement query */ + /** @psalm-suppress UndefinedInterfaceMethod */ + $result = $this->getAdapter()->query($query, $params); + $this->assertInstanceOf(ResultSet::class, $result); + $current = $result->current(); + // test as array value + $this->assertEquals($expected, (array) $current); + // test as object value + /** @var string $value */ + foreach ($expected as $key => $value) { + $this->assertEquals($value, $current->$key); + } + } + + /** + * @see https://github.com/zendframework/zend-db/issues/288 + * + * @throws Exception + */ + public function testSetSessionTimeZone(): void + { + /** @todo Have AdapterInterface implement query */ + /** @psalm-suppress UndefinedInterfaceMethod */ + $result = $this->getAdapter()->query('SET @@session.time_zone = :tz', [':tz' => 'SYSTEM']); + $this->assertInstanceOf(PdoResult::class, $result); + } + + /** + * @throws Exception + */ + public function testSelectWithNotPermittedBindParamName(): void + { + $this->expectException(RuntimeException::class); + /** @todo Have AdapterInterface implement query */ + /** @psalm-suppress UndefinedInterfaceMethod */ + $this->getAdapter()->query('SET @@session.time_zone = :tz$', [':tz$' => 'SYSTEM']); + } + + /** + * @see https://github.com/laminas/laminas-db/issues/47 + */ + public function testNamedParameters(): void + { + $this->assertNotNull($this->adapter); + $sql = new Sql($this->adapter); + + $insert = $sql->update('test'); + $insert->set([ + 'name' => ':name', + 'value' => ':value', + ])->where(['id' => ':id']); + $stmt = $sql->prepareStatementForSqlObject($insert); + $this->assertInstanceOf(StatementInterface::class, $stmt); + + //positional parameters + $stmt->execute([ + 'foo', + 'bar', + 1, + ]); + + //"mapped" named parameters + $stmt->execute([ + 'c_0' => 'foo', + 'c_1' => 'bar', + 'where1' => 1, + ]); + + //real named parameters + $stmt->execute([ + 'id' => 1, + 'name' => 'foo', + 'value' => 'bar', + ]); + } +} diff --git a/test/integration/Driver/Pdo/Mysql/TableGatewayAndAdapterTest.php b/test/integration/Driver/Pdo/Mysql/TableGatewayAndAdapterTest.php new file mode 100644 index 0000000..b0f5680 --- /dev/null +++ b/test/integration/Driver/Pdo/Mysql/TableGatewayAndAdapterTest.php @@ -0,0 +1,57 @@ +adapter->query('SELECT VERSION();'); + $table = new TableGateway( + 'test', + $this->adapter + ); + $select = $table->getSql()->select()->where(['name' => 'foo']); + $result = $table->selectWith($select); + self::assertCount(3, $result->current()); + } + + protected function tearDown(): void + { + if ($this->adapter->getDriver()->getConnection()->isConnected()) { + $this->adapter->getDriver()->getConnection()->disconnect(); + } + $this->adapter = null; + } + + public static function connections(): array + { + return array_fill(0, 200, []); + } +} diff --git a/test/integration/Driver/Pdo/Mysql/TableGatewayTest.php b/test/integration/Driver/Pdo/Mysql/TableGatewayTest.php new file mode 100644 index 0000000..3124c15 --- /dev/null +++ b/test/integration/Driver/Pdo/Mysql/TableGatewayTest.php @@ -0,0 +1,131 @@ +getAdapter()); + $this->assertInstanceOf(TableGateway::class, $tableGateway); + } + + public function testSelect(): void + { + $tableGateway = new TableGateway('test', $this->getAdapter()); + $rowset = $tableGateway->select(); + + $this->assertTrue(count($rowset) > 0); + /** @var object $row */ + foreach ($rowset as $row) { + $this->assertTrue(isset($row->id)); + $this->assertNotEmpty(isset($row->name)); + $this->assertNotEmpty(isset($row->value)); + } + } + + public function testInsert(): void + { + $tableGateway = new TableGateway('test', $this->getAdapter()); + + $tableGateway->select(); + $data = [ + 'name' => 'test_name', + 'value' => 'test_value', + ]; + $affectedRows = $tableGateway->insert($data); + $this->assertEquals(1, $affectedRows); + + $rowSet = $tableGateway->select(['id' => $tableGateway->getLastInsertValue()]); + /** @var object $row */ + $row = $rowSet->current(); + + foreach ($data as $key => $value) { + $this->assertEquals($row->$key, $value); + } + } + + /** + * @see https://github.com/zendframework/zend-db/issues/35 + * @see https://github.com/zendframework/zend-db/pull/178 + */ + public function testInsertWithExtendedCharsetFieldName(): int|string + { + $tableGateway = new TableGateway('test_charset', $this->getAdapter()); + + $affectedRows = $tableGateway->insert([ + 'field$' => 'test_value1', + 'field_' => 'test_value2', + ]); + $this->assertEquals(1, $affectedRows); + + return $tableGateway->getLastInsertValue(); + } + + #[Depends('testInsertWithExtendedCharsetFieldName')] + public function testUpdateWithExtendedCharsetFieldName(mixed $id): void + { + $tableGateway = new TableGateway('test_charset', $this->getAdapter()); + + $data = [ + 'field$' => 'test_value3', + 'field_' => 'test_value4', + ]; + $affectedRows = $tableGateway->update($data, ['id' => $id]); + $this->assertEquals(1, $affectedRows); + + $rowSet = $tableGateway->select(['id' => $id]); + /** @var object $row */ + $row = $rowSet->current(); + + foreach ($data as $key => $value) { + $this->assertEquals($row->$key, $value); + } + } + + #[DataProvider('tableProvider')] + public function testTableGatewayWithMetadataFeature(array|string|TableIdentifier $table): void + { + $tableGateway = new TableGateway( + $table, + $this->getAdapter(), + new MetadataFeature( + new MysqlMetadata($this->getAdapter()), + ) + ); + + self::assertInstanceOf(TableGateway::class, $tableGateway); + self::assertSame($table, $tableGateway->getTable()); + } + + /** @psalm-return array */ + public static function tableProvider(): array + { + return [ + 'string' => ['test'], + 'aliased string' => [['foo' => 'test']], + 'TableIdentifier' => [new TableIdentifier('test')], + 'aliased TableIdentifier' => [['foo' => new TableIdentifier('test')]], + ]; + } +} diff --git a/test/integration/Extension/IntegrationTestStartedListener.php b/test/integration/Extension/IntegrationTestStartedListener.php new file mode 100644 index 0000000..da85053 --- /dev/null +++ b/test/integration/Extension/IntegrationTestStartedListener.php @@ -0,0 +1,44 @@ +testSuite()->name() !== 'integration test') { + return; + } + + if (getenv('TESTS_LAMINAS_DB_ADAPTER_DRIVER_MYSQL')) { + $this->fixtureLoaders[] = new MysqlFixtureLoader(); + } + + if (empty($this->fixtureLoaders)) { + return; + } + + printf("\nIntegration test started.\n"); + + foreach ($this->fixtureLoaders as $fixtureLoader) { + $fixtureLoader->createDatabase(); + } + } +} diff --git a/test/integration/Extension/IntegrationTestStoppedListener.php b/test/integration/Extension/IntegrationTestStoppedListener.php new file mode 100644 index 0000000..339adc4 --- /dev/null +++ b/test/integration/Extension/IntegrationTestStoppedListener.php @@ -0,0 +1,33 @@ +testSuite()->name() !== 'integration test' + || empty($this->fixtureLoaders) + ) { + return; + } + + printf("\nIntegration test ended.\n"); + + foreach ($this->fixtureLoaders as $fixtureLoader) { + $fixtureLoader->dropDatabase(); + } + } +} diff --git a/test/integration/Extension/ListenerExtension.php b/test/integration/Extension/ListenerExtension.php new file mode 100644 index 0000000..866646e --- /dev/null +++ b/test/integration/Extension/ListenerExtension.php @@ -0,0 +1,24 @@ +registerSubscribers( + new IntegrationTestStartedListener(), + new IntegrationTestStoppedListener(), + ); + } +} diff --git a/test/integration/FixtureLoader/FixtureLoader.php b/test/integration/FixtureLoader/FixtureLoader.php new file mode 100644 index 0000000..eef8a68 --- /dev/null +++ b/test/integration/FixtureLoader/FixtureLoader.php @@ -0,0 +1,13 @@ +connect(); + + if ( + false === $this->pdo->exec(sprintf( + "CREATE DATABASE IF NOT EXISTS %s", + getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_DATABASE') + )) + ) { + throw new Exception(sprintf( + "I cannot create the MySQL %s test database: %s", + getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_DATABASE'), + print_r($this->pdo->errorInfo(), true) + )); + } + + $this->pdo->exec('USE ' . getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_DATABASE')); + + if (false === $this->pdo->exec(file_get_contents($this->fixtureFile))) { + throw new Exception(sprintf( + "I cannot create the table for %s database. Check the %s file. %s ", + getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_DATABASE'), + $this->fixtureFile, + print_r($this->pdo->errorInfo(), true) + )); + } + + $this->disconnect(); + } + + public function dropDatabase(): void + { + $this->connect(); + + $this->pdo->exec(sprintf( + "DROP DATABASE IF EXISTS %s", + getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_DATABASE') + )); + + $this->disconnect(); + } + + protected function connect(): void + { + $dsn = 'mysql:host=' . getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_HOSTNAME'); + if (getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_PORT')) { + $dsn .= ';port=' . getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_PORT'); + } + + $this->pdo = new PDO( + $dsn, + getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_USERNAME'), + getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_PASSWORD') + ); + } + + protected function disconnect(): void + { + $this->pdo = null; + } +} diff --git a/test/integration/Platform/MysqlTest.php b/test/integration/Platform/MysqlTest.php new file mode 100644 index 0000000..a938867 --- /dev/null +++ b/test/integration/Platform/MysqlTest.php @@ -0,0 +1,84 @@ + */ + public array|\PDO $adapters = []; + + #[Override] + protected function setUp(): void + { + if (! getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL')) { + $this->markTestSkipped(self::class . ' integration tests are not enabled!'); + } + if (extension_loaded('mysqli')) { + $this->adapters['mysqli'] = new \mysqli( + getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_HOSTNAME'), + getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_USERNAME'), + getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_PASSWORD'), + getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_DATABASE') + ); + } + if (extension_loaded('pdo')) { + $this->adapters['pdo_mysql'] = new \PDO( + 'mysql:host=' . getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_HOSTNAME') . ';dbname=' + . getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_DATABASE'), + getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_USERNAME'), + getenv('TESTS_LAMINAS_DB_ADAPTER_MYSQL_PASSWORD') + ); + } + } + + /** + * @return void + */ + public function testQuoteValueWithMysqli() + { + if (! $this->adapters['mysqli'] instanceof \Mysqli) { + $this->markTestSkipped('MySQL (Mysqli) not configured in unit test configuration file'); + } + $mysql = new Mysql($this->adapters['mysqli']); + $value = $mysql->quoteValue('value'); + self::assertEquals('\'value\'', $value); + + $mysql = new Mysql(new Mysqli\Mysqli(new Mysqli\Connection($this->adapters['mysqli']))); + $value = $mysql->quoteValue('value'); + self::assertEquals('\'value\'', $value); + } + + /** + * @return void + */ + public function testQuoteValueWithPdoMysql() + { + if (! $this->adapters['pdo_mysql'] instanceof \PDO) { + $this->markTestSkipped('MySQL (PDO_Mysql) not configured in unit test configuration file'); + } + $mysql = new Mysql($this->adapters['pdo_mysql']); + $value = $mysql->quoteValue('value'); + self::assertEquals('\'value\'', $value); + + $mysql = new Mysql(new Pdo\Pdo(new Pdo\Connection($this->adapters['pdo_mysql']))); + $value = $mysql->quoteValue('value'); + self::assertEquals('\'value\'', $value); + } +} diff --git a/test/integration/TestFixtures/mysql.sql b/test/integration/TestFixtures/mysql.sql new file mode 100644 index 0000000..ddddd7e --- /dev/null +++ b/test/integration/TestFixtures/mysql.sql @@ -0,0 +1,55 @@ +DROP TABLE IF EXISTS test; +CREATE TABLE IF NOT EXISTS test ( + id INT NOT NULL AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + value VARCHAR(255) NOT NULL, + PRIMARY KEY (id) +); + +INSERT INTO test (name, value) VALUES +('foo', 'bar'), +('bar', 'baz'), +('123a', 'bar'), +('123', 'bar'); + +DROP TABLE IF EXISTS test_charset; +CREATE TABLE IF NOT EXISTS test_charset ( + id INT NOT NULL AUTO_INCREMENT, + field$ VARCHAR(255) NOT NULL, + field_ VARCHAR(255) NOT NULL, + PRIMARY KEY (id) +); + +INSERT INTO test_charset (field$, field_) VALUES +('foo', 'bar'), +('bar', 'baz'); + +DROP TABLE IF EXISTS test_audit_trail; +CREATE TABLE IF NOT EXISTS test_audit_trail ( + id INT NOT NULL AUTO_INCREMENT, + test_id INT NOT NULL, + test_value_old VARCHAR(255) NOT NULL, + test_value_new VARCHAR(255) NOT NULL, + changed TIMESTAMP, + PRIMARY KEY (id) +); + +DROP VIEW IF EXISTS test_view; +CREATE VIEW test_view +AS +SELECT + name AS v_name, + value AS v_value +FROM + test; + +DROP TRIGGER IF EXISTS after_test_update; +CREATE TRIGGER after_test_update + AFTER UPDATE ON test + FOR EACH ROW + INSERT INTO test_audit_trail + SET + test_id = OLD.id, + test_value_old = OLD.value, + test_value_new = NEW.value, + changed = NOW(); diff --git a/test/unit/Adapter/Driver/Mysqli/ConnectionTest.php b/test/unit/Adapter/Driver/Mysqli/ConnectionTest.php new file mode 100644 index 0000000..9a9bf5f --- /dev/null +++ b/test/unit/Adapter/Driver/Mysqli/ConnectionTest.php @@ -0,0 +1,197 @@ +markTestSkipped('Mysqli test disabled'); + // } + $this->connection = new Connection([]); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown(): void + { + } + + public function testSetDriver(): void + { + self::assertEquals($this->connection, $this->connection->setDriver(new Mysqli([]))); + } + + public function testSetConnectionParameters(): void + { + self::assertEquals($this->connection, $this->connection->setConnectionParameters([])); + } + + public function testGetConnectionParameters(): void + { + $this->connection->setConnectionParameters(['foo' => 'bar']); + self::assertEquals(['foo' => 'bar'], $this->connection->getConnectionParameters()); + } + + public function testNonSecureConnection(): void + { + $mysqli = $this->createMockMysqli(0); + $connection = $this->createMockConnection( + $mysqli, + [ + 'hostname' => 'localhost', + 'username' => 'superuser', + 'password' => '1234', + 'database' => 'main', + 'port' => 123, + ] + ); + + $connection->connect(); + } + + public function testSslConnection(): void + { + $mysqli = $this->createMockMysqli(MYSQLI_CLIENT_SSL); + $connection = $this->createMockConnection( + $mysqli, + [ + 'hostname' => 'localhost', + 'username' => 'superuser', + 'password' => '1234', + 'database' => 'main', + 'port' => 123, + 'use_ssl' => true, + ] + ); + + $connection->connect(); + } + + public function testSslConnectionNoVerify(): void + { + $mysqli = $this->createMockMysqli(MYSQLI_CLIENT_SSL | MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT); + $connection = $this->createMockConnection( + $mysqli, + [ + 'hostname' => 'localhost', + 'username' => 'superuser', + 'password' => '1234', + 'database' => 'main', + 'port' => 123, + 'use_ssl' => true, + 'driver_options' => [ + MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT => true, + ], + ] + ); + + $connection->connect(); + } + + public function testConnectionFails(): void + { + $connection = new Connection([]); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Connection error'); + $connection->connect(); + } + + /** + * Create a mock mysqli + * + * @param int $flags Expected flags to real_connect + */ + protected function createMockMysqli(int $flags): MockObject + { + $mysqli = $this->getMockBuilder(\mysqli::class)->getMock(); + $mysqli->expects($flags ? $this->once() : $this->never()) + ->method('ssl_set') + ->with( + $this->equalTo(''), + $this->equalTo(''), + $this->equalTo(''), + $this->equalTo(''), + $this->equalTo('') + ); + + if ($flags === 0) { + // Do not pass $flags argument if invalid flags provided + $mysqli->expects($this->once()) + ->method('real_connect') + ->with( + $this->equalTo('localhost'), + $this->equalTo('superuser'), + $this->equalTo('1234'), + $this->equalTo('main'), + $this->equalTo(123), + $this->equalTo('') + ) + ->willReturn(true); + return $mysqli; + } + + $mysqli->expects($this->once()) + ->method('real_connect') + ->with( + $this->equalTo('localhost'), + $this->equalTo('superuser'), + $this->equalTo('1234'), + $this->equalTo('main'), + $this->equalTo(123), + $this->equalTo(''), + $this->equalTo($flags) + ) + ->willReturn(true); + + return $mysqli; + } + + /** + * Create a mock connection + * + * @param MockObject $mysqli Mock mysqli object + * @param array $params Connection params + */ + protected function createMockConnection(MockObject $mysqli, array $params): MockObject + { + $connection = $this->getMockBuilder(Connection::class) + ->onlyMethods(['createResource']) + ->setConstructorArgs([$params]) + ->getMock(); + $connection->expects($this->once()) + ->method('createResource') + ->willReturn($mysqli); + + return $connection; + } +} diff --git a/test/unit/Adapter/Driver/Pdo/ConnectionIntegrationTest.php b/test/unit/Adapter/Driver/Pdo/ConnectionIntegrationTest.php new file mode 100644 index 0000000..af66001 --- /dev/null +++ b/test/unit/Adapter/Driver/Pdo/ConnectionIntegrationTest.php @@ -0,0 +1,161 @@ + */ + protected array $variables = ['pdodriver' => 'sqlite', 'database' => ':memory:']; + + public function testGetCurrentSchema(): void + { + $connection = new Connection($this->variables); + self::assertIsString($connection->getCurrentSchema()); + } + + public function testSetResource(): void + { + $resource = new TestAsset\SqliteMemoryPdo(); + $connection = new Connection([]); + self::assertSame($connection, $connection->setResource($resource)); + + $connection->disconnect(); + unset($connection); + unset($resource); + } + + public function testGetResource(): void + { + $connection = new Connection($this->variables); + $connection->connect(); + self::assertInstanceOf('PDO', $connection->getResource()); + + $connection->disconnect(); + unset($connection); + } + + public function testConnect(): void + { + $connection = new Connection($this->variables); + self::assertSame($connection, $connection->connect()); + self::assertTrue($connection->isConnected()); + + $connection->disconnect(); + unset($connection); + } + + public function testIsConnected(): void + { + $connection = new Connection($this->variables); + self::assertFalse($connection->isConnected()); + self::assertSame($connection, $connection->connect()); + self::assertTrue($connection->isConnected()); + + $connection->disconnect(); + unset($connection); + } + + public function testDisconnect(): void + { + $connection = new Connection($this->variables); + $connection->connect(); + self::assertTrue($connection->isConnected()); + $connection->disconnect(); + self::assertFalse($connection->isConnected()); + } + + /** + * @todo Implement testBeginTransaction(). + */ + public function testBeginTransaction(): never + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * @todo Implement testCommit(). + */ + public function testCommit(): never + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * @todo Implement testRollback(). + */ + public function testRollback(): never + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + public function testExecute(): void + { + $sqlsrv = new Pdo($this->variables); + $connection = $sqlsrv->getConnection(); + + $result = $connection->execute('SELECT \'foo\''); + self::assertInstanceOf(Result::class, $result); + } + + public function testPrepare(): void + { + $sqlsrv = new Pdo($this->variables); + $connection = $sqlsrv->getConnection(); + + $statement = $connection->prepare('SELECT \'foo\''); + self::assertInstanceOf(Statement::class, $statement); + } + + public function testGetLastGeneratedValue(): never + { + $this->markTestIncomplete('Need to create a temporary sequence.'); + //$connection = new Connection($this->variables); + //$connection->getLastGeneratedValue(); + } + + #[Group('laminas3469')] + public function testConnectReturnsConnectionWhenResourceSet(): void + { + $resource = new TestAsset\SqliteMemoryPdo(); + $connection = new Connection([]); + $connection->setResource($resource); + self::assertSame($connection, $connection->connect()); + + $connection->disconnect(); + unset($connection); + unset($resource); + } +} diff --git a/test/unit/Adapter/Driver/Pdo/ConnectionTest.php b/test/unit/Adapter/Driver/Pdo/ConnectionTest.php new file mode 100644 index 0000000..0966f24 --- /dev/null +++ b/test/unit/Adapter/Driver/Pdo/ConnectionTest.php @@ -0,0 +1,117 @@ +connection = new Connection(); + } + + /** + * Test getResource method tries to connect to the database, it should never return null + */ + public function testResource(): void + { + $this->expectException(InvalidConnectionParametersException::class); + $this->connection->getResource(); + } + + /** + * Test getConnectedDsn returns a DSN string if it has been set + */ + public function testGetDsn(): void + { + $dsn = "sqlite::memory:"; + $this->connection->setConnectionParameters(['dsn' => $dsn]); + try { + $this->connection->connect(); + } catch (Exception) { + } + $responseString = $this->connection->getDsn(); + + self::assertEquals($dsn, $responseString); + } + + #[Group('2622')] + public function testArrayOfConnectionParametersCreatesCorrectDsn(): void + { + $this->connection->setConnectionParameters([ + 'driver' => 'pdo_mysql', + 'charset' => 'utf8', + 'dbname' => 'foo', + 'port' => '3306', + 'unix_socket' => '/var/run/mysqld/mysqld.sock', + ]); + try { + $this->connection->connect(); + } catch (Exception) { + } + $responseString = $this->connection->getDsn(); + + self::assertStringStartsWith('mysql:', $responseString); + self::assertStringContainsString('charset=utf8', $responseString); + self::assertStringContainsString('dbname=foo', $responseString); + self::assertStringContainsString('port=3306', $responseString); + self::assertStringContainsString('unix_socket=/var/run/mysqld/mysqld.sock', $responseString); + } + + public function testHostnameAndUnixSocketThrowsInvalidConnectionParametersException(): void + { + $this->expectException(InvalidConnectionParametersException::class); + $this->expectExceptionMessage( + 'Ambiguous connection parameters, both hostname and unix_socket parameters were set' + ); + + $this->connection->setConnectionParameters([ + 'driver' => 'pdo_mysql', + 'host' => '127.0.0.1', + 'dbname' => 'foo', + 'port' => '3306', + 'unix_socket' => '/var/run/mysqld/mysqld.sock', + ]); + $this->connection->connect(); + } + + public function testDblibArrayOfConnectionParametersCreatesCorrectDsn(): void + { + $this->connection->setConnectionParameters([ + 'driver' => 'pdo_dblib', + 'charset' => 'UTF-8', + 'dbname' => 'foo', + 'port' => '1433', + 'version' => '7.3', + ]); + try { + $this->connection->connect(); + } catch (Exception) { + } + $responseString = $this->connection->getDsn(); + + $this->assertStringStartsWith('dblib:', $responseString); + $this->assertStringContainsString('charset=UTF-8', $responseString); + $this->assertStringContainsString('dbname=foo', $responseString); + $this->assertStringContainsString('port=1433', $responseString); + $this->assertStringContainsString('version=7.3', $responseString); + } +} diff --git a/test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php b/test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php new file mode 100644 index 0000000..fd5e600 --- /dev/null +++ b/test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php @@ -0,0 +1,162 @@ +wrapper = new ConnectionWrapper(); + } + + public function testBeginTransactionReturnsInstanceOfConnection(): void + { + self::assertInstanceOf(Connection::class, $this->wrapper->beginTransaction()); + } + + public function testBeginTransactionSetsInTransactionAtTrue(): void + { + $this->wrapper->beginTransaction(); + self::assertTrue($this->wrapper->inTransaction()); + } + + public function testCommitReturnsInstanceOfConnection(): void + { + $this->wrapper->beginTransaction(); + self::assertInstanceOf(Connection::class, $this->wrapper->commit()); + } + + public function testCommitSetsInTransactionAtFalse(): void + { + $this->wrapper->beginTransaction(); + $this->wrapper->commit(); + self::assertFalse($this->wrapper->inTransaction()); + } + + /** + * Standalone commit after a SET autocommit=0; + */ + public function testCommitWithoutBeginReturnsInstanceOfConnection(): void + { + self::assertInstanceOf(Connection::class, $this->wrapper->commit()); + } + + public function testNestedTransactionsCommit(): void + { + $nested = 0; + + self::assertFalse($this->wrapper->inTransaction()); + + // 1st transaction + $this->wrapper->beginTransaction(); + self::assertTrue($this->wrapper->inTransaction()); + self::assertSame(++$nested, $this->wrapper->getNestedTransactionsCount()); + + // 2nd transaction + $this->wrapper->beginTransaction(); + self::assertTrue($this->wrapper->inTransaction()); + self::assertSame(++$nested, $this->wrapper->getNestedTransactionsCount()); + + // 1st commit + $this->wrapper->commit(); + self::assertTrue($this->wrapper->inTransaction()); + self::assertSame(--$nested, $this->wrapper->getNestedTransactionsCount()); + + // 2nd commit + $this->wrapper->commit(); + self::assertFalse($this->wrapper->inTransaction()); + self::assertSame(--$nested, $this->wrapper->getNestedTransactionsCount()); + } + + public function testNestedTransactionsRollback(): void + { + $nested = 0; + + self::assertFalse($this->wrapper->inTransaction()); + + // 1st transaction + $this->wrapper->beginTransaction(); + self::assertTrue($this->wrapper->inTransaction()); + self::assertSame(++$nested, $this->wrapper->getNestedTransactionsCount()); + + // 2nd transaction + $this->wrapper->beginTransaction(); + self::assertTrue($this->wrapper->inTransaction()); + self::assertSame(++$nested, $this->wrapper->getNestedTransactionsCount()); + + // Rollback + $this->wrapper->rollback(); + self::assertFalse($this->wrapper->inTransaction()); + self::assertSame(0, $this->wrapper->getNestedTransactionsCount()); + } + + public function testRollbackDisconnectedThrowsException(): void + { + $this->wrapper->disconnect(); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Must be connected before you can rollback'); + $this->wrapper->rollback(); + } + + public function testRollbackReturnsInstanceOfConnection(): void + { + $this->wrapper->beginTransaction(); + self::assertInstanceOf(Connection::class, $this->wrapper->rollback()); + } + + public function testRollbackSetsInTransactionAtFalse(): void + { + $this->wrapper->beginTransaction(); + $this->wrapper->rollback(); + self::assertFalse($this->wrapper->inTransaction()); + } + + public function testRollbackWithoutBeginThrowsException(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Must call beginTransaction() before you can rollback'); + $this->wrapper->rollback(); + } + + /** + * Standalone commit after a SET autocommit=0; + */ + public function testStandaloneCommit(): void + { + self::assertFalse($this->wrapper->inTransaction()); + self::assertSame(0, $this->wrapper->getNestedTransactionsCount()); + + $this->wrapper->commit(); + + self::assertFalse($this->wrapper->inTransaction()); + self::assertSame(0, $this->wrapper->getNestedTransactionsCount()); + } +} diff --git a/test/unit/Adapter/Driver/Pdo/PdoTest.php b/test/unit/Adapter/Driver/Pdo/PdoTest.php new file mode 100644 index 0000000..31db582 --- /dev/null +++ b/test/unit/Adapter/Driver/Pdo/PdoTest.php @@ -0,0 +1,89 @@ +pdo = new Pdo([]); + } + + public function testGetDatabasePlatformName(): void + { + // Test platform name for SqlServer + $this->pdo->getConnection()->setConnectionParameters(['pdodriver' => 'pdo_mysql']); + self::assertEquals('Mysql', $this->pdo->getDatabasePlatformName()); + self::assertEquals('MySQL', $this->pdo->getDatabasePlatformName(DriverInterface::NAME_FORMAT_NATURAL)); + } + + /** @psalm-return array */ + public static function getParamsAndType(): array + { + return [ + ['foo', null, ':foo'], + ['foo_bar', null, ':foo_bar'], + ['123foo', null, ':123foo'], + [1, null, '?'], + ['1', null, '?'], + ['foo', DriverInterface::PARAMETERIZATION_NAMED, ':foo'], + ['foo_bar', DriverInterface::PARAMETERIZATION_NAMED, ':foo_bar'], + ['123foo', DriverInterface::PARAMETERIZATION_NAMED, ':123foo'], + [1, DriverInterface::PARAMETERIZATION_NAMED, ':1'], + ['1', DriverInterface::PARAMETERIZATION_NAMED, ':1'], + [':foo', null, ':foo'], + ]; + } + + #[DataProvider('getParamsAndType')] + public function testFormatParameterName(int|string $name, ?string $type, string $expected): void + { + $result = $this->pdo->formatParameterName($name, $type); + $this->assertEquals($expected, $result); + } + + /** @psalm-return array */ + public static function getInvalidParamName(): array + { + return [ + ['foo%'], + ['foo-'], + ['foo$'], + ['foo0!'], + ]; + } + + #[DataProvider('getInvalidParamName')] + public function testFormatParameterNameWithInvalidCharacters(string $name): void + { + $this->expectException(RuntimeException::class); + $this->pdo->formatParameterName($name); + } + + public function testGetResultPrototype(): void + { + $resultPrototype = $this->pdo->getResultPrototype(); + + self::assertInstanceOf(Result::class, $resultPrototype); + } +} diff --git a/test/unit/Adapter/Driver/Pdo/ResultTest.php b/test/unit/Adapter/Driver/Pdo/ResultTest.php new file mode 100644 index 0000000..27ad883 --- /dev/null +++ b/test/unit/Adapter/Driver/Pdo/ResultTest.php @@ -0,0 +1,111 @@ +getMockBuilder('PDOStatement')->getMock(); + $stub->expects($this->any()) + ->method('fetch') + ->willReturnCallback(fn() => uniqid()); + + $result = new Result(); + $result->initialize($stub, null); + + self::assertEquals($result->current(), $result->current()); + } + + public function testFetchModeException(): void + { + $result = new Result(); + + $this->expectException(InvalidArgumentException::class); + $result->setFetchMode(13); + } + + /** + * Tests whether the fetch mode was set properly and + */ + public function testFetchModeAnonymousObject(): void + { + $stub = $this->getMockBuilder('PDOStatement')->getMock(); + $stub->expects($this->any()) + ->method('fetch') + ->willReturnCallback(fn() => new stdClass()); + + $result = new Result(); + $result->initialize($stub, null); + $result->setFetchMode(PDO::FETCH_OBJ); + + self::assertEquals(5, $result->getFetchMode()); + self::assertInstanceOf('stdClass', $result->current()); + } + + /** + * Tests whether the fetch mode has a broader range + */ + public function testFetchModeRange(): void + { + $stub = $this->getMockBuilder('PDOStatement')->getMock(); + $stub->expects($this->any()) + ->method('fetch') + ->willReturnCallback(fn() => new stdClass()); + $result = new Result(); + $result->initialize($stub, null); + $result->setFetchMode(PDO::FETCH_NAMED); + self::assertEquals(11, $result->getFetchMode()); + self::assertInstanceOf('stdClass', $result->current()); + } + + public function testMultipleRewind(): void + { + $data = [ + ['test' => 1], + ['test' => 2], + ]; + $position = 0; + + $stub = $this->getMockBuilder('PDOStatement')->getMock(); + assert($stub instanceof PDOStatement); // to suppress IDE type warnings + $stub->expects($this->any()) + ->method('fetch') + ->willReturnCallback(function () use ($data, &$position) { + return $data[$position++]; + }); + $result = new Result(); + $result->initialize($stub, null); + + $result->rewind(); + $result->rewind(); + + $this->assertEquals(0, $result->key()); + $this->assertEquals(1, $position); + $this->assertEquals($data[0], $result->current()); + + $result->next(); + $this->assertEquals(1, $result->key()); + $this->assertEquals(2, $position); + $this->assertEquals($data[1], $result->current()); + } +} diff --git a/test/unit/Adapter/Driver/Pdo/StatementIntegrationTest.php b/test/unit/Adapter/Driver/Pdo/StatementIntegrationTest.php new file mode 100644 index 0000000..66e2976 --- /dev/null +++ b/test/unit/Adapter/Driver/Pdo/StatementIntegrationTest.php @@ -0,0 +1,89 @@ +getMockBuilder(\Laminas\Db\Adapter\Driver\Pdo\Pdo::class) + ->onlyMethods(['createResult']) + ->disableOriginalConstructor() + ->getMock(); + + $this->statement = new Statement(); + $this->statement->setDriver($driver); + $this->statement->initialize(new TestAsset\CtorlessPdo( + $this->pdoStatementMock = $this->getMockBuilder('PDOStatement') + ->onlyMethods(['execute', 'bindParam']) + ->getMock() + )); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown(): void + { + } + + public function testStatementExecuteWillConvertPhpBoolToPdoBoolWhenBinding(): void + { + $this->pdoStatementMock->expects($this->any())->method('bindParam')->with( + $this->equalTo(':foo'), + $this->equalTo(false), + $this->equalTo(PDO::PARAM_BOOL) + ); + $this->statement->execute(['foo' => false]); + } + + public function testStatementExecuteWillUsePdoStrByDefaultWhenBinding(): void + { + $this->pdoStatementMock->expects($this->any())->method('bindParam')->with( + $this->equalTo(':foo'), + $this->equalTo('bar'), + $this->equalTo(PDO::PARAM_STR) + ); + $this->statement->execute(['foo' => 'bar']); + } + + public function testStatementExecuteWillUsePdoStrForStringIntegerWhenBinding(): void + { + $this->pdoStatementMock->expects($this->any())->method('bindParam')->with( + $this->equalTo(':foo'), + $this->equalTo('123'), + $this->equalTo(PDO::PARAM_STR) + ); + $this->statement->execute(['foo' => '123']); + } + + public function testStatementExecuteWillUsePdoIntForIntWhenBinding(): void + { + $this->pdoStatementMock->expects($this->any())->method('bindParam')->with( + $this->equalTo(':foo'), + $this->equalTo(123), + $this->equalTo(PDO::PARAM_INT) + ); + $this->statement->execute(['foo' => 123]); + } +} diff --git a/test/unit/Adapter/Driver/Pdo/StatementTest.php b/test/unit/Adapter/Driver/Pdo/StatementTest.php new file mode 100644 index 0000000..cf662c8 --- /dev/null +++ b/test/unit/Adapter/Driver/Pdo/StatementTest.php @@ -0,0 +1,112 @@ +statement = new Statement(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown(): void + { + } + + public function testSetDriver(): void + { + self::assertEquals($this->statement, $this->statement->setDriver(new Pdo([]))); + } + + public function testSetParameterContainer(): void + { + self::assertSame($this->statement, $this->statement->setParameterContainer(new ParameterContainer())); + } + + /** + * @todo Implement testGetParameterContainer(). + */ + public function testGetParameterContainer(): void + { + $container = new ParameterContainer(); + $this->statement->setParameterContainer($container); + self::assertSame($container, $this->statement->getParameterContainer()); + } + + public function testGetResource(): void + { + $pdo = new TestAsset\SqliteMemoryPdo(); + $stmt = $pdo->prepare('SELECT 1'); + $this->statement->setResource($stmt); + + self::assertSame($stmt, $this->statement->getResource()); + } + + public function testSetSql(): void + { + $this->statement->setSql('SELECT 1'); + self::assertEquals('SELECT 1', $this->statement->getSql()); + } + + public function testGetSql(): void + { + $this->statement->setSql('SELECT 1'); + self::assertEquals('SELECT 1', $this->statement->getSql()); + } + + /** + * @todo Implement testPrepare(). + */ + public function testPrepare(): void + { + $this->statement->initialize(new TestAsset\SqliteMemoryPdo()); + self::assertNull($this->statement->prepare('SELECT 1')); + } + + public function testIsPrepared(): void + { + self::assertFalse($this->statement->isPrepared()); + $this->statement->initialize(new TestAsset\SqliteMemoryPdo()); + $this->statement->prepare('SELECT 1'); + self::assertTrue($this->statement->isPrepared()); + } + + public function testExecute(): void + { + $this->statement->setDriver(new Pdo(new Connection($pdo = new TestAsset\SqliteMemoryPdo()))); + $this->statement->initialize($pdo); + $this->statement->prepare('SELECT 1'); + self::assertInstanceOf(Result::class, $this->statement->execute()); + } +} diff --git a/test/unit/Adapter/Driver/Pdo/TestAsset/CtorlessPdo.php b/test/unit/Adapter/Driver/Pdo/TestAsset/CtorlessPdo.php new file mode 100644 index 0000000..8f87123 --- /dev/null +++ b/test/unit/Adapter/Driver/Pdo/TestAsset/CtorlessPdo.php @@ -0,0 +1,26 @@ + $options + */ + #[Override] + public function prepare(string $query, $options = null): PDOStatement + { + return $this->mockStatement; + } +} diff --git a/test/unit/Adapter/Driver/Pdo/TestAsset/SqliteMemoryPdo.php b/test/unit/Adapter/Driver/Pdo/TestAsset/SqliteMemoryPdo.php new file mode 100644 index 0000000..846a567 --- /dev/null +++ b/test/unit/Adapter/Driver/Pdo/TestAsset/SqliteMemoryPdo.php @@ -0,0 +1,37 @@ +exec($sql)) { + throw new Exception(sprintf( + "Error: %s, %s", + $this->errorCode(), + implode(",", $this->errorInfo()) + )); + } + } +} diff --git a/test/unit/Adapter/Driver/TestAsset/ConnectionWrapper.php b/test/unit/Adapter/Driver/TestAsset/ConnectionWrapper.php new file mode 100644 index 0000000..693db77 --- /dev/null +++ b/test/unit/Adapter/Driver/TestAsset/ConnectionWrapper.php @@ -0,0 +1,24 @@ +resource = new PdoStubDriver('foo', 'bar', 'baz'); + } + + public function getNestedTransactionsCount(): int + { + return $this->nestedTransactionsCount; + } +} diff --git a/test/unit/Adapter/Driver/TestAsset/PdoMock.php b/test/unit/Adapter/Driver/TestAsset/PdoMock.php new file mode 100644 index 0000000..fce7702 --- /dev/null +++ b/test/unit/Adapter/Driver/TestAsset/PdoMock.php @@ -0,0 +1,43 @@ +platform = new Mysql(); + } + + public function testGetName(): void + { + self::assertEquals('MySQL', $this->platform->getName()); + } + + public function testGetQuoteIdentifierSymbol(): void + { + self::assertEquals('`', $this->platform->getQuoteIdentifierSymbol()); + } + + public function testQuoteIdentifier(): void + { + self::assertEquals('`identifier`', $this->platform->quoteIdentifier('identifier')); + self::assertEquals('`ident``ifier`', $this->platform->quoteIdentifier('ident`ifier')); + self::assertEquals('`namespace:$identifier`', $this->platform->quoteIdentifier('namespace:$identifier')); + } + + public function testQuoteIdentifierChain(): void + { + self::assertEquals('`identifier`', $this->platform->quoteIdentifierChain('identifier')); + self::assertEquals('`identifier`', $this->platform->quoteIdentifierChain(['identifier'])); + self::assertEquals('`schema`.`identifier`', $this->platform->quoteIdentifierChain(['schema', 'identifier'])); + + self::assertEquals('`ident``ifier`', $this->platform->quoteIdentifierChain('ident`ifier')); + self::assertEquals('`ident``ifier`', $this->platform->quoteIdentifierChain(['ident`ifier'])); + self::assertEquals( + '`schema`.`ident``ifier`', + $this->platform->quoteIdentifierChain(['schema', 'ident`ifier']) + ); + } + + public function testGetQuoteValueSymbol(): void + { + self::assertEquals("'", $this->platform->getQuoteValueSymbol()); + } + + public function testQuoteValueRaisesNoticeWithoutPlatformSupport(): void + { + /** + * @todo Determine if vulnerability warning is required during unit testing + */ + //$this->expectNotice(); + //$this->expectExceptionMessage( + // 'Attempting to quote a value in Laminas\Db\Adapter\Platform\Mysql without extension/driver support can ' + // . 'introduce security vulnerabilities in a production environment' + //); + $this->expectNotToPerformAssertions(); + $this->platform->quoteValue('value'); + } + + public function testQuoteValue(): void + { + self::assertEquals("'value'", @$this->platform->quoteValue('value')); + self::assertEquals("'Foo O\\'Bar'", @$this->platform->quoteValue("Foo O'Bar")); + self::assertEquals( + '\'\\\'; DELETE FROM some_table; -- \'', + @$this->platform->quoteValue('\'; DELETE FROM some_table; -- ') + ); + self::assertEquals( + "'\\\\\\'; DELETE FROM some_table; -- '", + @$this->platform->quoteValue('\\\'; DELETE FROM some_table; -- ') + ); + } + + public function testQuoteTrustedValue(): void + { + self::assertEquals("'value'", $this->platform->quoteTrustedValue('value')); + self::assertEquals("'Foo O\\'Bar'", $this->platform->quoteTrustedValue("Foo O'Bar")); + self::assertEquals( + '\'\\\'; DELETE FROM some_table; -- \'', + $this->platform->quoteTrustedValue('\'; DELETE FROM some_table; -- ') + ); + + // '\\\'; DELETE FROM some_table; -- ' <- actual below + self::assertEquals( + "'\\\\\\'; DELETE FROM some_table; -- '", + $this->platform->quoteTrustedValue('\\\'; DELETE FROM some_table; -- ') + ); + } + + public function testQuoteValueList(): void + { + /** + * @todo Determine if vulnerability warning is required during unit testing + */ + //$this->expectError(); + //$this->expectExceptionMessage( + // 'Attempting to quote a value in Laminas\Db\Adapter\Platform\Mysql without extension/driver support can ' + // . 'introduce security vulnerabilities in a production environment' + //); + self::assertEquals("'Foo O\\'Bar'", $this->platform->quoteValueList("Foo O'Bar")); + } + + public function testGetIdentifierSeparator(): void + { + self::assertEquals('.', $this->platform->getIdentifierSeparator()); + } + + public function testQuoteIdentifierInFragment(): void + { + self::assertEquals('`foo`.`bar`', $this->platform->quoteIdentifierInFragment('foo.bar')); + self::assertEquals('`foo` as `bar`', $this->platform->quoteIdentifierInFragment('foo as bar')); + self::assertEquals('`$TableName`.`bar`', $this->platform->quoteIdentifierInFragment('$TableName.bar')); + self::assertEquals( + '`cmis:$TableName` as `cmis:TableAlias`', + $this->platform->quoteIdentifierInFragment('cmis:$TableName as cmis:TableAlias') + ); + + $this->assertEquals( + '`foo-bar`.`bar-foo`', + $this->platform->quoteIdentifierInFragment('foo-bar.bar-foo') + ); + $this->assertEquals( + '`foo-bar` as `bar-foo`', + $this->platform->quoteIdentifierInFragment('foo-bar as bar-foo') + ); + $this->assertEquals( + '`$TableName-$ColumnName`.`bar-foo`', + $this->platform->quoteIdentifierInFragment('$TableName-$ColumnName.bar-foo') + ); + $this->assertEquals( + '`cmis:$TableName-$ColumnName` as `cmis:TableAlias-ColumnAlias`', + $this->platform->quoteIdentifierInFragment('cmis:$TableName-$ColumnName as cmis:TableAlias-ColumnAlias') + ); + + // single char words + self::assertEquals( + '(`foo`.`bar` = `boo`.`baz`)', + $this->platform->quoteIdentifierInFragment('(foo.bar = boo.baz)', ['(', ')', '=']) + ); + self::assertEquals( + '(`foo`.`bar`=`boo`.`baz`)', + $this->platform->quoteIdentifierInFragment('(foo.bar=boo.baz)', ['(', ')', '=']) + ); + self::assertEquals('`foo`=`bar`', $this->platform->quoteIdentifierInFragment('foo=bar', ['='])); + + $this->assertEquals( + '(`foo-bar`.`bar-foo` = `boo-baz`.`baz-boo`)', + $this->platform->quoteIdentifierInFragment('(foo-bar.bar-foo = boo-baz.baz-boo)', ['(', ')', '=']) + ); + $this->assertEquals( + '(`foo-bar`.`bar-foo`=`boo-baz`.`baz-boo`)', + $this->platform->quoteIdentifierInFragment('(foo-bar.bar-foo=boo-baz.baz-boo)', ['(', ')', '=']) + ); + $this->assertEquals( + '`foo-bar`=`bar-foo`', + $this->platform->quoteIdentifierInFragment('foo-bar=bar-foo', ['=']) + ); + + // case insensitive safe words + self::assertEquals( + '(`foo`.`bar` = `boo`.`baz`) AND (`foo`.`baz` = `boo`.`baz`)', + $this->platform->quoteIdentifierInFragment( + '(foo.bar = boo.baz) AND (foo.baz = boo.baz)', + ['(', ')', '=', 'and'] + ) + ); + + $this->assertEquals( + '(`foo-bar`.`bar-foo` = `boo-baz`.`baz-boo`) AND (`foo-baz`.`baz-foo` = `boo-baz`.`baz-boo`)', + $this->platform->quoteIdentifierInFragment( + '(foo-bar.bar-foo = boo-baz.baz-boo) AND (foo-baz.baz-foo = boo-baz.baz-boo)', + ['(', ')', '=', 'and'] + ) + ); + + // case insensitive safe words in field + self::assertEquals( + '(`foo`.`bar` = `boo`.baz) AND (`foo`.baz = `boo`.baz)', + $this->platform->quoteIdentifierInFragment( + '(foo.bar = boo.baz) AND (foo.baz = boo.baz)', + ['(', ')', '=', 'and', 'bAz'] + ) + ); + + // case insensitive safe words in field + $this->assertEquals( + '(`foo-bar`.`bar-foo` = `boo-baz`.baz-boo) AND (`foo-baz`.`baz-foo` = `boo-baz`.baz-boo)', + $this->platform->quoteIdentifierInFragment( + '(foo-bar.bar-foo = boo-baz.baz-boo) AND (foo-baz.baz-foo = boo-baz.baz-boo)', + ['(', ')', '=', 'and', 'bAz-BOo'] + ) + ); + } +}