diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..c78ba79 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,51 @@ +# This is a GitHub Actions workflow file. +# It defines a set of jobs that will be run automatically on every push or pull request +# to the repository, ensuring code quality and preventing regressions. + +name: Run PHP Tests & Static Analysis + +on: [push, pull_request] + +jobs: + test: + # The job will run on the latest version of Ubuntu. + runs-on: ubuntu-latest + + # This strategy block defines a build matrix. + # FIX: The matrix now starts from PHP 8.2, which is the minimum version + # required by the project's locked dependencies (PHPUnit 11). + strategy: + matrix: + php-version: ['8.2', '8.3'] + + steps: + # Step 1: Check out the repository code. + - name: Checkout code + uses: actions/checkout@v4 + + # Step 2: Set up the PHP environment for the current job. + - name: Setup PHP v${{ matrix.php-version }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: dom, mbstring + coverage: xdebug + + # Step 3: Install Composer dependencies using the lock file. + # This will now work consistently across all jobs in the matrix. + - name: Install Composer dependencies + run: composer install --prefer-dist --no-progress --no-suggest + + # Step 4: Run the PHPUnit test suite. + - name: Run tests + run: ./vendor/bin/phpunit + + # Step 5: Run PHPStan for static analysis (only on the latest PHP version). + - name: Run static analysis + if: matrix.php-version == '8.3' + run: ./vendor/bin/phpstan analyse src tests --level=8 + + # Step 6: Check for coding style violations (only on the latest PHP version). + - name: Check coding style + if: matrix.php-version == '8.3' + run: composer cs diff --git a/.gitignore b/.gitignore index 725ba6d..db34561 100644 --- a/.gitignore +++ b/.gitignore @@ -1,57 +1,99 @@ -# JetBrains IDE -.idea/ -*.iml - -# Eclipse -.buildpath -.project -.settings - -# VI -*.swp +# # # # # # # # # # # # # # # # # # +# GENERATED BY ImNotJavad # +# # # # # # # # # # # # # # # # # # -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.cache -nosetests.xml -coverage.xml +# ---------------------------------------------------------------- +# Composer - PHP Dependency Manager +# ---------------------------------------------------------------- +# The vendor directory contains all third-party code. It should be installed via `composer install`. +/vendor/ +# The composer.lock file locks the dependencies to specific versions. +# While some teams commit this file, it's often ignored in libraries to allow more flexibility. +# For applications, it's recommended to commit this file. +# composer.lock -# Sphinx documentation -docs/_build/ -# emacs auto-saving files -\#*# -.#*# - -# composer -vendor -composer.lock +# ---------------------------------------------------------------- +# Build & Test Artifacts +# ---------------------------------------------------------------- +# Directory for PHPUnit code coverage reports. +/coverage/ +# Build artifacts for PHAR archives. *.phar -phar7 -phar5 -.phpunit.result.cache +# Directory for any other build output. +/build/ -# Vscode -.vscode/* -!.vscode/settings.json -!.vscode/extensions.json -# Cache +# ---------------------------------------------------------------- +# Caching +# ---------------------------------------------------------------- +# Cache file for PHPUnit. +.phpunit.cache +/.phpunit.cache/ +# Cache file for PHP CS Fixer. .php-cs-fixer.cache -.phpdoc_cache -*bak +# Cache for static analysis tools like PHPStan or Psalm. +.phpstan.cache +.psalm.cache + -# ENV +# ---------------------------------------------------------------- +# Environment Files +# ---------------------------------------------------------------- +# Environment files contain sensitive data like API keys and database credentials. +# They should NEVER be committed to version control. A `.env.example` file +# should be committed instead as a template. .env +.env.local +.env.*.local +# But track the example file. +!/.env.example + + +# ---------------------------------------------------------------- +# IDE & Editor Configuration +# ---------------------------------------------------------------- +# These files are specific to a developer's local environment. +.idea/ +.vscode/ +*.sublime-project +*.sublime-workspace +nbproject/ +# Temporary/swap files from editors like Vim. +*.swp +*.swo +*~ + + +# ---------------------------------------------------------------- +# Operating System Files +# ---------------------------------------------------------------- +# macOS specific files. +.DS_Store +.AppleDouble +.LSOverride +# Windows specific files. +Thumbs.db +ehthumbs.db +Desktop.ini + + +# ---------------------------------------------------------------- +# Log & Temporary Files +# ---------------------------------------------------------------- +# Any file ending in .log. +*.log +# Temporary files directory. +/tmp/ + -app_test.json -app.json -config.json -/Dockerfile -/docker-compose.yml -/Caddyfile -/changelog +# ---------------------------------------------------------------- +# Node.js Dependencies (if used for front-end assets) +# ---------------------------------------------------------------- +/node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +package-lock.json +yarn.lock -# Project diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 91dc1f7..2f7dba8 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -1,9 +1,19 @@ getFinder() - ->in(__DIR__ . "/src"); + ->in(__DIR__ . "/src") + ->in(__DIR__ . "/tests"); $config->setCacheFile(__DIR__ . '/.php-cs-fixer.cache'); +// FIX: Allow running on unsupported PHP versions (like PHP 8.4+). +// The tool may not be fully compatible with the newest syntax, but this +// setting is necessary to allow development on newer PHP runtimes. +$config->setUnsupportedPhpVersionAllowed(true); + return $config; diff --git a/README.md b/README.md index f6bdda2..f3a1c2e 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,292 @@ -# Create html tags by php +# PhpTagMaker Library -- ### Install -``` +**PhpTagMaker** is a fluent and powerful PHP library for programmatically building HTML strings. It leverages `DOMDocument` behind the scenes, ensuring well-formed, valid, and secure output. This enhanced version includes advanced features for attribute management, child manipulation, and overall flexibility. + +## Table of Contents + +- [PhpTagMaker Library](#phptagmaker-library) + - [Table of Contents](#table-of-contents) + - [Key Features](#key-features) + - [Requirements](#requirements) + - [Installation](#installation) + - [Quick Start](#quick-start) + - [Security](#security) + - [Core Concepts](#core-concepts) + - [`TagMaker`](#tagmaker) + - [`HtmlTag`](#htmltag) + - [Node Types (`Node`)](#node-types-node) + - [`HtmlClass`](#htmlclass) + - [API Documentation \& Advanced Usage](#api-documentation--advanced-usage) + - [Creating Tags](#creating-tags) + - [Managing Children](#managing-children) + - [Managing Attributes](#managing-attributes) + - [Generic Attributes](#generic-attributes) + - [The `id` Attribute](#the-id-attribute) + - [CSS Classes](#css-classes) + - [Boolean Attributes](#boolean-attributes) + - [`data-*` Attributes](#data--attributes) + - [`aria-*` Attributes](#aria--attributes) + - [Changing the Tag Name](#changing-the-tag-name) + - [Output Formatting](#output-formatting) + - [Examples](#examples) + - [Contributing Guide](#contributing-guide) + - [License](#license) + +## Key Features + +* **Fluent Interface**: Build complex HTML structures in a readable, chainable way. +* **DOM-Powered**: Uses `DOMDocument` to generate standard and valid HTML. +* **Full Tag Support**: Includes static helper methods for most standard HTML5 tags. +* **Advanced Attribute Control**: Full management of generic, `id`, `class`, Boolean, `data-*`, and `aria-*` attributes. +* **Flexible Child Management**: Add children at creation time or with `appendChild` and `prependChild` methods. +* **Smart Error Handling**: Prevents adding children to void elements (like ``). +* **Built-in Security**: Prevents XSS attacks by automatically escaping text content. +* **Output Formatting**: Option for readable, indented HTML output for easier debugging. +* **Modern Coding**: Uses strict types and modern PHP features. + +## Requirements + +* PHP 8.0 or higher +* `ext-dom` extension + +## Installation + +You can install the library via [Composer](https://getcomposer.org/): + +```bash composer require ahjdev/phptagmaker +```` + +## Quick Start + +```php +setId('my-link') + )->addClass('content') + ), + true // Enable output formatting +); + +echo $output; +``` + + +Expected Output: + +```html +
+

Welcome to PhpTagMaker!

+

This is a simple paragraph with a link

+
+``` + +## Security + +This library helps mitigate Cross-Site Scripting (XSS) vulnerabilities by default. + + * **Automatic Escaping**: By using `DOMDocument`, all text content added via `HtmlText` nodes or plain strings is automatically escaped (e.g., `<` becomes `<`). + * **CDATA Sections**: For content that should not be parsed by the HTML parser (like inline scripts), you can use the `EscapedText` node, which wraps the content in ``. + +**Your Responsibility**: Despite the built-in security, you must still be cautious. Never pass untrusted user input directly into attributes that can execute code (like `href` with `javascript:` values or `onclick` events). Always validate and sanitize user input before using it in such attributes. + +## Core Concepts + +### `TagMaker` + +This is the main engine of the library that transforms the node structure into the final HTML string. + + * `TagMaker::build(Node $node, bool $format = false)`: A static method for quickly building HTML. + * `$maker->run(Node $node)`: Processes the node and generates the output. + * `$maker->formatOutput(true)`: Enables output formatting. + +### `HtmlTag` + +This class represents an HTML tag and is the most frequently used node in the library. + + * `HtmlTag::div(...)`, `HtmlTag::p(...)`, etc.: Static helper methods for quickly creating tags. + * `HtmlTag::make('tag', ...)`: Another way to create a tag. + +### Node Types (`Node`) + +All elements inherit from the `Node` class. + + * **`HtmlTag`**: A standard HTML tag. + * **`HtmlText`**: A simple text node whose special characters are automatically escaped. + * **`EscapedText`**: A CDATA section whose content is not processed by the parser. + * **`HtmlTagMulti`**: A tool for quickly creating deeply nested structures. + +### `HtmlClass` + +A powerful helper class for managing the CSS classes of a tag. It provides methods for adding (`add`), removing (`remove`), toggling (`toggle`), and merging (`merge`) classes, and prevents duplicates. + +## API Documentation & Advanced Usage + +### Creating Tags + +**1. Using static helper methods (recommended method):** + +```php +use AhjDev\PhpTagMaker\Node\HtmlTag; + +$div = HtmlTag::div('container', 'Div content'); +$link = HtmlTag::a('[https://example.com](https://example.com)', 'Click here'); +$image = HtmlTag::img('/image.jpg', 'Alternative text'); +``` + +**2. Using `make` or the main constructor:** + +```php +$customTag = HtmlTag::make('my-custom-tag', 'Content'); +$paragraph = new HtmlTag('p', 'A new paragraph.'); +``` + +### Managing Children + +**1. Adding children at creation time:** + +```php +$article = HtmlTag::article( + HtmlTag::h1('Article Title'), + HtmlTag::p('First paragraph.'), + 'This is a simple text as a child.' +); +``` + +**2. Adding children after creation:** + +```php +$list = HtmlTag::ul(); +$list->appendChild(HtmlTag::li('Item 2')); +$list->prependChild(HtmlTag::li('Item 1')); // Adds to the beginning of the list +``` + +### Managing Attributes + +#### Generic Attributes + +```php +$tag = HtmlTag::div() + ->setAttribute('title', 'My Title') + ->setAttribute('lang', 'en'); + +echo $tag->getAttribute('title'); // "My Title" +var_dump($tag->hasAttribute('lang')); // true +$tag->removeAttribute('lang'); +``` + +#### The `id` Attribute + +```php +$section = HtmlTag::section()->setId('main-content'); +echo $section->getId(); // "main-content" +``` + +#### CSS Classes + +```php +$button = HtmlTag::button('Submit'); + +// Replace all classes +$button->setClass('btn', 'btn-primary'); + +// Add a new class +$button->addClass('btn-large'); // "btn btn-primary btn-large" + +// Remove a class +$button->removeClass('btn-large'); // "btn btn-primary" + +// Toggle a class +$button->toggleClass('active'); // The 'active' class is added +$button->toggleClass('active'); // The 'active' class is removed +``` + +#### Boolean Attributes + +These attributes are added to the tag if `true` and removed if `false`. + +```php +$input = HtmlTag::input('checkbox') + ->checked() // Adds checked="checked" + ->disabled(); // Adds disabled="disabled" + +// To remove +$input->disabled(false); // The 'disabled' attribute is removed ``` -- ### Usage - - See [examples](https://github.com/ahjdev/PhpTagMaker/tree/main/examples) folder +#### `data-*` Attributes + +```php +$item = HtmlTag::li('My Item') + ->setDataAttribute('item-id', '123') + ->setDataAttribute('item-type', 'product'); + +echo $item->getDataAttribute('item-id'); // "123" +``` + +#### `aria-*` Attributes + +To improve accessibility: + +```php +$alert = HtmlTag::div() + ->setAriaAttribute('role', 'alert') + ->setAriaAttribute('live', 'assertive'); +``` + +### Changing the Tag Name + +You can change a tag's name after it has been created. Attributes and children are preserved. + +```php +$element = HtmlTag::div(null, 'Content')->setClass('box'); +$element->setName('section'); // The tag changes from
to
+``` + +### Output Formatting + +To make the HTML output more readable in a development environment, you can enable formatting. + +```php +$maker = new TagMaker(); +$maker->formatOutput(true); +$html = $maker->run( + HtmlTag::ul(HtmlTag::li('Item 1'), HtmlTag::li('Item 2')) +); +// The output will be displayed with indentation. +``` + +## Examples + +For more practical scenarios, Please see the `examples/` directory for more usage scenarios: + + * `examples/1-SimpleMaker.php`: Basic usage. + * `examples/2-FormatOutput.php`: Demonstrates output formatting and various node types. + * `examples/3-AdvancedUsage.php`: Showcases enhanced attribute handling, child management, and `setName`. + +## Contributing Guide + +Submissions intended to enhance this software are permissible under the condition that they conform to established project protocols. All proposed modifications shall be tendered via Pull Requests for subsequent formal review. The reporting of software anomalies or functional deficiencies is to be registered within the designated "Issues" section of the repository. + +For the purposes of local development and validation, a set of Composer scripts is provided. Adherence to these scripts is requisite for the maintenance of code quality and stylistic uniformity. + + * **`composer test`**: Executes the PHPUnit test suite to validate the functionality of the codebase. + * **`composer cs`**: Initiates a check for conformance with the established coding style standards. + * **`composer cs-fix`**: Engages a process to automatically rectify any deviations from the established coding style. + * **`composer analyse`**: Commences a static analysis of the source code, utilizing the PHPStan tool, for the purpose of identifying potential defects and logical inconsistencies prior to runtime execution. + +## License + +This library is released under the **GPL-3.0-only** License. See the [LICENSE](https://www.google.com/search?q=LICENSE) file for more details. diff --git a/composer.json b/composer.json index d8d77c1..907b3fa 100644 --- a/composer.json +++ b/composer.json @@ -1,16 +1,21 @@ { - "name" : "ahjdev/phptagmaker", + "name": "ahjdev/phptagmaker", "description": "Create html tags by php", - "license" : "GPL-3.0-only", - "keywords" : [ + "license": "GPL-3.0-only", + "keywords": [ "php", "html", - "tag" + "tag", + "builder" ], "authors": [ { - "name" : "AmirHossein Jafari", + "name": "AmirHossein Jafari", "email": "amirhosseinjafari8228@gmail.com" + }, + { + "name": "Seyed Mohammad Javad Mousavi", + "email": "mou17savi@gmail.com" } ], "autoload": { @@ -19,16 +24,19 @@ } }, "require": { - "ext-dom" : "*" + "php": "^8.0", + "ext-dom": "*" }, "require-dev": { - "amphp/php-cs-fixer-config": "^2" + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^10.0 || ^11.0", + "phpstan/phpstan": "^1.10" }, "scripts": { - "build": [ - "@cs-fix" - ], - "cs" : "php -d pcre.jit=0 vendor/bin/php-cs-fixer fix -v --diff --dry-run", - "cs-fix": "php -d pcre.jit=0 vendor/bin/php-cs-fixer fix -v --diff" + "test": "./vendor/bin/phpunit", + "test:coverage": "./vendor/bin/phpunit --coverage-html coverage", + "cs": "php -d pcre.jit=0 vendor/bin/php-cs-fixer fix -v --diff --dry-run", + "cs-fix": "php -d pcre.jit=0 vendor/bin/php-cs-fixer fix -v --diff", + "analyse": "./vendor/bin/phpstan analyse src tests --level=8" } -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..2958552 --- /dev/null +++ b/composer.lock @@ -0,0 +1,4318 @@ +{ + "_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": "fd4ae8eaa301fc307b8dd8d3c6685a8f", + "packages": [], + "packages-dev": [ + { + "name": "amphp/php-cs-fixer-config", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/php-cs-fixer-config.git", + "reference": "0fad9ec6a10a0a58fbf8cb77f41da34f80c031d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/php-cs-fixer-config/zipball/0fad9ec6a10a0a58fbf8cb77f41da34f80c031d6", + "reference": "0fad9ec6a10a0a58fbf8cb77f41da34f80c031d6", + "shasum": "" + }, + "require": { + "friendsofphp/php-cs-fixer": "^3.5", + "php": ">=7.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\CodeStyle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Sascha-Oliver Prolic", + "email": "saschaprolic@googlemail.com" + } + ], + "description": "Code style config for AMPHP.", + "support": { + "issues": "https://github.com/amphp/php-cs-fixer-config/issues", + "source": "https://github.com/amphp/php-cs-fixer-config/tree/v2.1.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-19T02:16:09+00:00" + }, + { + "name": "clue/ndjson-react", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-ndjson.git", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\NDJson\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "homepage": "https://github.com/clue/reactphp-ndjson", + "keywords": [ + "NDJSON", + "json", + "jsonlines", + "newline", + "reactphp", + "streaming" + ], + "support": { + "issues": "https://github.com/clue/reactphp-ndjson/issues", + "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2022-12-23T10:58:28+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-09-19T14:15:21+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "8520451a140d3f46ac33042715115e290cf5785f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", + "reference": "8520451a140d3f46ac33042715115e290cf5785f", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2024-08-06T10:04:20+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.76.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "0e3c484cef0ae9314b0f85986a36296087432c40" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/0e3c484cef0ae9314b0f85986a36296087432c40", + "reference": "0e3c484cef0ae9314b0f85986a36296087432c40", + "shasum": "" + }, + "require": { + "clue/ndjson-react": "^1.0", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.5", + "ext-filter": "*", + "ext-hash": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "fidry/cpu-core-counter": "^1.2", + "php": "^7.4 || ^8.0", + "react/child-process": "^0.6.6", + "react/event-loop": "^1.0", + "react/promise": "^2.11 || ^3.0", + "react/socket": "^1.0", + "react/stream": "^1.0", + "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0", + "symfony/console": "^5.4.45 || ^6.4.13 || ^7.0", + "symfony/event-dispatcher": "^5.4.45 || ^6.4.13 || ^7.0", + "symfony/filesystem": "^5.4.45 || ^6.4.13 || ^7.0", + "symfony/finder": "^5.4.45 || ^6.4.17 || ^7.0", + "symfony/options-resolver": "^5.4.45 || ^6.4.16 || ^7.0", + "symfony/polyfill-mbstring": "^1.32", + "symfony/polyfill-php80": "^1.32", + "symfony/polyfill-php81": "^1.32", + "symfony/process": "^5.4.47 || ^6.4.20 || ^7.2", + "symfony/stopwatch": "^5.4.45 || ^6.4.19 || ^7.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3.1 || ^2.6", + "infection/infection": "^0.29.14", + "justinrainbow/json-schema": "^5.3 || ^6.4", + "keradus/cli-executor": "^2.2", + "mikey179/vfsstream": "^1.6.12", + "php-coveralls/php-coveralls": "^2.8", + "php-cs-fixer/accessible-object": "^1.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6", + "phpunit/phpunit": "^9.6.23 || ^10.5.47 || ^11.5.25", + "symfony/polyfill-php84": "^1.32", + "symfony/var-dumper": "^5.4.48 || ^6.4.23 || ^7.3.1", + "symfony/yaml": "^5.4.45 || ^6.4.23 || ^7.3.1" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + }, + "exclude-from-classmap": [ + "src/Fixer/Internal/*" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.76.0" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2025-06-30T14:15:06+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-04-29T12:36:36+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.5.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" + }, + "time": "2025-05-31T08:24:38+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.12.27", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "3a6e423c076ab39dfedc307e2ac627ef579db162" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3a6e423c076ab39dfedc307e2ac627ef579db162", + "reference": "3a6e423c076ab39dfedc307e2ac627ef579db162", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-05-21T20:51:45+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "11.0.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "1a800a7446add2d79cc6b3c01c45381810367d76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1a800a7446add2d79cc6b3c01c45381810367d76", + "reference": "1a800a7446add2d79cc6b3c01c45381810367d76", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.4.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.0", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.2" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/show" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2025-06-18T08:56:18+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-27T05:02:59+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:07:44+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:08:43+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:09:35+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "11.5.25", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "864ab32b3ff52058f917c5b19b3cef821e4a4f1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/864ab32b3ff52058f917c5b19b3cef821e4a4f1b", + "reference": "864ab32b3ff52058f917c5b19b3cef821e4a4f1b", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.1", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.10", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.1", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.1", + "sebastian/exporter": "^6.3.0", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.1.2", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.25" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2025-06-27T04:36:07+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/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", + "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/cache", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "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": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/child-process", + "version": "v0.6.6", + "source": { + "type": "git", + "url": "https://github.com/reactphp/child-process.git", + "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/1721e2b93d89b745664353b9cfc8f155ba8a6159", + "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/event-loop": "^1.2", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/socket": "^1.16", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\ChildProcess\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "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": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven library for executing child processes with ReactPHP.", + "keywords": [ + "event-driven", + "process", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/child-process/issues", + "source": "https://github.com/reactphp/child-process/tree/v0.6.6" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-01-01T16:37:48+00:00" + }, + { + "name": "react/dns", + "version": "v1.13.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "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": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.13.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-13T14:18:03+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "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": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-13T13:48:05+00:00" + }, + { + "name": "react/promise", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.10.39 || 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.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-05-24T10:39:05+00:00" + }, + { + "name": "react/socket", + "version": "v1.16.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "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": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.16.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-07-26T10:38:09+00:00" + }, + { + "name": "react/stream", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "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": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:41:36+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-19T07:56:08+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:45:54+00:00" + }, + { + "name": "sebastian/comparator", + "version": "6.3.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/24b8fbc2c8e201bb1308e7b05148d6ab393b6959", + "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.4" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-07T06:57:01+00:00" + }, + { + "name": "sebastian/complexity", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:49:50+00:00" + }, + { + "name": "sebastian/diff", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:53:05+00:00" + }, + { + "name": "sebastian/environment", + "version": "7.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" + } + ], + "time": "2025-05-21T11:55:47+00:00" + }, + { + "name": "sebastian/exporter", + "version": "6.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3", + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-12-05T09:17:50+00:00" + }, + { + "name": "sebastian/global-state", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:57:36+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:58:38+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:00:13+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:01:32+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:10:34+00:00" + }, + { + "name": "sebastian/type", + "version": "5.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", + "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-18T13:35:50+00:00" + }, + { + "name": "sebastian/version", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-09T05:16:32+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "symfony/console", + "version": "v7.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/9e27aecde8f506ba0fd1d9989620c04a87697101", + "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.3.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T19:55:54+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "497f73ac996a598c92409b44ac43b6690c4f666d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/497f73ac996a598c92409b44ac43b6690c4f666d", + "reference": "497f73ac996a598c92409b44ac43b6690c4f666d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "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 tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-22T09:11:45+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "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 dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-25T15:15:23+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d", + "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.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/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-30T19:00:26+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/afb9a8038025e5dbc657378bfab9198d75f10fca", + "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "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 an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-04T13:12:05+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "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.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/process", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "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/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-17T09:11:12+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-25T09:37:31+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "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 a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-02-24T10:49:57+00:00" + }, + { + "name": "symfony/string", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", + "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-20T20:19:01+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.0", + "ext-dom": "*" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/examples/1- SimpleMaker.php b/examples/1- SimpleMaker.php index 918c5d6..98da58f 100644 --- a/examples/1- SimpleMaker.php +++ b/examples/1- SimpleMaker.php @@ -1,18 +1,28 @@ tag using the static helper method. HtmlTag::div( - 'my-class-name', - HtmlTag::pre('A Pre Tag'), - HtmlTag::div( - new HtmlClass('class-1', 'class-2') + // The first argument to div() can be a string of CSS classes. + 'container main-content', + + // Children can be other HtmlTag nodes. + HtmlTag::h1('Welcome to PhpTagMaker!'), + HtmlTag::p( + 'This is a simple paragraph created with the fluent API.' ) ) ); + +// Print the generated HTML string. +// Output will be:

Welcome to PhpTagMaker!

This is a simple paragraph created with the fluent API.

print($output); diff --git a/examples/2- FormatOutput.php b/examples/2- FormatOutput.php index 3e58f98..928c072 100644 --- a/examples/2- FormatOutput.php +++ b/examples/2- FormatOutput.php @@ -1,5 +1,8 @@ formatOutput() - ->run( - HtmlTag::span( - HtmlTag::a( - 'github.com/ahjdev', - 'My Github', - ), - 'Can also use a string', - HtmlTag::ul( - HtmlTag::li('one'), - HtmlTag::li('two'), - HtmlTag::li('three')->setClass('Class 1', 'Class 2'), - ), - HtmlText::make(' without escape'), - HtmlTag::br(), - EscapedText::make(' with escape'), - HtmlTag::br(), - HtmlTagMulti::make(['a', 'b', 'code'], 'Multi tag'), - HtmlTag::br(), - HtmlTagMulti::make( - ['c', 'r', 'y'], - HtmlTag::b('inside tag'), - ' ', - 'Just text' - ) - ) - ); -print($output); - -$myTag = HtmlTag::make('myTag', 'myValue'); -// Set attribute -$myTag->setAttribute('foo1', 'bar'); -$myTag->setAttribute('foo2', 'baz'); - -// Get value of attribute -var_dump($myTag->getAttribute('foo1')); // bar -// Remove attribute -$myTag->removeAttribute('foo1'); - -// Whether attribute exists -var_dump($myTag->hasAttribute('foo1')); // false -var_dump($myTag->hasAttribute('foo2')); // true - -// Set attribute directly -$myTag->setId('myid'); -// Set class directly -$myTag->setClass('blah'); -// Change tag name (Can check with getName method) -$myTag->setName('h1'); +// 2. Enable output formatting. This adds indentation and newlines. +// This is great for development but should be disabled in production. +$maker->formatOutput(true); + +// 3. Build a complex HTML structure. +$output = $maker->run( + HtmlTag::div('wrapper', + HtmlTag::h1('Demonstration of Node Types'), + + // A standard link. + HtmlTag::a('https://github.com/ahjdev/phptagmaker', 'Project on GitHub'), + + // An unordered list with children. + HtmlTag::ul( + HtmlTag::li('First item'), + HtmlTag::li('Second item'), + HtmlTag::li('Third item')->setClass('special-item') + ), + + // Using HtmlText to explicitly create a text node. + // The '<' and '>' will be escaped automatically to '<' and '>'. + HtmlTag::p( + HtmlText::make('This text contains special characters like < and >.') + ), + + // Using EscapedText to create a CDATA section. + // The content inside will NOT be parsed by the browser. + // Useful for inline scripts or style blocks. + HtmlTag::script( + EscapedText::make("if (x < 5 && y > 2) { console.log('CDATA works!'); }") + ), + + // Using HtmlTagMulti to create a deeply nested structure easily. + HtmlTag::p('A multi-tag structure:'), + HtmlTagMulti::make( + ['div', 'blockquote', 'p', 'strong'], + 'This text is deeply nested.' + ) + ) +); -// Iterate attributes -foreach ($myTag->iterAttributes() as $attr) { - // $attr is instanceof DOMAttr - // var_dump($attr); -} -print($maker->run($myTag)); +// 4. Print the formatted HTML. +print($output); diff --git a/examples/3- AdvancedUsage.php b/examples/3- AdvancedUsage.php new file mode 100644 index 0000000..9800ff4 --- /dev/null +++ b/examples/3- AdvancedUsage.php @@ -0,0 +1,75 @@ +formatOutput(true); + +echo "

Advanced Tag Features

\n\n"; + +// --- 1. Boolean, Data, and ARIA Attributes --- +echo "

1. Input with Boolean, Data, and ARIA Attributes:

\n"; +$input = HtmlTag::input('checkbox') + ->setId('subscribe-checkbox') + ->setDataAttribute('item-id', 'A123') + ->setAriaAttribute('label', 'Subscribe to newsletter') + ->checked() // Sets 'checked="checked"' + ->disabled(); // Sets 'disabled="disabled"' + +echo $maker->run($input); +echo "
\n"; + + +// --- 2. Appending and Prepending Children --- +echo "

2. List with Appended and Prepended Children:

\n"; +$list = HtmlTag::ul()->addClass('task-list'); + +// Add children after the object has been created. +$list->appendChild(HtmlTag::li('Second item, added via appendChild')); +$list->prependChild(HtmlTag::li('First item, added via prependChild')); +$list->appendChild(HtmlTag::li('Third item')); + +echo $maker->run($list); +echo "
\n"; + + +// --- 3. Changing Tag Name with setName() --- +echo "

3. Changing Tag Name (setName):

\n"; +$contentBlock = HtmlTag::div( + 'initial-class', + HtmlTag::p('This is a paragraph inside the original div.') +)->setId('content-block-1'); + +echo "

Original div:

\n"; +echo $maker->run($contentBlock); + +// Now, transform the
into an
+$contentBlock->setName('article'); +$contentBlock->addClass('important-article'); // Add another class +$contentBlock->appendChild(HtmlTag::footer('End of article.')); // Add a new child + +echo "\n

Changed to article (attributes and children preserved/updated):

\n"; +echo $maker->run($contentBlock); +echo "
\n"; + + +// --- 4. Toggling Classes --- +echo "

4. Toggling CSS Classes:

