diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..57f1752 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,13 @@ +*.php text eol=lf +*.phpt text eol=lf +/.gitattributes export-ignore +/.github/ export-ignore +/.gitignore export-ignore +/composer.lock export-ignore +/docs/ export-ignore +/phpbench.json export-ignore +/phpcs.xml.dist export-ignore +/phpstan.neon export-ignore +/phpunit.xml.dist export-ignore +/tests/ export-ignore +/tools/ export-ignore diff --git a/.github/workflows/docs-check.yml b/.github/workflows/docs-check.yml new file mode 100644 index 0000000..b36231f --- /dev/null +++ b/.github/workflows/docs-check.yml @@ -0,0 +1,48 @@ +# https://help.github.com/en/categories/automating-your-workflow-with-github-actions + +name: "Check Docs" + +on: + pull_request: + push: + branches: + - "[0-9]+.[0-9]+.x" + +jobs: + checkdocs: + name: "Check Docs" + + runs-on: ${{ matrix.operating-system }} + + strategy: + matrix: + dependencies: + - "locked" + php-version: + - "8.4" + operating-system: + - "ubuntu-latest" + + steps: + - name: "Checkout" + uses: actions/checkout@v6 + + - name: "Install PHP" + uses: "shivammathur/setup-php@2.37.2" + with: + coverage: none + php-version: "${{ matrix.php-version }}" + ini-values: memory_limit=-1, opcache.enable_cli=1 + + - uses: ramsey/composer-install@4.0.0 + with: + dependency-versions: ${{ matrix.dependencies }} + + - name: "extract php code" + run: "bin/docs-extract-php-code" + + - name: "lint php" + run: "php -l docs_php/*.php" + + - name: "docs code style" + run: "vendor/bin/phpcbf docs_php --exclude=SlevomatCodingStandard.TypeHints.DeclareStrictTypes,SlevomatCodingStandard.ControlStructures.EarlyExit" \ No newline at end of file diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml new file mode 100644 index 0000000..7cb425e --- /dev/null +++ b/.github/workflows/docs-deploy.yml @@ -0,0 +1,22 @@ +name: Publish docs + +on: + push: + branches: + - "[0-9]+.[0-9]+.x" + release: + types: + - published + +jobs: + trigger: + runs-on: ubuntu-latest + steps: + - name: Trigger workflow in other repo + run: | + curl -L -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2026-03-10" \ + https://api.github.com/repos/patchlevel/patchlevel.dev/actions/workflows/prod-deployment.yaml/dispatches \ + -d '{"ref":"main"}' \ No newline at end of file diff --git a/Makefile b/Makefile index d9a2d71..6d45666 100644 --- a/Makefile +++ b/Makefile @@ -46,3 +46,25 @@ benchmark-diff-test: vendor .PHONY: dev dev: static test ## run dev tools + +.PHONY: docs +docs: docs-extract-php docs-php-lint docs-phpcs docs-inject-php + +.PHONY: docs-extract-php +docs-extract-php: + bin/docs-extract-php-code + +.PHONY: docs-inject-php +docs-inject-php: + bin/docs-inject-php-code + +.PHONY: docs-format ## format docs +docs-format: docs-phpcs docs-inject-php + +.PHONY: docs-php-lint ## lint docs code +docs-php-lint: docs-extract-php + php -l docs_php/*.php | grep 'Parse error: ' || true + +.PHONY: docs-phpcs +docs-phpcs: docs-extract-php + vendor/bin/phpcbf docs_php --exclude=SlevomatCodingStandard.TypeHints.DeclareStrictTypes,SlevomatCodingStandard.ControlStructures.EarlyExit || true \ No newline at end of file diff --git a/README.md b/README.md index 904ebcc..f45826e 100644 --- a/README.md +++ b/README.md @@ -1,132 +1,51 @@ +[![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fpatchlevel%2Frango%2F1.1.x)](https://dashboard.stryker-mutator.io/reports/github.com/patchlevel/rango/1.1.x) +[![Latest Stable Version](https://poser.pugx.org/patchlevel/rango/v)](https://packagist.org/packages/patchlevel/rango) +[![License](https://poser.pugx.org/patchlevel/rango/license)](https://packagist.org/packages/patchlevel/rango) + # Rango

