diff --git a/.gitignore b/.gitignore
index 772df01..63e88fc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,7 @@ nixos.qcow2
terraform.tfstate.backup
.terraform.tfstate.lock.info
+
+# PHP/Composer
+vendor/
+*.cache
diff --git a/flake.nix b/flake.nix
index f5121cb..f4b1c70 100644
--- a/flake.nix
+++ b/flake.nix
@@ -37,6 +37,7 @@
./checks/flake-module.nix
./vm/flake-module.nix
./formatter.nix
+ ./pkgs/mediawiki-fastly-purge/flake-module.nix
];
perSystem =
{
diff --git a/pkgs/mediawiki-fastly-purge/.envrc b/pkgs/mediawiki-fastly-purge/.envrc
new file mode 100644
index 0000000..489f749
--- /dev/null
+++ b/pkgs/mediawiki-fastly-purge/.envrc
@@ -0,0 +1,2 @@
+# shellcheck shell=bash
+use flake .#fastly-purge
diff --git a/pkgs/mediawiki-fastly-purge/.phan/config.php b/pkgs/mediawiki-fastly-purge/.phan/config.php
new file mode 100644
index 0000000..87a323a
--- /dev/null
+++ b/pkgs/mediawiki-fastly-purge/.phan/config.php
@@ -0,0 +1,16 @@
+
+
+
+ .
+ .direnv
+ vendor
+
+
+
+
\ No newline at end of file
diff --git a/pkgs/mediawiki-fastly-purge/composer.json b/pkgs/mediawiki-fastly-purge/composer.json
new file mode 100644
index 0000000..14ac3e5
--- /dev/null
+++ b/pkgs/mediawiki-fastly-purge/composer.json
@@ -0,0 +1,30 @@
+{
+ "name": "mediawiki/fastly-purge",
+ "description": "MediaWiki extension for Fastly CDN cache purging",
+ "type": "mediawiki-extension",
+ "license": "MIT",
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "mediawiki/mediawiki-codesniffer": "48.0.0",
+ "mediawiki/mediawiki-phan-config": "0.17.0",
+ "php-parallel-lint/php-console-highlighter": "1.0.0",
+ "php-parallel-lint/php-parallel-lint": "1.4.0"
+ },
+ "scripts": {
+ "lint": [
+ "phan -d . --long-progress-bar",
+ "parallel-lint . --exclude vendor --exclude node_modules",
+ "phpcs -p -s"
+ ],
+ "fix": [
+ "phpcbf"
+ ]
+ },
+ "config": {
+ "allow-plugins": {
+ "dealerdirect/phpcodesniffer-composer-installer": true
+ }
+ }
+}
diff --git a/pkgs/mediawiki-fastly-purge/composer.lock b/pkgs/mediawiki-fastly-purge/composer.lock
new file mode 100644
index 0000000..9583233
--- /dev/null
+++ b/pkgs/mediawiki-fastly-purge/composer.lock
@@ -0,0 +1,2550 @@
+{
+ "_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": "efc47af4329e6c2178c71e5cf62ce4df",
+ "packages": [],
+ "packages-dev": [
+ {
+ "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.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/semver.git",
+ "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95",
+ "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95",
+ "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.4"
+ },
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ }
+ ],
+ "time": "2025-08-20T19:15:30+00:00"
+ },
+ {
+ "name": "composer/spdx-licenses",
+ "version": "1.5.9",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/spdx-licenses.git",
+ "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/edf364cefe8c43501e21e88110aac10b284c3c9f",
+ "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f",
+ "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": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\Spdx\\": "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": "SPDX licenses list and validation library.",
+ "keywords": [
+ "license",
+ "spdx",
+ "validator"
+ ],
+ "support": {
+ "irc": "ircs://irc.libera.chat:6697/composer",
+ "issues": "https://github.com/composer/spdx-licenses/issues",
+ "source": "https://github.com/composer/spdx-licenses/tree/1.5.9"
+ },
+ "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": "2025-05-12T21:07:07+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": "dealerdirect/phpcodesniffer-composer-installer",
+ "version": "v1.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCSStandards/composer-installer.git",
+ "reference": "e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1",
+ "reference": "e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^2.2",
+ "php": ">=5.4",
+ "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0"
+ },
+ "require-dev": {
+ "composer/composer": "^2.2",
+ "ext-json": "*",
+ "ext-zip": "*",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
+ "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": "opensource@frenck.dev",
+ "homepage": "https://frenck.dev",
+ "role": "Open source developer"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors"
+ }
+ ],
+ "description": "PHP_CodeSniffer Standards Composer Installer Plugin",
+ "keywords": [
+ "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",
+ "security": "https://github.com/PHPCSStandards/composer-installer/security/policy",
+ "source": "https://github.com/PHPCSStandards/composer-installer"
+ },
+ "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-07-17T20:45:56+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/advanced-json-rpc",
+ "version": "v3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git",
+ "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447",
+ "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447",
+ "shasum": ""
+ },
+ "require": {
+ "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0",
+ "php": "^7.1 || ^8.0",
+ "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.0 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "AdvancedJsonRpc\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "ISC"
+ ],
+ "authors": [
+ {
+ "name": "Felix Becker",
+ "email": "felix.b@outlook.com"
+ }
+ ],
+ "description": "A more advanced JSONRPC implementation",
+ "support": {
+ "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues",
+ "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1"
+ },
+ "time": "2021-06-11T22:34:44+00:00"
+ },
+ {
+ "name": "mediawiki/mediawiki-codesniffer",
+ "version": "v48.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/wikimedia/mediawiki-tools-codesniffer.git",
+ "reference": "6d46ca2334d5e1c5be10bf28e01f6010cfbff212"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/wikimedia/mediawiki-tools-codesniffer/zipball/6d46ca2334d5e1c5be10bf28e01f6010cfbff212",
+ "reference": "6d46ca2334d5e1c5be10bf28e01f6010cfbff212",
+ "shasum": ""
+ },
+ "require": {
+ "composer/semver": "^3.4.2",
+ "composer/spdx-licenses": "~1.5.2",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "php": ">=8.1.0",
+ "phpcsstandards/phpcsextra": "1.4.0",
+ "squizlabs/php_codesniffer": "3.13.2"
+ },
+ "require-dev": {
+ "ext-dom": "*",
+ "mediawiki/mediawiki-phan-config": "0.17.0",
+ "mediawiki/minus-x": "1.1.3",
+ "php-parallel-lint/php-console-highlighter": "1.0.0",
+ "php-parallel-lint/php-parallel-lint": "1.4.0",
+ "phpunit/phpunit": "9.6.21"
+ },
+ "type": "phpcodesniffer-standard",
+ "autoload": {
+ "psr-4": {
+ "MediaWiki\\Sniffs\\": "MediaWiki/Sniffs/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "description": "MediaWiki CodeSniffer Standards",
+ "homepage": "https://www.mediawiki.org/wiki/Manual:Coding_conventions/PHP",
+ "keywords": [
+ "codesniffer",
+ "mediawiki"
+ ],
+ "support": {
+ "source": "https://github.com/wikimedia/mediawiki-tools-codesniffer/tree/v48.0.0"
+ },
+ "time": "2025-09-04T20:12:57+00:00"
+ },
+ {
+ "name": "mediawiki/mediawiki-phan-config",
+ "version": "0.17.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/wikimedia/mediawiki-tools-phan.git",
+ "reference": "5e98f0ae2755650c6bebb682c0fcc06b371745c9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/wikimedia/mediawiki-tools-phan/zipball/5e98f0ae2755650c6bebb682c0fcc06b371745c9",
+ "reference": "5e98f0ae2755650c6bebb682c0fcc06b371745c9",
+ "shasum": ""
+ },
+ "require": {
+ "mediawiki/phan-taint-check-plugin": "7.0.0",
+ "phan/phan": "5.5.1",
+ "php": ">=8.1.0"
+ },
+ "require-dev": {
+ "mediawiki/mediawiki-codesniffer": "47.0.0",
+ "mediawiki/minus-x": "1.1.3",
+ "ockcyp/covers-validator": "1.6.0",
+ "php-parallel-lint/php-console-highlighter": "1.0.0",
+ "php-parallel-lint/php-parallel-lint": "1.4.0",
+ "phpunit/phpunit": "9.6.21"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "MediaWikiPhanConfig\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "MediaWiki developers",
+ "email": "wikitech-l@lists.wikimedia.org"
+ }
+ ],
+ "description": "Standard MediaWiki phan configuration",
+ "homepage": "https://www.mediawiki.org/wiki/Continuous_integration/Phan",
+ "support": {
+ "source": "https://github.com/wikimedia/mediawiki-tools-phan/tree/0.17.0"
+ },
+ "time": "2025-08-07T13:01:24+00:00"
+ },
+ {
+ "name": "mediawiki/phan-taint-check-plugin",
+ "version": "7.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/wikimedia/mediawiki-tools-phan-SecurityCheckPlugin.git",
+ "reference": "1d70b8bdbc0fcc048f204a86e07a8ac2fa324c55"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/wikimedia/mediawiki-tools-phan-SecurityCheckPlugin/zipball/1d70b8bdbc0fcc048f204a86e07a8ac2fa324c55",
+ "reference": "1d70b8bdbc0fcc048f204a86e07a8ac2fa324c55",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "phan/phan": "5.5.1",
+ "php": ">=8.1.0"
+ },
+ "require-dev": {
+ "ext-pdo": "*",
+ "mediawiki/mediawiki-codesniffer": "47.0.0",
+ "mediawiki/minus-x": "1.1.3",
+ "php-parallel-lint/php-console-highlighter": "1.0.0",
+ "php-parallel-lint/php-parallel-lint": "1.4.0",
+ "phpunit/phpunit": "9.6.21"
+ },
+ "suggest": {
+ "ext-mysqli": "Used for some mysqli function tests"
+ },
+ "bin": [
+ "scripts/seccheck"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "SecurityCheckPlugin\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Brian Wolff",
+ "email": "bawolff+wn@gmail.com"
+ },
+ {
+ "name": "Daimona Eaytoy",
+ "email": "daimona.wiki@gmail.com"
+ }
+ ],
+ "description": "A Phan plugin to do security checking",
+ "keywords": [
+ "analyzer",
+ "phan",
+ "php",
+ "security",
+ "static",
+ "taint"
+ ],
+ "support": {
+ "irc": "irc://irc.libera.chat/wikimedia-dev",
+ "issues": "https://phabricator.wikimedia.org/maniphest/task/edit/form/1/?projects=securitycheckplugin",
+ "source": "https://phabricator.wikimedia.org/diffusion/MTPS/",
+ "wiki": "https://www.mediawiki.org/wiki/SecurityCheckPlugin"
+ },
+ "time": "2025-08-07T00:01:54+00:00"
+ },
+ {
+ "name": "microsoft/tolerant-php-parser",
+ "version": "v0.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/microsoft/tolerant-php-parser.git",
+ "reference": "3eccfd273323aaf69513e2f1c888393f5947804b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/microsoft/tolerant-php-parser/zipball/3eccfd273323aaf69513e2f1c888393f5947804b",
+ "reference": "3eccfd273323aaf69513e2f1c888393f5947804b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5.15"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Microsoft\\PhpParser\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Rob Lourens",
+ "email": "roblou@microsoft.com"
+ }
+ ],
+ "description": "Tolerant PHP-to-AST parser designed for IDE usage scenarios",
+ "support": {
+ "issues": "https://github.com/microsoft/tolerant-php-parser/issues",
+ "source": "https://github.com/microsoft/tolerant-php-parser/tree/v0.1.2"
+ },
+ "time": "2022-10-05T17:30:19+00:00"
+ },
+ {
+ "name": "netresearch/jsonmapper",
+ "version": "v4.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/cweiske/jsonmapper.git",
+ "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8e76efb98ee8b6afc54687045e1b8dba55ac76e5",
+ "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5",
+ "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/v4.5.0"
+ },
+ "time": "2024-09-08T10:13:13+00:00"
+ },
+ {
+ "name": "phan/phan",
+ "version": "5.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phan/phan.git",
+ "reference": "2b6a846eff1a65dd0229ffa2370b4c35a96b7f3c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phan/phan/zipball/2b6a846eff1a65dd0229ffa2370b4c35a96b7f3c",
+ "reference": "2b6a846eff1a65dd0229ffa2370b4c35a96b7f3c",
+ "shasum": ""
+ },
+ "require": {
+ "composer/semver": "^1.4|^2.0|^3.0",
+ "composer/xdebug-handler": "^2.0|^3.0",
+ "ext-filter": "*",
+ "ext-json": "*",
+ "ext-tokenizer": "*",
+ "felixfbecker/advanced-json-rpc": "^3.0.4",
+ "microsoft/tolerant-php-parser": "0.1.2",
+ "netresearch/jsonmapper": "^1.6.0|^2.0|^3.0|^4.0|^5.0",
+ "php": "^7.2.0|^8.0.0",
+ "sabre/event": "^5.1.3",
+ "symfony/console": "^3.2|^4.0|^5.0|^6.0|^7.0",
+ "symfony/polyfill-mbstring": "^1.11.0",
+ "symfony/polyfill-php80": "^1.20.0",
+ "tysonandre/var_representation_polyfill": "^0.0.2|^0.1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5.0"
+ },
+ "suggest": {
+ "ext-ast": "Needed for parsing ASTs (unless --use-fallback-parser is used). 1.0.1+ is needed, 1.0.16+ is recommended.",
+ "ext-iconv": "Either iconv or mbstring is needed to ensure issue messages are valid utf-8",
+ "ext-igbinary": "Improves performance of polyfill when ext-ast is unavailable",
+ "ext-mbstring": "Either iconv or mbstring is needed to ensure issue messages are valid utf-8",
+ "ext-tokenizer": "Needed for fallback/polyfill parser support and file/line-based suppressions.",
+ "ext-var_representation": "Suggested for converting values to strings in issue messages"
+ },
+ "bin": [
+ "phan",
+ "phan_client",
+ "tocheckstyle"
+ ],
+ "type": "project",
+ "autoload": {
+ "psr-4": {
+ "Phan\\": "src/Phan"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Tyson Andre"
+ },
+ {
+ "name": "Rasmus Lerdorf"
+ },
+ {
+ "name": "Andrew S. Morrison"
+ }
+ ],
+ "description": "A static analyzer for PHP",
+ "keywords": [
+ "analyzer",
+ "php",
+ "static",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/phan/phan/issues",
+ "source": "https://github.com/phan/phan/tree/5.5.1"
+ },
+ "time": "2025-08-05T20:10:06+00:00"
+ },
+ {
+ "name": "php-parallel-lint/php-console-color",
+ "version": "v1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-parallel-lint/PHP-Console-Color.git",
+ "reference": "7adfefd530aa2d7570ba87100a99e2483a543b88"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-parallel-lint/PHP-Console-Color/zipball/7adfefd530aa2d7570ba87100a99e2483a543b88",
+ "reference": "7adfefd530aa2d7570ba87100a99e2483a543b88",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "replace": {
+ "jakub-onderka/php-console-color": "*"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-code-style": "^2.0",
+ "php-parallel-lint/php-parallel-lint": "^1.0",
+ "php-parallel-lint/php-var-dump-check": "0.*",
+ "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PHP_Parallel_Lint\\PhpConsoleColor\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-2-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jakub Onderka",
+ "email": "jakub.onderka@gmail.com"
+ }
+ ],
+ "description": "Simple library for creating colored console ouput.",
+ "support": {
+ "issues": "https://github.com/php-parallel-lint/PHP-Console-Color/issues",
+ "source": "https://github.com/php-parallel-lint/PHP-Console-Color/tree/v1.0.1"
+ },
+ "time": "2021-12-25T06:49:29+00:00"
+ },
+ {
+ "name": "php-parallel-lint/php-console-highlighter",
+ "version": "v1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-parallel-lint/PHP-Console-Highlighter.git",
+ "reference": "5b4803384d3303cf8e84141039ef56c8a123138d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-parallel-lint/PHP-Console-Highlighter/zipball/5b4803384d3303cf8e84141039ef56c8a123138d",
+ "reference": "5b4803384d3303cf8e84141039ef56c8a123138d",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": ">=5.3.2",
+ "php-parallel-lint/php-console-color": "^1.0.1"
+ },
+ "replace": {
+ "jakub-onderka/php-console-highlighter": "*"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-code-style": "^2.0",
+ "php-parallel-lint/php-parallel-lint": "^1.0",
+ "php-parallel-lint/php-var-dump-check": "0.*",
+ "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PHP_Parallel_Lint\\PhpConsoleHighlighter\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jakub Onderka",
+ "email": "acci@acci.cz",
+ "homepage": "http://www.acci.cz/"
+ }
+ ],
+ "description": "Highlight PHP code in terminal",
+ "support": {
+ "issues": "https://github.com/php-parallel-lint/PHP-Console-Highlighter/issues",
+ "source": "https://github.com/php-parallel-lint/PHP-Console-Highlighter/tree/v1.0.0"
+ },
+ "time": "2022-02-18T08:23:19+00:00"
+ },
+ {
+ "name": "php-parallel-lint/php-parallel-lint",
+ "version": "v1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-parallel-lint/PHP-Parallel-Lint.git",
+ "reference": "6db563514f27e19595a19f45a4bf757b6401194e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/6db563514f27e19595a19f45a4bf757b6401194e",
+ "reference": "6db563514f27e19595a19f45a4bf757b6401194e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "php": ">=5.3.0"
+ },
+ "replace": {
+ "grogy/php-parallel-lint": "*",
+ "jakub-onderka/php-parallel-lint": "*"
+ },
+ "require-dev": {
+ "nette/tester": "^1.3 || ^2.0",
+ "php-parallel-lint/php-console-highlighter": "0.* || ^1.0",
+ "squizlabs/php_codesniffer": "^3.6"
+ },
+ "suggest": {
+ "php-parallel-lint/php-console-highlighter": "Highlight syntax in code snippet"
+ },
+ "bin": [
+ "parallel-lint"
+ ],
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "./src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-2-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jakub Onderka",
+ "email": "ahoj@jakubonderka.cz"
+ }
+ ],
+ "description": "This tool checks the syntax of PHP files about 20x faster than serial check.",
+ "homepage": "https://github.com/php-parallel-lint/PHP-Parallel-Lint",
+ "keywords": [
+ "lint",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/issues",
+ "source": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/tree/v1.4.0"
+ },
+ "time": "2024-03-27T12:14:49+00:00"
+ },
+ {
+ "name": "phpcsstandards/phpcsextra",
+ "version": "1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCSStandards/PHPCSExtra.git",
+ "reference": "fa4b8d051e278072928e32d817456a7fdb57b6ca"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/fa4b8d051e278072928e32d817456a7fdb57b6ca",
+ "reference": "fa4b8d051e278072928e32d817456a7fdb57b6ca",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4",
+ "phpcsstandards/phpcsutils": "^1.1.0",
+ "squizlabs/php_codesniffer": "^3.13.0 || ^4.0"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-console-highlighter": "^1.0",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
+ "phpcsstandards/phpcsdevcs": "^1.1.6",
+ "phpcsstandards/phpcsdevtools": "^1.2.1",
+ "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4"
+ },
+ "type": "phpcodesniffer-standard",
+ "extra": {
+ "branch-alias": {
+ "dev-stable": "1.x-dev",
+ "dev-develop": "1.x-dev"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Juliette Reinders Folmer",
+ "homepage": "https://github.com/jrfnl",
+ "role": "lead"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors"
+ }
+ ],
+ "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.",
+ "keywords": [
+ "PHP_CodeSniffer",
+ "phpcbf",
+ "phpcodesniffer-standard",
+ "phpcs",
+ "standards",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues",
+ "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy",
+ "source": "https://github.com/PHPCSStandards/PHPCSExtra"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/PHPCSStandards",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/jrfnl",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "open_collective"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/phpcsstandards",
+ "type": "thanks_dev"
+ }
+ ],
+ "time": "2025-06-14T07:40:39+00:00"
+ },
+ {
+ "name": "phpcsstandards/phpcsutils",
+ "version": "1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCSStandards/PHPCSUtils.git",
+ "reference": "f7eb16f2fa4237d5db9e8fed8050239bee17a9bd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/f7eb16f2fa4237d5db9e8fed8050239bee17a9bd",
+ "reference": "f7eb16f2fa4237d5db9e8fed8050239bee17a9bd",
+ "shasum": ""
+ },
+ "require": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0",
+ "php": ">=5.4",
+ "squizlabs/php_codesniffer": "^3.13.0 || ^4.0"
+ },
+ "require-dev": {
+ "ext-filter": "*",
+ "php-parallel-lint/php-console-highlighter": "^1.0",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
+ "phpcsstandards/phpcsdevcs": "^1.1.6",
+ "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0 || ^3.0.0"
+ },
+ "type": "phpcodesniffer-standard",
+ "extra": {
+ "branch-alias": {
+ "dev-stable": "1.x-dev",
+ "dev-develop": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "PHPCSUtils/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Juliette Reinders Folmer",
+ "homepage": "https://github.com/jrfnl",
+ "role": "lead"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors"
+ }
+ ],
+ "description": "A suite of utility functions for use with PHP_CodeSniffer",
+ "homepage": "https://phpcsutils.com/",
+ "keywords": [
+ "PHP_CodeSniffer",
+ "phpcbf",
+ "phpcodesniffer-standard",
+ "phpcs",
+ "phpcs3",
+ "phpcs4",
+ "standards",
+ "static analysis",
+ "tokens",
+ "utility"
+ ],
+ "support": {
+ "docs": "https://phpcsutils.com/",
+ "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues",
+ "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy",
+ "source": "https://github.com/PHPCSStandards/PHPCSUtils"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/PHPCSStandards",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/jrfnl",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "open_collective"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/phpcsstandards",
+ "type": "thanks_dev"
+ }
+ ],
+ "time": "2025-08-10T01:04:45+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.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94f8051919d1b0369a6bcc7931d679a511c03fe9",
+ "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9",
+ "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.3"
+ },
+ "time": "2025-08-01T19:43:32+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.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpdoc-parser.git",
+ "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1e0cd5370df5dd2e556a36b9c62f62e555870495",
+ "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4 || ^8.0"
+ },
+ "require-dev": {
+ "doctrine/annotations": "^2.0",
+ "nikic/php-parser": "^5.3.0",
+ "php-parallel-lint/php-parallel-lint": "^1.2",
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpstan/phpstan-strict-rules": "^2.0",
+ "phpunit/phpunit": "^9.6",
+ "symfony/process": "^5.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\PhpDocParser\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPDoc parser with support for nullable, intersection and generic types",
+ "support": {
+ "issues": "https://github.com/phpstan/phpdoc-parser/issues",
+ "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.0"
+ },
+ "time": "2025-08-30T15:50:23+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/container/issues",
+ "source": "https://github.com/php-fig/container/tree/2.0.2"
+ },
+ "time": "2021-11-05T16:47:00+00:00"
+ },
+ {
+ "name": "psr/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": "sabre/event",
+ "version": "5.1.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sabre-io/event.git",
+ "reference": "86d57e305c272898ba3c28e9bd3d65d5464587c2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sabre-io/event/zipball/86d57e305c272898ba3c28e9bd3d65d5464587c2",
+ "reference": "86d57e305c272898ba3c28e9bd3d65d5464587c2",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.17.1||^3.63",
+ "phpstan/phpstan": "^0.12",
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "lib/coroutine.php",
+ "lib/Loop/functions.php",
+ "lib/Promise/functions.php"
+ ],
+ "psr-4": {
+ "Sabre\\Event\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Evert Pot",
+ "email": "me@evertpot.com",
+ "homepage": "http://evertpot.com/",
+ "role": "Developer"
+ }
+ ],
+ "description": "sabre/event is a library for lightweight event-based programming",
+ "homepage": "http://sabre.io/event/",
+ "keywords": [
+ "EventEmitter",
+ "async",
+ "coroutine",
+ "eventloop",
+ "events",
+ "hooks",
+ "plugin",
+ "promise",
+ "reactor",
+ "signal"
+ ],
+ "support": {
+ "forum": "https://groups.google.com/group/sabredav-discuss",
+ "issues": "https://github.com/sabre-io/event/issues",
+ "source": "https://github.com/fruux/sabre-event"
+ },
+ "time": "2024-08-27T11:23:05+00:00"
+ },
+ {
+ "name": "squizlabs/php_codesniffer",
+ "version": "3.13.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
+ "reference": "5b5e3821314f947dd040c70f7992a64eac89025c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5b5e3821314f947dd040c70f7992a64eac89025c",
+ "reference": "5b5e3821314f947dd040c70f7992a64eac89025c",
+ "shasum": ""
+ },
+ "require": {
+ "ext-simplexml": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4"
+ },
+ "bin": [
+ "bin/phpcbf",
+ "bin/phpcs"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Greg Sherwood",
+ "role": "Former lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "role": "Current lead"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors"
+ }
+ ],
+ "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
+ "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
+ "keywords": [
+ "phpcs",
+ "standards",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues",
+ "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy",
+ "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
+ "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/PHPCSStandards",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/jrfnl",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "open_collective"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/phpcsstandards",
+ "type": "thanks_dev"
+ }
+ ],
+ "time": "2025-06-17T22:17:01+00:00"
+ },
+ {
+ "name": "symfony/console",
+ "version": "v7.3.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
+ "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/string": "^7.2"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<6.4",
+ "symfony/dotenv": "<6.4",
+ "symfony/event-dispatcher": "<6.4",
+ "symfony/lock": "<6.4",
+ "symfony/process": "<6.4"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0|2.0|3.0"
+ },
+ "require-dev": {
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^6.4|^7.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/event-dispatcher": "^6.4|^7.0",
+ "symfony/http-foundation": "^6.4|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/lock": "^6.4|^7.0",
+ "symfony/messenger": "^6.4|^7.0",
+ "symfony/process": "^6.4|^7.0",
+ "symfony/stopwatch": "^6.4|^7.0",
+ "symfony/var-dumper": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Eases the creation of beautiful and testable command line interfaces",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "cli",
+ "command-line",
+ "console",
+ "terminal"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/console/tree/v7.3.3"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-25T06:35:40+00:00"
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v3.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-25T14:21:43+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.33.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.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-grapheme",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
+ "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70",
+ "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70",
+ "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.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-06-27T09:58:17+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-normalizer",
+ "version": "v1.33.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.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.33.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.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-12-23T08:48:59+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php80",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
+ "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
+ "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\\Php80\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-01-02T08:10:11+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v3.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
+ "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/container": "^1.1|^2.0",
+ "symfony/deprecation-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to writing services",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/service-contracts/tree/v3.6.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-04-25T09:37:31+00:00"
+ },
+ {
+ "name": "symfony/string",
+ "version": "v7.3.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/string.git",
+ "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
+ "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-intl-grapheme": "~1.0",
+ "symfony/polyfill-intl-normalizer": "~1.0",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "conflict": {
+ "symfony/translation-contracts": "<2.5"
+ },
+ "require-dev": {
+ "symfony/emoji": "^7.1",
+ "symfony/error-handler": "^6.4|^7.0",
+ "symfony/http-client": "^6.4|^7.0",
+ "symfony/intl": "^6.4|^7.0",
+ "symfony/translation-contracts": "^2.5|^3.0",
+ "symfony/var-exporter": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Resources/functions.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\String\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "grapheme",
+ "i18n",
+ "string",
+ "unicode",
+ "utf-8",
+ "utf8"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/string/tree/v7.3.3"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-25T06:35:40+00:00"
+ },
+ {
+ "name": "tysonandre/var_representation_polyfill",
+ "version": "0.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/TysonAndre/var_representation_polyfill.git",
+ "reference": "e9116c2c352bb0835ca428b442dde7767c11ad32"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/TysonAndre/var_representation_polyfill/zipball/e9116c2c352bb0835ca428b442dde7767c11ad32",
+ "reference": "e9116c2c352bb0835ca428b442dde7767c11ad32",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": "^7.2.0|^8.0.0"
+ },
+ "provide": {
+ "ext-var_representation": "*"
+ },
+ "require-dev": {
+ "phan/phan": "^5.4.1",
+ "phpunit/phpunit": "^8.5.0"
+ },
+ "suggest": {
+ "ext-var_representation": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "0.1.3-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/var_representation.php"
+ ],
+ "psr-4": {
+ "VarRepresentation\\": "src/VarRepresentation"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Tyson Andre"
+ }
+ ],
+ "description": "Polyfill for var_representation: convert a variable to a string in a way that fixes the shortcomings of var_export",
+ "keywords": [
+ "var_export",
+ "var_representation"
+ ],
+ "support": {
+ "issues": "https://github.com/TysonAndre/var_representation_polyfill/issues",
+ "source": "https://github.com/TysonAndre/var_representation_polyfill/tree/0.1.3"
+ },
+ "time": "2022-08-31T12:59:22+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": "stable",
+ "stability-flags": {},
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=8.2"
+ },
+ "platform-dev": {},
+ "plugin-api-version": "2.6.0"
+}
diff --git a/pkgs/mediawiki-fastly-purge/extension.json b/pkgs/mediawiki-fastly-purge/extension.json
new file mode 100644
index 0000000..db15914
--- /dev/null
+++ b/pkgs/mediawiki-fastly-purge/extension.json
@@ -0,0 +1,39 @@
+{
+ "name": "FastlyPurge",
+ "version": "1.0.0",
+ "author": [
+ "NixOS Wiki Contributors"
+ ],
+ "url": "https://github.com/NixOS/nixos-wiki-infra",
+ "descriptionmsg": "fastlypurge-desc",
+ "license-name": "MIT",
+ "type": "other",
+ "requires": {
+ "MediaWiki": ">= 1.39.0"
+ },
+ "config": {
+ "FastlyApiKey": {
+ "value": "",
+ "description": "Fastly API key for authentication"
+ },
+ "FastlyServiceId": {
+ "value": "",
+ "description": "Fastly service ID"
+ }
+ },
+ "MessagesDirs": {
+ "FastlyPurge": [
+ "i18n"
+ ]
+ },
+ "AutoloadNamespaces": {
+ "MediaWiki\\Extension\\FastlyPurge\\": "includes/"
+ },
+ "ServiceWiringFiles": [
+ "includes/ServiceWiring.php"
+ ],
+ "Hooks": {
+ "SetupAfterCache": "MediaWiki\\Extension\\FastlyPurge\\Hooks::onSetupAfterCache"
+ },
+ "manifest_version": 2
+}
diff --git a/pkgs/mediawiki-fastly-purge/flake-module.nix b/pkgs/mediawiki-fastly-purge/flake-module.nix
new file mode 100644
index 0000000..99c6d7f
--- /dev/null
+++ b/pkgs/mediawiki-fastly-purge/flake-module.nix
@@ -0,0 +1,38 @@
+{
+ perSystem =
+ { pkgs, ... }:
+ {
+ packages.fastly-purge = pkgs.callPackage ./package.nix { };
+
+ checks.fastly-purge-lint = pkgs.callPackage ./lint.nix { };
+
+ devShells.fastly-purge = pkgs.mkShell {
+ packages = [
+ pkgs.php83
+ pkgs.php83.packages.composer
+ pkgs.mediawiki
+ ];
+
+ MEDIAWIKI_PATH = "${pkgs.mediawiki}/share/mediawiki";
+
+ shellHook = ''
+ echo "FastlyPurge Extension Development Shell"
+ echo "========================================"
+ echo ""
+ echo "Available commands:"
+ echo " composer lint"
+ echo " composer fix"
+ echo ""
+ echo "PHP version: $(php --version | head -n1)"
+ echo "MediaWiki path: $MEDIAWIKI_PATH"
+ echo ""
+
+ # Create vendor directory if it doesn't exist
+ if [ ! -d "vendor" ]; then
+ echo "Installing composer dependencies..."
+ composer install --no-interaction
+ fi
+ '';
+ };
+ };
+}
diff --git a/pkgs/mediawiki-fastly-purge/i18n/en.json b/pkgs/mediawiki-fastly-purge/i18n/en.json
new file mode 100644
index 0000000..4b60b05
--- /dev/null
+++ b/pkgs/mediawiki-fastly-purge/i18n/en.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "NixOS Wiki Contributors"
+ ]
+ },
+ "fastlypurge-desc": "Automatically purges Fastly CDN cache when pages are modified using the EventRelayer system for comprehensive cache invalidation"
+}
diff --git a/pkgs/mediawiki-fastly-purge/includes/FastlyApiClient.php b/pkgs/mediawiki-fastly-purge/includes/FastlyApiClient.php
new file mode 100644
index 0000000..97b1bf1
--- /dev/null
+++ b/pkgs/mediawiki-fastly-purge/includes/FastlyApiClient.php
@@ -0,0 +1,157 @@
+serviceId . '/purge';
+
+ // Prepare request data
+ $data = [
+ 'urls' => $urls
+ ];
+
+ $json = json_encode( $data );
+ if ( $json === false ) {
+ $this->logger->error( 'Failed to encode purge request data' );
+ return false;
+ }
+
+ $this->logger->debug( 'Sending purge request to Fastly', [
+ 'endpoint' => $endpoint,
+ 'url_count' => count( $urls ),
+ // Log first 5 URLs for debugging
+ 'urls' => array_slice( $urls, 0, 5 )
+ ] );
+
+ try {
+ $req = $this->createRequest( $endpoint, [
+ 'method' => 'POST',
+ 'timeout' => 10,
+ 'postData' => $json,
+ 'headers' => [
+ 'Content-Type' => 'application/json',
+ // Always use soft purge to protect origin
+ 'Fastly-Soft-Purge' => '1'
+ ]
+ ] );
+ $status = $req->execute();
+
+ if ( !$status->isOK() ) {
+ $this->logger->error( 'Fastly purge request failed', [
+ 'status' => $req->getStatus(),
+ 'response' => substr( $req->getContent(), 0, 500 )
+ ] );
+ return false;
+ }
+
+ $responseData = json_decode( $req->getContent(), true );
+
+ $this->logger->debug( 'Fastly purge response', [
+ 'response' => $responseData
+ ] );
+
+ // Check if all URLs were successfully queued
+ if ( isset( $responseData['status'] ) && $responseData['status'] === 'ok' ) {
+ $this->logger->info( 'Successfully purged ' . count( $urls ) . ' URLs from Fastly' );
+ return true;
+ }
+
+ // Log error for unexpected response
+ $this->logger->error( 'Unexpected response from Fastly purge', [
+ 'response' => $responseData
+ ] );
+ return false;
+
+ } catch ( \Exception $e ) {
+ $this->logger->error( 'Exception during Fastly API call', [
+ 'exception' => $e->getMessage()
+ ] );
+ return false;
+ }
+ }
+
+ /**
+ * Purge all content from Fastly cache
+ */
+ public function purgeAll(): bool {
+ $endpoint = self::API_BASE_URL . '/service/' . $this->serviceId . '/purge_all';
+
+ $this->logger->debug( 'Sending purge_all request to Fastly' );
+
+ try {
+ $req = $this->createRequest( $endpoint, [
+ 'method' => 'POST',
+ 'timeout' => 10,
+ 'headers' => [
+ 'Fastly-Soft-Purge' => '1'
+ ]
+ ] );
+ $status = $req->execute();
+
+ if ( !$status->isOK() ) {
+ $this->logger->error( 'Fastly purge_all request failed', [
+ 'status' => $req->getStatus(),
+ 'response' => substr( $req->getContent(), 0, 500 )
+ ] );
+ return false;
+ }
+
+ $responseData = json_decode( $req->getContent(), true );
+
+ if ( isset( $responseData['status'] ) && $responseData['status'] === 'ok' ) {
+ $this->logger->info( 'Successfully purged all content from Fastly' );
+ return true;
+ }
+
+ $this->logger->error( 'Unexpected response from Fastly purge_all', [
+ 'response' => $responseData
+ ] );
+ return false;
+
+ } catch ( \Exception $e ) {
+ $this->logger->error( 'Exception during Fastly purge_all', [
+ 'exception' => $e->getMessage()
+ ] );
+ return false;
+ }
+ }
+
+ /**
+ * Create HTTP request with standard Fastly headers
+ * @param string $endpoint API endpoint URL
+ * @param array $options Request options
+ */
+ private function createRequest( string $endpoint, array $options ): \MWHttpRequest {
+ // Ensure Fastly headers are set
+ $options['headers'] ??= [];
+ $options['headers']['Fastly-Key'] = $this->apiKey;
+ $options['headers']['Accept'] = 'application/json';
+
+ return $this->httpFactory->create( $endpoint, $options, __METHOD__ );
+ }
+}
diff --git a/pkgs/mediawiki-fastly-purge/includes/FastlyConfig.php b/pkgs/mediawiki-fastly-purge/includes/FastlyConfig.php
new file mode 100644
index 0000000..9a0fcb5
--- /dev/null
+++ b/pkgs/mediawiki-fastly-purge/includes/FastlyConfig.php
@@ -0,0 +1,55 @@
+apiKey = (string)$config->get( 'FastlyApiKey' );
+ $this->serviceId = (string)$config->get( 'FastlyServiceId' );
+ }
+
+ /**
+ * Get the Fastly API key
+ */
+ public function getApiKey(): string {
+ return $this->apiKey;
+ }
+
+ /**
+ * Get the Fastly service ID
+ */
+ public function getServiceId(): string {
+ return $this->serviceId;
+ }
+
+ /**
+ * Check if the configuration is valid
+ */
+ public function isValid(): bool {
+ return $this->apiKey !== '' && $this->serviceId !== '';
+ }
+
+ /**
+ * Get validation errors
+ * @return string[]
+ */
+ public function getValidationErrors(): array {
+ $errors = [];
+
+ if ( $this->apiKey === '' ) {
+ $errors[] = 'FastlyApiKey is not configured';
+ }
+
+ if ( $this->serviceId === '' ) {
+ $errors[] = 'FastlyServiceId is not configured';
+ }
+
+ return $errors;
+ }
+}
diff --git a/pkgs/mediawiki-fastly-purge/includes/FastlyEventRelayer.php b/pkgs/mediawiki-fastly-purge/includes/FastlyEventRelayer.php
new file mode 100644
index 0000000..8585c7a
--- /dev/null
+++ b/pkgs/mediawiki-fastly-purge/includes/FastlyEventRelayer.php
@@ -0,0 +1,107 @@
+ $params Configuration parameters
+ * @param FastlyApiClient $apiClient
+ * @param LoggerInterface $fastlyLogger
+ */
+ public function __construct(
+ array $params,
+ private readonly FastlyApiClient $apiClient,
+ private readonly LoggerInterface $fastlyLogger
+ ) {
+ parent::__construct( $params );
+ $this->lastFlushTime = microtime( true );
+
+ // Register shutdown function to flush any remaining URLs
+ register_shutdown_function( [ $this, 'flushBatch' ] );
+ }
+
+ /**
+ * @param string $channel
+ * @param array> $events
+ */
+ protected function doNotify( $channel, array $events ): bool {
+ // Only handle CDN URL purge events
+ if ( $channel !== 'cdn-url-purges' ) {
+ return true;
+ }
+
+ $urls = [];
+ foreach ( $events as $event ) {
+ if ( isset( $event['url'] ) && is_string( $event['url'] ) ) {
+ // MediaWiki guarantees full URLs are passed to CdnCacheUpdate::purge()
+ $urls[] = $event['url'];
+ }
+ }
+
+ if ( !$urls ) {
+ return true;
+ }
+
+ // Add URLs to batch queue
+ $this->batchQueue = array_merge( $this->batchQueue, $urls );
+
+ // Remove duplicates
+ $this->batchQueue = array_values( array_unique( $this->batchQueue ) );
+
+ // Flush if batch size reached or if enough time has passed
+ if ( count( $this->batchQueue ) >= self::BATCH_SIZE ||
+ ( microtime( true ) - $this->lastFlushTime ) > 0.5 ) {
+ return $this->flushBatch();
+ }
+
+ return true;
+ }
+
+ /**
+ * Flush the current batch of URLs to Fastly
+ */
+ public function flushBatch(): bool {
+ if ( !$this->batchQueue ) {
+ return true;
+ }
+
+ // Process URLs in batches of BATCH_SIZE
+ $success = true;
+ $chunks = array_chunk( $this->batchQueue, self::BATCH_SIZE );
+
+ foreach ( $chunks as $batch ) {
+ try {
+ $this->fastlyLogger->debug( 'Purging batch of ' . count( $batch ) . ' URLs from Fastly' );
+
+ if ( !$this->apiClient->purgeUrls( $batch ) ) {
+ $this->fastlyLogger->error( 'Failed to purge URLs from Fastly', [
+ 'url_count' => count( $batch )
+ ] );
+ $success = false;
+ }
+ } catch ( \Exception $e ) {
+ $this->fastlyLogger->error( 'Exception during Fastly purge', [
+ 'exception' => $e->getMessage(),
+ 'url_count' => count( $batch )
+ ] );
+ $success = false;
+ }
+ }
+
+ // Clear the batch queue
+ $this->batchQueue = [];
+ $this->lastFlushTime = microtime( true );
+
+ return $success;
+ }
+}
diff --git a/pkgs/mediawiki-fastly-purge/includes/Hooks.php b/pkgs/mediawiki-fastly-purge/includes/Hooks.php
new file mode 100644
index 0000000..8063e4d
--- /dev/null
+++ b/pkgs/mediawiki-fastly-purge/includes/Hooks.php
@@ -0,0 +1,44 @@
+getMainConfig() );
+
+ if ( !$config->isValid() ) {
+ $logger = LoggerFactory::getInstance( 'FastlyPurge' );
+ foreach ( $config->getValidationErrors() as $error ) {
+ $logger->warning( $error );
+ }
+ return;
+ }
+
+ // Configure the EventRelayer to use our custom class
+ $wgEventRelayerConfig['cdn-url-purges'] = [
+ 'class' => FastlyEventRelayer::class,
+ ];
+
+ } catch ( \Exception $e ) {
+ LoggerFactory::getInstance( 'FastlyPurge' )->error(
+ 'Failed to initialize FastlyPurge extension',
+ [ 'exception' => $e->getMessage() ]
+ );
+ }
+ }
+
+}
diff --git a/pkgs/mediawiki-fastly-purge/includes/ServiceWiring.php b/pkgs/mediawiki-fastly-purge/includes/ServiceWiring.php
new file mode 100644
index 0000000..070a91c
--- /dev/null
+++ b/pkgs/mediawiki-fastly-purge/includes/ServiceWiring.php
@@ -0,0 +1,31 @@
+ static function ( MediaWikiServices $services ): FastlyConfig {
+ return new FastlyConfig( $services->getMainConfig() );
+ },
+
+ 'FastlyApiClient' => static function ( MediaWikiServices $services ): FastlyApiClient {
+ $config = $services->get( 'FastlyConfig' );
+ return new FastlyApiClient(
+ $config->getApiKey(),
+ $config->getServiceId(),
+ $services->getHttpRequestFactory(),
+ LoggerFactory::getInstance( 'FastlyPurge' )
+ );
+ },
+
+ 'FastlyEventRelayer' => static function ( MediaWikiServices $services ): FastlyEventRelayer {
+ return new FastlyEventRelayer(
+ [],
+ $services->get( 'FastlyApiClient' ),
+ LoggerFactory::getInstance( 'FastlyPurge' )
+ );
+ }
+];
diff --git a/pkgs/mediawiki-fastly-purge/lint.nix b/pkgs/mediawiki-fastly-purge/lint.nix
new file mode 100644
index 0000000..b5e6264
--- /dev/null
+++ b/pkgs/mediawiki-fastly-purge/lint.nix
@@ -0,0 +1,40 @@
+{
+ php83,
+ mediawiki,
+}:
+
+let
+ phpEnv = php83.buildEnv {
+ extensions = ({ enabled, all }: enabled ++ [ all.ast ]);
+ };
+in
+phpEnv.buildComposerProject2 {
+ pname = "fastly-purge-lint";
+ version = "0.0.1";
+ src = ./.;
+
+ vendorHash = "sha256-xySf8KVwZn8yrnMtXF2XE75Ep5xGTukJnbj8+6F2Whs=";
+ composerNoDev = false;
+
+ doCheck = true;
+ checkPhase = ''
+ runHook preCheck
+
+ export MEDIAWIKI_PATH=${mediawiki}/share/mediawiki
+
+ # Ensure vendor directory is available
+ cp -a --reflink=auto $composerVendor/vendor vendor
+ chmod -R u+w vendor
+
+ patchShebangs vendor/bin/*
+
+ # Run lint checks with proper phpcs configuration
+ ./vendor/bin/phan -d . --long-progress-bar
+ ./vendor/bin/parallel-lint . --exclude vendor --exclude node_modules
+ ./vendor/bin/phpcs --runtime-set installed_paths vendor/phpcsstandards/phpcsextra,vendor/mediawiki/mediawiki-codesniffer -p -s
+
+ rm -rf vendor
+
+ runHook postCheck
+ '';
+}
diff --git a/pkgs/mediawiki-fastly-purge/package.nix b/pkgs/mediawiki-fastly-purge/package.nix
new file mode 100644
index 0000000..5252f79
--- /dev/null
+++ b/pkgs/mediawiki-fastly-purge/package.nix
@@ -0,0 +1,27 @@
+{
+ lib,
+ stdenv,
+}:
+
+stdenv.mkDerivation {
+ pname = "mediawiki-fastlypurge";
+ version = "1.0.0";
+
+ src = ./.;
+
+ installPhase = ''
+ runHook preInstall
+
+ mkdir -p $out
+ cp -r * $out/
+
+ runHook postInstall
+ '';
+
+ meta = with lib; {
+ description = "MediaWiki extension for Fastly CDN cache purging";
+ homepage = "https://github.com/NixOS/nixos-wiki-infra";
+ license = licenses.mit;
+ platforms = platforms.all;
+ };
+}