\n"; +$panel = HtmlTag::div('panel')->setId('info-panel'); +echo "Initial panel: " . $maker->run($panel) . "\n"; + +// Add 'visible' and 'active' classes +$panel->toggleClass('visible', 'active'); +echo "Panel after toggling 'visible' and 'active': " . $maker->run($panel) . "\n"; + +// Remove 'active' class by toggling it again +$panel->toggleClass('active'); +echo "Panel after toggling 'active' again: " . $maker->run($panel) . "\n"; +echo "
\n"; diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..1f170c9 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,17 @@ + + + + + tests + + + + + src + + + diff --git a/src/HtmlClass.php b/src/HtmlClass.php index 3eab51f..2933860 100644 --- a/src/HtmlClass.php +++ b/src/HtmlClass.php @@ -3,76 +3,150 @@ namespace AhjDev\PhpTagMaker; use Countable; -use Stringable; use IteratorAggregate; +use Stringable; +use Traversable; +/** + * A robust and fluent manager for an element's CSS classes. + * + * @implements IteratorAggregate + */ final class HtmlClass implements Stringable, IteratorAggregate, Countable { + /** + * @var list An array holding the unique list of class names. A `list` is a more specific type of array with sequential integer keys starting from 0. + */ private array $classList = []; + /** + * HtmlClass constructor. + * + * @param string ...$classes Initial classes to add. + */ public function __construct(string ...$classes) { - $this->classList = array_map(static fn ($e) => trim($e), $classes); + $this->merge(...$classes); } + /** + * Returns the space-separated string of classes. + * + */ public function __toString(): string { - return implode(' ', $this->classList); + return \implode(' ', $this->classList); } + /** + * Checks if a specific class exists. + * + * @param string $class The class name to check. + */ public function has(string $class): bool { - return in_array(trim($class), $this->classList, true); + return \in_array(\trim($class), $this->classList, true); } + /** + * Adds a class if it does not already exist. + * + * @param string $class The class name to add. + */ public function add(string $class): self { - $class = trim($class); - if (!(empty($class) || $this->has($class))) { + $class = \trim($class); + if ($class !== '' && !$this->has($class)) { $this->classList[] = $class; } return $this; } + /** + * Removes a class if it exists. + * + * @param string $class The class name to remove. + */ public function remove(string $class): self { - if (($pos = array_search(trim($class), $this->classList, true)) !== false) { + $class = \trim($class); + $pos = \array_search($class, $this->classList, true); + if ($pos !== false) { unset($this->classList[$pos]); + // Re-index the array to maintain the `list` type. + $this->classList = \array_values($this->classList); + } + return $this; + } + + /** + * Toggles a class. + * + * @param string $class The class name to toggle. + */ + public function toggle(string $class): self + { + $class = \trim($class); + if ($class === '') { + return $this; + } + + if ($this->has($class)) { + $this->remove($class); + } else { + $this->add($class); } return $this; } + /** + * Merges classes from strings or other HtmlClass instances. + * + * @param string|self ...$classes A mix of strings or HtmlClass instances. + */ public function merge(string|self ...$classes): self { - foreach ($classes as $class) { - if ($class instanceof self) { - $this->classList = array_merge($this->classList, $class->asArray()); - array_shift($classes); + foreach ($classes as $classInput) { + $newClasses = []; + if ($classInput instanceof self) { + $newClasses = $classInput->asArray(); + } elseif (\is_string($classInput)) { + $newClasses = \explode(' ', $classInput); + } + + foreach ($newClasses as $nc) { + $this->add($nc); } } - // leftover strings - if ($classes) { - $this->classList = array_merge( - $this->classList, - array_map(static fn ($e) => trim($e), $classes) - ); - } - $this->classList = array_filter($this->classList); return $this; } + /** + * Returns the list of classes as an array. + * + * @return list + */ public function asArray(): array { return $this->classList; } + /** + * Returns the number of classes. + * + */ public function count(): int { - return count($this->classList); + return \count($this->classList); } - public function getIterator(): \Generator + /** + * Returns an iterator for the class list. + * + * @return Traversable + */ + public function getIterator(): Traversable { - return yield from $this->classList; + yield from $this->classList; } } diff --git a/src/Node.php b/src/Node.php index d4af1ec..5e88c97 100644 --- a/src/Node.php +++ b/src/Node.php @@ -2,7 +2,22 @@ namespace AhjDev\PhpTagMaker; +/** + * Represents the abstract concept of a Node in the HTML structure. + * + * All concrete elements that can be rendered, such as HTML tags or text nodes, + * must extend this class and implement the `toDomNode` method. + */ abstract class Node { - abstract public function toDomNode(): \DomNode; + /** + * Converts the current node into its corresponding DOMNode representation. + * + * This method is essential for the TagMaker to build the final HTML + * string using the underlying DOMDocument. + * + * @param \DOMDocument|null $doc The parent DOMDocument, if available, to prevent creating new instances. + * @return \DOMNode The concrete DOMNode instance (e.g., DOMElement, DOMText). + */ + abstract public function toDomNode(?\DOMDocument $doc = null): \DOMNode; } diff --git a/src/Node/EscapedText.php b/src/Node/EscapedText.php index fbde5a0..f610ca2 100644 --- a/src/Node/EscapedText.php +++ b/src/Node/EscapedText.php @@ -2,25 +2,53 @@ namespace AhjDev\PhpTagMaker\Node; -use DOMCdataSection; use AhjDev\PhpTagMaker\Node; +use DOMCdataSection; +use DOMDocument; +/** + * Represents a CDATA (Character Data) section. + * + * The content within this node is not parsed by the HTML parser. This is useful + * for embedding content that contains special characters, such as inline + * JavaScript or XML data, without needing to escape them manually. + */ final class EscapedText extends Node { - private DOMCdataSection $text; + /** + * @var string The content to be wrapped in a CDATA section. + */ + private string $text; + /** + * EscapedText constructor. + * + * @param string $text The content to be wrapped in a CDATA section. + */ public function __construct(string $text) { - $this->text = new DOMCdataSection($text); + $this->text = $text; } - public static function make($text): self + /** + * Static factory method for creating an EscapedText instance. + * + * @param string $text The content for the CDATA section. + */ + public static function make(string $text): self { return new self($text); } - public function toDomNode(): DOMCdataSection + /** + * Returns the underlying DOMCdataSection node. + * + * @param DOMDocument|null $doc The parent DOMDocument. + */ + public function toDomNode(?DOMDocument $doc = null): DOMCdataSection { - return $this->text; + // The DOMDocument context is not needed here either, but we accept it + // for signature consistency across all Node subclasses. + return new DOMCdataSection($this->text); } } diff --git a/src/Node/HtmlTag.php b/src/Node/HtmlTag.php index f689236..7a8cbe1 100644 --- a/src/Node/HtmlTag.php +++ b/src/Node/HtmlTag.php @@ -2,71 +2,172 @@ namespace AhjDev\PhpTagMaker\Node; -use DOMElement; -use AhjDev\PhpTagMaker\Node; use AhjDev\PhpTagMaker\HtmlClass; +use AhjDev\PhpTagMaker\Node; +use DOMElement; +use LogicException; +/** + * Represents a single HTML element (e.g.,
,

, ). + * + * This is the core class for building HTML structures, providing fluent methods + * for attribute management, child manipulation, and rendering to a DOM node. + */ final class HtmlTag extends Node { use Internal\Attributes; use Internal\DefaultTags; - /** @var list */ - private array $values = []; + /** + * @var string The name of the HTML tag (e.g., 'div', 'p'). + */ + private string $tag; - private HtmlClass $class; + /** + * @var Node[] A list of child nodes (HtmlTag, HtmlText, etc.). + */ + private array $values = []; - private DOMElement $domElement; + /** + * A set of HTML5 tags that cannot have any content (e.g.,
, ). + * @var string[] + */ + private const VOID_ELEMENTS = [ + 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', + 'link', 'meta', 'param', 'source', 'track', 'wbr' + ]; - public function __construct(private string $tag, Node|string ...$value) + /** + * HtmlTag constructor. + * + * @param string $tag The name of the HTML tag. + * @param Node|string ...$value Initial children for the tag. Strings are auto-converted to HtmlText nodes. + */ + public function __construct(string $tag, Node|string ...$value) { - $this->domElement = new DOMElement($tag); - $this->values = array_map(static fn ($v) => is_string($v) ? new HtmlText($v) : $v, $value); - $this->class = new HtmlClass; - // $this->domElement->getElementsByTagName(); - // $this->domElement->insertAdjacentElement(); - // $this->domElement->insertAdjacentText(); - // $this->domElement->insertBefore(); - // $this->domElement->removeChild(); + $this->tag = $tag; + $this->class = new HtmlClass(); // Initialize the class manager. + $this->attributes = []; // Initialize attributes array. + + // Append initial children, checking for void elements. + foreach ($value as $v) { + $this->appendChild($v); + } } + /** + * Static factory method for creating an HtmlTag instance. + * + * @param string $tag The name of the HTML tag. + * @param Node|string ...$value Initial children for the tag. + */ public static function make(string $tag, Node|string ...$value): self { return new self($tag, ...$value); } - public function getName() + /** + * Gets the name of the tag. + * + */ + public function getName(): string + { + return $this->tag; + } + + /** + * Changes the name of the tag. + * + * Attributes and children are preserved. + * + * @param string $newTagName The new tag name (e.g., 'section'). + * @return self The current instance for method chaining. + */ + public function setName(string $newTagName): self + { + $this->tag = $newTagName; + return $this; + } + + /** + * Appends a child Node to this tag. + * + * Strings are automatically wrapped in an HtmlText node. + * + * @param Node|string $child The child to append. + * @return self The current instance for method chaining. + * @throws LogicException If attempting to add a child to a void element. + */ + public function appendChild(Node|string $child): self { - return $this->domElement->nodeName; + if ($this->isVoidElement()) { + throw new LogicException("Cannot add children to a void element <{$this->tag}>."); + } + $this->values[] = \is_string($child) ? new HtmlText($child) : $child; + return $this; } - public function setName(string $tag): self + /** + * Prepends a child Node to this tag. + * + * Strings are automatically wrapped in an HtmlText node. + * + * @param Node|string $child The child to prepend. + * @return self The current instance for method chaining. + * @throws LogicException If attempting to add a child to a void element. + */ + public function prependChild(Node|string $child): self { - $element = new DOMElement($tag); - // Copy attributes and child nodes from old element to new element - foreach ($this->domElement->attributes as $attribute) { - $element->setAttribute( - $attribute->nodeName, - $attribute->nodeValue - ); + if ($this->isVoidElement()) { + throw new LogicException("Cannot add children to a void element <{$this->tag}>."); } - // while ($element->hasChildNodes()) { - // $newElement->appendChild($element->childNodes->item(0)); - // } - $this->domElement = $element; + \array_unshift($this->values, \is_string($child) ? new HtmlText($child) : $child); return $this; } - public function toDomNode(): DOMElement + /** + * Converts the HtmlTag instance to a DOMElement. + * + * This method builds the element, sets its attributes, and appends all its children recursively. + * + * @param \DOMDocument|null $doc The parent DOMDocument, if available. + */ + public function toDomNode(?\DOMDocument $doc = null): DOMElement { - $element = $this->domElement->cloneNode(true); - if ($this->class->count()) { + $document = $doc ?? new \DOMDocument(); + $element = $document->createElement($this->tag); + + // Set all generic attributes. + foreach ($this->attributes as $name => $value) { + $element->setAttribute($name, (string) $value); + } + + // Set the class attribute if any classes are present. + if ($this->class->count() > 0) { $element->setAttribute('class', (string) $this->class); } - array_map( - static fn (Node $v) => $element->append($v->toDomNode()), - $this->values - ); + + // Recursively append all child nodes, but only if it's not a void element. + if (!$this->isVoidElement()) { + foreach ($this->values as $valueNode) { + // Import the node if it belongs to a different document context. + $childDomNode = $valueNode->toDomNode($document); + if ($childDomNode->ownerDocument !== $document) { + $childDomNode = $document->importNode($childDomNode, true); + } + $element->appendChild($childDomNode); + } + } + return $element; } + + /** + * Checks if the current tag is a void element. + * + */ + private function isVoidElement(): bool + { + return \in_array(\strtolower($this->tag), self::VOID_ELEMENTS, true); + } } diff --git a/src/Node/HtmlTagMulti.php b/src/Node/HtmlTagMulti.php index 6416748..6504063 100644 --- a/src/Node/HtmlTagMulti.php +++ b/src/Node/HtmlTagMulti.php @@ -2,42 +2,92 @@ namespace AhjDev\PhpTagMaker\Node; -use IteratorAggregate; use AhjDev\PhpTagMaker\Node; +use DOMDocument; +use IteratorAggregate; +use Traversable; /** - * @implements IteratorAggregate + * A utility node for creating a deeply nested structure of tags. + * + * @implements IteratorAggregate */ final class HtmlTagMulti extends Node implements IteratorAggregate { - /** @var list */ + /** @var list The content to be placed at the deepest level of the nested structure. */ private array $values = []; - public function __construct(private array $tags, Node|string ...$value) + /** @var list The list of tag names to be nested, from outermost to innermost. */ + private array $tags; + + /** + * HtmlTagMulti constructor. + * + * @param list $tags An array of tag names to nest. + * @param Node|string ...$value The content to be wrapped by the nested tags. + */ + public function __construct(array $tags, Node|string ...$value) { - $this->values = array_map(static fn ($v) => is_string($v) ? new HtmlText($v) : $v, $value); + $this->tags = $tags; + + // FIX: Wrap the result of array_map in array_values(). + // This guarantees that the resulting array is a `list` (numerically indexed array), + // which satisfies the strict type definition of the `$values` property. + $this->values = \array_values(\array_map( + static fn ($v) => \is_string($v) ? new HtmlText($v) : $v, + $value + )); } + /** + * Static factory method for creating an HtmlTagMulti instance. + * + * @param list $tags An array of tag names. + * @param Node|string ...$value The content. + */ public static function make(array $tags, Node|string ...$value): self { return new self($tags, ...$value); } - public function getIterator(): \Generator + /** + * Returns an iterator for the tag names. + * + * @return Traversable + */ + public function getIterator(): Traversable { - return yield from $this->tags; + yield from $this->tags; } - public function toDomNode(): \DomNode + /** + * Builds the nested DOM structure. + * + * @param DOMDocument|null $doc The parent DOMDocument. + * @return \DOMNode The outermost DOMNode of the nested structure. + */ + public function toDomNode(?DOMDocument $doc = null): \DOMNode { - $before = $this->values; - foreach (array_reverse($this->tags) as $tag) { - $tag = new HtmlTag($tag, ...$before); - $before = [$tag]; - // foreach ($attributes as $name => $value) - // $tag->setAttribute($name, $value); + $document = $doc ?? new DOMDocument(); + + if (empty($this->tags)) { + $fragment = $document->createDocumentFragment(); + foreach ($this->values as $value) { + $fragment->appendChild($value->toDomNode($document)); + } + return $fragment; } - // Return DomElement - return $tag->toDomNode(); + + $currentNode = null; + $contentNodes = $this->values; + + foreach (\array_reverse($this->tags) as $tagName) { + $currentNode = new HtmlTag($tagName, ...$contentNodes); + $contentNodes = [$currentNode]; + } + + // $currentNode will never be null here if $this->tags is not empty. + // We can safely call toDomNode on it. + return $currentNode->toDomNode($document); } } diff --git a/src/Node/HtmlText.php b/src/Node/HtmlText.php index d78d680..02d3e95 100644 --- a/src/Node/HtmlText.php +++ b/src/Node/HtmlText.php @@ -2,25 +2,52 @@ namespace AhjDev\PhpTagMaker\Node; -use DOMText; use AhjDev\PhpTagMaker\Node; +use DOMDocument; +use DOMText; +/** + * Represents a plain text node within the HTML structure. + * + * When rendered, the content of this node will be properly escaped by the + * underlying DOMDocument to prevent XSS attacks and ensure well-formed HTML. + */ final class HtmlText extends Node { - private DOMText $domText; + /** + * @var string The raw text content for this node. + */ + private string $text; + /** + * HtmlText constructor. + * + * @param string $text The raw text content for this node. + */ public function __construct(string $text) { - $this->domText = new DOMText($text); + $this->text = $text; } - public static function make($text): self + /** + * Static factory method for creating an HtmlText instance. + * + * @param string $text The raw text content. + */ + public static function make(string $text): self { return new self($text); } - public function toDomNode(): DOMText + /** + * Returns the underlying DOMText node. + * + * @param DOMDocument|null $doc The parent DOMDocument. + */ + public function toDomNode(?DOMDocument $doc = null): DOMText { - return $this->domText; + // The DOMDocument context is not strictly needed to create a DOMText, + // but we accept the parameter to maintain a consistent method signature. + return new DOMText($this->text); } } diff --git a/src/Node/Internal/Attributes.php b/src/Node/Internal/Attributes.php index 05875e2..4a6bc64 100644 --- a/src/Node/Internal/Attributes.php +++ b/src/Node/Internal/Attributes.php @@ -2,74 +2,219 @@ namespace AhjDev\PhpTagMaker\Node\Internal; -use DOMAttr; -use DOMElement; -use Iterator; -use ArrayIterator; use AhjDev\PhpTagMaker\HtmlClass; +use ArrayIterator; +use Iterator; /** * @internal - * @property DOMElement $domElement + * A trait that provides a rich API for managing HTML attributes on an HtmlTag. */ trait Attributes { + /** + * @var HtmlClass Manages the CSS classes of the element. + */ + private HtmlClass $class; + + /** + * @var array Holds all standard attributes of the element. + */ + private array $attributes; + + /** + * Replaces all existing CSS classes with a new set. + * + * @param string ...$classes The new classes to set. + */ public function setClass(string ...$classes): self { - return $this->setAttribute('class', (string)(new HtmlClass(...$classes))); + $this->class = new HtmlClass(...$classes); + return $this; + } + + /** + * Adds one or more CSS classes. + * + * @param string ...$classes The classes to add. + */ + public function addClass(string ...$classes): self + { + $this->class->merge(...$classes); + return $this; + } + + /** + * Removes one or more CSS classes. + * + * @param string ...$classes The classes to remove. + */ + public function removeClass(string ...$classes): self + { + foreach ($classes as $class) { + $this->class->remove($class); + } + return $this; } - public function getClass(): null|string|array + /** + * Toggles one or more CSS classes. + * + * @param string ...$classes The classes to toggle. + */ + public function toggleClass(string ...$classes): self { - if ($attribute = $this->getAttribute('class')) { - $attribute = explode(' ', $attribute); - return count($attribute) === 1 ? $attribute[0] : $attribute; + foreach ($classes as $class) { + $this->class->toggle($class); } - return null; + return $this; } + /** + * Sets the 'id' attribute. + * + * @param string $id The ID value. + */ public function setId(string $id): self { return $this->setAttribute('id', $id); } + /** + * Gets the 'id' attribute. + * + */ public function getId(): ?string { return $this->getAttribute('id'); } + /** + * Sets a generic attribute. + * + * @param string $qualifiedName The name of the attribute. + * @param string $value The value of the attribute. + */ public function setAttribute(string $qualifiedName, string $value): self { - $this->domElement->setAttribute($qualifiedName, $value); + $this->attributes[$qualifiedName] = $value; return $this; } + /** + * Removes an attribute. + * + * @param string $qualifiedName The name of the attribute to remove. + */ public function removeAttribute(string $qualifiedName): self { - $this->domElement->removeAttribute($qualifiedName); + unset($this->attributes[$qualifiedName]); return $this; } + + /** + * Checks if an attribute exists. + * + * @param string $qualifiedName The name of the attribute. + */ public function hasAttribute(string $qualifiedName): bool { - return $this->domElement->hasAttribute($qualifiedName); + return \array_key_exists($qualifiedName, $this->attributes); } + /** + * Gets the value of a specific attribute. + * + * @param string $qualifiedName The name of the attribute. + */ public function getAttribute(string $qualifiedName): ?string { - $attribute = $this->domElement->getAttribute($qualifiedName); - return empty($attribute) ? null : $attribute; + return $this->attributes[$qualifiedName] ?? null; + } + + /** + * Sets a boolean attribute. + * + * @param string $qualifiedName The name of the boolean attribute. + * @param bool $value The boolean value. + */ + public function setBooleanAttribute(string $qualifiedName, bool $value = true): self + { + if ($value) { + $this->setAttribute($qualifiedName, $qualifiedName); + } else { + $this->removeAttribute($qualifiedName); + } + return $this; + } + + /** + * Sets the 'disabled' boolean attribute. + * + */ + public function disabled(bool $isDisabled = true): self + { + return $this->setBooleanAttribute('disabled', $isDisabled); + } + + /** + * Sets the 'checked' boolean attribute. + * + */ + public function checked(bool $isChecked = true): self + { + return $this->setBooleanAttribute('checked', $isChecked); + } + + /** + * Sets a 'data-*' attribute. + * + * @param string $key The key (without 'data-'). + * @param string $value The value. + */ + public function setDataAttribute(string $key, string $value): self + { + return $this->setAttribute('data-' . $key, $value); + } + + /** + * Gets a 'data-*' attribute. + * + * @param string $key The key (without 'data-'). + */ + public function getDataAttribute(string $key): ?string + { + return $this->getAttribute('data-' . $key); + } + + /** + * Sets an 'aria-*' attribute. + * + * @param string $key The key (without 'aria-'). + * @param string $value The value. + */ + public function setAriaAttribute(string $key, string $value): self + { + return $this->setAttribute('aria-' . $key, $value); + } + + /** + * Gets an 'aria-*' attribute. + * + * @param string $key The key (without 'aria-'). + */ + public function getAriaAttribute(string $key): ?string + { + return $this->getAttribute('aria-' . $key); } /** - * @return ArrayIterator + * Returns an iterator for all attributes. + * + * @return Iterator */ public function iterAttributes(): Iterator { - return new ArrayIterator( - array_map( - fn (string $name) => $this->domElement->getAttributeNode($name), - $this->domElement->getAttributeNames() - ) - ); + return new ArrayIterator($this->attributes); } } diff --git a/src/Node/Internal/DefaultTags.php b/src/Node/Internal/DefaultTags.php index d9db3bf..e0c9610 100644 --- a/src/Node/Internal/DefaultTags.php +++ b/src/Node/Internal/DefaultTags.php @@ -2,904 +2,504 @@ namespace AhjDev\PhpTagMaker\Node\Internal; -use AhjDev\PhpTagMaker\Node; use AhjDev\PhpTagMaker\HtmlClass; +use AhjDev\PhpTagMaker\Node; use AhjDev\PhpTagMaker\Node\HtmlTag; /** * @internal + * A trait that provides convenient static factory methods for all common HTML5 tags. + * This makes creating tags more fluent and readable (e.g., HtmlTag::div() instead of new HtmlTag('div')). */ trait DefaultTags { - public static function a(string $uri, Node|string ...$value): self + // --- Document Metadata --- + public static function head(Node|string ...$value): HtmlTag { - return HtmlTag::make('a', ...$value)->setAttribute('href', $uri); + return HtmlTag::make('head', ...$value); } - - public static function heading(int $size, Node|string ...$value): self + public static function title(Node|string ...$value): HtmlTag { - return HtmlTag::make("h$size", ...$value); + return HtmlTag::make('title', ...$value); } - - /** - * Defines a section in a document - */ - public static function div(HtmlClass|string $class = null, Node|string ...$value): self + public static function base(string $uri, string $target): HtmlTag { - $tag = HtmlTag::make('div', ...$value); - if ($class) { - $tag->class->merge($class); - } - return $tag; + return HtmlTag::make('base')->setAttribute('href', $uri)->setAttribute('target', $target); } - - /** - * Defines an abbreviation or an acronym - */ - public static function abbr(Node|string ...$value): self + public static function link(string $rel, string $uri): HtmlTag { - return HtmlTag::make('abbr', ...$value); + return HtmlTag::make('link')->setAttribute('rel', $rel)->setAttribute('href', $uri); } - - /** - * Defines contact information for the author/owner of a document - */ - public static function address(Node|string ...$value): self + public static function meta(): HtmlTag { - return HtmlTag::make('address', ...$value); + return HtmlTag::make('meta'); + } + public static function style(Node|string ...$value): HtmlTag + { + return HtmlTag::make('style', ...$value); } - /** - * Defines an area inside an image map - */ - public static function area(): self + // --- Sectioning Root --- + public static function body(Node|string ...$value): HtmlTag { - return HtmlTag::make('area'); + return HtmlTag::make('body', ...$value); } - /** - * Defines an article - */ - public static function article(Node|string ...$value): self + // --- Content Sectioning --- + public static function address(Node|string ...$value): HtmlTag + { + return HtmlTag::make('address', ...$value); + } + public static function article(Node|string ...$value): HtmlTag { return HtmlTag::make('article', ...$value); } - - /** - * Defines content aside from the page content - */ - public static function aside(Node|string ...$value): self + public static function aside(Node|string ...$value): HtmlTag { return HtmlTag::make('aside', ...$value); } - - /** - * Defines embedded sound content - */ - public static function audio(Node|string ...$value): self + public static function footer(Node|string ...$value): HtmlTag { - return HtmlTag::make('audio', ...$value); + return HtmlTag::make('footer', ...$value); } - - /** - * Defines bold text - */ - public static function b(Node|string ...$value): self + public static function header(Node|string ...$value): HtmlTag { - return HtmlTag::make('b', ...$value); + return HtmlTag::make('header', ...$value); } - - /** - * Specifies the base URL/target for all relative URLs in a document - */ - public static function base(string $uri, string $target): self + public static function h1(Node|string ...$value): HtmlTag { - return HtmlTag::make('base') - ->setAttribute('href', $uri) - ->setAttribute('target', $target); + return HtmlTag::make('h1', ...$value); } - - /** - * Isolates a part of text that might be formatted in a different direction from other text outside it - */ - public static function bdi(Node|string ...$value): self + public static function h2(Node|string ...$value): HtmlTag { - return HtmlTag::make('bdi', ...$value); + return HtmlTag::make('h2', ...$value); } - - /** - * Overrides the current text direction - */ - public static function bdo(Node|string ...$value): self + public static function h3(Node|string ...$value): HtmlTag { - return HtmlTag::make('bdo', ...$value); + return HtmlTag::make('h3', ...$value); } - - /** - * Defines a section that is quoted from another source - */ - public static function blockquote(Node|string ...$value): self + public static function h4(Node|string ...$value): HtmlTag { - return HtmlTag::make('blockquote', ...$value); + return HtmlTag::make('h4', ...$value); } - - /** - * Defines the document's body - */ - public static function body(Node|string ...$value): self + public static function h5(Node|string ...$value): HtmlTag { - return HtmlTag::make('body', ...$value); + return HtmlTag::make('h5', ...$value); } - - /** - * Defines a single line break - */ - public static function br(): self + public static function h6(Node|string ...$value): HtmlTag { - return HtmlTag::make('br'); + return HtmlTag::make('h6', ...$value); } - - /** - * Defines a clickable button - */ - public static function button(Node|string ...$value): self + public static function main(Node|string ...$value): HtmlTag { - return HtmlTag::make('button', ...$value); + return HtmlTag::make('main', ...$value); } - - /** - * Used to draw graphics, on the fly, via scripting (usually JavaScript) - */ - public static function canvas(Node|string ...$value): self + public static function nav(Node|string ...$value): HtmlTag { - return HtmlTag::make('canvas', ...$value); + return HtmlTag::make('nav', ...$value); } - - /** - * Defines a table caption - */ - public static function caption(Node|string ...$value): self + public static function section(Node|string ...$value): HtmlTag { - return HtmlTag::make('caption', ...$value); + return HtmlTag::make('section', ...$value); } - /** - * Defines the title of a work - */ - public static function cite(Node|string ...$value): self + // --- Text Content --- + public static function blockquote(Node|string ...$value): HtmlTag { - return HtmlTag::make('cite', ...$value); + return HtmlTag::make('blockquote', ...$value); } - - /** - * Defines a piece of computer code - */ - public static function code(Node|string ...$value): self + public static function dd(Node|string ...$value): HtmlTag { - return HtmlTag::make('code', ...$value); + return HtmlTag::make('dd', ...$value); } - - /** - * Specifies column properties for each column within a element - */ - public static function col(): self + public static function div(HtmlClass|string|null $class = null, Node|string ...$value): HtmlTag { - return HtmlTag::make('col'); + $tag = HtmlTag::make('div', ...$value); + if ($class) { + $tag->addClass($class instanceof HtmlClass ? (string) $class : $class); + } + return $tag; } - - /** - * Specifies a group of one or more columns in a table for formatting - */ - public static function colgroup(Node|string ...$value): self + public static function dl(Node|string ...$value): HtmlTag { - return HtmlTag::make('colgroup', ...$value); + return HtmlTag::make('dl', ...$value); } - - /** - * Adds a machine-readable translation of a given content - */ - public static function data(Node|string ...$value): self + public static function dt(Node|string ...$value): HtmlTag { - return HtmlTag::make('data', ...$value); + return HtmlTag::make('dt', ...$value); } - - /** - * Specifies a list of pre-defined options for input controls - */ - public static function datalist(Node|string ...$value): self + public static function figcaption(Node|string ...$value): HtmlTag { - return HtmlTag::make('datalist', ...$value); + return HtmlTag::make('figcaption', ...$value); } - - /** - * Defines a description/value of a term in a description list - */ - public static function dd(Node|string ...$value): self + public static function figure(Node|string ...$value): HtmlTag { - return HtmlTag::make('dd', ...$value); + return HtmlTag::make('figure', ...$value); } - - /** - * Defines text that has been deleted from a document - */ - public static function del(Node|string ...$value): self + public static function hr(): HtmlTag { - return HtmlTag::make('del', ...$value); + return HtmlTag::make('hr'); } - - /** - * Defines additional details that the user can view or hide - */ - public static function details(Node|string ...$value): self + public static function li(Node|string ...$value): HtmlTag { - return HtmlTag::make('details', ...$value); + return HtmlTag::make('li', ...$value); } - - /** - * Specifies a term that is going to be defined within the content - */ - public static function dfn(Node|string ...$value): self + public static function menu(Node|string ...$value): HtmlTag { - return HtmlTag::make('dfn', ...$value); + return HtmlTag::make('menu', ...$value); } - - /** - * Defines a dialog box or window - */ - public static function dialog(Node|string ...$value): self + public static function ol(Node|string ...$value): HtmlTag { - return HtmlTag::make('dialog', ...$value); + return HtmlTag::make('ol', ...$value); } - - /** - * Defines a description list - */ - public static function dl(Node|string ...$value): self + public static function p(Node|string ...$value): HtmlTag { - return HtmlTag::make('dl', ...$value); + return HtmlTag::make('p', ...$value); } - - /** - * Defines a term/name in a description list - */ - public static function dt(Node|string ...$value): self + public static function pre(Node|string ...$value): HtmlTag { - return HtmlTag::make('dt', ...$value); + return HtmlTag::make('pre', ...$value); } - - /** - * Defines emphasized text - */ - public static function em(Node|string ...$value): self + public static function ul(Node|string ...$value): HtmlTag { - return HtmlTag::make('em', ...$value); + return HtmlTag::make('ul', ...$value); } - /** - * Defines a container for an external application - */ - public static function embed(string $src, ?int $height = null, ?int $width = null, ?string $type = null): self + // --- Inline Text Semantics --- + public static function a(string $uri, Node|string ...$value): HtmlTag { - $tag = HtmlTag::make('embed')->setAttribute('src', $src); - - if ($height) - $tag->setAttribute('height', (string) $height); - - if ($width) - $tag->setAttribute('width', (string) $width); - - if ($type) - $tag->setAttribute('type', $type); - - return $tag; + return HtmlTag::make('a', ...$value)->setAttribute('href', $uri); } - - /** - * Groups related elements in a form - */ - public static function fieldset(Node|string ...$value): self + public static function abbr(Node|string ...$value): HtmlTag { - return HtmlTag::make('fieldset', ...$value); + return HtmlTag::make('abbr', ...$value); } - - /** - * Defines a caption for a

element - */ - public static function figcaption(Node|string ...$value): self + public static function b(Node|string ...$value): HtmlTag { - return HtmlTag::make('figcaption', ...$value); + return HtmlTag::make('b', ...$value); } - - /** - * Specifies self-contained content - */ - public static function figure(Node|string ...$value): self + public static function bdi(Node|string ...$value): HtmlTag { - return HtmlTag::make('figure', ...$value); + return HtmlTag::make('bdi', ...$value); } - - /** - * Defines a footer for a document or section - */ - public static function footer(Node|string ...$value): self + public static function bdo(Node|string ...$value): HtmlTag { - return HtmlTag::make('footer', ...$value); + return HtmlTag::make('bdo', ...$value); } - - /** - * Defines an HTML form for user input - */ - public static function form(Node|string ...$value): self + public static function br(): HtmlTag { - return HtmlTag::make('form', ...$value); + return HtmlTag::make('br'); } - - /** - * Contains metadata/information for the document - */ - public static function head(Node|string ...$value): self + public static function cite(Node|string ...$value): HtmlTag { - return HtmlTag::make('head', ...$value); + return HtmlTag::make('cite', ...$value); } - - /** - * Defines a header for a document or section - */ - public static function header(Node|string ...$value): self + public static function code(Node|string ...$value): HtmlTag { - return HtmlTag::make('header', ...$value); + return HtmlTag::make('code', ...$value); } - - /** - * Defines a header and related content - */ - public static function hgroup(Node|string ...$value): self + public static function data(Node|string ...$value): HtmlTag { - return HtmlTag::make('hgroup', ...$value); + return HtmlTag::make('data', ...$value); } - - /** - * Defines a thematic change in the content - */ - public static function hr(): self + public static function dfn(Node|string ...$value): HtmlTag { - return HtmlTag::make('hr'); + return HtmlTag::make('dfn', ...$value); } - - /** - * Defines the root of an HTML document - */ - public static function html(Node|string ...$value): self + public static function em(Node|string ...$value): HtmlTag { - return HtmlTag::make('html', ...$value); + return HtmlTag::make('em', ...$value); } - - /** - * Defines a part of text in an alternate voice or mood - */ - public static function i(Node|string ...$value): self + public static function i(Node|string ...$value): HtmlTag { return HtmlTag::make('i', ...$value); } - - /** - * Defines an inline frame - */ - public static function iframe(Node|string ...$value): self + public static function kbd(Node|string ...$value): HtmlTag { - return HtmlTag::make('iframe', ...$value); - } - - /** - * Defines an image - */ - public static function img(string $src, ?int $height = null, ?int $width = null, ?string $alt = null): self - { - $tag = HtmlTag::make('img')->setAttribute('src', $src); - - if ($height) - $tag->setAttribute('height', (string) $height); - - if ($width) - $tag->setAttribute('width', (string) $width); - - if ($alt) - $tag->setAttribute('alt', $alt); - - return $tag; + return HtmlTag::make('kbd', ...$value); } - - /** - * Defines an input control - */ - public static function input(string $type = 'text'): self + public static function mark(Node|string ...$value): HtmlTag { - return HtmlTag::make('input')->setAttribute('type', $type); + return HtmlTag::make('mark', ...$value); } - - /** - * Defines a text that has been inserted into a document - */ - public static function ins(Node|string ...$value): self + public static function q(Node|string ...$value): HtmlTag { - return HtmlTag::make('ins', ...$value); + return HtmlTag::make('q', ...$value); } - - /** - * Defines keyboard input - */ - public static function kbd(Node|string ...$value): self + public static function rp(Node|string ...$value): HtmlTag { - return HtmlTag::make('kbd', ...$value); + return HtmlTag::make('rp', ...$value); } - - /** - * Defines a label for an element - */ - public static function label(Node|string ...$value): self + public static function rt(Node|string ...$value): HtmlTag { - return HtmlTag::make('label', ...$value); + return HtmlTag::make('rt', ...$value); } - - /** - * Defines a caption for a
element - */ - public static function legend(Node|string ...$value): self + public static function ruby(Node|string ...$value): HtmlTag { - return HtmlTag::make('legend', ...$value); + return HtmlTag::make('ruby', ...$value); } - - /** - * Defines a list item - */ - public static function li(Node|string ...$value): self + public static function s(Node|string ...$value): HtmlTag { - return HtmlTag::make('li', ...$value); + return HtmlTag::make('s', ...$value); } - - /** - * Defines the relationship between a document and an external resource (most used to link to style sheets) - */ - public static function link(string $rel, string $uri): self + public static function samp(Node|string ...$value): HtmlTag { - return HtmlTag::make('link') - ->setAttribute('rel', $rel) - ->setAttribute('href', $uri); + return HtmlTag::make('samp', ...$value); } - - /** - * Specifies the main content of a document - */ - public static function main(Node|string ...$value): self + public static function small(Node|string ...$value): HtmlTag { - return HtmlTag::make('main', ...$value); + return HtmlTag::make('small', ...$value); } - - /** - * Defines an image map - */ - public static function map(Node|string ...$value): self + public static function span(Node|string ...$value): HtmlTag { - return HtmlTag::make('map', ...$value); + return HtmlTag::make('span', ...$value); } - - /** - * Defines marked/highlighted text - */ - public static function mark(Node|string ...$value): self + public static function strong(Node|string ...$value): HtmlTag { - return HtmlTag::make('mark', ...$value); + return HtmlTag::make('strong', ...$value); } - - /** - * Defines an unordered list - */ - public static function menu(Node|string ...$value): self + public static function sub(Node|string ...$value): HtmlTag { - return HtmlTag::make('menu', ...$value); + return HtmlTag::make('sub', ...$value); } - - /** - * Defines metadata about an HTML document - */ - public static function meta(): self + public static function sup(Node|string ...$value): HtmlTag { - return HtmlTag::make('meta'); + return HtmlTag::make('sup', ...$value); } - - /** - * Defines a scalar measurement within a known range (a gauge) - */ - public static function meter(Node|string ...$value): self + public static function time(Node|string ...$value): HtmlTag { - return HtmlTag::make('meter', ...$value); + return HtmlTag::make('time', ...$value); } - - /** - * Defines navigation links - */ - public static function nav(Node|string ...$value): self + public static function u(Node|string ...$value): HtmlTag { - return HtmlTag::make('nav', ...$value); + return HtmlTag::make('u', ...$value); } - - /** - * Defines an alternate content for users that do not support client-side scripts - */ - public static function noscript(Node|string ...$value): self + public static function var(Node|string ...$value): HtmlTag { - return HtmlTag::make('noscript', ...$value); + return HtmlTag::make('var', ...$value); } - - /** - * Defines a container for an external application - */ - public static function object(Node|string ...$value): self + public static function wbr(): HtmlTag { - return HtmlTag::make('object', ...$value); + return HtmlTag::make('wbr'); } - /** - * Defines an ordered list - */ - public static function ol(Node|string ...$value): self + // --- Image and Multimedia --- + public static function area(): HtmlTag { - return HtmlTag::make('ol', ...$value); + return HtmlTag::make('area'); } - - /** - * Defines a group of related options in a drop-down list - */ - public static function optgroup(Node|string ...$value): self + public static function audio(Node|string ...$value): HtmlTag { - return HtmlTag::make('optgroup', ...$value); + return HtmlTag::make('audio', ...$value); } - - /** - * Defines an option in a drop-down list - */ - public static function option(Node|string ...$value): self + public static function img(string $src, ?string $alt = null, ?int $width = null, ?int $height = null): HtmlTag { - return HtmlTag::make('option', ...$value); + $tag = HtmlTag::make('img')->setAttribute('src', $src); + if ($alt !== null) { + $tag->setAttribute('alt', $alt); + } + if ($width !== null) { + $tag->setAttribute('width', (string) $width); + } + if ($height !== null) { + $tag->setAttribute('height', (string) $height); + } + return $tag; } - - /** - * Defines the result of a calculation - */ - public static function output(Node|string ...$value): self + public static function map(Node|string ...$value): HtmlTag { - return HtmlTag::make('output', ...$value); + return HtmlTag::make('map', ...$value); } - - /** - * Defines a paragraph - */ - public static function p(Node|string ...$value): self + public static function track(): HtmlTag { - return HtmlTag::make('p', ...$value); + return HtmlTag::make('track'); } - - /** - * Defines a parameter for an object - */ - public static function param(string $name, string $value): self + public static function video(Node|string ...$value): HtmlTag { - return HtmlTag::make('param') - ->setAttribute('name', $name) - ->setAttribute('value', $value); + return HtmlTag::make('video', ...$value); } - /** - * Defines a container for multiple image resources - */ - public static function picture(Node|string ...$value): self + // --- Embedded Content --- + public static function embed(string $src, ?string $type = null, ?int $width = null, ?int $height = null): HtmlTag { - return HtmlTag::make('picture', ...$value); + $tag = HtmlTag::make('embed')->setAttribute('src', $src); + if ($type !== null) { + $tag->setAttribute('type', $type); + } + if ($width !== null) { + $tag->setAttribute('width', (string) $width); + } + if ($height !== null) { + $tag->setAttribute('height', (string) $height); + } + return $tag; } - - /** - * Defines preformatted text - */ - public static function pre(Node|string ...$value): self + public static function iframe(Node|string ...$value): HtmlTag { - return HtmlTag::make('pre', ...$value); + return HtmlTag::make('iframe', ...$value); } - - /** - * Represents the progress of a task - */ - public static function progress(Node|string ...$value): self + public static function object(Node|string ...$value): HtmlTag { - return HtmlTag::make('progress', ...$value); + return HtmlTag::make('object', ...$value); } - - /** - * Defines a short quotation - */ - public static function q(Node|string ...$value): self + public static function picture(Node|string ...$value): HtmlTag { - return HtmlTag::make('q', ...$value); + return HtmlTag::make('picture', ...$value); } - - /** - * Defines what to show in browsers that do not support ruby annotations - */ - public static function rp(Node|string ...$value): self + public static function portal(Node|string ...$value): HtmlTag { - return HtmlTag::make('rp', ...$value); + return HtmlTag::make('portal', ...$value); } - - /** - * Defines an explanation/pronunciation of characters (for East Asian typography) - */ - public static function rt(Node|string ...$value): self + public static function source(string $src, ?string $type = null): HtmlTag { - return HtmlTag::make('rt', ...$value); + $tag = HtmlTag::make('source')->setAttribute('src', $src); + if ($type !== null) { + $tag->setAttribute('type', $type); + } + return $tag; } - /** - * Defines a ruby annotation (for East Asian typography) - */ - public static function ruby(Node|string ...$value): self + // --- Scripting --- + public static function noscript(Node|string ...$value): HtmlTag { - return HtmlTag::make('ruby', ...$value); + return HtmlTag::make('noscript', ...$value); } - - /** - * Defines text that is no longer correct - */ - public static function s(Node|string ...$value): self + public static function script(Node|string ...$value): HtmlTag { - return HtmlTag::make('s', ...$value); + return HtmlTag::make('script', ...$value); } - /** - * Defines sample output from a computer program - */ - public static function samp(Node|string ...$value): self + // --- Demarcating Edits --- + public static function del(Node|string ...$value): HtmlTag { - return HtmlTag::make('samp', ...$value); + return HtmlTag::make('del', ...$value); } - - /** - * Defines a client-side script - */ - public static function script(Node|string ...$value): self + public static function ins(Node|string ...$value): HtmlTag { - return HtmlTag::make('script', ...$value); + return HtmlTag::make('ins', ...$value); } - /** - * Defines a search section - */ - public static function search(Node|string ...$value): self + // --- Table Content --- + public static function caption(Node|string ...$value): HtmlTag { - return HtmlTag::make('search', ...$value); + return HtmlTag::make('caption', ...$value); } - - /** - * Defines a section in a document - */ - public static function section(Node|string ...$value): self + public static function col(): HtmlTag { - return HtmlTag::make('section', ...$value); + return HtmlTag::make('col'); } - - /** - * Defines a drop-down list - */ - public static function select(Node|string ...$value): self + public static function colgroup(Node|string ...$value): HtmlTag { - return HtmlTag::make('select', ...$value); + return HtmlTag::make('colgroup', ...$value); } - - /** - * Defines smaller text - */ - public static function small(Node|string ...$value): self + public static function table(Node|string ...$value): HtmlTag { - return HtmlTag::make('small', ...$value); + return HtmlTag::make('table', ...$value); } - - /** - * Defines multiple media resources for media elements (
element - */ - public static function summary(Node|string ...$value): self + public static function tr(Node|string ...$value): HtmlTag { - return HtmlTag::make('summary', ...$value); + return HtmlTag::make('tr', ...$value); } - /** - * Defines superscripted text - */ - public static function sup(Node|string ...$value): self + // --- Forms --- + public static function button(Node|string ...$value): HtmlTag { - return HtmlTag::make('sup', ...$value); + return HtmlTag::make('button', ...$value); } - - /** - * Defines a container for SVG graphics - */ - public static function svg(Node|string ...$value): self + public static function datalist(Node|string ...$value): HtmlTag { - return HtmlTag::make('svg', ...$value); + return HtmlTag::make('datalist', ...$value); } - - /** - * Defines a table - */ - public static function table(Node|string ...$value): self + public static function fieldset(Node|string ...$value): HtmlTag { - return HtmlTag::make('table', ...$value); + return HtmlTag::make('fieldset', ...$value); } - - /** - * Groups the body content in a table - */ - public static function tbody(Node|string ...$value): self + public static function form(Node|string ...$value): HtmlTag { - return HtmlTag::make('tbody', ...$value); + return HtmlTag::make('form', ...$value); } - - /** - * Defines a cell in a table - */ - public static function td(Node|string ...$value): self + public static function input(string $type = 'text'): HtmlTag { - return HtmlTag::make('td', ...$value); + return HtmlTag::make('input')->setAttribute('type', $type); } - - /** - * Defines a container for content that should be hidden when the page loads - */ - public static function template(Node|string ...$value): self + public static function label(Node|string ...$value): HtmlTag { - return HtmlTag::make('template', ...$value); + return HtmlTag::make('label', ...$value); } - - /** - * Defines a multiline input control (text area) - */ - public static function textarea(Node|string ...$value): self + public static function legend(Node|string ...$value): HtmlTag { - return HtmlTag::make('textarea', ...$value); + return HtmlTag::make('legend', ...$value); } - - /** - * Groups the footer content in a table - */ - public static function tfoot(Node|string ...$value): self + public static function meter(Node|string ...$value): HtmlTag { - return HtmlTag::make('tfoot', ...$value); + return HtmlTag::make('meter', ...$value); } - - /** - * Defines a header cell in a table - */ - public static function th(Node|string ...$value): self + public static function optgroup(Node|string ...$value): HtmlTag { - return HtmlTag::make('th', ...$value); + return HtmlTag::make('optgroup', ...$value); } - - /** - * Groups the header content in a table - */ - public static function thead(Node|string ...$value): self + public static function option(Node|string ...$value): HtmlTag { - return HtmlTag::make('thead', ...$value); + return HtmlTag::make('option', ...$value); } - - /** - * Defines a specific time (or datetime) - */ - public static function time(Node|string ...$value): self + public static function output(Node|string ...$value): HtmlTag { - return HtmlTag::make('time', ...$value); + return HtmlTag::make('output', ...$value); } - - /** - * Defines a title for the document - */ - public static function title(Node|string ...$value): self + public static function progress(Node|string ...$value): HtmlTag { - return HtmlTag::make('title', ...$value); + return HtmlTag::make('progress', ...$value); } - - /** - * Defines a row in a table - */ - public static function tr(Node|string ...$value): self + public static function select(Node|string ...$value): HtmlTag { - return HtmlTag::make('tr', ...$value); + return HtmlTag::make('select', ...$value); } - - /** - * Defines text tracks for media elements (Click me'; + $this->assertXmlStringEqualsXmlString($expected, TagMaker::build($tag)); + } + + public function testAppendingChildToTag(): void + { + $tag = HtmlTag::ul()->appendChild(HtmlTag::li('Item 1')); + $this->assertXmlStringEqualsXmlString('
  • Item 1
', TagMaker::build($tag)); + } + + public function testPrependingChildToTag(): void + { + $list = HtmlTag::ul(HtmlTag::li('Item 2')); + $list->prependChild(HtmlTag::li('Item 1')); + $this->assertXmlStringEqualsXmlString('
  • Item 1
  • Item 2
', TagMaker::build($list)); + } + + public function testChangingTagName(): void + { + $element = HtmlTag::div(null, 'Content')->setClass('box'); + $element->setName('section'); + $this->assertXmlStringEqualsXmlString('
Content
', TagMaker::build($element)); + $this->assertSame('section', $element->getName()); + } + + public function testCannotAddChildToVoidElementOnConstruction(): void + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Cannot add children to a void element
.'); + HtmlTag::make('br', 'some text'); + } + + public function testCannotAppendChildToVoidElement(): void + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Cannot add children to a void element .'); + $tag = HtmlTag::img('/cat.jpg'); + $tag->appendChild(HtmlTag::span('A caption')); + } + + public function testCannotPrependChildToVoidElement(): void + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Cannot add children to a void element
.'); + $tag = HtmlTag::hr(); + $tag->prependChild(HtmlTag::span('A caption')); + } + + /** + * FIX: This test now inspects the DOM attributes directly instead of comparing strings. + * This is more robust and avoids HTML vs. XML parsing issues. + */ + public function testBooleanAttributeHandling(): void + { + $input = HtmlTag::input('checkbox')->checked()->disabled(); + + // Convert the tag to a DOMElement to inspect its properties. + $domElement = $input->toDomNode(); + + // Assert that the attributes exist and have the correct values. + $this->assertTrue($domElement->hasAttribute('checked')); + $this->assertEquals('checked', $domElement->getAttribute('checked')); + $this->assertTrue($domElement->hasAttribute('disabled')); + $this->assertEquals('disabled', $domElement->getAttribute('disabled')); + + // Test removing the attribute. + $input->disabled(false); + $domElementAfterRemove = $input->toDomNode(); + + // Assert that 'disabled' is now gone, but 'checked' remains. + $this->assertTrue($domElementAfterRemove->hasAttribute('checked')); + $this->assertFalse($domElementAfterRemove->hasAttribute('disabled')); + } +} diff --git a/tests/NodeTypesTest.php b/tests/NodeTypesTest.php new file mode 100644 index 0000000..3a211fd --- /dev/null +++ b/tests/NodeTypesTest.php @@ -0,0 +1,75 @@ + 3 & 2 < 4')); + $output = TagMaker::build($tag); + + // DOMDocument will escape '<', '>', and '&'. + $expected = '

5 > 3 & 2 < 4

'; + $this->assertXmlStringEqualsXmlString($expected, $output); + } + + /** + * Tests that EscapedText correctly creates a CDATA section, preventing + * the content from being parsed by the HTML parser. + */ + public function testEscapedTextCreatesCdataNodeInParent(): void + { + // FIX: The first argument to `div()` is reserved for classes. + // Pass `null` as the first argument to specify no class, and the + // EscapedText node as the second argument (a child). + $tag = HtmlTag::div(null, new EscapedText('if (a < b && b > c) { /* code */ }')); + + $domNode = $tag->toDomNode(); + + $this->assertTrue($domNode->hasChildNodes()); + $this->assertEquals(1, $domNode->childNodes->length); + $firstChild = $domNode->firstChild; + $this->assertInstanceOf(\DOMCdataSection::class, $firstChild); + $this->assertEquals('if (a < b && b > c) { /* code */ }', $firstChild->nodeValue); + } + + public function testHtmlTagMultiCreatesNestedStructure(): void + { + $multiTag = new HtmlTagMulti(['div', 'p', 'strong'], 'Deep Text'); + $output = TagMaker::build($multiTag); + + $expected = '

Deep Text

'; + $this->assertXmlStringEqualsXmlString($expected, $output); + } + + public function testHtmlTagMultiWithNodeChildren(): void + { + $multiTag = new HtmlTagMulti( + ['section', 'article'], + HtmlTag::b('Title'), + ' and text' + ); + $output = TagMaker::build($multiTag); + + $expected = '
Title and text
'; + $this->assertXmlStringEqualsXmlString($expected, $output); + } +}