-Rango is a high-performance PHP library that reimplements the **MongoDB PHP API** on top of **PostgreSQL** using the power of `JSONB`. +Rango is a high-performance PHP library that reimplements the **MongoDB PHP API** on top of **PostgreSQL** using the +power of `JSONB`. -It provides a drop-in compatible API, allowing you to use familiar MongoDB-style operations while storing your data in a reliable PostgreSQL database. This is ideal for applications that want to leverage PostgreSQL's ACID compliance and ecosystem without giving up the flexible document-based development experience of MongoDB. +It provides a drop-in compatible API, allowing you to use familiar MongoDB-style operations while storing your data in a +reliable PostgreSQL database. This is ideal for applications that want to leverage PostgreSQL's ACID compliance and +ecosystem without giving up the flexible document-based development experience of MongoDB. -## 🚀 Why Rango? +## Features -- **PostgreSQL Reliability**: Benefit from PostgreSQL's mature engine, backups, and ACID transactions. -- **JSONB Performance**: Rango leverages PostgreSQL's binary JSON format (`JSONB`) for efficient storage and indexing. -- **Seamless Migration**: Switch between PostgreSQL and MongoDB with minimal code changes. -- **Hybrid Power**: Mix relational and document data within the same database. +* [Drop-in MongoDB API](https://patchlevel.dev/docs/rango/latest/getting-started) with `Client`, `Database`, and `Collection` +* [CRUD operations](https://patchlevel.dev/docs/rango/latest/crud-operations) like `insertOne`, `find`, `updateMany`, and `deleteOne` +* [Rich query operators](https://patchlevel.dev/docs/rango/latest/querying) such as `$gt`, `$in`, `$or`, and `$elemMatch` +* [Update operators](https://patchlevel.dev/docs/rango/latest/update-operators) like `$set`, `$inc`, `$push`, and `$rename` +* [Projection and sorting](https://patchlevel.dev/docs/rango/latest/querying#projection) with dot-notation support +* [Aggregation pipelines](https://patchlevel.dev/docs/rango/latest/aggregation) with `$match`, `$group`, `$unwind`, and `$lookup` +* [Bulk writes](https://patchlevel.dev/docs/rango/latest/crud-operations#bulk-writes) wrapped in a single transaction +* [Index management](https://patchlevel.dev/docs/rango/latest/indexes) backed by native PostgreSQL indexes -## 📦 Installation +## Installation ```bash composer require patchlevel/rango ``` -## 🛠 How it Works - -Rango translates MongoDB queries into optimized PostgreSQL SQL statements. - -- **Databases** are mapped to **PostgreSQL Schemas**. -- **Collections** are mapped to **PostgreSQL Tables** with a single `data` column of type `JSONB`. -- **Indexes** are created as **GIN or B-tree indexes** on the JSONB field. -- **Queries** are translated using PostgreSQL's rich set of JSONB operators (like `@>`, `?`, `->>`). - -## 🚦 Quick Start - -```php -use Patchlevel\Rango\Client; - -// Connect to PostgreSQL using a standard PDO DSN -$client = new Client('pgsql:host=localhost;port=5432;dbname=app;user=postgres;password=postgres'); - -// Select database and collection (auto-created on first write) -$collection = $client->selectDatabase('test')->selectCollection('users'); - -// Insert a document (automatically generates an _id if missing) -$collection->insertOne([ - 'name' => 'John Doe', - 'email' => 'john@example.com', - 'tags' => ['php', 'postgres'], - 'metadata' => ['logins' => 0] -]); - -// Find documents using MongoDB syntax -$users = $collection->find([ - 'tags' => 'php', - 'metadata.logins' => ['$lt' => 10] -]); - -foreach ($users as $user) { - echo "Found: " . $user['name'] . " (" . $user['_id'] . ")\n"; -} - -// Atomic updates -$collection->updateOne( - ['email' => 'john@example.com'], - ['$inc' => ['metadata.logins' => 1]] -); -``` +## Documentation -## ✨ Supported Features +* Latest [Docs](https://patchlevel.dev/docs/rango/latest) +* Related [Blog](https://patchlevel.dev/blog) -### CRUD Operations -| Category | Supported Methods | -| --- | --- | -| **Create** | `insertOne`, `insertMany` | -| **Read** | `find`, `findOne`, `countDocuments`, `distinct` | -| **Update** | `updateOne`, `updateMany`, `replaceOne`, `bulkWrite` | -| **Delete** | `deleteOne`, `deleteMany` | -| **Atomic** | `findOneAndUpdate`, `findOneAndReplace`, `findOneAndDelete` | +## Integration -### Query Operators -| Category | Operators | -| --- | --- | -| **Comparison** | `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin` | -| **Logical** | `$and`, `$or`, `$nor`, `$not` | -| **Element** | `$exists`, `$type` | -| **Evaluation** | `$regex`, `$mod` | -| **Array** | `$all`, `$size`, `$elemMatch` | +* [odm](https://github.com/patchlevel/odm) +* [event-sourcing](https://github.com/patchlevel/event-sourcing) +* [hydrator](https://github.com/patchlevel/hydrator) -Projection supports dot-notation in both inclusion and exclusion (e.g. `['profile.stats.score' => 0]`). +## Contributing -### Aggregation Framework -| Feature | Details | -| --- | --- | -| **Stages** | `$match`, `$sort`, `$limit`, `$skip`, `$project`, `$unwind`, `$group`, `$lookup` (Join) | -| **Accumulators** | `$sum`, `$avg`, `$min`, `$max`, `$first`, `$last` | - -### Update Operators -| Category | Operators | -| --- | --- | -| **Field** | `$set`, `$setOnInsert`, `$inc`, `$mul`, `$unset`, `$rename`, `$min`, `$max`, `$currentDate` (incl. dot-notation) | -| **Array** | `$push` (inc. `$each`), `$pull`, `$addToSet`, `$pop` | -| **Bitwise** | `$bit` (`and`, `or`, `xor`) | - -### Management -- **Index Management**: `createIndex`, `dropIndex`, `listIndexes`. -- **Schema Management**: `listDatabases`, `listCollections`, `renameCollection`, `drop`. - -## ⚠️ Current Limitations - -While Rango covers the most common use cases, some MongoDB features are not yet implemented: -- **Geospatial queries** (`$near`, `$geoWithin`, etc.) -- **Capped collections** -- **Text search** (MongoDB-specific syntax) -- **Complex Aggregation expressions** (only basic accumulators are supported) - -## 👩‍💻 Development - -### Prerequisites -- PHP 8.3+ -- Docker & Docker Compose (for integration tests) - -### Running Tests - -We test Rango against **both** a real MongoDB and PostgreSQL to ensure 100% compatibility. - -```bash -docker compose up -d -vendor/bin/phpunit -``` +We are open to contributions as long as they are in line with +our [BC-Policy](https://patchlevel.dev/our-backward-compatibility-promise). ---- -Built with ❤️ by the patchlevel team. +Also note that the `composer.lock` is always generated with the newest supported PHP version as this is the version our tools run in the CI. diff --git a/bin/docs-extract-php-code b/bin/docs-extract-php-code new file mode 100755 index 0000000..d50b642 --- /dev/null +++ b/bin/docs-extract-php-code @@ -0,0 +1,53 @@ +#!/usr/bin/env php +addExtension(new MarkdownRendererExtension()); + +$parser = new MarkdownParser($environment); + +$targetDir = __DIR__ . '/../docs_php'; + +if (file_exists($targetDir)) { + exec('rm -rf ' . $targetDir); +} + +mkdir($targetDir); + +$finder = new Symfony\Component\Finder\Finder(); +$finder->files()->in(__DIR__ . '/../docs')->name('*.md'); + +foreach ($finder as $file) { + $fileName = pathinfo($file->getBasename(), PATHINFO_FILENAME); + + $content = file_get_contents($file->getPathname()); + $document = $parser->parse($content); + + $result = (new Query()) + ->where(Query::type(FencedCode::class)) + ->findAll($document); + + /** + * @var FencedCode $node + */ + foreach ($result as $i => $node) { + if ($node->getInfo() !== 'php') { + continue; + } + + $source = sprintf('%s:%s', $file->getRealPath(), $node->getStartLine()); + + $code = "getLiteral(); + + $targetPath = $targetDir . '/' . $fileName . '_' . $i . '.php'; + file_put_contents($targetPath, $code); + } +} diff --git a/bin/docs-inject-php-code b/bin/docs-inject-php-code new file mode 100755 index 0000000..e4e81ea --- /dev/null +++ b/bin/docs-inject-php-code @@ -0,0 +1,70 @@ +#!/usr/bin/env php +addExtension(new MarkdownRendererExtension()); + +$parser = new MarkdownParser($environment); +$markdownRenderer = new MarkdownRenderer($environment); + +$targetDir = __DIR__ . '/../docs_php'; + +if (!file_exists($targetDir)) { + exit(1); +} + +$finder = new Symfony\Component\Finder\Finder(); +$finder->files()->in(__DIR__ . '/../docs')->name('*.md'); + +foreach ($finder as $file) { + $fileName = pathinfo($file->getBasename(), PATHINFO_FILENAME); + + $content = file_get_contents($file->getPathname()); + $document = $parser->parse($content); + + $result = (new Query()) + ->where(Query::type(FencedCode::class)) + ->findAll($document); + + /** + * @var FencedCode $node + */ + foreach ($result as $i => $node) { + if ($node->getInfo() !== 'php') { + $node->setLiteral(trim($node->getLiteral())); + continue; + } + + $targetPath = $targetDir . '/' . $fileName . '_' . $i . '.php'; + + if (!file_exists($targetPath)) { + $node->setLiteral(trim($node->getLiteral())); + continue; + } + + $code = file_get_contents($targetPath); + + $lines = explode("\n", $code); + array_splice($lines, 0, 2); + $code = implode("\n", $lines); + + $node->setLiteral(trim($code)); + } + + file_put_contents($file->getPathname(), $markdownRenderer->renderDocument($document)); +} + +if (file_exists($targetDir)) { + exec('rm -rf ' . $targetDir); +} \ No newline at end of file diff --git a/composer.json b/composer.json index 36b6351..8adb0b4 100644 --- a/composer.json +++ b/composer.json @@ -2,12 +2,16 @@ "name": "patchlevel/rango", "type": "library", "license": "MIT", - "description": "Rango", + "description": "A high-performance reimplementation of the MongoDB PHP API on top of PostgreSQL JSONB", "keywords": [ + "patchlevel", "mongodb", - "postgres" + "postgres", + "postgresql", + "jsonb", + "document database" ], - "homepage": "https://github.com/patchlevel/hydrator", + "homepage": "https://patchlevel.dev/docs/rango/latest", "authors": [ { "name": "Daniel Badura", @@ -32,7 +36,8 @@ "phpstan/phpstan": "^2.1.32", "phpstan/phpstan-phpunit": "^2.0.8", "phpunit/phpunit": "^11.5.17", - "symfony/var-dumper": "^6.4.0 || ^7.0.0 || ^8.0.0" + "symfony/var-dumper": "^6.4.0 || ^7.0.0 || ^8.0.0", + "wnx/commonmark-markdown-renderer": "^1.6" }, "config": { "preferred-install": { diff --git a/composer.lock b/composer.lock index 3264e03..6b3a9b7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1a3ae198f8b01798683560d3b3c7629b", + "content-hash": "7a7febdcf0014ff700a4a07964302cc9", "packages": [], "packages-dev": [ { @@ -336,6 +336,81 @@ ], "time": "2025-11-11T04:32:07+00:00" }, + { + "name": "dflydev/dot-access-data", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" + }, + "time": "2024-07-08T12:26:09+00:00" + }, { "name": "doctrine/annotations", "version": "2.0.2", @@ -987,6 +1062,195 @@ }, "time": "2025-12-19T15:01:32+00:00" }, + { + "name": "league/commonmark", + "version": "2.8.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "59fb075d2101740c337c7216e3f32b36c204218b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/59fb075d2101740c337c7216e3f32b36c204218b", + "reference": "59fb075d2101740c337c7216e3f32b36c204218b", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4 || ^2.0", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0 || ^8.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.9-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2026-03-19T13:16:38+00:00" + }, + { + "name": "league/config", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2022-12-11T20:36:23+00:00" + }, { "name": "marc-mabe/php-enum", "version": "v4.7.2", @@ -1197,6 +1461,164 @@ ], "time": "2025-08-01T08:46:24+00:00" }, + { + "name": "nette/schema", + "version": "v1.3.5", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002", + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002", + "shasum": "" + }, + "require": { + "nette/utils": "^4.0", + "php": "8.1 - 8.5" + }, + "require-dev": { + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.6", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1.39@stable", + "tracy/tracy": "^2.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.3.5" + }, + "time": "2026-02-23T03:47:12+00:00" + }, + { + "name": "nette/utils", + "version": "v4.1.4", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/7da6c396d7ebe142bc857c20479d5e70a5e1aac7", + "reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7", + "shasum": "" + }, + "require": { + "php": "8.2 - 8.5" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.2", + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.5", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1@stable", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.1.4" + }, + "time": "2026-05-11T20:49:54+00:00" + }, { "name": "nikic/php-parser", "version": "v5.7.0", @@ -2459,6 +2881,56 @@ }, "time": "2021-11-05T16:47:00+00:00" }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, { "name": "psr/log", "version": "3.0.2", @@ -4734,6 +5206,90 @@ ], "time": "2024-12-23T08:48:59+00:00" }, + { + "name": "symfony/polyfill-php80", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "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.37.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": "2026-04-10T16:19:22+00:00" + }, { "name": "symfony/polyfill-php85", "version": "v1.33.0", @@ -5438,6 +5994,67 @@ "source": "https://github.com/webmozarts/glob/tree/4.7.0" }, "time": "2024-03-07T20:33:40+00:00" + }, + { + "name": "wnx/commonmark-markdown-renderer", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/stefanzweifel/commonmark-markdown-renderer.git", + "reference": "3a283076abd1a1ed043940f9be43cd35470cd0d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stefanzweifel/commonmark-markdown-renderer/zipball/3a283076abd1a1ed043940f9be43cd35470cd0d4", + "reference": "3a283076abd1a1ed043940f9be43cd35470cd0d4", + "shasum": "" + }, + "require": { + "league/commonmark": "^2.0", + "php": "^8.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.0", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^10.0", + "rector/rector": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Wnx\\CommonmarkMarkdownRenderer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stefan Zweifel", + "email": "stefan@stefanzweifel.dev", + "role": "Developer" + } + ], + "description": "Render Markdown AST back to Markdown.", + "homepage": "https://github.com/stefanzweifel/commonmark-markdown-renderer", + "keywords": [ + "commonmark-markdown-renderer", + "markdown", + "renderer", + "wnx" + ], + "support": { + "issues": "https://github.com/stefanzweifel/commonmark-markdown-renderer/issues", + "source": "https://github.com/stefanzweifel/commonmark-markdown-renderer/tree/v1.6.0" + }, + "funding": [ + { + "url": "https://github.com/stefanzweifel", + "type": "github" + } + ], + "time": "2025-11-23T20:14:14+00:00" } ], "aliases": [], diff --git a/docs/aggregation.md b/docs/aggregation.md new file mode 100644 index 0000000..a198383 --- /dev/null +++ b/docs/aggregation.md @@ -0,0 +1,89 @@ +# Aggregation + +Aggregation runs documents through a pipeline of stages, where each stage transforms the stream produced by the one before it. It is the right tool when a single [query](querying.md) is not enough, for example to group, reshape, or join documents. + +You pass a pipeline as a list of stages to `aggregate`, which returns a [Cursor](crud-operations.md): + +```php +$cursor = $collection->aggregate([ + ['$match' => ['status' => 'paid']], + ['$group' => ['_id' => '$userId', 'total' => ['$sum' => '$amount']]], + ['$sort' => ['total' => -1]], +]); + +foreach ($cursor as $row) { + echo $row['_id'] . ': ' . $row['total'] . "\n"; +} +``` +## Filtering and ordering stages + +`$match` filters documents using the same syntax as [query operators](querying.md). `$sort`, `$limit`, and `$skip` order and page the stream just like the [read options](querying.md#sorting): + +```php +$collection->aggregate([ + ['$match' => ['age' => ['$gte' => 18]]], + ['$sort' => ['age' => -1]], + ['$skip' => 10], + ['$limit' => 5], +]); +``` +## Reshaping stages + +`$project` selects and renames fields, and `$unwind` expands an array field into one document per element: + +```php +$collection->aggregate([ + ['$project' => ['name' => 1, '_id' => 0]], +]); + +$collection->aggregate([ + ['$unwind' => '$tags'], +]); +``` +## Grouping + +`$group` buckets documents by an `_id` expression and computes accumulators per bucket. A field reference is written with a leading `$`: + +```php +$collection->aggregate([ + [ + '$group' => [ + '_id' => '$status', + 'orders' => ['$sum' => 1], + 'revenue' => ['$sum' => '$total'], + 'average' => ['$avg' => '$total'], + 'highest' => ['$max' => '$total'], + 'lowest' => ['$min' => '$total'], + ], + ], +]); +``` +The supported accumulators are `$sum`, `$avg`, `$min`, `$max`, `$first`, and `$last`. Use `['$sum' => 1]` to count documents in each group. + +## Joining collections + +`$lookup` performs a left outer join against another collection in the same database. It matches `localField` against `foreignField` and stores the matches in the array named by `as`: + +```php +$client->selectCollection('app', 'users')->aggregate([ + [ + '$lookup' => [ + 'from' => 'orders', + 'localField' => '_id', + 'foreignField' => 'userId', + 'as' => 'orders', + ], + ], +]); +``` +Each `users` document gains an `orders` array holding the matching `orders` documents, or an empty array when there are none. + +:::note +Only the stages and accumulators listed here are implemented. Complex aggregation expressions are out of scope, as noted under [limitations](how-it-works.md#limitations). +::: + +## Learn more + +* [How to filter with the same operators used by `$match`](querying.md) +* [How to read and iterate the resulting cursor](crud-operations.md) +* [How Rango compiles a pipeline into SQL](how-it-works.md) diff --git a/docs/connection.md b/docs/connection.md new file mode 100644 index 0000000..428d9f4 --- /dev/null +++ b/docs/connection.md @@ -0,0 +1,98 @@ +# Connection + +The `Client` is the entry point to Rango. It owns the PostgreSQL connection and hands out `Database` and [Collection](crud-operations.md) objects that you use for everything else. The same client also lets you inspect and manage the databases and collections themselves. + +## Creating a client + +The simplest way to connect is with a PDO DSN string. Rango opens the connection and configures it to throw exceptions on errors: + +```php +use Patchlevel\Rango\Client; + +$client = new Client('pgsql:host=localhost;port=5432;dbname=app;user=postgres;password=postgres'); +``` +## Reusing an existing PDO + +If your application already manages a `PDO` instance, for example through a dependency injection container, you can pass it directly. Rango then uses your connection instead of opening its own: + +```php +use Patchlevel\Rango\Client; + +$pdo = new PDO('pgsql:host=localhost;dbname=app', 'postgres', 'postgres', [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, +]); + +$client = new Client($pdo); +``` +:::tip +Sharing a single `PDO` instance lets Rango operations take part in the same transaction as the rest of your application. +::: + +## Selecting databases and collections + +A `Client` gives you a `Database`, and a `Database` gives you a `Collection`. The `getXxx` and `selectXxx` methods are equivalent, so use whichever reads better: + +```php +$database = $client->selectDatabase('app'); +$collection = $database->selectCollection('users'); + +// or in one step +$collection = $client->selectCollection('app', 'users'); +``` +Nothing is queried while selecting. The schema and table are created lazily the first time you write to the collection, as explained in [how it works](how-it-works.md). + +## Listing databases + +`listDatabases` returns an iterator of `DatabaseInfo` objects, one per PostgreSQL schema: + +```php +foreach ($client->listDatabases() as $database) { + echo $database->getName(); +} +``` +## Listing collections + +`listCollections` returns the collections in a database as `CollectionInfo` objects. Call it on the client with a database name, or on a `Database`: + +```php +foreach ($client->listCollections('app') as $collection) { + echo $collection->getName(); +} + +$database = $client->selectDatabase('app'); +foreach ($database->listCollections() as $collection) { + echo $collection->getName(); +} +``` +## Renaming a collection + +`renameCollection` changes a collection's name within its database: + +```php +$database = $client->selectDatabase('app'); +$database->renameCollection('users', 'members'); +``` +## Dropping collections and databases + +Drop a single collection from its database, or drop the whole database with all of its collections: + +```php +$database = $client->selectDatabase('app'); + +$database->dropCollection('members'); + +// or drop the collection through its own handle +$client->selectCollection('app', 'members')->drop(); + +// remove the entire database (schema) +$database->drop(); +``` +:::danger +Dropping a collection or database is irreversible and removes all of its documents. The database drop cascades to every collection it contains. +::: + +## Learn more + +* [How to run CRUD operations on a collection](crud-operations.md) +* [How to query and shape results](querying.md) +* [How Rango maps a connection to PostgreSQL schemas](how-it-works.md) diff --git a/docs/crud-operations.md b/docs/crud-operations.md new file mode 100644 index 0000000..4e27a3c --- /dev/null +++ b/docs/crud-operations.md @@ -0,0 +1,180 @@ +# CRUD Operations + +A `Collection` exposes the create, read, update, and delete methods you know from MongoDB. Documents are plain PHP arrays, and every write returns a result object that tells you what happened. + +This page covers the core methods. The filter and update syntax they accept is documented under [query operators](querying.md) and [update operators](update-operators.md). + +## Create + +Use `insertOne` for a single document and `insertMany` for a batch. If a document has no `_id`, Rango generates one and returns it on the result: + +```php +$result = $collection->insertOne([ + 'name' => 'John Doe', + 'email' => 'john@example.com', + 'age' => 27, +]); + +echo $result->getInsertedId(); +echo $result->getInsertedCount(); // 1 +``` +```php +$result = $collection->insertMany([ + ['name' => 'Jane Roe', 'email' => 'jane@example.com'], + ['name' => 'Max Mustermann', 'email' => 'max@example.com'], +]); + +$result->getInsertedIds(); // [0 => '...', 1 => '...'] +``` +:::note +Generated ids are random 24-character hex strings. Provide your own `_id` whenever you need a stable, meaningful key. +::: + +## Read + +`find` returns a [Cursor](#working-with-the-cursor) over every matching document, while `findOne` returns the first match or `null`: + +```php +$cursor = $collection->find(['age' => ['$gte' => 25]]); + +$user = $collection->findOne(['email' => 'john@example.com']); +if ($user === null) { + // not found +} +``` +Use `countDocuments` to count matches without loading them, and `distinct` to collect the unique values of a field: + +```php +$total = $collection->countDocuments(['age' => ['$gte' => 18]]); + +$emails = $collection->distinct('email', ['age' => ['$gte' => 18]]); +``` +### Working with the cursor + +`find` returns a `Cursor`, which is iterable and countable. Iterate it directly, or materialize it with `toArray`: + +```php +$cursor = $collection->find(['tags' => 'php']); + +foreach ($cursor as $user) { + echo $user['name']; +} + +$users = $cursor->toArray(); +$count = $cursor->count(); +``` +:::warning +A cursor backed by a database statement streams its rows. Iterate it once, and call `toArray()` if you need to read the result more than once. +::: + +## Update + +`updateOne` and `updateMany` apply [update operators](update-operators.md) to matching documents, while `replaceOne` swaps the whole document. Each returns an `UpdateResult`: + +```php +$result = $collection->updateOne( + ['email' => 'john@example.com'], + ['$set' => ['age' => 28]], +); + +echo $result->getMatchedCount(); +echo $result->getModifiedCount(); +``` +```php +$collection->replaceOne( + ['_id' => '1'], + ['name' => 'John Doe', 'email' => 'john@new.example.com'], +); +``` +Pass the `upsert` option to insert the document when no match exists: + +```php +$collection->updateOne( + ['_id' => 'user-42'], + ['$set' => ['name' => 'New User']], + ['upsert' => true], +); +``` +:::warning +Upsert currently requires `_id` to be present in the filter, because the new document needs a primary key. +::: + +## Delete + +`deleteOne` removes the first match and `deleteMany` removes all matches. Both return a `DeleteResult`: + +```php +$result = $collection->deleteOne(['email' => 'max@example.com']); + +echo $result->getDeletedCount(); + +$collection->deleteMany(['age' => ['$lt' => 18]]); +``` +## Find and modify + +The atomic helpers return the matched document and change it in one step. `findOneAndUpdate` and `findOneAndReplace` apply a change, while `findOneAndDelete` removes the match: + +```php +$old = $collection->findOneAndUpdate( + ['_id' => '1'], + ['$inc' => ['profile.stats.score' => 1]], +); + +$removed = $collection->findOneAndDelete(['_id' => '2']); +``` +## Bulk writes + +`bulkWrite` runs many write operations against a collection in a single database transaction. If any operation fails, the whole batch is rolled back, so the collection never ends up in a half-written state. + +Each entry in the list is a single-key array naming the operation, with its arguments as a positional list. The arguments mirror the matching standalone method: + +```php +$result = $collection->bulkWrite([ + [ + 'insertOne' => [ + ['name' => 'John Doe', 'email' => 'john@example.com'], + ], + ], + [ + 'updateOne' => [ + ['email' => 'jane@example.com'], + ['$set' => ['age' => 31]], + ], + ], + [ + 'deleteOne' => [ + ['email' => 'max@example.com'], + ], + ], +]); +``` +The supported operations and their arguments are: + +| Operation | Arguments | +|---|---| +| `insertOne` | `[$document]` | +| `updateOne` | `[$filter, $update, $options?]` | +| `updateMany` | `[$filter, $update, $options?]` | +| `replaceOne` | `[$filter, $replacement, $options?]` | +| `deleteOne` | `[$filter]` | +| `deleteMany` | `[$filter]` | + +`bulkWrite` returns a `BulkWriteResult` that aggregates the counts across every operation in the batch: + +```php +echo $result->getInsertedCount(); +echo $result->getMatchedCount(); +echo $result->getModifiedCount(); +echo $result->getDeletedCount(); + +$result->getInsertedIds(); // ids of inserted documents +``` +:::warning +All operations run in one transaction. A failure in any of them rolls back every change in the batch, including ones that already succeeded. +::: + +## Learn more + +* [How to filter and shape results when querying](querying.md) +* [How to modify documents with update operators](update-operators.md) +* [How to reshape data with aggregation](aggregation.md) diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..b53f4aa --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,115 @@ +# Getting Started + +This tutorial walks you through Rango by building a small example: an `app` database with a `users` collection. You connect to PostgreSQL, insert documents, query them with MongoDB-style filters, and update them atomically. + +By the end you will know how to open a connection, run the core [CRUD operations](crud-operations.md), and where to go next for advanced features. + +## Installation + +Install Rango with Composer: + +```bash +composer require patchlevel/rango +``` +You need a running PostgreSQL instance. The fastest way to get one locally is Docker: + +```bash +docker run --rm -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres:16-alpine +``` +## Connect to PostgreSQL + +A [Client](connection.md) is the entry point. It takes a standard PDO DSN and manages the underlying connection: + +```php +use Patchlevel\Rango\Client; + +$client = new Client('pgsql:host=localhost;port=5432;dbname=app;user=postgres;password=postgres'); +``` +:::note +You can also pass an existing `PDO` instance instead of a DSN string. See the [connection](connection.md) page for details. +::: + +## Select a collection + +In Rango, a database maps to a PostgreSQL schema and a collection maps to a table. You select them by name, and they are created automatically on the first write: + +```php +$collection = $client->selectDatabase('app')->selectCollection('users'); +``` +:::success +You never run migrations by hand. Rango creates the schema and table the first time you write to a collection. +::: + +## Insert some documents + +Documents are plain PHP arrays. If you omit the `_id`, Rango generates one for you: + +```php +$collection->insertOne([ + 'name' => 'John Doe', + 'email' => 'john@example.com', + 'age' => 27, + 'tags' => ['php', 'postgres'], + 'profile' => ['stats' => ['score' => 42]], +]); + +$result = $collection->insertMany([ + ['name' => 'Jane Roe', 'email' => 'jane@example.com', 'age' => 31, 'tags' => ['php']], + ['name' => 'Max Mustermann', 'email' => 'max@example.com', 'age' => 19, 'tags' => ['mongodb']], +]); + +echo $result->getInsertedCount(); // 2 +``` +## Find documents + +Pass a MongoDB-style filter to `find`. Here you combine an array match with a [comparison operator](querying.md#comparison-operators): + +```php +$users = $collection->find([ + 'tags' => 'php', + 'age' => ['$gte' => 25], +]); + +foreach ($users as $user) { + echo $user['name'] . "\n"; +} +``` +:::note +`find` returns a [Cursor](crud-operations.md), which you can iterate directly or turn into an array with `toArray()`. +::: + +To fetch a single document, use `findOne`: + +```php +$user = $collection->findOne(['email' => 'john@example.com']); +``` +## Update documents + +Updates use [update operators](update-operators.md). This increments a nested counter and adds a tag atomically: + +```php +$collection->updateOne( + ['email' => 'john@example.com'], + [ + '$inc' => ['profile.stats.score' => 1], + '$push' => ['tags' => 'mongodb'], + ], +); +``` +## Delete documents + +Deleting works the same way, with a filter: + +```php +$collection->deleteOne(['email' => 'max@example.com']); +``` +## Result + +You now have a working Rango setup: a client connected to PostgreSQL, a collection that stores documents as `JSONB`, and the full CRUD cycle running through the MongoDB-style API. Everything you wrote lives in a regular PostgreSQL table you can back up, replicate, and query with SQL. + +## Learn more + +* [How to connect and configure the client](connection.md) +* [How to query with operators](querying.md) +* [How to build aggregation pipelines](aggregation.md) +* [How Rango maps documents to PostgreSQL](how-it-works.md) diff --git a/docs/how-it-works.md b/docs/how-it-works.md new file mode 100644 index 0000000..b298122 --- /dev/null +++ b/docs/how-it-works.md @@ -0,0 +1,59 @@ +# How it Works + +Rango speaks the MongoDB PHP API but stores everything in PostgreSQL. Understanding the mapping helps you reason about performance, write SQL against the same tables, and know what to expect from each operation. + +## The mapping + +Rango translates MongoDB concepts into native PostgreSQL structures: + +| MongoDB concept | PostgreSQL structure | +|---|---| +| Database | Schema | +| Collection | Table with `_id` and `data` columns | +| Document | Row, stored as `JSONB` in `data` | +| Index | B-tree index on a `JSONB` expression | +| Query operators | `JSONB` operators and conditions | + +Every collection is a table with two columns: a `TEXT` `_id` primary key and a `JSONB` `data` column holding the full document. The `_id` is also kept inside the document so reads return it like any other field. + +## Lazy schema creation + +You never run migrations by hand. The first write to a [collection](crud-operations.md) creates the schema and table if they do not exist: + +```sql +CREATE SCHEMA IF NOT EXISTS "app"; +CREATE TABLE IF NOT EXISTS "app"."users" (_id TEXT PRIMARY KEY, data JSONB NOT NULL); +``` +:::success +Selecting a database or collection never touches PostgreSQL. The structure is created on demand the first time you insert or update. +::: + +## Generated ids + +When you insert a document without an `_id`, Rango generates a random 24-character hex string and uses it as the primary key. Provide your own `_id` whenever you need a stable, meaningful key. + +## From queries to SQL + +[Query operators](querying.md) compile to PostgreSQL `JSONB` conditions, dot-notation paths become `->` and `->>` accessors, [update operators](update-operators.md) become `jsonb_set`-style expressions, and [aggregation](aggregation.md) pipelines become nested `SELECT` statements. Because the result is ordinary SQL against ordinary tables, you can inspect, back up, and query the data with any PostgreSQL tool. + +## Limitations + +Rango covers the most common MongoDB use cases, but it does not reimplement the entire MongoDB feature set. The following features are currently out of scope: + +* **Geospatial queries** such as `$near` and `$geoWithin` +* **Capped collections** +* **Text search** with MongoDB-specific syntax and text indexes +* **Complex aggregation expressions**, beyond the basic accumulators in [aggregation](aggregation.md) +* **Special index types**: only ascending and descending [indexes](indexes.md) are supported, so geospatial (`2dsphere`), text, sparse, and TTL indexes are not, and the matching `IndexInfo` checks always report `false` + +[Upserts](update-operators.md) also need `_id` to be present in the filter, because Rango builds the primary key of the inserted document from it. An upsert without `_id` in the filter raises an exception. + +:::note +This list reflects the current state of Rango. Features may be added over time, so check the changelog and the [aggregation](aggregation.md) and [indexes](indexes.md) pages for what is available in your version. +::: + +## Learn more + +* [How to connect a client to PostgreSQL](connection.md) +* [How to create indexes on the JSONB column](indexes.md) +* [How to run aggregation pipelines](aggregation.md) diff --git a/docs/indexes.md b/docs/indexes.md new file mode 100644 index 0000000..e12a205 --- /dev/null +++ b/docs/indexes.md @@ -0,0 +1,49 @@ +# Indexes + +Indexes speed up [queries](querying.md) and [sorts](querying.md#sorting) by letting PostgreSQL find documents without scanning the whole table. Rango creates them as native PostgreSQL indexes on the `JSONB` `data` column, so you keep MongoDB-style ergonomics with PostgreSQL performance. + +## Creating an index + +Pass a key map to `createIndex`, where each field maps to `1` for ascending or `-1` for descending order: + +```php +$collection->createIndex(['email' => 1]); + +$collection->createIndex(['age' => -1, 'name' => 1]); +``` +Use the `unique` option to enforce uniqueness, and `name` to choose the index name. Without a name, Rango derives one from the database, collection, and fields: + +```php +$collection->createIndex(['email' => 1], ['unique' => true, 'name' => 'users_email_unique']); +``` +:::tip +Index the fields you filter and sort on most often, such as the keys you pass to `find` and the `sort` option. +::: + +## Listing indexes + +`listIndexes` returns an iterator of `IndexInfo` objects describing the indexes on the collection: + +```php +foreach ($collection->listIndexes() as $index) { + echo $index->getName(); + $index->getKey(); // ['email' => 1] + $index->isUnique(); // true or false +} +``` +## Dropping an index + +Drop an index by name: + +```php +$collection->dropIndex('users_email_unique'); +``` +:::note +Geospatial, text, sparse, and TTL indexes are not supported. The matching `IndexInfo` checks always report `false`, as listed under [limitations](how-it-works.md#limitations). +::: + +## Learn more + +* [How to write the queries an index accelerates](querying.md) +* [How sorting benefits from matching indexes](querying.md#sorting) +* [How Rango maps documents and indexes onto PostgreSQL](how-it-works.md) diff --git a/docs/introduction.md b/docs/introduction.md new file mode 100644 index 0000000..d7a2dfa --- /dev/null +++ b/docs/introduction.md @@ -0,0 +1,33 @@ +# Rango + +Rango is a high-performance PHP library that reimplements the MongoDB PHP API on top of PostgreSQL using the power of `JSONB`. + +It provides a drop-in compatible API, so you can use familiar MongoDB-style operations while storing your data in a reliable PostgreSQL database. This is ideal for applications that want PostgreSQL's ACID compliance and ecosystem without giving up the flexible document-based development experience of MongoDB. + +## Features + +* [Drop-in MongoDB API](getting-started.md) with `Client`, `Database`, and `Collection` +* [CRUD operations](crud-operations.md) like `insertOne`, `find`, `updateMany`, and `deleteOne` +* [Rich query operators](querying.md) such as `$gt`, `$in`, `$or`, and `$elemMatch` +* [Update operators](update-operators.md) like `$set`, `$inc`, `$push`, and `$rename` +* [Projection and sorting](querying.md#projection) with dot-notation support +* [Aggregation pipelines](aggregation.md) with `$match`, `$group`, `$unwind`, and `$lookup` +* [Bulk writes](crud-operations.md#bulk-writes) wrapped in a single transaction +* [Index management](indexes.md) backed by native PostgreSQL indexes + +## Installation + +```bash +composer require patchlevel/rango +``` +Rango needs the PDO extension and a PostgreSQL connection. The MongoDB extension is only required for the test suite, not at runtime. + +## Integration + +* [odm](https://github.com/patchlevel/odm) +* [event-sourcing](https://github.com/patchlevel/event-sourcing) +* [hydrator](https://github.com/patchlevel/hydrator) + +:::tip +New to Rango? Start with the [getting started](getting-started.md) tutorial, which builds a small application step by step. +::: diff --git a/docs/project.json b/docs/project.json new file mode 100644 index 0000000..76efcf8 --- /dev/null +++ b/docs/project.json @@ -0,0 +1,28 @@ +{ + "navigation": [ + { "title": "Introduction", "file": "introduction.md" }, + { "title": "Getting Started", "file": "getting-started.md" }, + { + "title": "Basics", + "subEntries": [ + { "title": "Connection", "file": "connection.md" }, + { "title": "CRUD Operations", "file": "crud-operations.md" }, + { "title": "Querying", "file": "querying.md" }, + { "title": "Update Operators", "file": "update-operators.md" } + ] + }, + { + "title": "Advanced", + "subEntries": [ + { "title": "Aggregation", "file": "aggregation.md" }, + { "title": "Indexes", "file": "indexes.md" } + ] + }, + { + "title": "Reference", + "subEntries": [ + { "title": "How it Works", "file": "how-it-works.md" } + ] + } + ] +} diff --git a/docs/querying.md b/docs/querying.md new file mode 100644 index 0000000..330565b --- /dev/null +++ b/docs/querying.md @@ -0,0 +1,146 @@ +# Querying + +Reading documents has two parts: a filter that selects which documents match, and options that shape and order the results. This page covers both. Filters are also accepted by the update and delete methods and by the `$match` stage of an [aggregation](aggregation.md). + +A filter is an array where each key is a field and each value is either a literal to match or an operator expression. Rango translates these into PostgreSQL `JSONB` conditions. + +## Matching fields + +A plain value matches documents where the field equals that value. Nested fields use dot-notation, and matching an array field against a scalar checks whether the array contains that value: + +```php +$collection->find(['name' => 'John Doe']); +$collection->find(['profile.stats.score' => 42]); +$collection->find(['tags' => 'php']); // documents whose tags array contains "php" +``` +## Comparison operators + +Comparison operators wrap the value in an array keyed by the operator: + +```php +$collection->find(['age' => ['$gt' => 20]]); +$collection->find(['age' => ['$gte' => 20]]); +$collection->find(['age' => ['$lt' => 30]]); +$collection->find(['age' => ['$lte' => 30]]); +$collection->find(['age' => ['$ne' => 30]]); +$collection->find(['age' => ['$in' => [20, 40]]]); +$collection->find(['age' => ['$nin' => [20, 40]]]); +``` +| Operator | Matches when the field | +|---|---| +| `$eq` | equals the value | +| `$ne` | does not equal the value | +| `$gt` / `$gte` | is greater than / greater than or equal | +| `$lt` / `$lte` | is less than / less than or equal | +| `$in` | is one of the listed values | +| `$nin` | is none of the listed values | + +## Logical operators + +Logical operators combine sub-filters. `$and`, `$or`, and `$nor` take a list of filters, while `$not` negates a single operator expression: + +```php +$collection->find([ + '$or' => [ + ['age' => ['$lt' => 18]], + ['age' => ['$gte' => 65]], + ], +]); + +$collection->find([ + '$and' => [ + ['tags' => 'php'], + ['age' => ['$gte' => 21]], + ], +]); + +$collection->find(['age' => ['$not' => ['$gt' => 30]]]); +``` +## Element operators + +Element operators test the presence or type of a field: + +```php +$collection->find(['name' => ['$exists' => true]]); +$collection->find(['deletedAt' => ['$exists' => false]]); +$collection->find(['age' => ['$type' => 'number']]); +``` +## Evaluation operators + +`$regex` matches a string field against a pattern, and `$mod` matches numbers by their remainder: + +```php +$collection->find(['email' => ['$regex' => '@example\\.com$']]); +$collection->find(['age' => ['$mod' => [2, 0]]]); // even ages +``` +## Array operators + +Array operators inspect array fields. `$all` requires every listed value, `$size` matches by length, and `$elemMatch` matches array elements against a sub-filter: + +```php +$collection->find(['tags' => ['$all' => ['php', 'postgres']]]); +$collection->find(['tags' => ['$size' => 2]]); + +$collection->find([ + 'orders' => [ + '$elemMatch' => ['total' => ['$gt' => 100], 'status' => 'paid'], + ], +]); +``` +:::tip +Operators combine freely. A single field can carry several operators, and several fields act as an implicit `$and`, so `['age' => ['$gte' => 18, '$lt' => 65], 'tags' => 'php']` reads naturally. +::: + +## Projection + +Read operations accept an options array as their last argument. A projection selects which fields come back. Use `1` to include a field and `0` to exclude it, with dot-notation for nested fields: + +```php +// only return name (and _id, which is included by default) +$user = $collection->findOne(['_id' => '1'], ['projection' => ['name' => 1]]); + +// return everything except age +$user = $collection->findOne(['_id' => '1'], ['projection' => ['age' => 0]]); + +// include name but drop the _id +$user = $collection->findOne(['_id' => '1'], ['projection' => ['name' => 1, '_id' => 0]]); + +// exclude a deeply nested field +$user = $collection->findOne(['_id' => '1'], ['projection' => ['profile.stats.score' => 0]]); +``` +:::note +As in MongoDB, `_id` is included unless you explicitly exclude it with `'_id' => 0`. +::: + +## Sorting + +The `sort` option orders results by one or more fields. Use `1` for ascending and `-1` for descending, with dot-notation for nested fields: + +```php +$cursor = $collection->find([], ['sort' => ['name' => 1]]); + +$cursor = $collection->find([], ['sort' => ['age' => -1, 'name' => 1]]); + +$cursor = $collection->find([], ['sort' => ['profile.stats.score' => -1]]); +``` +## Limit and skip + +`limit` caps the number of returned documents and `skip` offsets the start. Together with `sort` they implement pagination: + +```php +// second page of 20 results, newest first +$cursor = $collection->find([], [ + 'sort' => ['createdAt' => -1], + 'limit' => 20, + 'skip' => 20, +]); +``` +:::tip +Rango maps `sort`, `limit`, and `skip` to SQL `ORDER BY`, `LIMIT`, and `OFFSET`, so paging stays efficient. +::: + +## Learn more + +* [How to change the matched documents with update operators](update-operators.md) +* [How to run multi-stage queries with aggregation](aggregation.md) +* [How to speed up filtered and sorted reads with indexes](indexes.md) diff --git a/docs/update-operators.md b/docs/update-operators.md new file mode 100644 index 0000000..8895823 --- /dev/null +++ b/docs/update-operators.md @@ -0,0 +1,92 @@ +# Update Operators + +Update operators describe how to change matching documents without replacing them. You pass them to [updateOne, updateMany, findOneAndUpdate](crud-operations.md), and to update operations inside a [bulk write](crud-operations.md#bulk-writes). Each operator is keyed by its name and takes a map of fields to values. + +All operators support dot-notation, so you can reach into nested documents. + +## Field operators + +Field operators set, remove, and adjust individual values: + +```php +$collection->updateOne(['_id' => '1'], [ + '$set' => ['name' => 'John Doe', 'profile.stats.score' => 100], + '$unset' => ['temporary' => ''], + '$rename' => ['username' => 'name'], +]); +``` +| Operator | Effect | +|---|---| +| `$set` | sets the field to the value | +| `$setOnInsert` | sets the field only when an upsert inserts a new document | +| `$unset` | removes the field | +| `$rename` | renames the field | +| `$inc` | increments the field by the value | +| `$mul` | multiplies the field by the value | +| `$min` | sets the field only if the value is smaller | +| `$max` | sets the field only if the value is larger | +| `$currentDate` | sets the field to the current date | + +```php +$collection->updateOne(['_id' => '1'], [ + '$inc' => ['profile.stats.score' => 5], + '$mul' => ['profile.stats.multiplier' => 2], + '$min' => ['lowest' => 10], + '$max' => ['highest' => 90], + '$currentDate' => ['updatedAt' => true], +]); +``` +## Array operators + +Array operators modify list fields in place. `$push` appends, `$pull` removes by match, `$addToSet` appends only if absent, and `$pop` removes from an end: + +```php +$collection->updateOne(['_id' => '1'], [ + '$push' => ['tags' => 'mongodb'], + '$addToSet' => ['roles' => 'admin'], +]); + +$collection->updateOne(['_id' => '1'], [ + '$pull' => ['tags' => 'deprecated'], + '$pop' => ['history' => 1], // 1 removes the last, -1 the first +]); +``` +Use `$each` with `$push` to append several values at once: + +```php +$collection->updateOne(['_id' => '1'], [ + '$push' => ['tags' => ['$each' => ['php', 'postgres']]], +]); +``` +## Bitwise operator + +`$bit` applies a bitwise `and`, `or`, or `xor` to an integer field: + +```php +$collection->updateOne(['_id' => '1'], [ + '$bit' => ['flags' => ['or' => 4]], +]); +``` +## Upsert and setOnInsert + +When you combine the `upsert` option with `$setOnInsert`, the extra fields are only written if a new document is created: + +```php +$collection->updateOne( + ['_id' => 'user-42'], + [ + '$set' => ['name' => 'New User'], + '$setOnInsert' => ['createdAt' => '2026-01-01'], + ], + ['upsert' => true], +); +``` +:::warning +Upsert needs `_id` in the filter so Rango can build the primary key for the inserted document. +::: + +## Learn more + +* [How to select the documents to update with query operators](querying.md) +* [How to apply many updates in one transaction with bulk write](crud-operations.md#bulk-writes) +* [How to replace or atomically modify documents](crud-operations.md) diff --git a/tools/composer.json b/tools/composer.json new file mode 100644 index 0000000..ff61614 --- /dev/null +++ b/tools/composer.json @@ -0,0 +1,6 @@ +{ + "require": { + "php": "~8.5.0", + "roave/backward-compatibility-check": "^8.21.0" + } +} diff --git a/tools/composer.lock b/tools/composer.lock new file mode 100644 index 0000000..6992538 --- /dev/null +++ b/tools/composer.lock @@ -0,0 +1,4680 @@ +{ + "_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": "4705d21107ae6e4e393cde5a3787e0b1", + "packages": [ + { + "name": "beberlei/assert", + "version": "v3.3.4", + "source": { + "type": "git", + "url": "https://github.com/beberlei/assert.git", + "reference": "f193f4613c7d7fbcee2c05e4daff4061d49c040e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/beberlei/assert/zipball/f193f4613c7d7fbcee2c05e4daff4061d49c040e", + "reference": "f193f4613c7d7fbcee2c05e4daff4061d49c040e", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "*", + "phpstan/phpstan": "*", + "phpunit/phpunit": ">=6.0.0", + "yoast/phpunit-polyfills": "^0.1.0" + }, + "suggest": { + "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles" + }, + "type": "library", + "autoload": { + "files": [ + "lib/Assert/functions.php" + ], + "psr-4": { + "Assert\\": "lib/Assert" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de", + "role": "Lead Developer" + }, + { + "name": "Richard Quadling", + "email": "rquadling@gmail.com", + "role": "Collaborator" + } + ], + "description": "Thin assertion library for input validation in business models.", + "keywords": [ + "assert", + "assertion", + "validation" + ], + "support": { + "issues": "https://github.com/beberlei/assert/issues", + "source": "https://github.com/beberlei/assert/tree/v3.3.4" + }, + "time": "2026-06-10T19:47:05+00:00" + }, + { + "name": "composer/ca-bundle", + "version": "1.5.12", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "00a2f4201641d5c53f7fc0195e6c8d9fcc321a78" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/00a2f4201641d5c53f7fc0195e6c8d9fcc321a78", + "reference": "00a2f4201641d5c53f7fc0195e6c8d9fcc321a78", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8 || ^9", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/ca-bundle/issues", + "source": "https://github.com/composer/ca-bundle/tree/1.5.12" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-05-19T11:26:22+00:00" + }, + { + "name": "composer/class-map-generator", + "version": "1.7.3", + "source": { + "type": "git", + "url": "https://github.com/composer/class-map-generator.git", + "reference": "86d8208fc3c649a3a999daf1a63c25201be2990f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/86d8208fc3c649a3a999daf1a63c25201be2990f", + "reference": "86d8208fc3c649a3a999daf1a63c25201be2990f", + "shasum": "" + }, + "require": { + "composer/pcre": "^2.1 || ^3.1", + "php": "^7.2 || ^8.0", + "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7 || ^8" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", + "phpunit/phpunit": "^8", + "symfony/filesystem": "^5.4 || ^6 || ^7 || ^8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\ClassMapGenerator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Utilities to scan PHP code and generate class maps.", + "keywords": [ + "classmap" + ], + "support": { + "issues": "https://github.com/composer/class-map-generator/issues", + "source": "https://github.com/composer/class-map-generator/tree/1.7.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-05-05T09:17:07+00:00" + }, + { + "name": "composer/composer", + "version": "2.10.1", + "source": { + "type": "git", + "url": "https://github.com/composer/composer.git", + "reference": "4120703b9bda8795075047b40361d7ec4d2abe49" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/composer/zipball/4120703b9bda8795075047b40361d7ec4d2abe49", + "reference": "4120703b9bda8795075047b40361d7ec4d2abe49", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.5", + "composer/class-map-generator": "^1.4.0", + "composer/metadata-minifier": "^1.0", + "composer/pcre": "^2.3 || ^3.3", + "composer/semver": "^3.3", + "composer/spdx-licenses": "^1.5.7", + "composer/xdebug-handler": "^2.0.2 || ^3.0.3", + "ext-json": "*", + "justinrainbow/json-schema": "^6.5.1", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "react/promise": "^3.3", + "seld/jsonlint": "^1.4", + "seld/phar-utils": "^1.2", + "seld/signal-handler": "^2.0", + "symfony/console": "^5.4.47 || ^6.4.25 || ^7.1.10 || ^8.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.1.10 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.1.10 || ^8.0", + "symfony/polyfill-php73": "^1.24", + "symfony/polyfill-php80": "^1.24", + "symfony/polyfill-php81": "^1.24", + "symfony/polyfill-php84": "^1.30", + "symfony/process": "^5.4.47 || ^6.4.25 || ^7.1.10 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11.8", + "phpstan/phpstan-deprecation-rules": "^1.2.0", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-strict-rules": "^1.6.0", + "phpstan/phpstan-symfony": "^1.4.0", + "symfony/phpunit-bridge": "^6.4.25 || ^7.3.3 || ^8.0" + }, + "suggest": { + "ext-curl": "Provides HTTP support (will fallback to PHP streams if missing)", + "ext-openssl": "Enables access to repositories and packages over HTTPS", + "ext-zip": "Allows direct extraction of ZIP archives (unzip/7z binaries will be used instead if available)", + "ext-zlib": "Enables gzip for HTTP requests" + }, + "bin": [ + "bin/composer" + ], + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "phpstan/rules.neon" + ] + }, + "branch-alias": { + "dev-main": "2.10-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\": "src/Composer/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "https://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", + "homepage": "https://getcomposer.org/", + "keywords": [ + "autoload", + "dependency", + "package" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/composer/issues", + "security": "https://github.com/composer/composer/security/policy", + "source": "https://github.com/composer/composer/tree/2.10.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-06-04T08:25:59+00:00" + }, + { + "name": "composer/metadata-minifier", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/metadata-minifier.git", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/c549d23829536f0d0e984aaabbf02af91f443207", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "composer/composer": "^2", + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\MetadataMinifier\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Small utility library that handles metadata minification and expansion.", + "keywords": [ + "composer", + "compression" + ], + "support": { + "issues": "https://github.com/composer/metadata-minifier/issues", + "source": "https://github.com/composer/metadata-minifier/tree/1.0.0" + }, + "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": "2021-04-07T13:37:33+00:00" + }, + { + "name": "composer/pcre", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<2.2.2" + }, + "require-dev": { + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-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.4.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-06-07T11:47:49+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.6.0", + "source": { + "type": "git", + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "5ecd0cb4177696f9fd48f1605dda81db3dee7889" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/5ecd0cb4177696f9fd48f1605dda81db3dee7889", + "reference": "5ecd0cb4177696f9fd48f1605dda81db3dee7889", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^6.4.25 || ^7.3.3 || ^8.0" + }, + "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.6.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-04-08T20:18:39+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": "jetbrains/phpstorm-stubs", + "version": "v2026.1", + "source": { + "type": "git", + "url": "https://github.com/JetBrains/phpstorm-stubs", + "reference": "2cdd054c4109dfb76667c9198bf9427606354243" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/2cdd054c4109dfb76667c9198bf9427606354243", + "reference": "2cdd054c4109dfb76667c9198bf9427606354243", + "shasum": "" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v3.86", + "nikic/php-parser": "^v5.6", + "phpdocumentor/reflection-docblock": "^5.6", + "phpunit/phpunit": "^12.3" + }, + "type": "library", + "autoload": { + "files": [ + "PhpStormStubsMap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "PHP runtime & extensions header files for PhpStorm", + "homepage": "https://www.jetbrains.com/phpstorm", + "keywords": [ + "autocomplete", + "code", + "inference", + "inspection", + "jetbrains", + "phpstorm", + "stubs", + "type" + ], + "time": "2026-02-19T20:12:01+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "6.9.0", + "source": { + "type": "git", + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "bd1bda2ebfc8bff418565941771ea8f03c557886" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/bd1bda2ebfc8bff418565941771ea8f03c557886", + "reference": "bd1bda2ebfc8bff418565941771ea8f03c557886", + "shasum": "" + }, + "require": { + "ext-json": "*", + "marc-mabe/php-enum": "^4.4", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.3.0", + "json-schema/json-schema-test-suite": "^23.2", + "marc-mabe/php-enum-phpstan": "^2.0", + "phpspec/prophecy": "^1.19", + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^8.5" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/jsonrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/6.9.0" + }, + "time": "2026-06-05T14:05:24+00:00" + }, + { + "name": "marc-mabe/php-enum", + "version": "v4.7.2", + "source": { + "type": "git", + "url": "https://github.com/marc-mabe/php-enum.git", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/bb426fcdd65c60fb3638ef741e8782508fda7eef", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef", + "shasum": "" + }, + "require": { + "ext-reflection": "*", + "php": "^7.1 | ^8.0" + }, + "require-dev": { + "phpbench/phpbench": "^0.16.10 || ^1.0.4", + "phpstan/phpstan": "^1.3.1", + "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", + "vimeo/psalm": "^4.17.0 | ^5.26.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.2-dev", + "dev-master": "4.7-dev" + } + }, + "autoload": { + "psr-4": { + "MabeEnum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marc Bennewitz", + "email": "dev@mabe.berlin", + "homepage": "https://mabe.berlin/", + "role": "Lead" + } + ], + "description": "Simple and fast implementation of enumerations with native PHP", + "homepage": "https://github.com/marc-mabe/php-enum", + "keywords": [ + "enum", + "enum-map", + "enum-set", + "enumeration", + "enumerator", + "enummap", + "enumset", + "map", + "set", + "type", + "type-hint", + "typehint" + ], + "support": { + "issues": "https://github.com/marc-mabe/php-enum/issues", + "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.2" + }, + "time": "2025-09-14T11:18:39+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "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.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" + }, + { + "name": "nikolaposa/version", + "version": "4.2.1", + "source": { + "type": "git", + "url": "https://github.com/nikolaposa/version.git", + "reference": "2b9ee2f0b09333b6ce00bd6b63132cdf1d7a1428" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikolaposa/version/zipball/2b9ee2f0b09333b6ce00bd6b63132cdf1d7a1428", + "reference": "2b9ee2f0b09333b6ce00bd6b63132cdf1d7a1428", + "shasum": "" + }, + "require": { + "beberlei/assert": "^3.2", + "php": "^8.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.44", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-beberlei-assert": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Version\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nikola Poša", + "email": "posa.nikola@gmail.com", + "homepage": "https://www.nikolaposa.in.rs" + } + ], + "description": "Value Object that represents a SemVer-compliant version number.", + "homepage": "https://github.com/nikolaposa/version", + "keywords": [ + "semantic", + "semver", + "version", + "versioning" + ], + "support": { + "issues": "https://github.com/nikolaposa/version/issues", + "source": "https://github.com/nikolaposa/version/tree/4.2.1" + }, + "time": "2025-03-24T19:12:02+00:00" + }, + { + "name": "ocramius/package-versions", + "version": "2.12.0", + "source": { + "type": "git", + "url": "https://github.com/Ocramius/PackageVersions.git", + "reference": "18b02a63e837246e812cae72e211db32d7980019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/18b02a63e837246e812cae72e211db32d7980019", + "reference": "18b02a63e837246e812cae72e211db32d7980019", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2.0", + "php": "~8.4.0 || ~8.5.0" + }, + "replace": { + "composer/package-versions-deprecated": "*" + }, + "require-dev": { + "composer/composer": "^2.9.8", + "doctrine/coding-standard": "^14.0.0", + "ext-zip": "^1.15.0", + "phpunit/phpunit": "^13.1.11", + "psalm/plugin-phpunit": "^0.19.7", + "roave/infection-static-analysis-plugin": "^1.44.0", + "vimeo/psalm": "^6.16.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "Provides efficient querying for installed package versions (no runtime IO)", + "support": { + "issues": "https://github.com/Ocramius/PackageVersions/issues", + "source": "https://github.com/Ocramius/PackageVersions/tree/2.12.0" + }, + "funding": [ + { + "url": "https://github.com/Ocramius", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ocramius/package-versions", + "type": "tidelift" + } + ], + "time": "2026-05-21T19:52:53+00:00" + }, + { + "name": "php-standard-library/async", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/async.git", + "reference": "8c0c63d3e304318a7c78846044edc1dfef398d1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/async/zipball/8c0c63d3e304318a7c78846044edc1dfef398d1d", + "reference": "8c0c63d3e304318a7c78846044edc1dfef398d1d", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/date-time": "^6.0", + "php-standard-library/default": "^6.0", + "php-standard-library/foundation": "^6.0", + "php-standard-library/promise": "^6.0", + "revolt/event-loop": "^1.0.8" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "php-standard-library/dict": "^6.0", + "php-standard-library/result": "^6.0", + "php-standard-library/str": "^6.0", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\Async\\": "src/Psl/Async/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Fiber-based structured concurrency using cooperative multitasking", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "Fibers", + "async", + "concurrency", + "cooperative-multitasking" + ], + "support": { + "source": "https://github.com/php-standard-library/async/tree/6.2.1" + }, + "time": "2026-05-23T20:26:52+00:00" + }, + { + "name": "php-standard-library/channel", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/channel.git", + "reference": "1d90ea4e262978a583ad24c2c9a97c9daa26b6f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/channel/zipball/1d90ea4e262978a583ad24c2c9a97c9daa26b6f1", + "reference": "1d90ea4e262978a583ad24c2c9a97c9daa26b6f1", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/async": "^6.0", + "php-standard-library/foundation": "^6.0", + "revolt/event-loop": "^1.0.8" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "php-standard-library/date-time": "^6.0", + "php-standard-library/file": "^6.0", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\Channel\\": "src/Psl/Channel/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Message-passing channels for async communication, inspired by Go and Rust", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "async", + "channel", + "concurrency", + "message-passing" + ], + "support": { + "source": "https://github.com/php-standard-library/channel/tree/6.2.1" + }, + "time": "2026-03-28T18:39:47+00:00" + }, + { + "name": "php-standard-library/collection", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/collection.git", + "reference": "0e8b757a16ccb1f68a70f4ada8565099743d50b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/collection/zipball/0e8b757a16ccb1f68a70f4ada8565099743d50b9", + "reference": "0e8b757a16ccb1f68a70f4ada8565099743d50b9", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/default": "^6.0", + "php-standard-library/foundation": "^6.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "php-standard-library/str": "^6.0", + "php-standard-library/vec": "^6.0", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psl\\Collection\\": "src/Psl/Collection/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Generic, object-oriented Vector, Map, and Set collections with immutable and mutable variants", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "collection", + "generics", + "map", + "set", + "vector" + ], + "support": { + "source": "https://github.com/php-standard-library/collection/tree/6.2.1" + }, + "time": "2026-05-18T22:19:21+00:00" + }, + { + "name": "php-standard-library/comparison", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/comparison.git", + "reference": "1884e2218c231c285b6039e9b2010f5dd956ae20" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/comparison/zipball/1884e2218c231c285b6039e9b2010f5dd956ae20", + "reference": "1884e2218c231c285b6039e9b2010f5dd956ae20", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/default": "^6.0", + "php-standard-library/foundation": "^6.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\Comparison\\": "src/Psl/Comparison/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Interfaces and functions for type-safe, consistent value comparison", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "comparable", + "comparison", + "ordering" + ], + "support": { + "source": "https://github.com/php-standard-library/comparison/tree/6.2.1" + }, + "time": "2026-03-28T18:39:47+00:00" + }, + { + "name": "php-standard-library/date-time", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/date-time.git", + "reference": "1e5e2b51eb2b27c2933859872e7c4a45abc5226e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/date-time/zipball/1e5e2b51eb2b27c2933859872e7c4a45abc5226e", + "reference": "1e5e2b51eb2b27c2933859872e7c4a45abc5226e", + "shasum": "" + }, + "require": { + "ext-intl": "*", + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/comparison": "^6.0", + "php-standard-library/default": "^6.0", + "php-standard-library/foundation": "^6.0", + "php-standard-library/interoperability": "^6.0", + "php-standard-library/locale": "^6.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "php-standard-library/json": "^6.0", + "php-standard-library/math": "^6.0", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\DateTime\\": "src/Psl/DateTime/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Immutable, timezone-aware date and time types with Duration, Period, and Interval", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "datetime", + "duration", + "immutable", + "timezone" + ], + "support": { + "source": "https://github.com/php-standard-library/date-time/tree/6.2.1" + }, + "time": "2026-05-23T20:26:52+00:00" + }, + { + "name": "php-standard-library/default", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/default.git", + "reference": "89f05ec6e6a29e8c07de7b6755d14d05b06048e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/default/zipball/89f05ec6e6a29e8c07de7b6755d14d05b06048e2", + "reference": "89f05ec6e6a29e8c07de7b6755d14d05b06048e2", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psl\\Default\\": "src/Psl/Default/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "DefaultInterface for classes to provide standardized default instances", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "default", + "interface" + ], + "support": { + "source": "https://github.com/php-standard-library/default/tree/6.2.1" + }, + "time": "2026-03-28T18:39:47+00:00" + }, + { + "name": "php-standard-library/dict", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/dict.git", + "reference": "e06a4b7dea0f870b909e9f7d81d9dc70395f0eeb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/dict/zipball/e06a4b7dea0f870b909e9f7d81d9dc70395f0eeb", + "reference": "e06a4b7dea0f870b909e9f7d81d9dc70395f0eeb", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/foundation": "^6.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "php-standard-library/collection": "^6.0", + "php-standard-library/iter": "^6.0", + "php-standard-library/str": "^6.0", + "php-standard-library/vec": "^6.0", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\Dict\\": "src/Psl/Dict/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Functions for creating and transforming associative arrays with preserved keys", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "Associative", + "Dict", + "array", + "map" + ], + "support": { + "source": "https://github.com/php-standard-library/dict/tree/6.2.1" + }, + "time": "2026-03-31T16:59:27+00:00" + }, + { + "name": "php-standard-library/either-or-both", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/either-or-both.git", + "reference": "a1f4d80ee5ee616272688941390d2eae2d1948e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/either-or-both/zipball/a1f4d80ee5ee616272688941390d2eae2d1948e6", + "reference": "a1f4d80ee5ee616272688941390d2eae2d1948e6", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/comparison": "^6.0", + "php-standard-library/foundation": "^6.0", + "php-standard-library/option": "^6.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "php-standard-library/str": "^6.0", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\EitherOrBoth\\": "src/Psl/EitherOrBoth/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Three-variant disjoint union type (Left/Right/Both) for values that may be present on either or both of two sides", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "both", + "either", + "either-or-both", + "these", + "union-type" + ], + "support": { + "source": "https://github.com/php-standard-library/either-or-both/tree/6.2.1" + }, + "time": "2026-05-23T20:26:52+00:00" + }, + { + "name": "php-standard-library/env", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/env.git", + "reference": "1c6d72eef53e904506daa021141a5981a30c4403" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/env/zipball/1c6d72eef53e904506daa021141a5981a30c4403", + "reference": "1c6d72eef53e904506daa021141a5981a30c4403", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/foundation": "^6.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "php-standard-library/filesystem": "^6.0", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\Env\\": "src/Psl/Env/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Functions for inspecting and modifying environment variables, working directory, and paths", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "env", + "environment" + ], + "support": { + "source": "https://github.com/php-standard-library/env/tree/6.2.1" + }, + "time": "2026-03-31T16:59:27+00:00" + }, + { + "name": "php-standard-library/file", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/file.git", + "reference": "64c064f8a599cb6621e7b9329a4f1b7c5bc04152" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/file/zipball/64c064f8a599cb6621e7b9329a4f1b7c5bc04152", + "reference": "64c064f8a599cb6621e7b9329a4f1b7c5bc04152", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/async": "^6.0", + "php-standard-library/date-time": "^6.0", + "php-standard-library/foundation": "^6.0", + "php-standard-library/io": "^6.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "php-standard-library/env": "^6.0", + "php-standard-library/filesystem": "^6.0", + "php-standard-library/os": "^6.0", + "php-standard-library/str": "^6.0", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\File\\": "src/Psl/File/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Typed file handles for reading and writing with write modes and advisory locking", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "file", + "handle", + "io" + ], + "support": { + "source": "https://github.com/php-standard-library/file/tree/6.2.1" + }, + "time": "2026-04-06T03:33:20+00:00" + }, + { + "name": "php-standard-library/filesystem", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/filesystem.git", + "reference": "d1e87eaee4d8180842f38d010747ee5d963a8d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/filesystem/zipball/d1e87eaee4d8180842f38d010747ee5d963a8d5b", + "reference": "d1e87eaee4d8180842f38d010747ee5d963a8d5b", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/foundation": "^6.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "php-standard-library/env": "^6.0", + "php-standard-library/file": "^6.0", + "php-standard-library/os": "^6.0", + "php-standard-library/str": "^6.0", + "php-standard-library/type": "^6.0", + "php-standard-library/vec": "^6.0", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\Filesystem\\": "src/Psl/Filesystem/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Type-safe functions for file system operations with proper exception handling", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "file-system", + "filesystem" + ], + "support": { + "source": "https://github.com/php-standard-library/filesystem/tree/6.2.1" + }, + "time": "2026-03-31T16:59:27+00:00" + }, + { + "name": "php-standard-library/foundation", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/foundation.git", + "reference": "7f65cec48c8ed3d53dc8dd7643796e2ba6de6008" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/foundation/zipball/7f65cec48c8ed3d53dc8dd7643796e2ba6de6008", + "reference": "7f65cec48c8ed3d53dc8dd7643796e2ba6de6008", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\": "src/Psl/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Exceptions, Ref, and invariant functions", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "exceptions", + "foundation", + "invariant" + ], + "support": { + "source": "https://github.com/php-standard-library/foundation/tree/6.2.1" + }, + "time": "2026-04-28T06:28:49+00:00" + }, + { + "name": "php-standard-library/interoperability", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/interoperability.git", + "reference": "09ae66cc3e6463538c3a4592bb8762cf60113821" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/interoperability/zipball/09ae66cc3e6463538c3a4592bb8762cf60113821", + "reference": "09ae66cc3e6463538c3a4592bb8762cf60113821", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psl\\Interoperability\\": "src/Psl/Interoperability/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Interfaces for converting between PSL types and PHP stdlib/intl equivalents", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "conversion", + "interoperability" + ], + "support": { + "source": "https://github.com/php-standard-library/interoperability/tree/6.2.1" + }, + "time": "2026-03-28T18:39:47+00:00" + }, + { + "name": "php-standard-library/io", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/io.git", + "reference": "0b61f6b5e0a392c52d3cb2b386d8bba379b91f3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/io/zipball/0b61f6b5e0a392c52d3cb2b386d8bba379b91f3c", + "reference": "0b61f6b5e0a392c52d3cb2b386d8bba379b91f3c", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/async": "^6.0", + "php-standard-library/channel": "^6.0", + "php-standard-library/foundation": "^6.0", + "php-standard-library/result": "^6.0", + "revolt/event-loop": "^1.0.8" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "php-standard-library/date-time": "^6.0", + "php-standard-library/os": "^6.0", + "php-standard-library/str": "^6.0", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\IO\\": "src/Psl/IO/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Handle-based I/O abstractions - composable, testable, and async-ready", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "async", + "handle", + "io", + "stream" + ], + "support": { + "source": "https://github.com/php-standard-library/io/tree/6.2.1" + }, + "time": "2026-04-28T03:11:45+00:00" + }, + { + "name": "php-standard-library/iter", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/iter.git", + "reference": "d3f2db3adec4cbe5129f3428969ae8508a54aeb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/iter/zipball/d3f2db3adec4cbe5129f3428969ae8508a54aeb7", + "reference": "d3f2db3adec4cbe5129f3428969ae8508a54aeb7", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/comparison": "^6.0", + "php-standard-library/either-or-both": "^6.0", + "php-standard-library/foundation": "^6.0", + "php-standard-library/option": "^6.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "php-standard-library/collection": "^6.0", + "php-standard-library/dict": "^6.0", + "php-standard-library/math": "^6.0", + "php-standard-library/vec": "^6.0", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\Iter\\": "src/Psl/Iter/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Utility functions for inspecting and reducing iterables - arrays, generators, and iterators", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "generator", + "iter", + "iterable", + "iterator" + ], + "support": { + "source": "https://github.com/php-standard-library/iter/tree/6.2.1" + }, + "time": "2026-04-28T06:28:49+00:00" + }, + { + "name": "php-standard-library/json", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/json.git", + "reference": "03e00062efdd704d874012b5412340f3003a043d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/json/zipball/03e00062efdd704d874012b5412340f3003a043d", + "reference": "03e00062efdd704d874012b5412340f3003a043d", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/foundation": "^6.0", + "php-standard-library/type": "^6.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "php-standard-library/collection": "^6.0", + "php-standard-library/math": "^6.0", + "php-standard-library/str": "^6.0", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\Json\\": "src/Psl/Json/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "JSON encoding and decoding with typed exceptions and sensible defaults", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "decoding", + "encoding", + "json" + ], + "support": { + "source": "https://github.com/php-standard-library/json/tree/6.2.1" + }, + "time": "2026-03-28T18:39:47+00:00" + }, + { + "name": "php-standard-library/locale", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/locale.git", + "reference": "0771cfb19757e8923c66b7f46ab90280775caecd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/locale/zipball/0771cfb19757e8923c66b7f46ab90280775caecd", + "reference": "0771cfb19757e8923c66b7f46ab90280775caecd", + "shasum": "" + }, + "require": { + "ext-intl": "*", + "php": "~8.4.0 || ~8.5.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "php-standard-library/str": "^6.0", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psl\\Locale\\": "src/Psl/Locale/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Backed enum with 700+ locale identifiers for type-safe internationalization", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "i18n", + "internationalization", + "locale" + ], + "support": { + "source": "https://github.com/php-standard-library/locale/tree/6.2.1" + }, + "time": "2026-03-28T18:39:47+00:00" + }, + { + "name": "php-standard-library/option", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/option.git", + "reference": "afe2059dbb3a9e2e3ba6edd371ccbb7d87355a66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/option/zipball/afe2059dbb3a9e2e3ba6edd371ccbb7d87355a66", + "reference": "afe2059dbb3a9e2e3ba6edd371ccbb7d87355a66", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/comparison": "^6.0", + "php-standard-library/foundation": "^6.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\Option\\": "src/Psl/Option/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Option type (Some/None) replacing nullable types with explicit presence semantics", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "monad", + "none", + "option", + "some" + ], + "support": { + "source": "https://github.com/php-standard-library/option/tree/6.2.1" + }, + "time": "2026-05-23T20:26:52+00:00" + }, + { + "name": "php-standard-library/process", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/process.git", + "reference": "ab41e8b8de07289971f447ce289ec1effde321f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/process/zipball/ab41e8b8de07289971f447ce289ec1effde321f3", + "reference": "ab41e8b8de07289971f447ce289ec1effde321f3", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/async": "^6.0", + "php-standard-library/date-time": "^6.0", + "php-standard-library/foundation": "^6.0", + "php-standard-library/io": "^6.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "php-standard-library/env": "^6.0", + "php-standard-library/filesystem": "^6.0", + "php-standard-library/os": "^6.0", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psl\\Process\\": "src/Psl/Process/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Typed, non-blocking API for spawning and managing child processes", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "async", + "process", + "subprocess" + ], + "support": { + "source": "https://github.com/php-standard-library/process/tree/6.2.1" + }, + "time": "2026-05-23T20:46:16+00:00" + }, + { + "name": "php-standard-library/promise", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/promise.git", + "reference": "a38a2694e06609874e856e68951bca24ad00009c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/promise/zipball/a38a2694e06609874e856e68951bca24ad00009c", + "reference": "a38a2694e06609874e856e68951bca24ad00009c", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psl\\Promise\\": "src/Psl/Promise/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Promise interface for deferred computations - resolved or rejected", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "async", + "deferred", + "promise" + ], + "support": { + "source": "https://github.com/php-standard-library/promise/tree/6.2.1" + }, + "time": "2026-04-26T16:26:10+00:00" + }, + { + "name": "php-standard-library/range", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/range.git", + "reference": "34befd85fa3e048972aabac7cdf6a86e2f4b833f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/range/zipball/34befd85fa3e048972aabac7cdf6a86e2f4b833f", + "reference": "34befd85fa3e048972aabac7cdf6a86e2f4b833f", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/foundation": "^6.0", + "php-standard-library/iter": "^6.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "php-standard-library/math": "^6.0", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\Range\\": "src/Psl/Range/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Range types for integer sequences with iteration support", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "iterator", + "range" + ], + "support": { + "source": "https://github.com/php-standard-library/range/tree/6.2.1" + }, + "time": "2026-03-28T18:39:47+00:00" + }, + { + "name": "php-standard-library/regex", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/regex.git", + "reference": "150533c75ee7aaf83a1254837690603b22bc0191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/regex/zipball/150533c75ee7aaf83a1254837690603b22bc0191", + "reference": "150533c75ee7aaf83a1254837690603b22bc0191", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/foundation": "^6.0", + "php-standard-library/type": "^6.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\Regex\\": "src/Psl/Regex/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Type-safe regular expressions with typed capture groups and proper error handling", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "preg", + "regex", + "regular-expression" + ], + "support": { + "source": "https://github.com/php-standard-library/regex/tree/6.2.1" + }, + "time": "2026-05-23T20:26:52+00:00" + }, + { + "name": "php-standard-library/result", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/result.git", + "reference": "242bbf872662f5091a37d7db9d45f119225a01f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/result/zipball/242bbf872662f5091a37d7db9d45f119225a01f5", + "reference": "242bbf872662f5091a37d7db9d45f119225a01f5", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/foundation": "^6.0", + "php-standard-library/promise": "^6.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "php-standard-library/fun": "^6.0", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\Result\\": "src/Psl/Result/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Result type capturing success or failure as a value for controlled error handling", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "error-handling", + "monad", + "result" + ], + "support": { + "source": "https://github.com/php-standard-library/result/tree/6.2.1" + }, + "time": "2026-04-26T16:26:10+00:00" + }, + { + "name": "php-standard-library/shell", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/shell.git", + "reference": "6d57e1192d0ecce76e8c33e7507505b03c1bc539" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/shell/zipball/6d57e1192d0ecce76e8c33e7507505b03c1bc539", + "reference": "6d57e1192d0ecce76e8c33e7507505b03c1bc539", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/async": "^6.0", + "php-standard-library/default": "^6.0", + "php-standard-library/foundation": "^6.0", + "php-standard-library/process": "^6.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "php-standard-library/date-time": "^6.0", + "php-standard-library/env": "^6.0", + "php-standard-library/os": "^6.0", + "php-standard-library/secure-random": "^6.0", + "php-standard-library/str": "^6.0", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\Shell\\": "src/Psl/Shell/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Shell command execution with argument escaping and error output management", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "command", + "execute", + "shell" + ], + "support": { + "source": "https://github.com/php-standard-library/shell/tree/6.2.1" + }, + "time": "2026-05-23T20:26:52+00:00" + }, + { + "name": "php-standard-library/str", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/str.git", + "reference": "e88e7c79e1c07227aa3c50005a3a91e88425e56a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/str/zipball/e88e7c79e1c07227aa3c50005a3a91e88425e56a", + "reference": "e88e7c79e1c07227aa3c50005a3a91e88425e56a", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/default": "^6.0", + "php-standard-library/foundation": "^6.0", + "php-standard-library/range": "^6.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\Str\\": "src/Psl/Str/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Unicode-aware string functions replacing PHP mb_* and standard string functions", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "mbstring", + "string", + "unicode", + "utf8" + ], + "support": { + "source": "https://github.com/php-standard-library/str/tree/6.2.1" + }, + "time": "2026-04-29T06:23:35+00:00" + }, + { + "name": "php-standard-library/type", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/type.git", + "reference": "ffc050e32bc5def0e03aaae3d52c78dfc746e143" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/type/zipball/ffc050e32bc5def0e03aaae3d52c78dfc746e143", + "reference": "ffc050e32bc5def0e03aaae3d52c78dfc746e143", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/collection": "^6.0", + "php-standard-library/foundation": "^6.0", + "php-standard-library/iter": "^6.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "php-standard-library/dict": "^6.0", + "php-standard-library/math": "^6.0", + "php-standard-library/result": "^6.0", + "php-standard-library/str": "^6.0", + "php-standard-library/vec": "^6.0", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\Type\\": "src/Psl/Type/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Runtime type validation implementing Parse, Don't Validate - coerce and assert unstructured input into well-typed data", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "coercion", + "type", + "type-safety", + "validation" + ], + "support": { + "source": "https://github.com/php-standard-library/type/tree/6.2.1" + }, + "time": "2026-05-23T20:26:52+00:00" + }, + { + "name": "php-standard-library/vec", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/vec.git", + "reference": "ac119fa952177c10eeb3d5d6bca04130491c7f2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/vec/zipball/ac119fa952177c10eeb3d5d6bca04130491c7f2e", + "reference": "ac119fa952177c10eeb3d5d6bca04130491c7f2e", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/foundation": "^6.0" + }, + "conflict": { + "azjezz/psl": "*" + }, + "require-dev": { + "php-standard-library/collection": "^6.0", + "php-standard-library/fun": "^6.0", + "php-standard-library/iter": "^6.0", + "php-standard-library/str": "^6.0", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-next": "6.2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psl/bootstrap.php" + ], + "psr-4": { + "Psl\\Vec\\": "src/Psl/Vec/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Seifeddine Gmati", + "email": "azjezz@carthage.software" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-standard-library/php-standard-library/graphs/contributors" + } + ], + "description": "Functions for creating and transforming sequential, 0-indexed arrays (lists)", + "homepage": "https://php-standard-library.dev", + "keywords": [ + "array", + "list", + "vec", + "vector" + ], + "support": { + "source": "https://github.com/php-standard-library/vec/tree/6.2.1" + }, + "time": "2026-03-28T18:39:47+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": "react/promise", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.12.28 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-08-19T18:57:03+00:00" + }, + { + "name": "revolt/event-loop", + "version": "v1.0.9", + "source": { + "type": "git", + "url": "https://github.com/revoltphp/event-loop.git", + "reference": "44061cf513e53c6200372fc935ac42271566295d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/44061cf513e53c6200372fc935ac42271566295d", + "reference": "44061cf513e53c6200372fc935ac42271566295d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^9", + "psalm/phar": "6.16.*" + }, + "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.9" + }, + "time": "2026-05-16T17:55:38+00:00" + }, + { + "name": "roave/backward-compatibility-check", + "version": "8.21.0", + "source": { + "type": "git", + "url": "https://github.com/Roave/BackwardCompatibilityCheck.git", + "reference": "ee85fb8879cfe939814d84e18ec6cb5c33904b75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Roave/BackwardCompatibilityCheck/zipball/ee85fb8879cfe939814d84e18ec6cb5c33904b75", + "reference": "ee85fb8879cfe939814d84e18ec6cb5c33904b75", + "shasum": "" + }, + "require": { + "composer/composer": "^2.9.8", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-simplexml": "*", + "nikic/php-parser": "^5.7.0", + "nikolaposa/version": "^4.2.1", + "ocramius/package-versions": "^2.11.0", + "php": "~8.4.0 || ~8.5.0", + "php-standard-library/async": "^6.1.1", + "php-standard-library/dict": "^6.1.1", + "php-standard-library/env": "^6.1.1", + "php-standard-library/file": "^6.1.1", + "php-standard-library/filesystem": "^6.1.1", + "php-standard-library/foundation": "^6.1.1", + "php-standard-library/iter": "^6.1.1", + "php-standard-library/json": "^6.1.1", + "php-standard-library/regex": "^6.1.1", + "php-standard-library/shell": "^6.1.1", + "php-standard-library/str": "^6.1.1", + "php-standard-library/type": "^6.1.1", + "php-standard-library/vec": "^6.1.1", + "roave/better-reflection": "^6.71.0", + "symfony/console": "^7.4.11" + }, + "conflict": { + "marc-mabe/php-enum": "<4.7.2", + "revolt/event-loop": "<0.2.5", + "symfony/process": "<5.3.7" + }, + "require-dev": { + "doctrine/coding-standard": "^14.0.0", + "justinrainbow/json-schema": "^6.8.2", + "php-standard-library/hash": "^6.1.1", + "php-standard-library/psalm-plugin": "^2.4.0", + "php-standard-library/secure-random": "^6.1.1", + "phpunit/phpunit": "^13.1.10", + "psalm/plugin-phpunit": "^0.19.7", + "roave/infection-static-analysis-plugin": "^1.44.0", + "roave/security-advisories": "dev-master", + "squizlabs/php_codesniffer": "^4.0.1", + "vimeo/psalm": "^6.16.1" + }, + "bin": [ + "bin/roave-backward-compatibility-check" + ], + "type": "library", + "autoload": { + "psr-4": { + "Roave\\BackwardCompatibility\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "James Titcumb", + "email": "james@asgrim.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "Tool to compare two revisions of a public API to check for BC breaks", + "support": { + "issues": "https://github.com/Roave/BackwardCompatibilityCheck/issues", + "source": "https://github.com/Roave/BackwardCompatibilityCheck/tree/8.21.0" + }, + "time": "2026-05-15T14:19:15+00:00" + }, + { + "name": "roave/better-reflection", + "version": "6.71.0", + "source": { + "type": "git", + "url": "https://github.com/Roave/BetterReflection.git", + "reference": "3ec176d4a161b4e8764edc625d69ff624ca390b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Roave/BetterReflection/zipball/3ec176d4a161b4e8764edc625d69ff624ca390b1", + "reference": "3ec176d4a161b4e8764edc625d69ff624ca390b1", + "shasum": "" + }, + "require": { + "ext-json": "*", + "jetbrains/phpstorm-stubs": "2026.1", + "nikic/php-parser": "^5.7.0", + "php": "~8.4.1 || ~8.5.0" + }, + "conflict": { + "thecodingmachine/safe": "<1.1.3" + }, + "require-dev": { + "phpbench/phpbench": "^1.6.1", + "phpunit/phpunit": "^13.1.8" + }, + "suggest": { + "composer/composer": "Required to use the ComposerSourceLocator" + }, + "type": "library", + "autoload": { + "psr-4": { + "Roave\\BetterReflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "James Titcumb", + "email": "james@asgrim.com", + "homepage": "https://github.com/asgrim" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + }, + { + "name": "Gary Hockin", + "email": "gary@roave.com", + "homepage": "https://github.com/geeh" + }, + { + "name": "Jaroslav Hanslík", + "email": "kukulich@kukulich.cz", + "homepage": "https://github.com/kukulich" + } + ], + "description": "Better Reflection - an improved code reflection API", + "support": { + "issues": "https://github.com/Roave/BetterReflection/issues", + "source": "https://github.com/Roave/BetterReflection/tree/6.71.0" + }, + "time": "2026-05-02T10:56:00+00:00" + }, + { + "name": "seld/jsonlint", + "version": "1.12.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "9a90eb5d32d5a500296bf43f946d60246444d5f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9a90eb5d32d5a500296bf43f946d60246444d5f7", + "reference": "9a90eb5d32d5a500296bf43f946d60246444d5f7", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.12.1" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2026-06-12T11:32:29+00:00" + }, + { + "name": "seld/phar-utils", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", + "keywords": [ + "phar" + ], + "support": { + "issues": "https://github.com/Seldaek/phar-utils/issues", + "source": "https://github.com/Seldaek/phar-utils/tree/1.2.1" + }, + "time": "2022-08-31T10:31:18+00:00" + }, + { + "name": "seld/signal-handler", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/signal-handler.git", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/signal-handler/zipball/04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "phpstan/phpstan": "^1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^7.5.20 || ^8.5.23", + "psr/log": "^1 || ^2 || ^3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\Signal\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Simple unix signal handler that silently fails where signals are not supported for easy cross-platform development", + "keywords": [ + "posix", + "sigint", + "signal", + "sigterm", + "unix" + ], + "support": { + "issues": "https://github.com/Seldaek/signal-handler/issues", + "source": "https://github.com/Seldaek/signal-handler/tree/2.0.2" + }, + "time": "2023-09-03T09:24:00+00:00" + }, + { + "name": "symfony/console", + "version": "v7.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "85095d2573eaefaf35e40b9513a9bf09f72cd217" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/85095d2573eaefaf35e40b9513a9bf09f72cd217", + "reference": "85095d2573eaefaf35e40b9513a9bf09f72cd217", + "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|^8.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "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.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-24T08:56:14+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-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.7.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": "2026-04-13T15:52:40+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "99aec13b82b4967ec5088222c4a3ecca955949c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/99aec13b82b4967ec5088222c4a3ecca955949c2", + "reference": "99aec13b82b4967ec5088222c4a3ecca955949c2", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^7.4|^8.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/v8.1.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": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/finder", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "58d2e767a66052c1487356f953445634a8194c64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/58d2e767a66052c1487356f953445634a8194c64", + "reference": "58d2e767a66052c1487356f953445634a8194c64", + "shasum": "" + }, + "require": { + "php": ">=8.4.1" + }, + "require-dev": { + "symfony/filesystem": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "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": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v8.1.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": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "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.37.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": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "e9247d281d694a5120554d9afaf54e070e88a603" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603", + "reference": "e9247d281d694a5120554d9afaf54e070e88a603", + "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.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-26T05:58:03+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.38.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "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.38.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": "2026-05-25T13:48:31+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.38.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "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.38.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-27T06:59:30+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "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\\Php73\\": "" + }, + "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 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.37.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-php80", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "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.37.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": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", + "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\\Php81\\": "" + }, + "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.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-26T12:45:58+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "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.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-26T12:51:13+00:00" + }, + { + "name": "symfony/process", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", + "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", + "shasum": "" + }, + "require": { + "php": ">=8.4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "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": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v8.1.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": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "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.7-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.7.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": "2026-03-28T09:44:51+00:00" + }, + { + "name": "symfony/string", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/afd5944f4005862d961efb85c8bbd5c523c4e3c9", + "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^7.4|^8.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/v8.1.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": "2026-05-29T05:06:50+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "~8.5.0" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +}