diff --git a/.github/workflows/php_lint.yml b/.github/workflows/php_lint.yml new file mode 100644 index 0000000..3f46814 --- /dev/null +++ b/.github/workflows/php_lint.yml @@ -0,0 +1,34 @@ +name: PHP Linting + +on: + push: + branches-ignore: + - 'master' + - 'main' + pull_request: + branches-ignore: + - 'master' + - 'main' + +jobs: + php-cs-fixer: + name: PHP-CS-Fixer + runs-on: ubuntu-latest + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + - name: Install PHP-CS-Fixer + run: | + wget https://cs.symfony.com/download/php-cs-fixer-v3.phar -O php-cs-fixer + sudo chmod a+x php-cs-fixer + sudo mv php-cs-fixer /usr/local/bin/php-cs-fixer + - name: Checkout source code + uses: actions/checkout@master + - name: Run PHP-CS-Fixer - Fix + run: php-cs-fixer fix src -vvv --diff --config=.php-cs-fixer.php-highest.php + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Fix PHP code via php-cs-fixer (auto) [no ci] diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml new file mode 100644 index 0000000..f4c5e96 --- /dev/null +++ b/.github/workflows/phpunit.yml @@ -0,0 +1,32 @@ +name: PHPUnit tests + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + tests_suite: + strategy: + matrix: + php_version: [ '8.2', '8.3', '8.4', '8.5' ] + runs-on: ubuntu-latest + steps: + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php_version }} + extensions: json + - uses: actions/checkout@v3 + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v3 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + - name: Install Dependencies + run: | + composer install + - name: Execute PHPUnit tests + run: vendor/bin/phpunit diff --git a/.gitignore b/.gitignore index 870de85..92af99c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ +/.phpunit.cache/ /vendor/ /.idea/ +/var/ .php_cs.cache +.php_cs-fixer.cache +.php-cs-fixer.cache .phpunit.result.cache test.php \ No newline at end of file diff --git a/.php-cs-fixer.cache b/.php-cs-fixer.cache deleted file mode 100644 index d772139..0000000 --- a/.php-cs-fixer.cache +++ /dev/null @@ -1 +0,0 @@ -{"php":"7.4.24","version":"3.5.0:v3.5.0#333f15e07c866e33e2765e84ba1e0b88e6a3af3b","indent":" ","lineEnding":"\n","rules":{"array_syntax":true,"backtick_to_shell_exec":true,"binary_operator_spaces":true,"blank_line_before_statement":{"statements":["return"]},"braces":{"allow_single_line_anonymous_class_with_empty_body":true,"allow_single_line_closure":true},"cast_spaces":true,"class_attributes_separation":{"elements":{"method":"one"}},"class_definition":{"single_line":true},"clean_namespace":true,"concat_space":true,"echo_tag_syntax":true,"empty_loop_body":{"style":"braces"},"empty_loop_condition":true,"fully_qualified_strict_types":true,"function_typehint_space":true,"general_phpdoc_tag_rename":{"replacements":{"inheritDocs":"inheritDoc"}},"include":true,"increment_style":true,"integer_literal_case":true,"lambda_not_used_import":true,"linebreak_after_opening_tag":true,"magic_constant_casing":true,"magic_method_casing":true,"method_argument_space":{"on_multiline":"ignore"},"native_function_casing":true,"native_function_type_declaration_casing":true,"no_alias_language_construct_call":true,"no_alternative_syntax":true,"no_binary_string":true,"no_blank_lines_after_phpdoc":true,"no_empty_comment":true,"no_empty_phpdoc":true,"no_empty_statement":true,"no_extra_blank_lines":{"tokens":["case","continue","curly_brace_block","default","extra","parenthesis_brace_block","square_brace_block","switch","throw","use"]},"no_leading_namespace_whitespace":true,"no_mixed_echo_print":true,"no_multiline_whitespace_around_double_arrow":true,"no_short_bool_cast":true,"no_singleline_whitespace_before_semicolons":true,"no_spaces_around_offset":true,"no_superfluous_phpdoc_tags":{"allow_mixed":true,"allow_unused_params":true},"no_trailing_comma_in_list_call":true,"no_trailing_comma_in_singleline_array":true,"no_unneeded_control_parentheses":{"statements":["break","clone","continue","echo_print","return","switch_case","yield","yield_from"]},"no_unneeded_curly_braces":{"namespaces":true},"no_unset_cast":true,"no_unused_imports":true,"no_whitespace_before_comma_in_array":true,"normalize_index_brace":true,"object_operator_without_whitespace":true,"ordered_imports":true,"php_unit_fqcn_annotation":true,"php_unit_method_casing":true,"phpdoc_align":true,"phpdoc_annotation_without_dot":true,"phpdoc_indent":true,"phpdoc_inline_tag_normalizer":true,"phpdoc_no_access":true,"phpdoc_no_alias_tag":true,"phpdoc_no_package":true,"phpdoc_no_useless_inheritdoc":true,"phpdoc_return_self_reference":true,"phpdoc_scalar":true,"phpdoc_separation":true,"phpdoc_single_line_var_spacing":true,"phpdoc_summary":true,"phpdoc_tag_type":{"tags":{"inheritDoc":"inline"}},"phpdoc_to_comment":true,"phpdoc_trim":true,"phpdoc_trim_consecutive_blank_line_separation":true,"phpdoc_types":true,"phpdoc_types_order":{"null_adjustment":"always_last","sort_algorithm":"none"},"phpdoc_var_without_name":true,"protected_to_private":true,"semicolon_after_instruction":true,"single_class_element_per_statement":true,"single_line_comment_style":{"comment_types":["hash"]},"single_line_throw":true,"single_quote":true,"single_space_after_construct":true,"space_after_semicolon":{"remove_in_empty_for_expressions":true},"standardize_increment":true,"standardize_not_equals":true,"switch_continue_to_break":true,"trailing_comma_in_multiline":true,"trim_array_spaces":true,"types_spaces":true,"unary_operator_spaces":true,"whitespace_after_comma_in_array":true,"yoda_style":true,"blank_line_after_opening_tag":true,"compact_nullable_typehint":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"new_with_braces":true,"no_blank_lines_after_class_opening":true,"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"return_type_declaration":true,"short_scalar_cast":true,"single_blank_line_before_namespace":true,"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"visibility_required":true,"blank_line_after_namespace":true,"constant_case":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"no_break_comment":true,"no_closing_tag":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_import_per_statement":true,"single_line_after_imports":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true},"hashes":{"src\\Client.php":43118670,"src\\DBAL\\Expression\\CollectionOperation.php":2939472196,"src\\DBAL\\Expression\\Comparison.php":4237095711,"src\\DBAL\\Expression\\CompositeDomain.php":354536269,"src\\DBAL\\Expression\\ConversionException.php":107016403,"src\\DBAL\\Expression\\CustomDomain.php":3489925073,"src\\DBAL\\Expression\\DomainInterface.php":3877458668,"src\\DBAL\\Expression\\ExpressionBuilder.php":1657354195,"src\\DBAL\\Expression\\ExpressionBuilderAwareTrait.php":3796741222,"src\\DBAL\\Query\\AbstractQuery.php":2930363797,"src\\DBAL\\Query\\NativeQuery.php":2335259464,"src\\DBAL\\Query\\NoResultException.php":1860820185,"src\\DBAL\\Query\\NoUniqueResultException.php":1183675382,"src\\DBAL\\Query\\OrmQuery.php":632474652,"src\\DBAL\\Query\\QueryBuilder.php":3685258252,"src\\DBAL\\Query\\QueryException.php":754063031,"src\\DBAL\\Query\\QueryInterface.php":3966439455,"src\\DBAL\\RecordManager.php":1113792547,"src\\DBAL\\Repository\\RecordNotFoundException.php":3899077087,"src\\DBAL\\Repository\\RecordRepository.php":377204786,"src\\DBAL\\Schema\\Choice.php":2356126375,"src\\DBAL\\Schema\\Field.php":2188356149,"src\\DBAL\\Schema\\Model.php":145712388,"src\\DBAL\\Schema\\Schema.php":240188129,"src\\DBAL\\Schema\\SchemaException.php":195684899,"src\\DBAL\\Schema\\Selection.php":1041914487,"src\\Endpoint.php":1412879540,"src\\Exception\\AuthenticationException.php":2083575707,"src\\Exception\\ExceptionInterface.php":141028630,"src\\Exception\\MissingConfigParameterException.php":3577118993,"src\\Exception\\RemoteException.php":2392187581,"src\\Exception\\RequestException.php":1948041852}} \ No newline at end of file diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..005f762 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,60 @@ +ignoreDotFiles(false) + ->ignoreVCSIgnored(true) + ->exclude('tests/Fixtures') + ->in(__DIR__) + ->append([ + __DIR__.'/dev-tools/doc.php', + // __DIR__.'/php-cs-fixer', disabled, as we want to be able to run bootstrap file even on lower PHP version, to show nice message + ]) +; + +$config = new PhpCsFixer\Config(); +$config + ->setRiskyAllowed(true) + ->setRules([ + '@PHP74Migration' => true, + '@PHP74Migration:risky' => true, + '@PHPUnit100Migration:risky' => true, + '@PhpCsFixer' => true, + '@PhpCsFixer:risky' => true, + '@Symfony' => true, + 'general_phpdoc_annotation_remove' => ['annotations' => ['expectedDeprecation']], // one should use PHPUnit built-in method instead + 'header_comment' => ['header' => $header], + 'heredoc_indentation' => false, // TODO switch on when # of PR's is lower + 'modernize_strpos' => true, // needs PHP 8+ or polyfill + 'no_useless_concat_operator' => false, // TODO switch back on when the `src/Console/Application.php` no longer needs the concat + 'use_arrow_functions' => false, // TODO switch on when # of PR's is lower + ]) + ->setFinder($finder) +; + +// special handling of fabbot.io service if it's using too old PHP CS Fixer version +if (false !== getenv('FABBOT_IO')) { + try { + PhpCsFixer\FixerFactory::create() + ->registerBuiltInFixers() + ->registerCustomFixers($config->getCustomFixers()) + ->useRuleSet(new PhpCsFixer\RuleSet($config->getRules())) + ; + } catch (PhpCsFixer\ConfigurationException\InvalidConfigurationException $e) { + $config->setRules([]); + } catch (UnexpectedValueException $e) { + $config->setRules([]); + } catch (InvalidArgumentException $e) { + $config->setRules([]); + } +} + +return $config; diff --git a/.php-cs-fixer.php-highest.php b/.php-cs-fixer.php-highest.php new file mode 100644 index 0000000..db24d09 --- /dev/null +++ b/.php-cs-fixer.php-highest.php @@ -0,0 +1,20 @@ += 80200) { + fwrite(STDERR, "PHP CS Fixer's config for PHP-HIGHEST can be executed only on highest supported PHP version - 8.1.*.\n"); + fwrite(STDERR, "Running it on lower PHP version would prevent calling migration rules.\n"); + + exit(1); +} + +$config = require __DIR__.'/.php-cs-fixer.dist.php'; + +$config->setRules(array_merge($config->getRules(), [ + '@PHP81Migration' => true, + '@PHP80Migration:risky' => true, + 'heredoc_indentation' => false, +])); + +return $config; diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..2def437 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,14 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +build: + os: ubuntu-20.04 + tools: + python: "3.9" + +mkdocs: + configuration: mkdocs.yaml \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0803c40..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: php - -php: - - 7.2 - - 7.3 - - 7.4 - -before_install: - - composer self-update - - composer install - -install: - - php -d memory_limit=-1 $(phpenv which composer) install --no-suggest --prefer-dist \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b58e22..5baacb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ CHANGELOG ========= +8.0 +--- + +* Support for PHP >= 8.1 only - The PHP extension `php-xmlrpc` is not used anymore. +* Transport layer - XMLRPC replaced by JSONRPC. +* Code review + 7.0 --- diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..80b3df8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM php:8.2-cli-alpine + +# Install system packages for compilation +RUN apk add --no-cache \ + bash \ + git \ + unzip \ + curl \ + autoconf \ + make \ + gcc \ + g++ \ + musl-dev \ + icu-dev \ + zlib-dev \ + libzip-dev \ + oniguruma-dev \ + linux-headers + +# Install PHP extensions +RUN docker-php-ext-install intl zip + +# Install PCOV +RUN pecl install pcov \ + && docker-php-ext-enable pcov \ + && echo "pcov.enabled=1" >> /usr/local/etc/php/conf.d/pcov.ini \ + && echo "pcov.directory=/app/src" >> /usr/local/etc/php/conf.d/pcov.ini + +# Install Composer +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + +WORKDIR /app diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dd6e505 --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +SHELL := /bin/bash + +DC := docker compose +SERVICE := php + +.DEFAULT_GOAL := help + +.PHONY: help build up down bin logs + +help: + @echo "Targets:" + @echo " build Build image" + @echo " up Launch the container" + @echo " down Stop and delete the container" + @echo " logs Display logs" + +build: + $(DC) build + +up: + $(DC) up -d + +down: + $(DC) down --remove-orphans + +logs: + $(DC) logs -f $(SERVICE) \ No newline at end of file diff --git a/README.md b/README.md index 81f97a3..3112a30 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,35 @@ PHP Odoo API client =================== -[![Build Status](https://travis-ci.org/Ang3/php-odoo-api-client.svg?branch=master)](https://travis-ci.org/Ang3/php-odoo-api-client) +[![Code Quality](https://github.com/ang3/php-odoo-api-client/actions/workflows/php_lint.yml/badge.svg)](https://github.com/ang3/php-odoo-api-client/actions/workflows/php_lint.yml) +[![PHPUnit tests](https://github.com/ang3/php-odoo-api-client/actions/workflows/phpunit.yml/badge.svg)](https://github.com/ang3/php-odoo-api-client/actions/workflows/phpunit.yml) [![Latest Stable Version](https://poser.pugx.org/ang3/php-odoo-api-client/v/stable)](https://packagist.org/packages/ang3/php-odoo-api-client) [![Latest Unstable Version](https://poser.pugx.org/ang3/php-odoo-api-client/v/unstable)](https://packagist.org/packages/ang3/php-odoo-api-client) [![Total Downloads](https://poser.pugx.org/ang3/php-odoo-api-client/downloads)](https://packagist.org/packages/ang3/php-odoo-api-client) -Odoo API client using -[XML-RPC Odoo ORM External API](https://www.odoo.com/documentation/12.0/webservices/odoo.html). It allows -you call your odoo instance and manage records easily. +This package is a PHP library to connect and interact with an Odoo instance via JSON-RPC by default. +It follows [the official Odoo documentation](https://www.odoo.com/documentation/13.0/developer/misc/api/odoo.html). -**You are reading the documentation of version ```7.0```, if your version is older, please read -[this documentation (6.1.3)](https://github.com/Ang3/php-odoo-api-client/tree/v6.1.3).** -Please see the file [UPGRADE-7.0.md](https://github.com/Ang3/php-odoo-api-client/blob/7.0/UPGRADE-7.0.md) -to upgrade your version easily. +> From v8.x, this package is dedicated to the client only and PHP 8.1+. +> The PHP extension `xmlrpc` is not required anymore. +> +> - For DBAL features, please report to the package [PHP Odoo DBAL](https://github.com/ang3/php-odoo-dbal). +> +> - For older versions of PHP, please use the version 7.x. -**Main features** - -- Authentication ```<7.0``` -- Basic XML-RPC calls ```<7.0``` -- Expression builder ```<7.0``` -- Database Abstraction Layer (DBAL) ```>=7.0``` - - Record manager - - Repositories` - - Query builder - -**Good to know** - -If you are in Symfony application you should be interested in the bundle -[ang3/odoo-bundle](https://github.com/Ang3/odoo-bundle) (client integration). - -Requirements -============ - -- The PHP extension ```php-xmlrpc``` must be enabled. - -| Odoo server | Compatibility | Comment | -| --- | --- | --- | -| newer | Unknown | Needs feddback | -| v13.0 | Yes | Some Odoo model names changed (e.g account.invoice > account.move) | -| v12.0 | Yes | First tested version | -| < v12 | Unknown | Needs feddback | +| Odoo series | Compatibility | Comment | +|-------------|---------------|-----------------| +| v13.0+ | Unknown | Needs feedbacks | +| v13.0 | Yes | | +| v12.0 | Yes | | +| Older | Unknown | Needs feedbacks | Installation -============ +------------ + +PHP version 8.2 or newer to develop using the client. Other requirements, such as PHP extensions, are enforced by +composer. See the `require` section of [composer.json file](../composer.json) +for details. Open a command console, enter your project directory and execute the following command to download the latest stable version of the client: @@ -57,777 +43,126 @@ in the [installation chapter](https://getcomposer.org/doc/00-intro.md) of the Composer documentation. Basic usage -=========== - -First, you have to create a client instance: - -```php -', '', '', '', $logger = null); - -// Option 2 : by calling the static method ::createFromConfig() with configuration as array -$client = Client::createFromConfig([ - 'url' => '', - 'database' => '', - 'username' => '', - 'password' => '', -], $logger = null); -``` - -Exceptions: -- ```Ang3\Component\Odoo\Exception\MissingConfigParameterException``` when a required parameter is missing -from the static method ```createFromConfig()```. - -Then, make your call: - -```php -$result = $client->call($name, $method, $parameters = [], $options = []); -``` - -Exceptions: -- ```Ang3\Component\Odoo\Exception\AuthenticationException``` when authentication failed. -- ```Ang3\Component\Odoo\Exception\RequestException``` when request failed. +----------- -These previous exception can be thrown by all methods of the client. - -DBAL (Database Abstraction Layer) -================================= +### Create a client -First of all, Odoo is a database. Each "model" is a table and has its own fields. +You can create a client with DSN or array configuration. +Both methods will create a connection to construct the client. -> DBAL features was added in version ```7.0``` - If your version is older, please use the built-in -ORM methods of the client like explained in the -> [dedicated documentation](https://github.com/Ang3/php-odoo-api-client/tree/v6.1.3): -be aware that these client ORM methods are deprecated since version ```7.0```. +#### DSN -Record manager --------------- +First, create your DSN from your connection settings. The DSN must look like this: -The client provides a record manager to manage records of your Odoo models. +`https://:@/` -You can get the related manager of the client like below: +The scheme must be `http` or `https` (by default). +If your password contains special characters, encode it with the native function `urlencode()`: ```php -$recordManager = $client->getRecordManager(); +$myEncodedPassword = urlencode('high_password_with_special_charaters'); ``` -You can also create your own with a client instance: +Then, use the DSN to create a connection: ```php -use Ang3\Component\Odoo\DBAL\RecordManager; +use Ang3\Component\Odoo\Connection; -/** @var \Ang3\Component\Odoo\Client $myClient */ -$recordManager = new RecordManager($myClient); +$connection = Connection::createFromDsn($dsn, $transport = null, $logger = null); ``` -### Built-in ORM methods - -Here is all built-in ORM methods provided by the record manager: +Finally, create your client with the connection: ```php -use Ang3\Component\Odoo\DBAL\Expression\DomainInterface; - -/** - * Create a new record. - * - * @return int the ID of the new record - */public function create(string $modelName, array $data): int; - -/** - * Update record(s). - * - * NB: It is not currently possible to perform “computed” updates (by criteria). - * To do it, you have to perform a search then an update with search result IDs. - * - * @param array|int $ids - */ -public function update(string $modelName, $ids, array $data = []): void; - -/** - * Delete record(s). - * - * NB: It is not currently possible to perform “computed” deletes (by criteria). - * To do it, you have to perform a search then a delete with search result IDs. - * - * @param array|int $ids - */ -public function delete(string $modelName, $ids): void; - -/** - * Search one ID of record by criteria. - */ -public function searchOne(string $modelName, ?DomainInterface $criteria): ?int; - -/** - * Search all ID of record(s). - * - * @return int[] - */ -public function searchAll(string $modelName, array $orders = [], int $limit = null, int $offset = null): array; - -/** - * Search ID of record(s) by criteria. - * - * @return int[] - */ -public function search(string $modelName, ?DomainInterface $criteria = null, array $orders = [], int $limit = null, int $offset = null): array; - -/** - * Find ONE record by ID. - * - * @throws RecordNotFoundException when the record was not found - */ -public function read(string $modelName, int $id, array $fields = []): array; - -/** - * Find ONE record by ID. - */ -public function find(string $modelName, int $id, array $fields = []): ?array; - -/** - * Find ONE record by criteria. - */ -public function findOneBy(string $modelName, ?DomainInterface $criteria = null, array $fields = [], array $orders = [], int $offset = null): ?array; - -/** - * Find all records. - * - * @return array[] - */ -public function findAll(string $modelName, array $fields = [], array $orders = [], int $limit = null, int $offset = null): array; - -/** - * Find record(s) by criteria. - * - * @return array[] - */ -public function findBy(string $modelName, ?DomainInterface $criteria = null, array $fields = [], array $orders = [], int $limit = null, int $offset = null): array; - -/** - * Check if a record exists. - */ -public function exists(string $modelName, int $id): bool; - -/** - * Count number of all records for the model. - */ -public function countAll(string $modelName): int; - -/** - * Count number of records for a model and criteria. - */ -public function count(string $modelName, ?DomainInterface $criteria = null): int; -``` - -For ```$criteria``` in select/search queries and ```$data``` for data writing context, please read the section -[Expression builder](#expression-builder). - -Schema ------- - -You can get the schema of your Odoo database by calling the getter method -```RecordManager::getSchema()```: +use Ang3\Component\Odoo\Client; -```php -/** @var \Ang3\Component\Odoo\DBAL\Schema\Schema $schema */ -$schema = $recordManager->getSchema(); +$client = new Client($connection, $transport = null, $logger = null); ``` -The schema helps you to get all model names or get metadata of a model. +#### Array configuration -### Get all model names +To create a connection from a configuration as array: ```php -/** @var string[] $modelNames */ -$modelNames = $schema->getModelNames(); +$client = Client::create([ + 'host' => '', // required (without scheme!) + 'database' => '', // required + 'username' => '', // required + 'password' => '', // required + 'scheme' => 'https', // optional +], $transport = null, $logger = null); ``` -### Get model metadata +#### Error handling -```php -/** @var \Ang3\Component\Odoo\DBAL\Schema\Model $model */ -$model = $schema->getModel('res.company'); -``` - -An exception of type ```Ang3\Component\Odoo\DBAL\Schema\SchemaException``` is thrown if the model -does not exist. - -Query builder -------------- - -It helps you to create queries easily by chaining helpers methods (like Doctrine for SQL databases). +Exceptions: +- ```Ang3\Component\Odoo\Exception\ConnectionException``` on connection errors. -### Create a query builder +### Make a request ```php -/** @var string|null $modelName */ -$queryBuilder = $recordManager->createQueryBuilder($modelName); +$result = $client->request('service_name', 'method_name', 'argument_1', 'argument_2'/*, ...*/); ``` -The variable ```$modelName``` represents the target model of your query (clause ```from```). - -### Build your query +Exceptions: +- ```Ang3\Component\Odoo\Exception\AuthenticationException``` when authentication failed. +- ```Ang3\Component\Odoo\Exception\RequestException``` when request failed. +- ```Ang3\Component\Odoo\Exception\TransportException``` on transport errors. -Here is a complete list of helper methods available in ```QueryBuilder```: +These previous exception can be thrown by all methods of the client. -```php -/** - * Defines the query of type "SELECT" with selected fields. - * No fields selected = all fields returned. - * - * @param array|string|null $fields - */ -public function select($fields = null): self; - -/** - * Defines the query of type "SEARCH". - */ -public function search(): self; - -/** - * Defines the query of type "INSERT". - */ -public function insert(): self; - -/** - * Defines the query of type "UPDATE" with ids of records to update. - * - * @param int[] $ids - */ -public function update(array $ids): self; - -/** - * Defines the query of type "DELETE" with ids of records to delete. - */ -public function delete(array $ids): self; - -/** - * Adds a field to select. - * - * @throws LogicException when the type of the query is not "SELECT". - */ -public function addSelect(string $fieldName): self; - -/** - * Gets selected fields. - */ -public function getSelect(): array; - -/** - * Sets the target model name. - */ -public function from(string $modelName): self; - -/** - * Gets the target model name of the query. - */ -public function getFrom(): ?string; - -/** - * Sets target IDs in case of query of type "UPDATE" or "DELETE". - * - * @throws LogicException when the type of the query is not "UPDATE" nor "DELETE". - */ -public function setIds(array $ids): self; - -/** - * Adds target ID in case of query of type "UPDATE" or "DELETE". - * - * @throws LogicException when the type of the query is not "UPDATE" nor "DELETE". - */ -public function addId(int $id): self; - -/** - * Sets field values in case of query of type "INSERT" or "UPDATE". - * - * @throws LogicException when the type of the query is not "INSERT" nor "UPDATE". - */ -public function setValues(array $values = []): self; - -/** - * Set a field value in case of query of type "INSERT" or "UPDATE". - * - * @param mixed $value - * - * @throws LogicException when the type of the query is not "INSERT" nor "UPDATE". - */ -public function set(string $fieldName, $value): self; - -/** - * Gets field values set in case of query of type "INSERT" or "UPDATE". - */ -public function getValues(): array; - -/** - * Sets criteria for queries of type "SELECT" and "SEARCH". - * - * @throws LogicException when the type of the query is not "SELECT" not "SEARCH". - */ -public function where(?DomainInterface $domain = null): self; - -/** - * Takes the WHERE clause and adds a node with logical operator AND. - * - * @throws LogicException when the type of the query is not "SELECT" nor "SEARCH". - */ -public function andWhere(DomainInterface $domain): self; - -/** - * Takes the WHERE clause and adds a node with logical operator OR. - * - * @throws LogicException when the type of the query is not "SELECT" nor "SEARCH". - */ -public function orWhere(DomainInterface $domain): self; - -/** - * Gets the WHERE clause. - */ -public function getWhere(): ?DomainInterface; - -/** - * Sets orders. - */ -public function setOrders(array $orders = []): self; - -/** - * Clears orders and adds one. - */ -public function orderBy(string $fieldName, bool $isAsc = true): self; - -/** - * Adds order. - * - * @throws LogicException when the query type is not valid. - */ -public function addOrderBy(string $fieldName, bool $isAsc = true): self; - -/** - * Gets ordered fields. - */ -public function getOrders(): array; - -/** - * Sets the max results of the query (limit). - */ -public function setMaxResults(?int $maxResults): self; - -/** - * Gets the max results of the query. - */ -public function getMaxResults(): ?int; - -/** - * Sets the first results of the query (offset). - */ -public function setFirstResult(?int $firstResult): self; - -/** - * Gets the first results of the query. - */ -public function getFirstResult(): ?int; -``` +### ExecuteKw -Then, build your query like below: +The client has a shortcut for the Odoo method `execute_kw` of Odoo service `object`. +By calling the method `executeKw`, the client tries to authenticate then makes the request and returns result. ```php -$query = $queryBuilder->getQuery(); +$result = $client->executeKw('name', 'method', $parameters = [], $options = []); ``` -Your query is an instance of ```Ang3\Component\Odoo\Query\OrmQuery```. - -### Execute your query - -You can get/count results or execute insert/update/delete by differents ways depending on the query type. +The UID is stored in the client to process authentication once. You can retrieve the UID by calling the client getter: ```php -/** - * Counts the number of records from parameters. - * Allowed methods: SEARCH, SEARCH_READ. - * - * @throws QueryException on invalid query method. - */ -public function count(): int; - -/** - * Gets just ONE scalar result. - * Allowed methods: SEARCH, SEARCH_READ. - * - * @return bool|int|float|string - * - * @throws NoUniqueResultException on no unique result - * @throws NoResultException on no result - * @throws QueryException on invalid query method. - */ -public function getSingleScalarResult(); - -/** - * Gets one or NULL scalar result. - * Allowed methods: SEARCH, SEARCH_READ. - * - * @return bool|int|float|string|null - * - * @throws NoUniqueResultException on no unique result - * @throws QueryException on invalid query method. - */ -public function getOneOrNullScalarResult(); - -/** - * Gets a list of scalar result. - * Allowed methods: SEARCH, SEARCH_READ. - * - * @throws QueryException on invalid query method. - * - * @return array - */ -public function getScalarResult(): array; - -/** - * Gets one row. - * Allowed methods: SEARCH, SEARCH_READ. - * - * @throws NoUniqueResultException on no unique result - * @throws NoResultException on no result - * @throws QueryException on invalid query method. - */ -public function getSingleResult(): array; - -/** - * Gets one or NULL row. - * Allowed methods: SEARCH, SEARCH_READ. - * - * @throws NoUniqueResultException on no unique result - * @throws QueryException on invalid query method. - */ -public function getOneOrNullResult(): ?array; - -/** - * Gets all result rows. - * Allowed methods: SEARCH, SEARCH_READ. - * - * @throws QueryException on invalid query method. - */ -public function getResult(): array; - -/** - * Execute the query. - * Allowed methods: all. - * - * @return mixed - */ -public function execute(); +$uid = $client->getUid(); // int|null ``` -Repositories -============ - -Sometimes, you would want to keep your queries in memory to reuse it in your code. To do it, you should use -a repository. A repository is a class that helps you to isolate queries for a dedicated model. - -For example, let's create the repository for your companies and define a query to get all french companies: +### Get the Odoo version ```php -namespace App\Odoo\Repository; - -use Ang3\Component\Odoo\DBAL\RecordManager; -use Ang3\Component\Odoo\DBAL\Repository\RecordRepository; - -class CompanyRepository extends RecordRepository -{ - public function __construct(RecordManager $recordManager) - { - parent::__construct($recordManager, 'res.company'); - } - - public function findFrenchCompanies(): array - { - return $this - ->createQueryBuilder() - ->select('name') - ->where($this->expr()->eq('country_id.code', 'FR')) - ->getQuery() - ->getResult(); - } -} +$version = $client->version(); // \Ang3\Component\Odoo\Metadata\Version +dump($version); ``` -Note that Odoo will always return the record ID in the result, even if you didn't select it explicitly. +### Custom transport -Each repository is registered inside the record manager on construct. -That's why you can retrieve your repository directly from the record manager: +By default, the library provides a JSON-RPC transport and the client creates it by default +if no transport is explicitly provided. -```php -/** @var \App\Odoo\Repository\CompanyRepository $companyRepository */ -$companyRepository = $recordManager->getRepository('res.company'); -``` - -If no repository exists for a model, the default repository ```Ang3\Component\Odoo\DBAL\Repository\RecordRepository``` -is used. Last but not least, all repositories are stored into the related record manager to avoid creating multiple -instances of same repository. +You can provide your own transport when you create your client as second argument. By this way, the JSON-RPC transport +will not be created and the client will use yours instead. -Expression builder -================== +Your custom transport must implement the interface `Ang3\Component\Odoo\Transport\TransportInterface`. -There are two kinds of expressions : ```domains``` for criteria -and ```collection operations``` in data writing context. -Odoo has its own array format for those expressions. -The aim of the expression builder is to provide some -helper methods to simplify your programmer's life. +More features +------------- -Here is an example of how to get a builder from a client or record manager: +Here is the list of packages using the client to provide more features: -```php -$expr = $clientOrRecordManager->expr(); -// or $expr = $clientOrRecordManager->getExpressionBuilder(); -``` +- [Odoo Database Abstraction Layer (DBAL)](https://github.com/ang3/php-odoo-dbal): Execute queries like Doctrine to +your Odoo database. -You can still use the expression builder as standalone by creating a new instance: +Tests +----- -```php -use Ang3\Component\Odoo\DBAL\Expression\ExpressionBuilder; +To run tests: -$expr = new ExpressionBuilder(); +```console +$ git clone git@github.com:Ang3/php-odoo-api-client.git +$ composer install +$ vendor/bin/simple-phpunit ``` -Domains +License ------- -For all **select/search/count** queries, -Odoo is waiting for an array of [domains](https://www.odoo.com/documentation/13.0/reference/orm.html#search-domains) -with a *polish notation* for logical operations (```AND```, ```OR``` and ```NOT```). - -It could be quickly ugly to do a complex domain, but don't worry the builder makes all -for you. :-) - -Each domain builder method creates an instance of ```Ang3\Component\Odoo\Expression\DomainInterface```. -The only one method of this interface is ```toArray()``` to get a normalized array of the expression. - -To illustrate how to work with it, here is an example using ```ExpressionBuilder``` helper methods: - -```php -// Get the expression builder -$expr = $recordManager->expr(); - -$result = $recordManager->findBy('model_name', $expr->andX( // Logical node "AND" - $expr->gte('id', 10), // id >= 10 - $expr->lte('id', 100), // id <= 10 -)); -``` - -Of course, you can nest logical nodes: - -```php -$result = $recordManager->findBy('model_name', $expr->andX( - $expr->orX( - $expr->eq('A', 1), - $expr->eq('B', 1) - ), - $expr->orX( - $expr->eq('C', 1), - $expr->eq('D', 1), - $expr->eq('E', 1) - ) -)); -``` - -Internally, the client formats automatically all domains by calling the special builder -method ```normalizeDomains()```. - -Here is a complete list of helper methods available in ```ExpressionBuilder``` for domain expressions: - -```php -/** - * Create a logical operation "AND". - */ -public function andX(DomainInterface ...$domains): CompositeDomain; - -/** - * Create a logical operation "OR". - */ -public function orX(DomainInterface ...$domains): CompositeDomain; - -/** - * Create a logical operation "NOT". - */ -public function notX(DomainInterface ...$domains): CompositeDomain; - -/** - * Check if the field is EQUAL TO the value. - * - * @param mixed $value - */ -public function eq(string $fieldName, $value): Comparison; - -/** - * Check if the field is NOT EQUAL TO the value. - * - * @param mixed $value - */ -public function neq(string $fieldName, $value): Comparison; - -/** - * Check if the field is UNSET OR EQUAL TO the value. - * - * @param mixed $value - */ -public function ueq(string $fieldName, $value): Comparison; - -/** - * Check if the field is LESS THAN the value. - * - * @param mixed $value - */ -public function lt(string $fieldName, $value): Comparison; - -/** - * Check if the field is LESS THAN OR EQUAL the value. - * - * @param mixed $value - */ -public function lte(string $fieldName, $value): Comparison; - -/** - * Check if the field is GREATER THAN the value. - * - * @param mixed $value - */ -public function gt(string $fieldName, $value): Comparison; - -/** - * Check if the field is GREATER THAN OR EQUAL the value. - * - * @param mixed $value - */ -public function gte(string $fieldName, $value): Comparison; - -/** - * Check if the variable is LIKE the value. - * - * An underscore _ in the pattern stands for (matches) any single character - * A percent sign % matches any string of zero or more characters. - * - * If $strict is set to FALSE, the value pattern is "%value%" (automatically wrapped into signs %). - * - * @param mixed $value - */ -public function like(string $fieldName, $value, bool $strict = false, bool $caseSensitive = true): Comparison; - -/** - * Check if the field is IS NOT LIKE the value. - * - * @param mixed $value - */ -public function notLike(string $fieldName, $value, bool $caseSensitive = true): Comparison; - -/** - * Check if the field is IN values list. - */ -public function in(string $fieldName, array $values = []): Comparison; - -/** - * Check if the field is NOT IN values list. - */ -public function notIn(string $fieldName, array $values = []): Comparison; -``` - -Collection operations ---------------------- - -In data writing context with queries of type **insert/update**, Odoo allows you to manage ***toMany** collection -fields with special commands. - -Please read the [ORM documentation](https://www.odoo.com/documentation/13.0/reference/orm.html#openerp-models-relationals-format) -to known what we are talking about. - -The expression builder provides helper methods to build a well-formed *operation command*: -each operation method returns an instance of ```Ang3\Component\Odoo\DBAL\Expression\CollectionOperation```. -Like domains, the only one method of this interface is ```toArray()``` to get a normalized array of the expression. - -To illustrate how to work with operations, here is an example using ```ExpressionBuilder``` helper methods: - -```php -// Get the expression builder -$expr = $recordManager->expr(); - -// Prepare data for a new record -$data = [ - 'foo' => 'bar', - 'bar_ids' => [ // Field of type "manytoMany" - $expr->addRecord(3), // Add the record of ID 3 to the set - $expr->createRecord([ // Create a new sub record and add it to the set - 'bar' => 'baz' - // ... - ]) - ] -]; - -$result = $recordManager->create('model_name', $data); -``` - -Internally, the client formats automatically the whole query parameters for all writing methods -(```create``` and ```update```) by calling the special builder -method ```normalizeData()```. - -Here is a complete list of helper methods available in ```ExpressionBuilder``` for operation expressions: - -```php -/** - * Adds a new record created from data. - */ -public function createRecord(array $data): CollectionOperation; - -/** - * Updates an existing record of id $id with data. - * /!\ Can not be used in record CREATE query. - */ -public function updateRecord(int $id, array $data): CollectionOperation; - -/** - * Adds an existing record of id $id to the collection. - */ -public function addRecord(int $id): CollectionOperation; - -/** - * Removes the record of id $id from the collection, but does not delete it. - * /!\ Can not be used in record CREATE query. - */ -public function removeRecord(int $id): CollectionOperation; - -/** - * Removes the record of id $id from the collection, then deletes it from the database. - * /!\ Can not be used in record CREATE query. - */ -public function deleteRecord(int $id): CollectionOperation; - -/** - * Replaces all existing records in the collection by the $ids list, - * Equivalent to using the command "clear" followed by a command "add" for each id in $ids. - */ -public function replaceRecords(array $ids = []): CollectionOperation; - -/** - * Removes all records from the collection, equivalent to using the command "remove" on every record explicitly. - * /!\ Can not be used in record CREATE query. - */ -public function clearRecords(): CollectionOperation; -``` - -Data support ------------- - -- Scalar values are unchanged -- Arrays recursive conversion -- Objects of type ```\DateTimeInterface``` are automatically formatted into string in UTC timezone -- Iterable/generator are fetched into an array -- Non-iterable values are automatically casted to string - (so any non-supported objects must define the method ```__toString()```) - -Resources -========= - -- [CHANGELOG.md](CHANGELOG.md) \ No newline at end of file +This software is published under the [MIT License](./LICENCE). diff --git a/UPGRADE-7.0.md b/UPGRADE-7.0.md index 849f799..8f220db 100644 --- a/UPGRADE-7.0.md +++ b/UPGRADE-7.0.md @@ -35,4 +35,4 @@ Expression builder - Implemented non-scalar values support: - Dates into string - Iterable / Generator into array - - Object into string \ No newline at end of file + - Object into string diff --git a/UPGRADE-8.0.md b/UPGRADE-8.0.md new file mode 100644 index 0000000..1171353 --- /dev/null +++ b/UPGRADE-8.0.md @@ -0,0 +1,10 @@ +UPGRADE FROM 7.x to 8.0 +======================= + +**Binary compatibility break (BC break)** + +- PHP 8.1+ required +- The PHP extension ```php-xmlrpc``` is not used anymore. +- The PHP extension ```php-json``` is now required. +- DBAL features moved into the package [ang3/php-odoo-dbal](https://github.com/ang3/php-odoo-dbal) +- Github workflows and tests diff --git a/bin/check-code b/bin/check-code new file mode 100755 index 0000000..58888f5 --- /dev/null +++ b/bin/check-code @@ -0,0 +1,5 @@ +#!/bin/bash +set -uoe pipefail + +echo -e "\033[33;1mChecking source code\033[0m" +vendor/bin/phpstan analyse src --memory-limit=2G "$@" diff --git a/bin/dev/check_code.sh b/bin/dev/check_code.sh deleted file mode 100644 index 9240572..0000000 --- a/bin/dev/check_code.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/sh - -# shellcheck disable=SC2039 -echo -e "\033[33;1m" -echo -e "Checking code" -echo -e "=============\033[0m" -echo - -if [ -z "$1" ] -then - directory='src' - echo -e "No directory specified (default: src)." - echo -else - directory=$1 - echo -e "Level:" $1 - echo -fi - -if [ -z "$2" ] -then - level=7 - echo -e "No level specified (default: 7 [max])." - echo -else - level=$2 - echo -e "Level:" $2 - echo -fi - -if [ $directory = "src" ] -then - config_file='phpstan.neon' -else - config_file='phpstan.'$directory'.neon' -fi - -echo -e "Config file:" $config_file -echo - -vendor/bin/phpstan analyse $directory -c $config_file -l $level -vvv \ No newline at end of file diff --git a/bin/dev/fix_code.sh b/bin/dev/fix_code.sh deleted file mode 100644 index cbcde6b..0000000 --- a/bin/dev/fix_code.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -# shellcheck disable=SC2039 -echo -e "\033[33;1m" -echo -e "Fixing code" -echo -e "===========\033[0m" -echo - -if [ -z "$1" ] -then - directory='src' - echo "No directory specified (default: src)." - echo -else - directory=$1 - echo "Directory:" $directory - echo -fi - -vendor/bin/php-cs-fixer -v fix $directory --rules='{"@Symfony": true}' \ No newline at end of file diff --git a/bin/fix-code b/bin/fix-code new file mode 100755 index 0000000..1627f74 --- /dev/null +++ b/bin/fix-code @@ -0,0 +1,9 @@ +#!/bin/bash +set -uoe pipefail + +echo -e "\033[33;1mFixing code in '/src'\033[0m" +vendor/bin/php-cs-fixer fix src "$@" + +echo +echo -e "\033[33;1mFixing code in '/tests'\033[0m" +vendor/bin/php-cs-fixer fix tests "$@" diff --git a/bin/test b/bin/test new file mode 100755 index 0000000..a1469a5 --- /dev/null +++ b/bin/test @@ -0,0 +1,5 @@ +#!/bin/bash +set -uoe pipefail + +echo -e "\033[33;1mRunning PHPUnit\033[0m" +vendor/bin/phpunit "$@" diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..2edbe63 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,9 @@ +services: + php: + build: + context: . + dockerfile: Dockerfile + volumes: + - ./:/app + working_dir: /app + tty: true \ No newline at end of file diff --git a/composer.json b/composer.json index 6ec98a2..694ffc7 100644 --- a/composer.json +++ b/composer.json @@ -2,12 +2,11 @@ "name": "ang3/php-odoo-api-client", "type": "component", "description": "Odoo API client", - "keywords": ["odoo", "api", "client", "domain", "operation", "dbal", "query", "builder", "repository", "builder", "12", "13"], + "keywords": ["odoo", "api", "client", "transport", "json"], "license": "MIT", "authors": [ { "name": "Joanis ROUANET", - "email": "joanis.ang3@gmail.com", "homepage": "https://github.com/Ang3" } ], @@ -18,18 +17,16 @@ } }, "require": { - "php": ">=7.2", + "php": ">=8.2", "ext-json": "*", - "ang3/php-xmlrpc-client": "^1.0.2", - "psr/log": "^1.1|^2.0|^3.0" + "psr/log": "^1.1|^2.0|^3.0", + "nyholm/dsn": "^2.0", + "webmozart/assert": "^2.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.0", - "roave/security-advisories": "dev-master", - "phpstan/phpstan": "^0.12.94", - "symfony/var-dumper": "^3.4 || ^4.0 || ^5.0", - "symfony/phpunit-bridge": "^3.4 || ^4.0 || ^5.0", - "symfony/property-info": "^3.4 || ^4.0 || ^5.0", - "symfony/inflector": "^3.4 || ^4.0 || ^5.0" + "symfony/test-pack": "^1.0", + "symfony/var-dumper": "^6.3", + "phpstan/phpstan": "^2.1", + "friendsofphp/php-cs-fixer": "^3.92" } } diff --git a/composer.lock b/composer.lock index 249023b..1f05f5c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,34 +4,37 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "33d129d1729c00c64a7f6028ec0393da", + "content-hash": "c7770aad4076f9c223f8da2ca5590321", "packages": [ { - "name": "ang3/php-xmlrpc-client", - "version": "v1.0.3", + "name": "nyholm/dsn", + "version": "2.0.1", "source": { "type": "git", - "url": "https://github.com/Ang3/php-xmlrpc-client.git", - "reference": "c51c06ac2293977e21ce0be546891a5431284d1a" + "url": "https://github.com/Nyholm/dsn.git", + "reference": "9445621b426bac8c0ca161db8cd700da00a4e618" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Ang3/php-xmlrpc-client/zipball/c51c06ac2293977e21ce0be546891a5431284d1a", - "reference": "c51c06ac2293977e21ce0be546891a5431284d1a", + "url": "https://api.github.com/repos/Nyholm/dsn/zipball/9445621b426bac8c0ca161db8cd700da00a4e618", + "reference": "9445621b426bac8c0ca161db8cd700da00a4e618", "shasum": "" }, "require": { - "ext-xmlrpc": "*", - "php": "^7.2|^8.0" + "php": ">=7.1" }, "require-dev": { - "roave/security-advisories": "dev-master", - "symfony/phpunit-bridge": "^3.4 || ^4.0" + "symfony/phpunit-bridge": "^5.1" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, "autoload": { "psr-4": { - "Ang3\\Component\\XmlRpc\\": "src/" + "Nyholm\\Dsn\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -40,36 +43,42 @@ ], "authors": [ { - "name": "Joanis Rouanet", - "email": "joanis.ang3@gmail.com" + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" } ], - "description": "PHP XML-RPC client", + "description": "Parse your DSN strings in a powerful and flexible way", + "homepage": "http://tnyholm.se", "keywords": [ - "client", - "php", - "rpc", - "xml", - "xmlrpc" + "database", + "dsn", + "dsn parser", + "parser" ], "support": { - "issues": "https://github.com/Ang3/php-xmlrpc-client/issues", - "source": "https://github.com/Ang3/php-xmlrpc-client/tree/v1.0.3" + "issues": "https://github.com/Nyholm/dsn/issues", + "source": "https://github.com/Nyholm/dsn/tree/2.0.1" }, - "time": "2021-12-13T18:06:52+00:00" + "funding": [ + { + "url": "https://github.com/Nyholm", + "type": "github" + } + ], + "time": "2021-11-18T09:23:29+00:00" }, { "name": "psr/log", - "version": "3.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { @@ -104,36 +113,170 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "webmozart/assert", + "version": "2.1.2", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "ce6a2f100c404b2d32a1dd1270f9b59ad4f57649" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/ce6a2f100c404b2d32a1dd1270f9b59ad4f57649", + "reference": "ce6a2f100c404b2d32a1dd1270f9b59ad4f57649", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-feature/2-0": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/2.1.2" }, - "time": "2021-07-14T16:46:02+00:00" + "time": "2026-01-13T14:02:24+00:00" } ], "packages-dev": [ + { + "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.1.0", + "version": "3.3.2", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2" + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", - "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", + "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.3", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" + "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" } @@ -163,7 +306,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.0" + "source": "https://github.com/composer/pcre/tree/3.3.2" }, "funding": [ { @@ -179,28 +322,28 @@ "type": "tidelift" } ], - "time": "2022-11-17T09:50:14+00:00" + "time": "2024-11-12T16:29:46+00:00" }, { "name": "composer/semver", - "version": "3.3.2", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", "extra": { @@ -242,9 +385,9 @@ "versioning" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.3.2" + "source": "https://github.com/composer/semver/tree/3.4.4" }, "funding": [ { @@ -254,26 +397,22 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2022-04-01T19:23:25+00:00" + "time": "2025-08-20T19:15:30+00:00" }, { "name": "composer/xdebug-handler", - "version": "3.0.3", + "version": "3.0.5", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "ced299686f41dce890debac69273b47ffe98a40c" + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", - "reference": "ced299686f41dce890debac69273b47ffe98a40c", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", "shasum": "" }, "require": { @@ -284,7 +423,7 @@ "require-dev": { "phpstan/phpstan": "^1.0", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^6.0" + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" }, "type": "library", "autoload": { @@ -308,9 +447,9 @@ "performance" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "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.3" + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" }, "funding": [ { @@ -326,43 +465,32 @@ "type": "tidelift" } ], - "time": "2022-02-25T21:32:43+00:00" + "time": "2024-05-06T16:37:16+00:00" }, { - "name": "doctrine/annotations", - "version": "2.0.1", + "name": "evenement/evenement", + "version": "v3.0.2", "source": { "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f" + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", - "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", "shasum": "" }, "require": { - "doctrine/lexer": "^2 || ^3", - "ext-tokenizer": "*", - "php": "^7.2 || ^8.0", - "psr/cache": "^1 || ^2 || ^3" + "php": ">=7.0" }, "require-dev": { - "doctrine/cache": "^2.0", - "doctrine/coding-standard": "^10", - "phpstan/phpstan": "^1.8.0", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "symfony/cache": "^5.4 || ^6", - "vimeo/psalm": "^4.10" - }, - "suggest": { - "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + "phpunit/phpunit": "^9 || ^6" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + "Evenement\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -371,67 +499,53 @@ ], "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" } ], - "description": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "description": "Événement is a very simple event dispatching library for PHP", "keywords": [ - "annotations", - "docblock", - "parser" + "event-dispatcher", + "event-emitter" ], "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/2.0.1" + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" }, - "time": "2023-02-02T22:02:53+00:00" + "time": "2023-08-08T05:53:35+00:00" }, { - "name": "doctrine/lexer", - "version": "3.0.0", + "name": "fidry/cpu-core-counter", + "version": "1.3.0", "source": { "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "84a527db05647743d50373e0ec53a152f2cde568" + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/84a527db05647743d50373e0ec53a152f2cde568", - "reference": "84a527db05647743d50373e0ec53a152f2cde568", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^10", - "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^9.5", - "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^5.0" + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Common\\Lexer\\": "src" + "Fidry\\CpuCoreCounter\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -440,96 +554,81 @@ ], "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" } ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "description": "Tiny utility to get the number of CPU cores.", "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", - "php" + "CPU", + "core" ], "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/3.0.0" + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" }, "funding": [ { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" + "url": "https://github.com/theofidry", + "type": "github" } ], - "time": "2022-12-15T16:57:16+00:00" + "time": "2025-08-14T07:29:31+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.17.0", + "version": "v3.93.1", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "3f0ed862f22386c55a767461ef5083bddceeed79" + "reference": "b3546ab487c0762c39f308dc1ec0ea2c461fc21a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/3f0ed862f22386c55a767461ef5083bddceeed79", - "reference": "3f0ed862f22386c55a767461ef5083bddceeed79", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/b3546ab487c0762c39f308dc1ec0ea2c461fc21a", + "reference": "b3546ab487c0762c39f308dc1ec0ea2c461fc21a", "shasum": "" }, "require": { - "composer/semver": "^3.3", - "composer/xdebug-handler": "^3.0.3", - "doctrine/annotations": "^2", - "doctrine/lexer": "^2 || ^3", + "clue/ndjson-react": "^1.3", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.5", + "ext-filter": "*", + "ext-hash": "*", "ext-json": "*", "ext-tokenizer": "*", + "fidry/cpu-core-counter": "^1.3", "php": "^7.4 || ^8.0", - "sebastian/diff": "^4.0 || ^5.0", - "symfony/console": "^5.4 || ^6.0", - "symfony/event-dispatcher": "^5.4 || ^6.0", - "symfony/filesystem": "^5.4 || ^6.0", - "symfony/finder": "^5.4 || ^6.0", - "symfony/options-resolver": "^5.4 || ^6.0", - "symfony/polyfill-mbstring": "^1.27", - "symfony/polyfill-php80": "^1.27", - "symfony/polyfill-php81": "^1.27", - "symfony/process": "^5.4 || ^6.0", - "symfony/stopwatch": "^5.4 || ^6.0" + "react/child-process": "^0.6.6", + "react/event-loop": "^1.5", + "react/socket": "^1.16", + "react/stream": "^1.4", + "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0", + "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/options-resolver": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.33", + "symfony/polyfill-php80": "^1.33", + "symfony/polyfill-php81": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2 || ^8.0", + "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0" }, "require-dev": { - "justinrainbow/json-schema": "^5.2", - "keradus/cli-executor": "^2.0", - "mikey179/vfsstream": "^1.6.11", - "php-coveralls/php-coveralls": "^2.5.3", - "php-cs-fixer/accessible-object": "^1.1", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", - "phpspec/prophecy": "^1.16", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5", - "phpunitgoodpractices/polyfill": "^1.6", - "phpunitgoodpractices/traits": "^1.9.2", - "symfony/phpunit-bridge": "^6.2.3", - "symfony/yaml": "^5.4 || ^6.0" + "facile-it/paraunit": "^1.3.1 || ^2.7", + "infection/infection": "^0.32", + "justinrainbow/json-schema": "^6.6", + "keradus/cli-executor": "^2.3", + "mikey179/vfsstream": "^1.6.12", + "php-coveralls/php-coveralls": "^2.9", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6", + "phpunit/phpunit": "^9.6.31 || ^10.5.60 || ^11.5.48", + "symfony/polyfill-php85": "^1.33", + "symfony/var-dumper": "^5.4.48 || ^6.4.26 || ^7.4.0 || ^8.0", + "symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -542,7 +641,10 @@ "autoload": { "psr-4": { "PhpCsFixer\\": "src/" - } + }, + "exclude-from-classmap": [ + "src/**/Internal/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -567,7 +669,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.17.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.93.1" }, "funding": [ { @@ -575,934 +677,2603 @@ "type": "github" } ], - "time": "2023-05-22T19:59:32+00:00" + "time": "2026-01-28T23:50:50+00:00" }, { - "name": "phpstan/phpstan", - "version": "0.12.100", + "name": "masterminds/html5", + "version": "2.10.0", "source": { "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "48236ddf823547081b2b153d1cd2994b784328c3" + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "fcf91eb64359852f00d921887b219479b4f21251" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/48236ddf823547081b2b153d1cd2994b784328c3", - "reference": "48236ddf823547081b2b153d1cd2994b784328c3", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", "shasum": "" }, "require": { - "php": "^7.1|^8.0" + "ext-dom": "*", + "php": ">=5.3.0" }, - "conflict": { - "phpstan/phpstan-shim": "*" + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" }, - "bin": [ - "phpstan", - "phpstan.phar" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "0.12-dev" + "dev-master": "2.7-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ] + "psr-4": { + "Masterminds\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "PHPStan - PHP Static Analysis Tool", - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/0.12.100" - }, - "funding": [ + "authors": [ { - "url": "https://github.com/ondrejmirtes", - "type": "github" + "name": "Matt Butcher", + "email": "technosophos@gmail.com" }, { - "url": "https://github.com/phpstan", - "type": "github" + "name": "Matt Farina", + "email": "matt@mattfarina.com" }, { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" } ], - "time": "2022-11-01T09:52:08+00:00" + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" + }, + "time": "2025-07-25T09:04:22+00:00" }, { - "name": "psr/cache", - "version": "3.0.0", + "name": "myclabs/deep-copy", + "version": "1.13.4", "source": { "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": "^7.1 || ^8.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "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": { - "Psr\\Cache\\": "src/" + "DeepCopy\\": "src/DeepCopy/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", + "description": "Create deep copies (clones) of your objects", "keywords": [ - "cache", - "psr", - "psr-6" + "clone", + "copy", + "duplicate", + "object", + "object graph" ], "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, - "time": "2021-02-03T23:26:27+00:00" + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" }, { - "name": "psr/container", - "version": "2.0.2", + "name": "nikic/php-parser", + "version": "v5.7.0", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { - "php": ">=7.4.0" + "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": "2.0.x-dev" + "dev-master": "5.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Container\\": "src/" + "PhpParser\\": "lib/PhpParser" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Nikita Popov" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "A PHP parser written in PHP", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "parser", + "php" ], "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2021-11-05T16:47:00+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { - "name": "psr/event-dispatcher", - "version": "1.0.0", + "name": "phar-io/manifest", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { - "php": ">=7.2.0" + "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": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "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": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2019-01-08T18:20:26+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { - "name": "roave/security-advisories", - "version": "dev-master", + "name": "phar-io/version", + "version": "3.2.1", "source": { "type": "git", - "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "7d728f50cf52ce838ac1c1504190823b4dc06b4d" + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/7d728f50cf52ce838ac1c1504190823b4dc06b4d", - "reference": "7d728f50cf52ce838ac1c1504190823b4dc06b4d", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, - "conflict": { - "3f/pygmentize": "<1.2", - "admidio/admidio": "<4.1.9", - "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3", - "aheinze/cockpit": "<=2.2.1", - "akaunting/akaunting": "<2.1.13", - "akeneo/pim-community-dev": "<5.0.119|>=6,<6.0.53", - "alextselegidis/easyappointments": "<1.5", - "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", - "amazing/media2click": ">=1,<1.3.3", - "amphp/artax": "<1.0.6|>=2,<2.0.6", - "amphp/http": "<1.0.1", - "amphp/http-client": ">=4,<4.4", - "anchorcms/anchor-cms": "<=0.12.7", - "andreapollastri/cipi": "<=3.1.15", - "andrewhaine/silverstripe-form-capture": ">=0.2,<=0.2.3|>=1,<=1.0.1|>=2,<=2.2.4", - "apereo/phpcas": "<1.6", - "api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6|>=2.6,<2.7.10|>=3,<3.0.12|>=3.1,<3.1.3", - "appwrite/server-ce": "<=1.2.1", - "arc/web": "<3", - "area17/twill": "<1.2.5|>=2,<2.5.3", - "asymmetricrypt/asymmetricrypt": ">=0,<9.9.99", - "automad/automad": "<1.8", - "awesome-support/awesome-support": "<=6.0.7", - "aws/aws-sdk-php": ">=3,<3.2.1", - "azuracast/azuracast": "<0.18.3", - "backdrop/backdrop": "<1.24.2", - "badaso/core": "<2.7", - "bagisto/bagisto": "<0.1.5", - "barrelstrength/sprout-base-email": "<1.2.7", - "barrelstrength/sprout-forms": "<3.9", - "barryvdh/laravel-translation-manager": "<0.6.2", - "barzahlen/barzahlen-php": "<2.0.1", - "baserproject/basercms": "<4.7.5", - "bassjobsen/bootstrap-3-typeahead": ">4.0.2", - "bigfork/silverstripe-form-capture": ">=3,<=3.1", - "billz/raspap-webgui": "<=2.6.6", - "bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3", - "bmarshall511/wordpress_zero_spam": "<5.2.13", - "bolt/bolt": "<3.7.2", - "bolt/core": "<=4.2", - "bottelet/flarepoint": "<2.2.1", - "brightlocal/phpwhois": "<=4.2.5", - "brotkrueml/codehighlight": "<2.7", - "brotkrueml/schema": "<1.13.1|>=2,<2.5.1", - "brotkrueml/typo3-matomo-integration": "<1.3.2", - "buddypress/buddypress": "<7.2.1", - "bugsnag/bugsnag-laravel": ">=2,<2.0.2", - "bytefury/crater": "<6.0.2", - "cachethq/cachet": "<2.5.1", - "cakephp/cakephp": "<3.10.3|>=4,<4.0.10|>=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10|= 1.3.7|>=4.1,<4.1.4", - "cakephp/database": ">=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", - "cardgate/magento2": "<2.0.33", - "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", - "cartalyst/sentry": "<=2.1.6", - "catfan/medoo": "<1.7.5", - "centreon/centreon": "<22.10-beta.1", - "cesnet/simplesamlphp-module-proxystatistics": "<3.1", - "cockpit-hq/cockpit": "<2.4.1", - "codeception/codeception": "<3.1.3|>=4,<4.1.22", - "codeigniter/framework": "<=3.0.6", - "codeigniter4/framework": "<4.3.5", - "codeigniter4/shield": "<1-beta.4|= 1.0.0-beta", - "codiad/codiad": "<=2.8.4", - "composer/composer": "<1.10.26|>=2-alpha.1,<2.2.12|>=2.3,<2.3.5", - "concrete5/concrete5": "<9.2|>= 9.0.0RC1, < 9.1.3", - "concrete5/core": "<8.5.8|>=9,<9.1", - "contao-components/mediaelement": ">=2.14.2,<2.21.1", - "contao/contao": ">=4,<4.4.56|>=4.5,<4.9.40|>=4.10,<4.11.7|>=4.13,<4.13.21|>=5.1,<5.1.4", - "contao/core": ">=2,<3.5.39", - "contao/core-bundle": "<4.9.40|>=4.10,<4.11.7|>=4.13,<4.13.21|>=5.1,<5.1.4|= 4.10.0", - "contao/listing-bundle": ">=4,<4.4.8", - "contao/managed-edition": "<=1.5", - "craftcms/cms": ">= 4.0.0-RC1, < 4.4.12|>= 4.0.0-RC1, <= 4.4.5|>= 4.0.0-RC1, <= 4.4.6|<=3.8.5|>=4,<4.4.6|>= 4.0.0-RC1, < 4.4.6|>= 4.0.0-RC1, < 4.3.7|>= 4.0.0-RC1, < 4.2.1", - "croogo/croogo": "<3.0.7", - "cuyz/valinor": "<0.12", - "czproject/git-php": "<4.0.3", - "darylldoyle/safe-svg": "<1.9.10", - "datadog/dd-trace": ">=0.30,<0.30.2", - "david-garcia/phpwhois": "<=4.3.1", - "dbrisinajumi/d2files": "<1", - "dcat/laravel-admin": "<=2.1.3-beta", - "derhansen/fe_change_pwd": "<2.0.5|>=3,<3.0.3", - "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1", - "directmailteam/direct-mail": "<5.2.4", - "doctrine/annotations": ">=1,<1.2.7", - "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2", - "doctrine/common": ">=2,<2.4.3|>=2.5,<2.5.1", - "doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2|>=3,<3.1.4", - "doctrine/doctrine-bundle": "<1.5.2", - "doctrine/doctrine-module": "<=0.7.1", - "doctrine/mongodb-odm": ">=1,<1.0.2", - "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", - "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4", - "dolibarr/dolibarr": "<17.0.1|= 12.0.5|>= 3.3.beta1, < 13.0.2", - "dompdf/dompdf": "<2.0.2|= 2.0.2", - "drupal/core": ">=7,<7.96|>=8,<9.4.14|>=9.5,<9.5.8|>=10,<10.0.8", - "drupal/drupal": ">=7,<7.80|>=8,<8.9.16|>=9,<9.1.12|>=9.2,<9.2.4", - "dweeves/magmi": "<=0.7.24", - "ecodev/newsletter": "<=4", - "ectouch/ectouch": "<=2.7.2", - "elefant/cms": "<1.3.13", - "elgg/elgg": "<3.3.24|>=4,<4.0.5", - "encore/laravel-admin": "<=1.8.19", - "endroid/qr-code-bundle": "<3.4.2", - "enshrined/svg-sanitize": "<0.15", - "erusev/parsedown": "<1.7.2", - "ether/logs": "<3.0.4", - "exceedone/exment": "<4.4.3|>=5,<5.0.3", - "exceedone/laravel-admin": "= 3.0.0|<2.2.3", - "ezsystems/demobundle": ">=5.4,<5.4.6.1", - "ezsystems/ez-support-tools": ">=2.2,<2.2.3", - "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1", - "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1|>=5.4,<5.4.11.1|>=2017.12,<2017.12.0.1", - "ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24", - "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.26", - "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1", - "ezsystems/ezplatform-graphql": ">=1-rc.1,<1.0.13|>=2-beta.1,<2.3.12", - "ezsystems/ezplatform-kernel": "<1.2.5.1|>=1.3,<1.3.26", - "ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8", - "ezsystems/ezplatform-richtext": ">=2.3,<2.3.7.1", - "ezsystems/ezplatform-user": ">=1,<1.0.1", - "ezsystems/ezpublish-kernel": "<6.13.8.2|>=7,<7.5.30", - "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.3.5.1", - "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", - "ezsystems/repository-forms": ">=2.3,<2.3.2.1|>=2.5,<2.5.15", - "ezyang/htmlpurifier": "<4.1.1", - "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2", - "facturascripts/facturascripts": "<=2022.8", - "feehi/cms": "<=2.1.1", - "feehi/feehicms": "<=2.1.1", - "fenom/fenom": "<=2.12.1", - "filegator/filegator": "<7.8", - "firebase/php-jwt": "<6", - "fixpunkt/fp-masterquiz": "<2.2.1|>=3,<3.5.2", - "fixpunkt/fp-newsletter": "<1.1.1|>=2,<2.1.2|>=2.2,<3.2.6", - "flarum/core": "<1.7", - "flarum/mentions": "<1.6.3", - "flarum/sticky": ">=0.1-beta.14,<=0.1-beta.15", - "flarum/tags": "<=0.1-beta.13", - "fluidtypo3/vhs": "<5.1.1", - "fof/byobu": ">=0.3-beta.2,<1.1.7", - "fof/upload": "<1.2.3", - "fooman/tcpdf": "<6.2.22", - "forkcms/forkcms": "<5.11.1", - "fossar/tcpdf-parser": "<6.2.22", - "francoisjacquet/rosariosis": "<11", - "frappant/frp-form-answers": "<3.1.2|>=4,<4.0.2", - "friendsofsymfony/oauth2-php": "<1.3", - "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", - "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", - "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", - "froala/wysiwyg-editor": "<3.2.7", - "froxlor/froxlor": "<2.1", - "fuel/core": "<1.8.1", - "funadmin/funadmin": "<=3.2", - "gaoming13/wechat-php-sdk": "<=1.10.2", - "genix/cms": "<=1.1.11", - "getgrav/grav": "<1.7.34", - "getkirby/cms": "= 3.8.0|<3.5.8.2|>=3.6,<3.6.6.2|>=3.7,<3.7.5.1", - "getkirby/panel": "<2.5.14", - "getkirby/starterkit": "<=3.7.0.2", - "gilacms/gila": "<=1.11.4", - "globalpayments/php-sdk": "<2", - "google/protobuf": "<3.15", - "gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3", - "gree/jose": "<2.2.1", - "gregwar/rst": "<1.0.3", - "grumpydictator/firefly-iii": "<6", - "guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5", - "guzzlehttp/psr7": "<1.9.1|>=2,<2.4.5", - "harvesthq/chosen": "<1.8.7", - "helloxz/imgurl": "= 2.31|<=2.31", - "hillelcoren/invoice-ninja": "<5.3.35", - "himiklab/yii2-jqgrid-widget": "<1.0.8", - "hjue/justwriting": "<=1", - "hov/jobfair": "<1.0.13|>=2,<2.0.2", - "httpsoft/http-message": "<1.0.12", - "hyn/multi-tenant": ">=5.6,<5.7.2", - "ibexa/admin-ui": ">=4.2,<4.2.3", - "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4|>=4.2,<4.2.3", - "ibexa/graphql": ">=2.5,<2.5.31|>=3.3,<3.3.28|>=4.2,<4.2.3", - "ibexa/post-install": "<=1.0.4", - "ibexa/user": ">=4,<4.4.3", - "icecoder/icecoder": "<=8.1", - "idno/known": "<=1.3.1", - "illuminate/auth": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.10", - "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<=4.1.99999|>=4.2,<=4.2.99999|>=5,<=5.0.99999|>=5.1,<=5.1.99999|>=5.2,<=5.2.99999|>=5.3,<=5.3.99999|>=5.4,<=5.4.99999|>=5.5,<=5.5.49|>=5.6,<=5.6.99999|>=5.7,<=5.7.99999|>=5.8,<=5.8.99999|>=6,<6.18.31|>=7,<7.22.4", - "illuminate/database": "<6.20.26|>=7,<7.30.5|>=8,<8.40", - "illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15", - "illuminate/view": "<6.20.42|>=7,<7.30.6|>=8,<8.75", - "impresscms/impresscms": "<=1.4.3", - "in2code/femanager": "<5.5.3|>=6,<6.3.4|>=7,<7.1", - "in2code/lux": "<17.6.1|>=18,<24.0.2", - "innologi/typo3-appointments": "<2.0.6", - "intelliants/subrion": "<=4.2.1", - "islandora/islandora": ">=2,<2.4.1", - "ivankristianto/phpwhois": "<=4.3", - "jackalope/jackalope-doctrine-dbal": "<1.7.4", - "james-heinrich/getid3": "<1.9.21", - "jasig/phpcas": "<1.3.3", - "joomla/archive": "<1.1.12|>=2,<2.0.1", - "joomla/filesystem": "<1.6.2|>=2,<2.0.1", - "joomla/filter": "<1.4.4|>=2,<2.0.1", - "joomla/input": ">=2,<2.0.2", - "joomla/session": "<1.3.1", - "joyqi/hyper-down": "<=2.4.27", - "jsdecena/laracom": "<2.0.9", - "jsmitty12/phpwhois": "<5.1", - "kazist/phpwhois": "<=4.2.6", - "kelvinmo/simplexrd": "<3.1.1", - "kevinpapst/kimai2": "<1.16.7", - "kimai/kimai": "<1.1", - "kitodo/presentation": "<3.1.2", - "klaviyo/magento2-extension": ">=1,<3", - "knplabs/knp-snappy": "<1.4.2", - "krayin/laravel-crm": "<1.2.2", - "kreait/firebase-php": ">=3.2,<3.8.1", - "la-haute-societe/tcpdf": "<6.2.22", - "laminas/laminas-diactoros": "<2.18.1|>=2.24,<2.24.2|>=2.25,<2.25.2|= 2.23.0|= 2.22.0|= 2.21.0|= 2.20.0|= 2.19.0", - "laminas/laminas-form": "<2.17.1|>=3,<3.0.2|>=3.1,<3.1.1", - "laminas/laminas-http": "<2.14.2", - "laravel/fortify": "<1.11.1", - "laravel/framework": "<6.20.42|>=7,<7.30.6|>=8,<8.75", - "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", - "latte/latte": "<2.10.8", - "lavalite/cms": "<=9", - "lcobucci/jwt": ">=3.4,<3.4.6|>=4,<4.0.4|>=4.1,<4.1.5", - "league/commonmark": "<0.18.3", - "league/flysystem": "<1.1.4|>=2,<2.1.1", - "lexik/jwt-authentication-bundle": "<2.10.7|>=2.11,<2.11.3", - "librenms/librenms": "<22.10", - "liftkit/database": "<2.13.2", - "limesurvey/limesurvey": "<3.27.19", - "livehelperchat/livehelperchat": "<=3.91", - "livewire/livewire": ">2.2.4,<2.2.6", - "lms/routes": "<2.1.1", - "localizationteam/l10nmgr": "<7.4|>=8,<8.7|>=9,<9.2", - "luyadev/yii-helpers": "<1.2.1", - "magento/community-edition": ">=2,<2.2.10|>=2.3,<2.3.3", - "magento/magento1ce": "<1.9.4.3", - "magento/magento1ee": ">=1,<1.14.4.3", - "magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2-p.2", - "maikuolan/phpmussel": ">=1,<1.6", - "mantisbt/mantisbt": "<=2.25.5", - "marcwillmann/turn": "<0.3.3", - "matyhtf/framework": "<3.0.6", - "mautic/core": "<4.3|= 2.13.1", - "mediawiki/core": ">=1.27,<1.27.6|>=1.29,<1.29.3|>=1.30,<1.30.2|>=1.31,<1.31.9|>=1.32,<1.32.6|>=1.32.99,<1.33.3|>=1.33.99,<1.34.3|>=1.34.99,<1.35", - "mediawiki/matomo": "<2.4.3", - "melisplatform/melis-asset-manager": "<5.0.1", - "melisplatform/melis-cms": "<5.0.1", - "melisplatform/melis-front": "<5.0.1", - "mezzio/mezzio-swoole": "<3.7|>=4,<4.3", - "mgallegos/laravel-jqgrid": "<=1.3", - "microweber/microweber": "<=1.3.4", - "miniorange/miniorange-saml": "<1.4.3", - "mittwald/typo3_forum": "<1.2.1", - "mobiledetect/mobiledetectlib": "<2.8.32", - "modx/revolution": "<= 2.8.3-pl|<2.8", - "mojo42/jirafeau": "<4.4", - "monolog/monolog": ">=1.8,<1.12", - "moodle/moodle": "<4.2-rc.2|= 3.11", - "mustache/mustache": ">=2,<2.14.1", - "namshi/jose": "<2.2", - "neoan3-apps/template": "<1.1.1", - "neorazorx/facturascripts": "<2022.4", - "neos/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", - "neos/form": ">=1.2,<4.3.3|>=5,<5.0.9|>=5.1,<5.1.3", - "neos/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.9.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<5.3.10|>=7,<7.0.9|>=7.1,<7.1.7|>=7.2,<7.2.6|>=7.3,<7.3.4|>=8,<8.0.2", - "neos/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", - "netgen/tagsbundle": ">=3.4,<3.4.11|>=4,<4.0.15", - "nette/application": ">=2,<2.0.19|>=2.1,<2.1.13|>=2.2,<2.2.10|>=2.3,<2.3.14|>=2.4,<2.4.16|>=3,<3.0.6", - "nette/nette": ">=2,<2.0.19|>=2.1,<2.1.13", - "nilsteampassnet/teampass": "<3.0.9", - "notrinos/notrinos-erp": "<=0.7", - "noumo/easyii": "<=0.9", - "nukeviet/nukeviet": "<4.5.2", - "nyholm/psr7": "<1.6.1", - "nystudio107/craft-seomatic": "<3.4.12", - "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1", - "october/backend": "<1.1.2", - "october/cms": "= 1.1.1|= 1.0.471|= 1.0.469|>=1.0.319,<1.0.469", - "october/october": ">=1.0.319,<1.0.466|>=2.1,<2.1.12", - "october/rain": "<1.0.472|>=1.1,<1.1.2", - "october/system": "<1.0.476|>=1.1,<1.1.12|>=2,<2.2.34|>=3,<3.0.66", - "onelogin/php-saml": "<2.10.4", - "oneup/uploader-bundle": "<1.9.3|>=2,<2.1.5", - "open-web-analytics/open-web-analytics": "<1.7.4", - "opencart/opencart": "<=3.0.3.7", - "openid/php-openid": "<2.3", - "openmage/magento-lts": "<19.4.22|>=20,<20.0.19", - "orchid/platform": ">=9,<9.4.4", - "oro/commerce": ">=4.1,<5.0.6", - "oro/crm": ">=1.7,<1.7.4|>=3.1,<4.1.17|>=4.2,<4.2.7", - "oro/platform": ">=1.7,<1.7.4|>=3.1,<3.1.29|>=4.1,<4.1.17|>=4.2,<4.2.8", - "packbackbooks/lti-1-3-php-library": "<5", - "padraic/humbug_get_contents": "<1.1.2", - "pagarme/pagarme-php": ">=0,<3", - "pagekit/pagekit": "<=1.0.18", - "paragonie/random_compat": "<2", - "passbolt/passbolt_api": "<2.11", - "paypal/merchant-sdk-php": "<3.12", - "pear/archive_tar": "<1.4.14", - "pear/crypt_gpg": "<1.6.7", - "pegasus/google-for-jobs": "<1.5.1|>=2,<2.1.1", - "personnummer/personnummer": "<3.0.2", - "phanan/koel": "<5.1.4", - "php-mod/curl": "<2.3.2", - "phpbb/phpbb": ">=3.2,<3.2.10|>=3.3,<3.3.1", - "phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7", - "phpmailer/phpmailer": "<6.5", - "phpmussel/phpmussel": ">=1,<1.6", - "phpmyadmin/phpmyadmin": "<5.2.1", - "phpmyfaq/phpmyfaq": "<=3.1.7", - "phpoffice/phpexcel": "<1.8", - "phpoffice/phpspreadsheet": "<1.16", - "phpseclib/phpseclib": "<2.0.31|>=3,<3.0.19", - "phpservermon/phpservermon": "<=3.5.2", - "phpsysinfo/phpsysinfo": "<3.2.5", - "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5,<5.6.3", - "phpwhois/phpwhois": "<=4.2.5", - "phpxmlrpc/extras": "<0.6.1", - "phpxmlrpc/phpxmlrpc": "<4.9.2", - "pimcore/customer-management-framework-bundle": "<3.3.10", - "pimcore/data-hub": "<1.2.4", - "pimcore/perspective-editor": "<1.5.1", - "pimcore/pimcore": "<10.5.23", - "pixelfed/pixelfed": "<=0.11.4", - "pocketmine/bedrock-protocol": "<8.0.2", - "pocketmine/pocketmine-mp": "<4.20.5|>=4.21,<4.21.1|< 4.18.0-ALPHA2|>= 4.0.0-BETA5, < 4.4.2", - "pressbooks/pressbooks": "<5.18", - "prestashop/autoupgrade": ">=4,<4.10.1", - "prestashop/blockwishlist": ">=2,<2.1.1", - "prestashop/contactform": ">=1.0.1,<4.3", - "prestashop/gamification": "<2.3.2", - "prestashop/prestashop": "<8.0.4", - "prestashop/productcomments": "<5.0.2", - "prestashop/ps_emailsubscription": "<2.6.1", - "prestashop/ps_facetedsearch": "<3.4.1", - "prestashop/ps_linklist": "<3.1", - "privatebin/privatebin": "<1.4", - "processwire/processwire": "<=3.0.200", - "propel/propel": ">=2-alpha.1,<=2-alpha.7", - "propel/propel1": ">=1,<=1.7.1", - "pterodactyl/panel": "<1.7", - "ptrofimov/beanstalk_console": "<1.7.14", - "pusher/pusher-php-server": "<2.2.1", - "pwweb/laravel-core": "<=0.3.6-beta", - "pyrocms/pyrocms": "<=3.9.1", - "rainlab/debugbar-plugin": "<3.1", - "rankmath/seo-by-rank-math": "<=1.0.95", - "react/http": ">=0.7,<1.9", - "really-simple-plugins/complianz-gdpr": "<6.4.2", - "remdex/livehelperchat": "<3.99", - "rmccue/requests": ">=1.6,<1.8", - "robrichards/xmlseclibs": "<3.0.4", - "roots/soil": "<4.1", - "rudloff/alltube": "<3.0.3", - "s-cart/core": "<6.9", - "s-cart/s-cart": "<6.9", - "sabberworm/php-css-parser": ">=1,<1.0.1|>=2,<2.0.1|>=3,<3.0.1|>=4,<4.0.1|>=5,<5.0.9|>=5.1,<5.1.3|>=5.2,<5.2.1|>=6,<6.0.2|>=7,<7.0.4|>=8,<8.0.1|>=8.1,<8.1.1|>=8.2,<8.2.1|>=8.3,<8.3.1", - "sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9", - "scheb/two-factor-bundle": ">=0,<3.26|>=4,<4.11", - "sensiolabs/connect": "<4.2.3", - "serluck/phpwhois": "<=4.2.6", - "shopware/core": "<=6.4.20", - "shopware/platform": "<=6.4.20", - "shopware/production": "<=6.3.5.2", - "shopware/shopware": "<=5.7.14", - "shopware/storefront": "<=6.4.8.1", - "shopxo/shopxo": "<2.2.6", - "showdoc/showdoc": "<2.10.4", - "silverstripe/admin": "<1.12.7", - "silverstripe/assets": ">=1,<1.11.1", - "silverstripe/cms": "<4.11.3", - "silverstripe/comments": ">=1.3,<1.9.99|>=2,<2.9.99|>=3,<3.1.1", - "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", - "silverstripe/framework": "<4.12.5", - "silverstripe/graphql": "<3.5.2|>=4-alpha.1,<4-alpha.2|>=4.1.1,<4.1.2|>=4.2.2,<4.2.3|= 4.0.0-alpha1", - "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1", - "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", - "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4", - "silverstripe/silverstripe-omnipay": "<2.5.2|>=3,<3.0.2|>=3.1,<3.1.4|>=3.2,<3.2.1", - "silverstripe/subsites": ">=2,<2.6.1", - "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1", - "silverstripe/userforms": "<3", - "silverstripe/versioned-admin": ">=1,<1.11.1", - "simple-updates/phpwhois": "<=1", - "simplesamlphp/saml2": "<1.10.6|>=2,<2.3.8|>=3,<3.1.4", - "simplesamlphp/simplesamlphp": "<1.18.6", - "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", - "simplesamlphp/simplesamlphp-module-openid": "<1", - "simplesamlphp/simplesamlphp-module-openidprovider": "<0.9", - "simplito/elliptic-php": "<1.0.6", - "sitegeist/fluid-components": "<3.5", - "slim/psr7": "<1.4.1|>=1.5,<1.5.1|>=1.6,<1.6.1", - "slim/slim": "<2.6", - "smarty/smarty": "<3.1.48|>=4,<4.3.1", - "snipe/snipe-it": "<=6.0.14|>= 6.0.0-RC-1, <= 6.0.0-RC-5", - "socalnick/scn-social-auth": "<1.15.2", - "socialiteproviders/steam": "<1.1", - "spatie/browsershot": "<3.57.4", - "spipu/html2pdf": "<5.2.4", - "spoonity/tcpdf": "<6.2.22", - "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", - "ssddanbrown/bookstack": "<22.2.3", - "statamic/cms": "<3.2.39|>=3.3,<3.3.2", - "stormpath/sdk": ">=0,<9.9.99", - "studio-42/elfinder": "<2.1.59", - "subrion/cms": "<=4.2.1", - "sukohi/surpass": "<1", - "sulu/sulu": "= 2.4.0-RC1|<1.6.44|>=2,<2.2.18|>=2.3,<2.3.8", - "sumocoders/framework-user-bundle": "<1.4", - "swag/paypal": "<5.4.4", - "swiftmailer/swiftmailer": ">=4,<5.4.5", - "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", - "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", - "sylius/grid-bundle": "<1.10.1", - "sylius/paypal-plugin": ">=1,<1.2.4|>=1.3,<1.3.1", - "sylius/resource-bundle": "<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4", - "sylius/sylius": "<1.9.10|>=1.10,<1.10.11|>=1.11,<1.11.2", - "symbiote/silverstripe-multivaluefield": ">=3,<3.0.99", - "symbiote/silverstripe-queuedjobs": ">=3,<3.0.2|>=3.1,<3.1.4|>=4,<4.0.7|>=4.1,<4.1.2|>=4.2,<4.2.4|>=4.3,<4.3.3|>=4.4,<4.4.3|>=4.5,<4.5.1|>=4.6,<4.6.4", - "symbiote/silverstripe-seed": "<6.0.3", - "symbiote/silverstripe-versionedfiles": "<=2.0.3", - "symfont/process": ">=0", - "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", - "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", - "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4", - "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", - "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<=5.3.14|>=5.4.3,<=5.4.3|>=6.0.3,<=6.0.3|= 6.0.3|= 5.4.3|= 5.3.14", - "symfony/http-foundation": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7", - "symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", - "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", - "symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1", - "symfony/mime": ">=4.3,<4.3.8", - "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", - "symfony/polyfill": ">=1,<1.10", - "symfony/polyfill-php55": ">=1,<1.10", - "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", - "symfony/routing": ">=2,<2.0.19", - "symfony/security": ">=2,<2.7.51|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.8", - "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", - "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.9", - "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", - "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8", - "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.3.2", - "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", - "symfony/symfony": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", - "symfony/translation": ">=2,<2.0.17", - "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", - "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", - "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", - "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", - "t3/dce": ">=2.2,<2.6.2", - "t3g/svg-sanitizer": "<1.0.3", - "tastyigniter/tastyigniter": "<3.3", - "tcg/voyager": "<=1.4", - "tecnickcom/tcpdf": "<6.2.22", - "terminal42/contao-tablelookupwizard": "<3.3.5", - "thelia/backoffice-default-template": ">=2.1,<2.1.2", - "thelia/thelia": ">=2.1-beta.1,<2.1.3", - "theonedemon/phpwhois": "<=4.2.5", - "thinkcmf/thinkcmf": "<=5.1.7", - "thorsten/phpmyfaq": "<3.2-beta", - "tinymce/tinymce": "<5.10.7|>=6,<6.3.1", - "tinymighty/wiki-seo": "<1.2.2", - "titon/framework": ">=0,<9.9.99", - "tobiasbg/tablepress": "<= 2.0-RC1", - "topthink/framework": "<6.0.14", - "topthink/think": "<=6.1.1", - "topthink/thinkphp": "<=3.2.3", - "tribalsystems/zenario": "<=9.3.57595", - "truckersmp/phpwhois": "<=4.3.1", - "ttskch/pagination-service-provider": "<1", - "twig/twig": "<1.44.7|>=2,<2.15.3|>=3,<3.4.3", - "typo3/cms": "<2.0.5|>=3,<3.0.3|>=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.38|>=9,<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", - "typo3/cms-backend": ">=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", - "typo3/cms-core": "<8.7.51|>=9,<9.5.40|>=10,<10.4.36|>=11,<11.5.23|>=12,<12.2", - "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", - "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", - "typo3/html-sanitizer": ">=1,<1.5|>=2,<2.1.1", - "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3", - "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", - "typo3/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", - "typo3fluid/fluid": ">=2,<2.0.8|>=2.1,<2.1.7|>=2.2,<2.2.4|>=2.3,<2.3.7|>=2.4,<2.4.4|>=2.5,<2.5.11|>=2.6,<2.6.10", - "ua-parser/uap-php": "<3.8", - "unisharp/laravel-filemanager": "<=2.5.1", - "userfrosting/userfrosting": ">=0.3.1,<4.6.3", - "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2", - "uvdesk/community-skeleton": "<=1.1.1", - "vanilla/safecurl": "<0.9.2", - "verot/class.upload.php": "<=1.0.3|>=2,<=2.0.4", - "vova07/yii2-fileapi-widget": "<0.1.9", - "vrana/adminer": "<4.8.1", - "wallabag/tcpdf": "<6.2.22", - "wallabag/wallabag": "<2.5.4", - "wanglelecc/laracms": "<=1.0.3", - "web-auth/webauthn-framework": ">=3.3,<3.3.4", - "webbuilders-group/silverstripe-kapost-bridge": "<0.4", - "webcoast/deferred-image-processing": "<1.0.2", - "webpa/webpa": "<3.1.2", - "wikimedia/parsoid": "<0.12.2", - "willdurand/js-translation-bundle": "<2.1.1", - "wintercms/winter": "<1.0.475|>=1.1,<1.1.10|>=1.2,<1.2.1", - "woocommerce/woocommerce": "<6.6", - "wp-cli/wp-cli": "<2.5", - "wp-graphql/wp-graphql": "<0.3.5", - "wpanel/wpanel4-cms": "<=4.3.1", - "wpcloud/wp-stateless": "<3.2", - "wwbn/avideo": "<=12.4", - "xataface/xataface": "<3", - "xpressengine/xpressengine": "<3.0.15", - "yeswiki/yeswiki": "<4.1", - "yetiforce/yetiforce-crm": "<=6.4", - "yidashi/yii2cmf": "<=2", - "yii2mod/yii2-cms": "<1.9.2", - "yiisoft/yii": "<1.1.27", - "yiisoft/yii2": "<2.0.38", - "yiisoft/yii2-bootstrap": "<2.0.4", - "yiisoft/yii2-dev": "<2.0.43", - "yiisoft/yii2-elasticsearch": "<2.0.5", - "yiisoft/yii2-gii": "<=2.2.4", - "yiisoft/yii2-jui": "<2.0.4", - "yiisoft/yii2-redis": "<2.0.8", - "yikesinc/yikes-inc-easy-mailchimp-extender": "<6.8.6", - "yoast-seo-for-typo3/yoast_seo": "<7.2.3", - "yourls/yourls": "<=1.8.2", - "zendesk/zendesk_api_client_php": "<2.2.11", - "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3", - "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", - "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", - "zendframework/zend-db": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.10|>=2.3,<2.3.5", - "zendframework/zend-developer-tools": ">=1.2.2,<1.2.3", - "zendframework/zend-diactoros": "<1.8.4", - "zendframework/zend-feed": "<2.10.3", - "zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-http": "<2.8.1", - "zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6", - "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3", - "zendframework/zend-mail": ">=2,<2.4.11|>=2.5,<2.7.2", - "zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-session": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.9|>=2.3,<2.3.4", - "zendframework/zend-validator": ">=2.3,<2.3.6", - "zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6", - "zendframework/zendframework": "<=3", - "zendframework/zendframework1": "<1.12.20", - "zendframework/zendopenid": ">=2,<2.0.2", - "zendframework/zendxml": ">=1,<1.0.1", - "zetacomponents/mail": "<1.8.2", - "zf-commons/zfc-user": "<1.2.2", - "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3", - "zfr/zfr-oauth2-server-module": "<0.1.2", - "zoujingli/thinkadmin": "<6.0.22" - }, - "type": "metapackage", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" + "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": "2.1.38", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dfaf1f530e1663aa167bc3e52197adb221582629", + "reference": "dfaf1f530e1663aa167bc3e52197adb221582629", + "shasum": "" + }, + "require": { + "php": "^7.4|^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": "2026-01-30T17:12:46+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "11.0.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.7.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.1", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.3.1" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.46" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.12" + }, + "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-12-24T07:01:01+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", + "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": "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.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/phpunit/php-file-iterator", + "type": "tidelift" + } + ], + "time": "2026-02-02T13:52:54+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.50", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", + "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.12", + "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.3", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.1", + "sebastian/exporter": "^6.3.2", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.1.3", + "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.50" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2026-01-27T05:59:18+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": "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.7", + "source": { + "type": "git", + "url": "https://github.com/reactphp/child-process.git", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/970f0e71945556422ee4570ccbabaedc3cf04ad3", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3", + "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.7" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-12-23T15:25:20+00:00" + }, + { + "name": "react/dns", + "version": "v1.14.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3", + "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.14.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-18T19:34:28+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "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.6.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-17T20:46:25+00:00" + }, + { + "name": "react/promise", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.12.28 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-08-19T18:57:03+00:00" + }, + { + "name": "react/socket", + "version": "v1.17.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08", + "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.17.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-19T20:47:34+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.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", + "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.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-01-24T09:26:40+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.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", + "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.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": "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.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:12:51+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.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "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.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-13T04:42:22+00:00" + }, + { + "name": "sebastian/type", + "version": "5.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "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.3" + }, + "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/type", + "type": "tidelift" + } + ], + "time": "2025-08-09T06:55:48+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": "Marco Pivetta", - "email": "ocramius@gmail.com", - "role": "maintainer" - }, + "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": [ { - "name": "Ilya Tribusean", - "email": "slash3b@gmail.com", - "role": "maintainer" + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", + "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": [ - "dev" + "static analysis" ], "support": { - "issues": "https://github.com/Roave/SecurityAdvisories/issues", - "source": "https://github.com/Roave/SecurityAdvisories/tree/latest" + "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/Ocramius", + "url": "https://github.com/staabm", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/roave/security-advisories", - "type": "tidelift" } ], - "time": "2023-06-12T19:03:43+00:00" + "time": "2024-10-20T05:08:20+00:00" }, { - "name": "sebastian/diff", - "version": "5.0.3", + "name": "symfony/browser-kit", + "version": "v7.4.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b" + "url": "https://github.com/symfony/browser-kit.git", + "reference": "bed167eadaaba641f51fc842c9227aa5e251309e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/912dc2fbe3e3c1e7873313cc801b100b6c68c87b", - "reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/bed167eadaaba641f51fc842c9227aa5e251309e", + "reference": "bed167eadaaba641f51fc842c9227aa5e251309e", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/dom-crawler": "^6.4|^7.0|^8.0" }, "require-dev": { - "phpunit/phpunit": "^10.0", - "symfony/process": "^4.2 || ^5" + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], + "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.0.3" + "source": "https://github.com/symfony/browser-kit/tree/v7.4.4" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2023-05-01T07:48:21+00:00" + "time": "2026-01-13T10:40:19+00:00" }, { "name": "symfony/console", - "version": "v6.3.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7" + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7", - "reference": "8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7", + "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^5.4|^6.0" + "symfony/string": "^7.2|^8.0" }, "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" + "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": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/lock": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -1536,7 +3307,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.3.0" + "source": "https://github.com/symfony/console/tree/v7.4.4" }, "funding": [ { @@ -1547,43 +3318,41 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2023-05-29T12:49:39+00:00" + "time": "2026-01-13T11:36:38+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v3.3.0", + "name": "symfony/css-selector", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + "url": "https://github.com/symfony/css-selector.git", + "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab862f478513e7ca2fe9ec117a6f01a8da6e1135", + "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, "autoload": { - "files": [ - "function.php" + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -1592,18 +3361,22 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "A generic function and convention to trigger deprecation notices", + "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/css-selector/tree/v7.4.0" }, "funding": [ { @@ -1614,56 +3387,47 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2025-10-30T13:39:42+00:00" }, { - "name": "symfony/event-dispatcher", - "version": "v6.3.0", + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa", - "reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/event-dispatcher-contracts": "^2.5|^3" - }, - "conflict": { - "symfony/dependency-injection": "<5.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": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/error-handler": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^5.4|^6.0" + "php": ">=8.1" }, "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" }, - "exclude-from-classmap": [ - "/Tests/" + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -1672,18 +3436,18 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.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", + "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.3.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -1699,40 +3463,40 @@ "type": "tidelift" } ], - "time": "2023-04-21T14:41:17+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { - "name": "symfony/event-dispatcher-contracts", - "version": "v3.3.0", + "name": "symfony/dom-crawler", + "version": "v7.4.4", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "71fd6a82fc357c8b5de22f78b228acfc43dee965" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", - "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/71fd6a82fc357c8b5de22f78b228acfc43dee965", + "reference": "71fd6a82fc357c8b5de22f78b228acfc43dee965", "shasum": "" }, "require": { - "php": ">=8.1", - "psr/event-dispatcher": "^1" + "masterminds/html5": "^2.6", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } + "require-dev": { + "symfony/css-selector": "^6.4|^7.0|^8.0" }, + "type": "library", "autoload": { "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1740,26 +3504,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to dispatching event", + "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/dom-crawler/tree/v7.4.4" }, "funding": [ { @@ -1770,36 +3526,58 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2026-01-05T08:47:25+00:00" }, { - "name": "symfony/filesystem", - "version": "v6.3.0", + "name": "symfony/event-dispatcher", + "version": "v7.4.4", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "97b698e1d77d356304def77a8d0cd73090b359ea" + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "dc2c0eba1af673e736bb851d747d266108aea746" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/97b698e1d77d356304def77a8d0cd73090b359ea", - "reference": "97b698e1d77d356304def77a8d0cd73090b359ea", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/dc2c0eba1af673e736bb851d747d266108aea746", + "reference": "dc2c0eba1af673e736bb851d747d266108aea746", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" + "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|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Filesystem\\": "" + "Symfony\\Component\\EventDispatcher\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -1819,10 +3597,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides basic utilities for the filesystem", + "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/filesystem/tree/v6.3.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.4" }, "funding": [ { @@ -1833,41 +3611,49 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2023-05-30T17:12:32+00:00" + "time": "2026-01-05T11:45:34+00:00" }, { - "name": "symfony/finder", - "version": "v6.3.0", + "name": "symfony/event-dispatcher-contracts", + "version": "v3.6.0", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "d9b01ba073c44cef617c7907ce2419f8d00d75e2" + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/d9b01ba073c44cef617c7907ce2419f8d00d75e2", - "reference": "d9b01ba073c44cef617c7907ce2419f8d00d75e2", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", "shasum": "" }, "require": { - "php": ">=8.1" - }, - "require-dev": { - "symfony/filesystem": "^6.0" + "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\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Contracts\\EventDispatcher\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1875,18 +3661,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], "support": { - "source": "https://github.com/symfony/finder/tree/v6.3.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" }, "funding": [ { @@ -1902,32 +3696,34 @@ "type": "tidelift" } ], - "time": "2023-04-02T01:25:41+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { - "name": "symfony/inflector", - "version": "v5.4.21", + "name": "symfony/filesystem", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/symfony/inflector.git", - "reference": "d56964e2b19253ba331a829c20d911dd27aa61b6" + "url": "https://github.com/symfony/filesystem.git", + "reference": "d551b38811096d0be9c4691d406991b47c0c630a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/inflector/zipball/d56964e2b19253ba331a829c20d911dd27aa61b6", - "reference": "d56964e2b19253ba331a829c20d911dd27aa61b6", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a", + "reference": "d551b38811096d0be9c4691d406991b47c0c630a", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16", - "symfony/string": "^5.3.10|^6.0" + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Inflector\\": "" + "Symfony\\Component\\Filesystem\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -1939,26 +3735,18 @@ ], "authors": [ { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Converts words between their singular and plural forms (English only)", + "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", - "keywords": [ - "inflection", - "pluralize", - "singularize", - "string", - "symfony", - "words" - ], "support": { - "source": "https://github.com/symfony/inflector/tree/v5.4.21" + "source": "https://github.com/symfony/filesystem/tree/v7.4.0" }, "funding": [ { @@ -1969,36 +3757,41 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "abandoned": "EnglishInflector from the String component", - "time": "2023-02-14T08:03:56+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { - "name": "symfony/options-resolver", - "version": "v6.3.0", + "name": "symfony/finder", + "version": "v7.4.5", "source": { "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "a10f19f5198d589d5c33333cffe98dc9820332dd" + "url": "https://github.com/symfony/finder.git", + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/a10f19f5198d589d5c33333cffe98dc9820332dd", - "reference": "a10f19f5198d589d5c33333cffe98dc9820332dd", + "url": "https://api.github.com/repos/symfony/finder/zipball/ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\OptionsResolver\\": "" + "Symfony\\Component\\Finder\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -2018,15 +3811,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides an improved replacement for the array_replace PHP function", + "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", - "keywords": [ - "config", - "configuration", - "options" - ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v6.3.0" + "source": "https://github.com/symfony/finder/tree/v7.4.5" }, "funding": [ { @@ -2037,56 +3825,39 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2023-05-12T14:21:09+00:00" + "time": "2026-01-26T15:07:59+00:00" }, { - "name": "symfony/phpunit-bridge", - "version": "v5.4.23", + "name": "symfony/options-resolver", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "1572c5b7cad812bdf0414d89a32a33a2dafb38ba" + "url": "https://github.com/symfony/options-resolver.git", + "reference": "b38026df55197f9e39a44f3215788edf83187b80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/1572c5b7cad812bdf0414d89a32a33a2dafb38ba", - "reference": "1572c5b7cad812bdf0414d89a32a33a2dafb38ba", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b38026df55197f9e39a44f3215788edf83187b80", + "reference": "b38026df55197f9e39a44f3215788edf83187b80", "shasum": "" }, "require": { - "php": ">=7.1.3", - "symfony/deprecation-contracts": "^2.1|^3" - }, - "conflict": { - "phpunit/phpunit": "<7.5|9.1.2" - }, - "require-dev": { - "symfony/error-handler": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/error-handler": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" - }, - "bin": [ - "bin/simple-phpunit" - ], - "type": "symfony-bridge", - "extra": { - "thanks": { - "name": "phpunit/phpunit", - "url": "https://github.com/sebastianbergmann/phpunit" - } + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" }, + "type": "library", "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Bridge\\PhpUnit\\": "" + "Symfony\\Component\\OptionsResolver\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -2098,18 +3869,23 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Provides utilities for PHPUnit, especially user deprecation notices management", + "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/phpunit-bridge/tree/v5.4.23" + "source": "https://github.com/symfony/options-resolver/tree/v7.4.0" }, "funding": [ { @@ -2120,29 +3896,33 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2023-04-18T09:42:03+00:00" + "time": "2025-11-12T15:39:26+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.27.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -2152,12 +3932,9 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2191,7 +3968,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -2202,41 +3979,42 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.27.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2272,7 +4050,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -2283,41 +4061,42 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.27.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2356,7 +4135,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -2367,29 +4146,34 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { - "php": ">=7.1" + "ext-iconv": "*", + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -2399,12 +4183,9 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2439,7 +4220,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -2450,38 +4231,39 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.27.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2522,7 +4304,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -2533,38 +4315,39 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.27.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a" + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2601,7 +4384,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0" }, "funding": [ { @@ -2612,37 +4395,50 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/process", - "version": "v6.3.0", + "name": "symfony/polyfill-php84", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "8741e3ed7fe2e91ec099e02446fb86667a0f1628" + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/8741e3ed7fe2e91ec099e02446fb86667a0f1628", - "reference": "8741e3ed7fe2e91ec099e02446fb86667a0f1628", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.2" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\Process\\": "" + "Symfony\\Polyfill\\Php84\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2651,18 +4447,24 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Executes commands in sub-processes", + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/process/tree/v6.3.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" }, "funding": [ { @@ -2673,56 +4475,38 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2023-05-19T08:06:44+00:00" + "time": "2025-06-24T13:30:11+00:00" }, { - "name": "symfony/property-info", - "version": "v5.4.24", + "name": "symfony/process", + "version": "v7.4.5", "source": { "type": "git", - "url": "https://github.com/symfony/property-info.git", - "reference": "d43b85b00699b4484964c297575b5c6f9dc5f6e1" + "url": "https://github.com/symfony/process.git", + "reference": "608476f4604102976d687c483ac63a79ba18cc97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/d43b85b00699b4484964c297575b5c6f9dc5f6e1", - "reference": "d43b85b00699b4484964c297575b5c6f9dc5f6e1", + "url": "https://api.github.com/repos/symfony/process/zipball/608476f4604102976d687c483ac63a79ba18cc97", + "reference": "608476f4604102976d687c483ac63a79ba18cc97", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16", - "symfony/string": "^5.1|^6.0" - }, - "conflict": { - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/dependency-injection": "<4.4" - }, - "require-dev": { - "doctrine/annotations": "^1.10.4|^2", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "phpstan/phpdoc-parser": "^1.0", - "symfony/cache": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/serializer": "^4.4|^5.0|^6.0" - }, - "suggest": { - "phpdocumentor/reflection-docblock": "To use the PHPDoc", - "psr/cache-implementation": "To cache results", - "symfony/doctrine-bridge": "To use Doctrine metadata", - "symfony/serializer": "To use Serializer metadata" + "php": ">=8.2" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\PropertyInfo\\": "" + "Symfony\\Component\\Process\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -2734,26 +4518,18 @@ ], "authors": [ { - "name": "Kévin Dunglas", - "email": "dunglas@gmail.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Extracts information about PHP class' properties using metadata of popular sources", + "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", - "keywords": [ - "doctrine", - "phpdoc", - "property", - "symfony", - "type", - "validator" - ], "support": { - "source": "https://github.com/symfony/property-info/tree/v5.4.24" + "source": "https://github.com/symfony/process/tree/v7.4.5" }, "funding": [ { @@ -2764,42 +4540,47 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2023-05-15T20:11:03+00:00" + "time": "2026-01-26T15:07:59+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.3.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4" + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", - "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { "php": ">=8.1", - "psr/container": "^2.0" + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "ext-psr": "<1.1|>=2" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { @@ -2835,7 +4616,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -2846,29 +4627,33 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "symfony/stopwatch", - "version": "v6.3.0", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2" + "reference": "8a24af0a2e8a872fb745047180649b8418303084" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2", - "reference": "fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/8a24af0a2e8a872fb745047180649b8418303084", + "reference": "8a24af0a2e8a872fb745047180649b8418303084", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/service-contracts": "^2.5|^3" }, "type": "library", @@ -2897,7 +4682,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v6.3.0" + "source": "https://github.com/symfony/stopwatch/tree/v7.4.0" }, "funding": [ { @@ -2908,31 +4693,36 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2023-02-16T10:14:28+00:00" + "time": "2025-08-04T07:05:15+00:00" }, { "name": "symfony/string", - "version": "v6.3.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f" + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f2e190ee75ff0f5eced645ec0be5c66fac81f51f", - "reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f", + "url": "https://api.github.com/repos/symfony/string/zipball/1c4b10461bf2ec27537b5f36105337262f5f5d6f", + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-grapheme": "~1.33", "symfony/polyfill-intl-normalizer": "~1.0", "symfony/polyfill-mbstring": "~1.0" }, @@ -2940,11 +4730,11 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/intl": "^6.2", + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^5.4|^6.0" + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -2983,7 +4773,64 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.3.0" + "source": "https://github.com/symfony/string/tree/v7.4.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-12T10:54:30+00:00" + }, + { + "name": "symfony/test-pack", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/test-pack.git", + "reference": "88285bd1b12ec408c09fd6abac0ba290ff81fd04" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/test-pack/zipball/88285bd1b12ec408c09fd6abac0ba290ff81fd04", + "reference": "88285bd1b12ec408c09fd6abac0ba290ff81fd04", + "shasum": "" + }, + "require": { + "phpunit/phpunit": "*", + "symfony/browser-kit": "*", + "symfony/css-selector": "*" + }, + "conflict": { + "phpunit/phpunit": "<11.1" + }, + "require-dev": { + "phpunit/phpunit": "*", + "symfony/browser-kit": "*", + "symfony/css-selector": "*" + }, + "type": "symfony-pack", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A pack for functional and end-to-end testing within a Symfony app", + "support": { + "issues": "https://github.com/symfony/test-pack/issues", + "source": "https://github.com/symfony/test-pack/tree/v1.2.0" }, "funding": [ { @@ -2999,42 +4846,38 @@ "type": "tidelift" } ], - "time": "2023-03-21T21:06:29+00:00" + "time": "2025-05-20T15:03:19+00:00" }, { "name": "symfony/var-dumper", - "version": "v5.4.24", + "version": "v6.4.32", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "8e12706bf9c68a2da633f23bfdc15b4dce5970b3" + "reference": "131fc9915e0343052af5ed5040401b481ca192aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/8e12706bf9c68a2da633f23bfdc15b4dce5970b3", - "reference": "8e12706bf9c68a2da633f23bfdc15b4dce5970b3", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/131fc9915e0343052af5ed5040401b481ca192aa", + "reference": "131fc9915e0343052af5ed5040401b481ca192aa", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/console": "<4.4" + "symfony/console": "<5.4" }, "require-dev": { - "ext-iconv": "*", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/uid": "^5.1|^6.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^6.3|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", "twig/twig": "^2.13|^3.0.4" }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, "bin": [ "Resources/bin/var-dump-server" ], @@ -3071,7 +4914,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.4.24" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.32" }, "funding": [ { @@ -3082,25 +4925,77 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2023-05-25T13:05:00+00:00" + "time": "2026-01-01T13:34:06+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-11-17T20:03:58+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "roave/security-advisories": 20 - }, + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.2", + "php": ">=8.2", "ext-json": "*" }, - "platform-dev": [], - "plugin-api-version": "2.3.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } diff --git a/docker/logs b/docker/logs new file mode 100755 index 0000000..013f26c --- /dev/null +++ b/docker/logs @@ -0,0 +1,5 @@ +#!/bin/bash +set -uoe pipefail + +echo -e "\033[33;1mChecking logs for PHP container\033[0m" +docker compose -f compose.yaml logs php diff --git a/docker/php b/docker/php new file mode 100755 index 0000000..37071a3 --- /dev/null +++ b/docker/php @@ -0,0 +1,5 @@ +#!/bin/bash +set -uoe pipefail + +echo -e "\033[33;1mExecuting bash inside PHP container ('exit' to quit)\033[0m" +docker compose -f compose.yaml exec php bash diff --git a/examples/expression_builder.php b/examples/expression_builder.php deleted file mode 100644 index 69a77d9..0000000 --- a/examples/expression_builder.php +++ /dev/null @@ -1,30 +0,0 @@ -andX( - $expr->orX( - $expr->eq('A', 1), - $expr->eq('B', 1) - ), - $expr->orX( - $expr->eq('C', 1), - $expr->eq('D', 1), - $expr->eq('E', 1) - ) -); - -dump($domain); -dump($domain->toArray()); - -// Expected: [ '&', '|', (A), (B), '|', (C), '|', (D), (E) ] \ No newline at end of file diff --git a/grumphp.yml b/grumphp.yml deleted file mode 100644 index 2b8c5d4..0000000 --- a/grumphp.yml +++ /dev/null @@ -1,25 +0,0 @@ -parameters: - git_dir: . - bin_dir: vendor/bin - tasks: - composer: ~ - # phpcsfixer2: - # allow_risky: false - # cache_file: ~ - # config: ~ - # rules: - # '@Symfony': true - # indentation_type: false - # braces: - # allow_single_line_closure: false - # position_after_functions_and_oop_constructs: "next" - # using_cache: true - # verbose: true - # diff: false - # triggered_by: ['php'] - phplint: ~ - #phpunit: ~ - yamllint: ~ - ascii: - failed: ~ - succeeded: ~ \ No newline at end of file diff --git a/mkdocs.yaml b/mkdocs.yaml new file mode 100644 index 0000000..8d13c62 --- /dev/null +++ b/mkdocs.yaml @@ -0,0 +1,3 @@ +site_name: PHP Odoo API client +repo_url: https://github.com/Ang3/php-odoo-api-client +theme: readthedocs \ No newline at end of file diff --git a/phpstan.neon b/phpstan.neon index 7fab944..089aeed 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,8 +1,5 @@ parameters: - inferPrivatePropertyTypeFromConstructor: true - checkGenericClassInNonGenericObjectType: false - checkMissingIterableValueType: false - paths: - - %currentWorkingDirectory%/src - ignoreErrors: - #- '#(.*) expects class-string\\|T of object, (.*) given$#' \ No newline at end of file + level: max + paths: + - src/ + treatPhpDocTypesAsCertain: false diff --git a/phpunit.xml.dist b/phpunit.xml.dist index fb5f654..bebbda7 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -2,30 +2,38 @@ - - - - + + + + - - - tests - - + + + tests + + - - - ./src - ./vendor - - ./tests - ./vendor - - - + + + src + + + + + + + + + diff --git a/src/Client.php b/src/Client.php old mode 100755 new mode 100644 index aea16fd..cafc9b8 --- a/src/Client.php +++ b/src/Client.php @@ -1,464 +1,137 @@ + */ class Client { - use ExpressionBuilderAwareTrait; - - /** - * Endpoints. - */ - public const ENDPOINT_COMMON = 'xmlrpc/2/common'; - public const ENDPOINT_OBJECT = 'xmlrpc/2/object'; - - /** - * Special commands. - */ - public const LIST_FIELDS = 'fields_get'; - - /** - * URL of the database. - * - * @var string - */ - private $url; - - /** - * Name of the database. - * - * @var string - */ - private $database; - - /** - * Username of internal user. - * - * @var string - */ - private $username; - - /** - * Password of internal user. - * - * @var string - */ - private $password; - - /** - * COMMON endpoint. - * - * @var Endpoint - */ - private $commonEndpoint; - - /** - * OBJECT endpoint. - * - * @var Endpoint - */ - private $objectEndpoint; - - /** - * ORM record manager. - * - * @var RecordManager - */ - private $recordManager; - - /** - * Optional logger. - * - * @var LoggerInterface|null - */ - private $logger; - - /** - * @var int|null - */ - private $uid; - - public function __construct(string $url, string $database, string $username, string $password, LoggerInterface $logger = null) - { - $this->url = $url; - $this->database = $database; - $this->username = $username; - $this->password = $password; - $this->recordManager = new RecordManager($this); - $this->logger = $logger; - $this->initEndpoints(); - } - - /** - * Create a new client instance from array configuration. - * The configuration array must have keys "url", "database", "username" and "password". - * - * @static - * - * @throws MissingConfigParameterException when a required parameter is missing - */ - public static function createFromConfig(array $config, LoggerInterface $logger = null): self - { - $getParam = static function ($config, $paramName, $paramKey) { - $value = $config[$paramName] ?? $config[$paramKey] ?? null; - - if (null === $value) { - throw new MissingConfigParameterException(sprintf('Missing config parameter name "%s" or parameter key %d', $paramName, $paramKey)); - } - - return $value; - }; - - $url = $getParam($config, 'url', 0); - $database = $getParam($config, 'database', 1); - $username = $getParam($config, 'username', 2); - $password = $getParam($config, 'password', 3); + private TransportInterface $transport; + private ?int $uid = null; - return new self($url, $database, $username, $password, $logger); + public function __construct( + private readonly Connection $connection, + ?TransportInterface $transport = null, + private readonly ?LoggerInterface $logger = null, + ) { + $this->transport = $transport ?: new JsonRpcTransport($this->connection); } /** - * Create a new record. - * - * @throws InvalidArgumentException when $data is empty - * @throws RequestException when request failed - * - * @return int the ID of the new record + * @param mixed[] $parameters + * @param mixed[] $options */ - public function create(string $modelName, array $data): int + public function executeKw(string $name, string $method, array $parameters = [], array $options = []): mixed { - if (!$data) { - throw new InvalidArgumentException('Data cannot be empty'); - } - - return (int) $this->call($modelName, OrmQuery::CREATE, [$data]); + return $this->request( + OdooRpcService::Object->value, + OdooRpcMethod::ExecuteKw->value, + $this->connection->getDatabase(), + $this->authenticate(), + $this->connection->getPassword(), + $name, + $method, + $parameters, + $options + ); } - /** - * Read records. - * - * @param array|int $ids - * - * @throws RequestException when request failed - */ - public function read(string $modelName, $ids, array $options = []): array + public function version(): Version { - $ids = [is_int($ids) ? [$ids] : (array) $ids]; - - return (array) $this->call($modelName, OrmQuery::READ, $ids, $options); + return Version::create((array) $this->request(OdooRpcService::Common->value, OdooRpcMethod::Version->value)); } /** - * Update a record(s). - * - * @param array|int $ids - * - * @throws RequestException when request failed + * @throws AuthenticationException on authentication error */ - public function update(string $modelName, $ids, array $data = []): void + public function authenticate(): int { - if (!$data) { - return; + /** @var int|null $uid */ + $uid = $this->request( + OdooRpcService::Common->value, + OdooRpcMethod::Login->value, + $this->connection->getDatabase(), + $this->connection->getUsername(), + $this->connection->getPassword() + ); + + if (!$uid) { + throw new AuthenticationException(); } - $this->call($modelName, OrmQuery::WRITE, [(array) $ids, $data]); - } - - /** - * Delete record(s). - * - * @param array|int $ids - * - * @throws RequestException when request failed - */ - public function delete(string $modelName, $ids): void - { - $ids = is_array($ids) ? $ids : [(int) $ids]; - $this->call($modelName, OrmQuery::UNLINK, [$ids]); - } + $this->uid = $uid; - /** - * Search one ID of record by criteria and options. - * - * @throws InvalidArgumentException when $criteria value is not valid - * @throws RequestException when request failed - */ - public function searchOne(string $modelName, iterable $criteria = null, array $options = []): ?int - { - $options['limit'] = 1; - $result = $this->search($modelName, $criteria, $options); - - return array_shift($result); + return $this->uid; } /** - * Search all ID of record(s) with options. - * - * @throws InvalidArgumentException when $criteria value is not valid - * @throws RequestException when request failed - * - * @return array + * @throws RequestException on request errors + * @throws TransportException on transport errors */ - public function searchAll(string $modelName, array $options = []): array + public function request(string $service, string $method, mixed ...$arguments): mixed { - $options['fields'] = ['id']; - - return array_column($this->findBy($modelName, null, $options), 'id'); - } + $uid = $this->uid; - /** - * Find ID of record(s) by criteria and options. - * - * @throws InvalidArgumentException when $criteria value is not valid - * @throws RequestException when request failed - * - * @return array - */ - public function search(string $modelName, iterable $criteria = null, array $options = []): array - { - if (array_key_exists('fields', $options)) { - unset($options['fields']); + if ($service !== OdooRpcService::Common->value || $method !== OdooRpcMethod::Login->value) { + $uid ?: throw new RequestException('You must authenticate before making requests.'); } - return (array) $this->call($modelName, OrmQuery::SEARCH, $this->expr()->normalizeDomains($criteria), $options); - } - - /** - * Find ONE record by ID and options. - * - * @throws RequestException when request failed - */ - public function find(string $modelName, int $id, array $options = []): ?array - { - return $this->findOneBy($modelName, [ - 'id' => $id, - ], $options); - } - - /** - * Find ONE record by criteria and options. - * - * @throws InvalidArgumentException when $criteria value is not valid - * @throws RequestException when request failed - */ - public function findOneBy(string $modelName, iterable $criteria = null, array $options = []): ?array - { - $result = $this->findBy($modelName, $criteria, $options); - - return array_pop($result); - } - - /** - * Find all record(s) with options. - * - * @throws RequestException when request failed - * - * @return array - */ - public function findAll(string $modelName, array $options = []): array - { - return $this->findBy($modelName, null, $options); - } - - /** - * Find record(s) by criteria and options. - * - * @throws InvalidArgumentException when $criteria value is not valid - * @throws RequestException when request failed - * - * @return array - */ - public function findBy(string $modelName, iterable $criteria = null, array $options = []): array - { - return (array) $this->call($modelName, OrmQuery::SEARCH_READ, $this->expr()->normalizeDomains($criteria), $options); - } - - /** - * Check if a record exists. - * - * @throws RequestException when request failed - */ - public function exists(string $modelName, int $id): bool - { - return 1 === $this->count($modelName, [ - 'id' => $id, - ]); - } - - /** - * Count all records for a model. - * - * @throws InvalidArgumentException when $criteria value is not valid - * @throws RequestException when request failed - */ - public function countAll(string $modelName): int - { - return $this->count($modelName); - } - - /** - * Count number of records for a model and criteria. - * - * @throws InvalidArgumentException when $criteria value is not valid - * @throws RequestException when request failed - */ - public function count(string $modelName, iterable $criteria = null): int - { - return (int) $this->call($modelName, OrmQuery::SEARCH_COUNT, $this->expr()->normalizeDomains($criteria)); - } - - /** - * List model fields. - * - * @deprecated since version 7.0 and will be removed in 8.0, use the record manager schema instead. - */ - public function listFields(string $modelName, array $options = []): array - { - return (array) $this->call($modelName, self::LIST_FIELDS, [], $options); - } - - /** - * @return mixed - */ - public function call(string $name, string $method, array $parameters = [], array $options = []) - { - $loggerContext = [ - 'request_id' => uniqid('rpc', true), - 'name' => $name, + $context = [ + 'service' => $service, 'method' => $method, - 'parameters' => $parameters, - 'options' => $options, + 'uid' => $uid, + 'arguments' => \array_slice($arguments, 3), + 'request_id' => uniqid('rpc', true), ]; - if ($this->logger) { - $this->logger->debug('Calling method {name}::{method}', $loggerContext); - } + $this->logger?->info('Odoo request #{request_id} - {service}::{method}({arguments}) (uid: #{uid})', $context); - $result = $this->objectEndpoint->call('execute_kw', [ - $this->database, - $this->authenticate(), - $this->password, - $name, - $method, - $parameters, - $options, - ]); + $runtime = microtime(true); + $result = $this->transport->request($service, $method, $arguments); + $runtime = microtime(true) - $runtime; - if ($this->logger) { - $loggedResult = is_scalar($result) ? $result : json_encode($result); - $this->logger->debug(sprintf('Request result: %s', $loggedResult), [ - 'request_id' => $loggerContext['request_id'], - ]); - } + $this->logger?->debug('Odoo request #{request_id} finished - Runtime: {runtime}s.', [ + 'request_id' => $context['request_id'], + 'runtime' => number_format($runtime, 3, '.', ' '), + 'payload' => $result, + ]); return $result; } - public function version(): array - { - return $this->commonEndpoint->call('version'); - } - - /** - * @throws AuthenticationException when authentication failed - */ - public function authenticate(): int - { - if (null === $this->uid) { - $this->uid = $this->commonEndpoint->call('authenticate', [ - $this->database, - $this->username, - $this->password, - [], - ]); - - if (!$this->uid || !is_int($this->uid)) { - throw new AuthenticationException(); - } - } - - return $this->uid; - } - - public function getIdentifier(): string - { - $database = preg_replace('([^a-zA-Z0-9_])', '_', $this->database); - $user = preg_replace('([^a-zA-Z0-9_])', '_', $this->username); - - return sprintf('%s.%s.%s', sha1($this->url), $database, $user); - } - - public function getUrl(): string - { - return $this->url; - } - - public function setUrl(string $url): self - { - $this->url = $url; - $this->initEndpoints(); - - return $this; - } - - public function getDatabase(): string - { - return $this->database; - } - - public function setDatabase(string $database): self + public function getConnection(): Connection { - $this->database = $database; - - return $this; - } - - public function getUsername(): string - { - return $this->username; - } - - public function setUsername(string $username): self - { - $this->username = $username; - - return $this; - } - - public function getPassword(): string - { - return $this->password; + return $this->connection; } - public function setPassword(string $password): self + public function getTransport(): TransportInterface { - $this->password = $password; - - return $this; - } - - public function getCommonEndpoint(): Endpoint - { - return $this->commonEndpoint; + return $this->transport; } - public function getObjectEndpoint(): Endpoint + public function withTransport(TransportInterface $transport): self { - return $this->objectEndpoint; - } - - public function getRecordManager(): RecordManager - { - return $this->recordManager; + return new self($this->connection, $transport, $this->logger); } public function getLogger(): ?LoggerInterface @@ -466,19 +139,13 @@ public function getLogger(): ?LoggerInterface return $this->logger; } - public function setLogger(?LoggerInterface $logger): self + public function withLogger(LoggerInterface $logger): self { - $this->logger = $logger; - - return $this; + return new self($this->connection, $this->transport, $logger); } - /** - * @internal - */ - private function initEndpoints(): void + public function getUid(): ?int { - $this->commonEndpoint = new Endpoint($this->url.'/'.self::ENDPOINT_COMMON); - $this->objectEndpoint = new Endpoint($this->url.'/'.self::ENDPOINT_OBJECT); + return $this->uid; } } diff --git a/src/Connection.php b/src/Connection.php new file mode 100644 index 0000000..517d9cc --- /dev/null +++ b/src/Connection.php @@ -0,0 +1,122 @@ + + */ +readonly class Connection +{ + public function __construct( + private string $host, + private string $username, + private string $password, + private string $database, + private string $scheme = 'https', + ) { + } + + public function __toString(): string + { + return \sprintf('%s://%s:%s@%s/%s', $this->scheme, $this->username, urlencode($this->password), $this->host, $this->database); + } + + /** + * @throws \InvalidArgumentException on invalid config + */ + public static function createFromDsn(string $dsn): self + { + try { + $dsn = DsnParser::parse($dsn); + } catch (InvalidDsnException $e) { + throw new \InvalidArgumentException('Invalid DSN', 0, $e); + } + + $path = (string) $dsn->getPath(); + + return self::createFromConfig([ + 'host' => (string) $dsn->getHost(), + 'username' => (string) $dsn->getUser(), + 'password' => urldecode((string) $dsn->getPassword()), + 'database' => str_starts_with($path, '/') ? substr($path, 1) : $path, + 'scheme' => (string) $dsn->getScheme(), + ]); + } + + /** + * @param array{host: string, username: string, password: string, database: string, scheme: string|null} $config + * + * @throws InvalidArgumentException on invalid configuration + */ + public static function createFromConfig(array $config): self + { + $host = $config['host'] ?? null; + Assert::stringNotEmpty($host, 'The host cannot be empty.'); + + $username = $config['username'] ?? null; + Assert::stringNotEmpty($username, 'The username cannot be empty.'); + + $password = $config['password'] ?? null; + Assert::stringNotEmpty($password, 'The password cannot be empty.'); + + $database = $config['database'] ?? null; + Assert::stringNotEmpty($database, 'The database cannot be empty.'); + + $scheme = $config['scheme'] ?? 'https'; + Assert::inArray($scheme, ['http', 'https'], \sprintf('The DSN scheme "%s" is not supported (supported: "http" or "https").', $scheme)); + + return new self($host, $username, $password, $database, $scheme); + } + + /** + * Gets the unique name of this connection. + */ + public function getIdentifier(): string + { + return sha1(\sprintf('%s.%s.%s', $this->host, $this->database, $this->username)); + } + + public function getHost(): string + { + return $this->host; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getPassword(): string + { + return $this->password; + } + + public function getDatabase(): string + { + return $this->database; + } + + public function getScheme(): string + { + return $this->scheme; + } + + public function getUrl(): string + { + return \sprintf('%s://%s', $this->scheme, $this->host); + } +} diff --git a/src/DBAL/Expression/CollectionOperation.php b/src/DBAL/Expression/CollectionOperation.php deleted file mode 100644 index d3d0718..0000000 --- a/src/DBAL/Expression/CollectionOperation.php +++ /dev/null @@ -1,143 +0,0 @@ -type = $type; - $this->id = $id; - $this->data = $data; - } - - /** - * @throws InvalidArgumentException when data is empty - */ - public static function create(array $data): self - { - if (!$data) { - throw new InvalidArgumentException('Data cannot be empty'); - } - - return new self(self::CREATE, 0, $data); - } - - /** - * @throws InvalidArgumentException when data is empty - */ - public static function update(int $id, array $data = []): self - { - if (!$data) { - throw new InvalidArgumentException('Data cannot be empty'); - } - - return new self(self::UPDATE, $id, $data); - } - - public static function add(int $id): self - { - return new self(self::ADD, $id); - } - - public static function remove(int $id): self - { - return new self(self::REMOVE, $id); - } - - public static function delete(int $id): self - { - return new self(self::DELETE, $id); - } - - /** - * @param int[] $ids - */ - public static function replace(array $ids): self - { - return new self(self::REPLACE, 0, $ids); - } - - public static function clear(): self - { - return new self(self::CLEAR); - } - - public function getType(): int - { - return $this->type; - } - - public function setType(int $type): self - { - $this->type = $type; - - return $this; - } - - public function getId(): int - { - return $this->id; - } - - public function setId(int $id): self - { - $this->id = $id; - - return $this; - } - - /** - * @return array|int - */ - public function getData() - { - return $this->data; - } - - /** - * @param array|int $data - */ - public function setData($data): self - { - $this->data = $data; - - return $this; - } - - public function toArray(): array - { - return [$this->type, $this->id, $this->data]; - } -} diff --git a/src/DBAL/Expression/Comparison.php b/src/DBAL/Expression/Comparison.php deleted file mode 100644 index bf4cc15..0000000 --- a/src/DBAL/Expression/Comparison.php +++ /dev/null @@ -1,138 +0,0 @@ -'; - public const GREATER_THAN_OR_EQUAL = '>='; - public const EQUAL_LIKE = '=like'; - public const INSENSITIVE_EQUAL_LIKE = '=ilike'; - public const LIKE = 'like'; - public const NOT_LIKE = 'not like'; - public const INSENSITIVE_LIKE = 'ilike'; - public const INSENSITIVE_NOT_LIKE = 'not ilike'; - public const IN = 'in'; - public const NOT_IN = 'not in'; - - /** - * @var string[] - */ - private static $operators = [ - self::UNSET_OR_EQUAL_TO, - self::EQUAL_TO, - self::NOT_EQUAL_TO, - self::LESS_THAN, - self::LESS_THAN_OR_EQUAL, - self::GREATER_THAN, - self::GREATER_THAN_OR_EQUAL, - self::EQUAL_LIKE, - self::INSENSITIVE_EQUAL_LIKE, - self::LIKE, - self::NOT_LIKE, - self::INSENSITIVE_LIKE, - self::INSENSITIVE_NOT_LIKE, - self::IN, - self::NOT_IN, - ]; - - /** - * @var string - */ - private $fieldName; - - /** - * @var string - */ - private $operator; - - /** - * @var mixed - */ - private $value; - - /** - * @param mixed $value - */ - public function __construct(string $fieldName, string $operator, $value) - { - $this->fieldName = $fieldName; - $this->operator = $operator; - $this->value = $value; - } - - public function __clone() - { - $this->value = is_object($this->value) ? clone $this->value : $this->value; - } - - public function getIterator(): ArrayIterator - { - return new ArrayIterator($this->toArray()); - } - - public function toArray(): array - { - return [$this->fieldName, $this->operator, $this->value]; - } - - /** - * @return string[] - */ - public static function getOperators(): array - { - return self::$operators; - } - - public function getFieldName(): string - { - return $this->fieldName; - } - - public function setFieldName(string $fieldName): self - { - $this->fieldName = $fieldName; - - return $this; - } - - public function getOperator(): string - { - return $this->operator; - } - - public function setOperator(string $operator): self - { - $this->operator = $operator; - - return $this; - } - - /** - * @return mixed - */ - public function getValue() - { - return $this->value; - } - - /** - * @param mixed $value - */ - public function setValue($value): self - { - $this->value = $value; - - return $this; - } -} diff --git a/src/DBAL/Expression/CompositeDomain.php b/src/DBAL/Expression/CompositeDomain.php deleted file mode 100644 index 31a3a17..0000000 --- a/src/DBAL/Expression/CompositeDomain.php +++ /dev/null @@ -1,214 +0,0 @@ -operator = $operator; - $this->setDomains($domains); - } - - public function __clone() - { - foreach ($this->domains as $key => $domain) { - $this->domains[$key] = clone $domain; - } - } - - /** - * {@inheritdoc} - * - * @return DomainInterface[]|Generator - */ - public function getIterator(): Generator - { - foreach ($this->getDomains() as $key => $domain) { - yield $key => $domain; - } - } - - /** - * @static - * - * @return string[] - */ - public static function getOperators(): array - { - return self::$operators; - } - - public function toArray(): array - { - $domain = $this->prepare(); - - if (!($domain instanceof self)) { - return $domain ? $domain->toArray() : []; - } - - $result = [$domain->getOperator()]; - - foreach ($domain->getDomains() as $domain) { - $domainArray = $domain->toArray(); - - if ($domain instanceof self) { - foreach ($domainArray as $value) { - $result[] = $value; - } - - continue; - } - - $result[] = $domainArray; - } - - return $result; - } - - /** - * @internal - * - * Create a copy according to arity policy of Odoo polish notation - */ - private function prepare(): ?DomainInterface - { - $domains = $this->domains; - $nbDomains = count($domains); - - if (0 === $nbDomains) { - return null; - } - - if (1 === $nbDomains) { - return self::NOT === $this->operator ? $this : array_shift($domains); - } - - if (self::NOT === $this->operator) { - $andX = new self(self::AND, $domains); - - return new self($this->operator, [$andX->prepare()]); - } - - if (2 === $nbDomains) { - return $this; - } - - foreach ($domains as $key => $subDomain) { - if ($subDomain instanceof self) { - $domains[$key] = $subDomain->prepare(); - - if (!$domains[$key]) { - unset($domains[$key]); - } - - continue; - } - } - - $firstDomain = array_shift($domains); - $subDomain = new self($this->operator, $domains); - - return new self($this->operator, [ - $firstDomain, $subDomain->prepare(), - ]); - } - - public function getOperator(): string - { - return $this->operator; - } - - public function setOperator(string $operator): self - { - $this->operator = $operator; - - return $this; - } - - /** - * @return DomainInterface[] - */ - public function getDomains(): array - { - return array_values($this->domains); - } - - public function setDomains(array $domains = []): self - { - $this->domains = []; - - foreach ($domains as $domain) { - if (!$domain) { - continue; - } - - $this->add($domain); - } - - return $this; - } - - public function add(DomainInterface $domain): self - { - if (!$this->has($domain)) { - $this->domains[] = $domain; - } - - return $this; - } - - public function remove(DomainInterface $domain): self - { - foreach ($this->domains as $key => $value) { - if ($value === $domain) { - unset($this->domains[$key]); - } - } - - return $this; - } - - public function has(DomainInterface $domain): bool - { - return in_array($domain, $this->domains, true); - } - - public function count(): int - { - return count($this->domains); - } - - public function isEmpty(): bool - { - return 0 === count($this->domains); - } -} diff --git a/src/DBAL/Expression/ConversionException.php b/src/DBAL/Expression/ConversionException.php deleted file mode 100644 index b069ac6..0000000 --- a/src/DBAL/Expression/ConversionException.php +++ /dev/null @@ -1,7 +0,0 @@ -data = $data; - } - - public function getIterator(): ArrayIterator - { - return new ArrayIterator($this->toArray()); - } - - public function toArray(): array - { - return $this->data; - } - - public function getData(): array - { - return $this->data; - } - - public function setData(array $data): self - { - $this->data = $data; - - return $this; - } -} diff --git a/src/DBAL/Expression/DomainInterface.php b/src/DBAL/Expression/DomainInterface.php deleted file mode 100644 index 33a80c2..0000000 --- a/src/DBAL/Expression/DomainInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -getValues($values)); - } - - /** - * Check if the field is NOT IN values list. - * - * @param bool|int|float|string|array $values - */ - public function notIn(string $fieldName, $values): Comparison - { - return new Comparison($fieldName, Comparison::NOT_IN, $this->getValues($values)); - } - - /** - * @internal - * - * @param bool|int|float|string|array $values - */ - private function getValues($values): array - { - return is_array($values) ? $values : [$values]; - } - - /** - * Adds a new record created from data. - * - * @throws InvalidArgumentException when $data is empty - */ - public function createRecord(array $data): CollectionOperation - { - return CollectionOperation::create($data); - } - - /** - * Updates an existing record of id $id with data. - * /!\ Can not be used in record create operation. - * - * @throws InvalidArgumentException when $data is empty - */ - public function updateRecord(int $id, array $data): CollectionOperation - { - if (!$data) { - throw new InvalidArgumentException('Data cannot be empty'); - } - - return CollectionOperation::update($id, $data); - } - - /** - * Adds an existing record of id $id to the collection. - */ - public function addRecord(int $id): CollectionOperation - { - return CollectionOperation::add($id); - } - - /** - * Removes the record of id $id from the collection, but does not delete it. - * /!\ Can not be used in record create operation. - */ - public function removeRecord(int $id): CollectionOperation - { - return CollectionOperation::remove($id); - } - - /** - * Removes the record of id $id from the collection, then deletes it from the database. - * /!\ Can not be used in record create operation. - */ - public function deleteRecord(int $id): CollectionOperation - { - return CollectionOperation::delete($id); - } - - /** - * Replaces all existing records in the collection by the $ids list, - * Equivalent to using the command "clear" followed by a command "add" for each id in $ids. - */ - public function replaceRecords(array $ids = []): CollectionOperation - { - return CollectionOperation::replace($ids); - } - - /** - * Removes all records from the collection, equivalent to using the command "remove" on every record explicitly. - * /!\ Can not be used in record create operation. - */ - public function clearRecords(): CollectionOperation - { - return CollectionOperation::clear(); - } - - /** - * @throws InvalidArgumentException when $criteria value is not valid - * @throws ConversionException on data conversion failure - */ - public function normalizeDomains(iterable $criteria = null): array - { - if (!$criteria) { - return [[]]; - } - - if (is_array($criteria)) { - $normalizedCriteria = $this->andX(); - - foreach ($criteria as $fieldName => $value) { - $normalizedCriteria->add($this->eq($fieldName, $this->formatValue($value))); - } - - $criteria = $normalizedCriteria; - } - - if (!$criteria instanceof DomainInterface) { - throw new InvalidArgumentException(sprintf('Expected parameter #1 of type %s|array<%s|array>, %s given', DomainInterface::class, DomainInterface::class, gettype($criteria))); - } - - return $criteria instanceof CompositeDomain ? [$this->formatValue($criteria->toArray())] : [[$this->formatValue($criteria->toArray())]]; - } - - /** - * @throws ConversionException on data conversion failure - */ - public function normalizeData(array $data = []): array - { - return $this->formatValue($data); - } - - /** - * @param mixed $value - * - * @throws ConversionException on data conversion failure - * - * @return mixed - */ - private function formatValue($value) - { - if (is_scalar($value)) { - return $value; - } - - if (is_array($value) || is_iterable($value)) { - $values = []; - - foreach ($value as $key => $aValue) { - $values[$key] = $this->formatValue($aValue); - } - - return $values; - } - - if (is_object($value)) { - if ($value instanceof DomainInterface) { - return $this->formatValue($value->toArray()); - } - - if ($value instanceof CollectionOperation) { - return $this->formatValue($value->toArray()); - } - - if ($value instanceof DateTimeInterface) { - try { - $date = new DateTime(sprintf('@%s', $value->getTimestamp())); - } catch (Exception $e) { - throw new ConversionException(sprintf('Failed to convert date from timestamp "%d"', $value->getTimestamp()), 0, $e); - } - - $date->setTimezone(new \DateTimeZone('UTC')); - - return $date->format('Y-m-d H:i:s'); - } - } - - try { - return (string) $value; - } catch (Exception $e) { - throw new ConversionException(sprintf('Failed to convert value of type "%s" to string.', gettype($value)), 0, $e); - } - } -} diff --git a/src/DBAL/Expression/ExpressionBuilderAwareTrait.php b/src/DBAL/Expression/ExpressionBuilderAwareTrait.php deleted file mode 100644 index 69bdfb3..0000000 --- a/src/DBAL/Expression/ExpressionBuilderAwareTrait.php +++ /dev/null @@ -1,25 +0,0 @@ -getExpressionBuilder(); - } - - public function getExpressionBuilder(): ExpressionBuilder - { - if (!$this->expressionBuilder) { - $this->expressionBuilder = new ExpressionBuilder(); - } - - return $this->expressionBuilder; - } -} diff --git a/src/DBAL/Query/AbstractQuery.php b/src/DBAL/Query/AbstractQuery.php deleted file mode 100644 index dc5ee01..0000000 --- a/src/DBAL/Query/AbstractQuery.php +++ /dev/null @@ -1,133 +0,0 @@ -recordManager = $recordManager; - $this->name = $name; - $this->method = $method; - } - - public function getName(): string - { - return $this->name; - } - - /** - * @return $this - */ - public function setName(string $name) - { - $this->name = $name; - - return $this; - } - - public function getMethod(): string - { - return $this->method; - } - - /** - * @return $this - */ - public function setMethod(string $method) - { - $this->method = $method; - - return $this; - } - - public function getParameters(): array - { - return $this->parameters; - } - - /** - * @return $this - */ - public function setParameters(array $parameters = []) - { - $this->parameters = $parameters; - - return $this; - } - - public function getOptions(): array - { - return $this->options; - } - - /** - * @return $this - */ - public function setOptions(array $options = []) - { - $this->options = $options; - - return $this; - } - - /** - * Add an option on the query. - * - * @param mixed $value - * - * @return $this - */ - public function addOption(string $name, $value) - { - $this->options[$name] = $value; - - return $this; - } - - /** - * Execute the query. - * Allowed methods: all. - * - * @return mixed - */ - public function execute() - { - return $this->recordManager->executeQuery($this); - } - - /** - * Gets the related manager of the query. - */ - public function getRecordManager(): RecordManager - { - return $this->recordManager; - } -} diff --git a/src/DBAL/Query/NativeQuery.php b/src/DBAL/Query/NativeQuery.php deleted file mode 100644 index 3f34622..0000000 --- a/src/DBAL/Query/NativeQuery.php +++ /dev/null @@ -1,7 +0,0 @@ -method = $method; - - return $this; - } - - /** - * Counts the number of records from parameters. - * Allowed methods: SEARCH, SEARCH_READ. - * - * @throws QueryException on invalid query method - */ - public function count(): int - { - if (!in_array($this->method, [self::SEARCH, self::SEARCH_READ])) { - throw new QueryException(sprintf('You can count results with method "%s" and "%s" only.', self::SEARCH, self::SEARCH_READ)); - } - - $query = new self($this->recordManager, $this->name, self::SEARCH_COUNT); - $query->setParameters($this->parameters); - - return (int) $query->execute(); - } - - /** - * Gets just ONE scalar result. - * Allowed methods: SEARCH, SEARCH_READ. - * - * @return bool|int|float|string - * - * @throws NoUniqueResultException on no unique result - * @throws NoResultException on no result - * @throws QueryException on invalid query method - */ - public function getSingleScalarResult() - { - $result = $this->getOneOrNullScalarResult(); - - if (!$result) { - throw new NoResultException(); - } - - return $result; - } - - /** - * Gets one or NULL scalar result. - * Allowed methods: SEARCH, SEARCH_READ. - * - * @return bool|int|float|string|null - * - * @throws NoUniqueResultException on no unique result - * @throws QueryException on invalid query method - */ - public function getOneOrNullScalarResult() - { - $result = $this->getScalarResult(); - - if (count($result) > 1) { - throw new NoUniqueResultException(); - } - - return array_shift($result); - } - - /** - * Gets a list of scalar result. - * Allowed methods: SEARCH, SEARCH_READ. - * - * @throws QueryException on invalid query method - * - * @return array - */ - public function getScalarResult(): array - { - $result = $this->getResult(); - - if (self::SEARCH === $this->method) { - return $result; - } - - $selectedFields = $this->options['fields'] ?? []; - if (count($selectedFields) > 1) { - throw new QueryException('More than one field selected.'); - } - - $selectedFieldName = $selectedFields[0] ?? 'id'; - - foreach ($result as $key => $value) { - $result[$key] = $value[$selectedFieldName] ?? null; - } - - return $result; - } - - /** - * Gets one row. - * Allowed methods: SEARCH, SEARCH_READ. - * - * @throws NoUniqueResultException on no unique result - * @throws NoResultException on no result - * @throws QueryException on invalid query method - */ - public function getSingleResult(): array - { - $result = $this->getOneOrNullResult(); - $result = array_shift($result); - - if (!$result) { - throw new NoResultException(); - } - - return $result; - } - - /** - * Gets one or NULL row. - * Allowed methods: SEARCH, SEARCH_READ. - * - * @throws NoUniqueResultException on no unique result - * @throws QueryException on invalid query method - */ - public function getOneOrNullResult(): ?array - { - $result = $this->getResult(); - - if (count($result) > 1) { - throw new NoUniqueResultException(); - } - - return array_shift($result); - } - - /** - * Gets all result rows. - * Allowed methods: SEARCH, SEARCH_READ. - * - * @throws QueryException on invalid query method - */ - public function getResult(): array - { - if (!in_array($this->method, [self::SEARCH, self::SEARCH_READ])) { - throw new QueryException(sprintf('You can get results with methods "%s" and "%s" only.', self::SEARCH, self::SEARCH_READ)); - } - - return (array) $this->execute(); - } -} diff --git a/src/DBAL/Query/QueryBuilder.php b/src/DBAL/Query/QueryBuilder.php deleted file mode 100644 index c40b95f..0000000 --- a/src/DBAL/Query/QueryBuilder.php +++ /dev/null @@ -1,546 +0,0 @@ -recordManager = $recordManager; - $this->from($modelName); - } - - /** - * Defines the query of type "SELECT" with selected fields. - * No fields selected = all fields returned. - * - * @param array|string|null $fields - */ - public function select($fields = null): self - { - $this->type = self::SELECT; - $this->select = []; - $this->values = []; - $this->ids = []; - - $fields = $fields ? (array) $fields : []; - - foreach ($fields as $fieldName) { - $this->addSelect($fieldName); - } - - return $this; - } - - /** - * Defines the query of type "SEARCH". - */ - public function search(): self - { - $this->type = self::SEARCH; - $this->select = []; - $this->values = []; - $this->ids = []; - - return $this; - } - - /** - * Defines the query of type "INSERT". - */ - public function insert(): self - { - $this->type = self::INSERT; - $this->select = []; - $this->ids = []; - $this->where = null; - - return $this; - } - - /** - * Defines the query of type "UPDATE" with ids of records to update. - * - * @param int[]|int $ids - */ - public function update($ids): self - { - $this->type = self::UPDATE; - $this->select = []; - - return $this->setIds(is_array($ids) ? $ids : [(int) $ids]); - } - - /** - * Defines the query of type "DELETE" with ids of records to delete. - * - * @param int[]|int $ids - */ - public function delete($ids): self - { - $this->type = self::DELETE; - $this->select = []; - $this->values = []; - - return $this->setIds(is_array($ids) ? $ids : [(int) $ids]); - } - - /** - * Adds a field to select. - * - * @throws QueryException when the type of the query is not "SELECT" - */ - public function addSelect(string $fieldName): self - { - if (self::SELECT !== $this->type) { - throw new QueryException('You can select fields in query of type "SELECT" only.'); - } - - if (!in_array($fieldName, $this->select)) { - $this->select[] = $fieldName; - } - - return $this; - } - - /** - * Gets selected fields. - */ - public function getSelect(): array - { - return $this->select; - } - - /** - * Sets the target model name. - */ - public function from(string $modelName): self - { - $this->from = $modelName; - - return $this; - } - - /** - * Gets the target model name of the query. - */ - public function getFrom(): ?string - { - return $this->from; - } - - /** - * Sets target IDs in case of query of type "UPDATE" or "DELETE". - * - * @throws QueryException when the type of the query is not "UPDATE" nor "DELETE" - */ - public function setIds(array $ids): self - { - $this->ids = []; - - foreach ($ids as $id) { - $this->addId($id); - } - - return $this; - } - - /** - * Adds target ID in case of query of type "UPDATE" or "DELETE". - * - * @throws QueryException when the type of the query is not "UPDATE" nor "DELETE" - */ - public function addId(int $id): self - { - if (!in_array($this->type, [self::UPDATE, self::DELETE])) { - throw new QueryException('You can set indexes in query of type "UPDATE" or "DELETE" only.'); - } - - if (!in_array($id, $this->ids, true)) { - $this->ids[] = $id; - } - - return $this; - } - - /** - * @return int[] - */ - public function getIds(): array - { - return $this->ids; - } - - /** - * Sets field values in case of query of type "INSERT" or "UPDATE". - * - * @throws QueryException when the type of the query is not "INSERT" nor "UPDATE" - */ - public function setValues(array $values = []): self - { - $this->values = []; - - foreach ($values as $fieldName => $value) { - $this->set($fieldName, $value); - } - - return $this; - } - - /** - * Set a field value in case of query of type "INSERT" or "UPDATE". - * - * @param mixed $value - * - * @throws QueryException when the type of the query is not "INSERT" nor "UPDATE" - */ - public function set(string $fieldName, $value): self - { - if (!in_array($this->type, [self::INSERT, self::UPDATE])) { - throw new QueryException('You can set values in query of type "INSERT" or "UPDATE" only.'); - } - - $this->values[$fieldName] = $value; - - return $this; - } - - /** - * Gets field values set in case of query of type "INSERT" or "UPDATE". - */ - public function getValues(): array - { - return $this->values; - } - - /** - * Sets criteria for queries of type "SELECT" and "SEARCH". - * - * @throws QueryException when the type of the query is not "SELECT" not "SEARCH" - */ - public function where(?DomainInterface $domain = null): self - { - $this->assertSupportsWhereClause(); - $this->where = $domain; - - return $this; - } - - /** - * Takes the WHERE clause and adds a node with logical operator AND. - * - * @throws QueryException when the type of the query is not "SELECT" nor "SEARCH" - */ - public function andWhere(DomainInterface $domain): self - { - $this->assertSupportsWhereClause(); - $this->where = $this->expr()->andX($this->where, $domain); - - return $this; - } - - /** - * Takes the WHERE clause and adds a node with logical operator OR. - * - * @throws QueryException when the type of the query is not "SELECT" nor "SEARCH" - */ - public function orWhere(DomainInterface $domain): self - { - $this->assertSupportsWhereClause(); - $this->where = $this->expr()->orX($this->where, $domain); - - return $this; - } - - /** - * Gets the WHERE clause. - */ - public function getWhere(): ?DomainInterface - { - return $this->where; - } - - /** - * @internal - * - * @throws QueryException when the type of the query is not "SELECT" nor "SEARCH" - */ - private function assertSupportsWhereClause(): void - { - if (!in_array($this->type, [self::SELECT, self::SEARCH])) { - throw new QueryException('You can set criteria in query of type "SELECT" or "SEARCH" only.'); - } - } - - /** - * Sets orders. - */ - public function setOrders(array $orders = []): self - { - $this->orders = []; - - foreach ($orders as $fieldName => $isAsc) { - $this->addOrderBy($fieldName, $isAsc); - } - - return $this; - } - - /** - * Clears orders and adds one. - */ - public function orderBy(string $fieldName, bool $isAsc = true): self - { - $this->orders = []; - - return $this->addOrderBy($fieldName, $isAsc); - } - - /** - * Adds order. - * - * @throws QueryException when the query type is not valid - */ - public function addOrderBy(string $fieldName, bool $isAsc = true): self - { - if (!in_array($this->type, [self::SELECT, self::SEARCH])) { - throw new QueryException('You can set orders in query of type "SELECT", "SEARCH" only.'); - } - - $this->orders[$fieldName] = $isAsc; - - return $this; - } - - /** - * Gets ordered fields. - */ - public function getOrders(): array - { - return $this->orders; - } - - /** - * Sets the max results of the query (limit). - */ - public function setMaxResults(?int $maxResults): self - { - $this->maxResults = $maxResults; - - return $this; - } - - /** - * Gets the max results of the query. - */ - public function getMaxResults(): ?int - { - return $this->maxResults; - } - - /** - * Sets the first results of the query (offset). - */ - public function setFirstResult(?int $firstResult): self - { - $this->firstResult = $firstResult; - - return $this; - } - - /** - * Gets the first results of the query. - */ - public function getFirstResult(): ?int - { - return $this->firstResult; - } - - /** - * Computes and returns the query. - * - * @throws QueryException on invalid query - * @throws ConversionException on data conversion failure - */ - public function getQuery(): OrmQuery - { - $from = $this->from; - if (null === $from) { - throw new QueryException('Missing FROM clause (model name).'); - } - - switch ($this->type) { - case self::SELECT: - $method = OrmQuery::SEARCH_READ; - break; - case self::SEARCH: - $method = OrmQuery::SEARCH; - break; - case self::INSERT: - $method = OrmQuery::CREATE; - break; - case self::UPDATE: - $method = OrmQuery::WRITE; - break; - case self::DELETE: - $method = OrmQuery::UNLINK; - break; - default: - throw new InvalidArgumentException(sprintf('The query type "%s" is not valid.', $this->type)); - } - - $query = new OrmQuery($this->recordManager, $from, $method); - - switch ($this->type) { - case self::SEARCH: - case self::SELECT: - $parameters = $this->expr()->normalizeDomains($this->where); - break; - case self::DELETE: - if (!$this->ids) { - throw new QueryException('You must set indexes for queries of type "DELETE".'); - } - - $parameters = [$this->ids]; - break; - default: - if (!$this->values) { - throw new QueryException('You must set values for queries of type "INSERT" or "UPDATE".'); - } - - $parameters = $this->expr()->normalizeData($this->values); - - if (self::UPDATE === $this->type) { - if (!$this->ids) { - throw new QueryException('You must set indexes for queries of type "UPDATE".'); - } - - $parameters = [$this->ids, $parameters]; - } - break; - } - - $query->setParameters($parameters); - - if (in_array($this->type, [self::SELECT, self::SEARCH])) { - $options = []; - - if (self::SELECT === $this->type && $this->select) { - $options['fields'] = $this->select; - } - - $orders = $this->orders; - - if ($orders) { - foreach ($orders as $fieldName => $isAsc) { - $orders[$fieldName] = sprintf('%s %s', $fieldName, $isAsc ? 'asc' : 'desc'); - } - - $options['order'] = implode(', ', $orders); - } - - if ($this->firstResult) { - $options['offset'] = $this->firstResult; - } - - if ($this->maxResults) { - $options['limit'] = $this->maxResults; - } - - $query->setOptions($options); - } - - return $query; - } - - /** - * Gets the type of the query. - */ - public function getType(): string - { - return $this->type; - } - - /** - * Gets the related manager of the query. - */ - public function getRecordManager(): RecordManager - { - return $this->recordManager; - } - - /** - * Shortcut to the expression builder of the related client. - */ - public function expr(): ExpressionBuilder - { - return $this->recordManager->getExpressionBuilder(); - } -} diff --git a/src/DBAL/Query/QueryException.php b/src/DBAL/Query/QueryException.php deleted file mode 100644 index 9e24bba..0000000 --- a/src/DBAL/Query/QueryException.php +++ /dev/null @@ -1,7 +0,0 @@ -client = $client; - $this->schema = new Schema($client); - } - - /** - * Create a new record. - * - * @return int the ID of the new record - */ - public function create(string $modelName, array $data): int - { - return $this - ->getRepository($modelName) - ->insert($data); - } - - /** - * Update record(s). - * - * NB: It is not currently possible to perform “computed” updates (by criteria). - * To do it, you have to perform a search then an update with search result IDs. - * - * @param array|int $ids - */ - public function update(string $modelName, $ids, array $data = []): void - { - $this - ->getRepository($modelName) - ->update($ids, $data); - } - - /** - * Delete record(s). - * - * NB: It is not currently possible to perform “computed” deletes (by criteria). - * To do it, you have to perform a search then a delete with search result IDs. - * - * @param array|int $ids - */ - public function delete(string $modelName, $ids): void - { - $this - ->getRepository($modelName) - ->delete($ids); - } - - /** - * Search one ID of record by criteria. - */ - public function searchOne(string $modelName, ?DomainInterface $criteria): ?int - { - return $this - ->getRepository($modelName) - ->searchOne($criteria); - } - - /** - * Search all ID of record(s). - * - * @return int[] - */ - public function searchAll(string $modelName, array $orders = [], int $limit = null, int $offset = null): array - { - return $this - ->getRepository($modelName) - ->searchAll($orders, $limit, $offset); - } - - /** - * Search ID of record(s) by criteria. - * - * @return int[] - */ - public function search(string $modelName, ?DomainInterface $criteria = null, array $orders = [], int $limit = null, int $offset = null): array - { - return $this - ->getRepository($modelName) - ->search($criteria, $orders, $limit, $offset); - } - - /** - * Find ONE record by ID. - * - * @throws RecordNotFoundException when the record was not found - */ - public function read(string $modelName, int $id, array $fields = []): array - { - return $this - ->getRepository($modelName) - ->read($id, $fields); - } - - /** - * Find ONE record by ID. - */ - public function find(string $modelName, int $id, array $fields = []): ?array - { - return $this - ->getRepository($modelName) - ->find($id, $fields); - } - - /** - * Find ONE record by criteria. - */ - public function findOneBy(string $modelName, ?DomainInterface $criteria = null, array $fields = [], array $orders = [], int $offset = null): ?array - { - return $this - ->getRepository($modelName) - ->findOneBy($criteria, $fields, $orders, $offset); - } - - /** - * Find all records. - * - * @return array[] - */ - public function findAll(string $modelName, array $fields = [], array $orders = [], int $limit = null, int $offset = null): array - { - return $this - ->getRepository($modelName) - ->findAll($fields, $orders, $limit, $offset); - } - - /** - * Find record(s) by criteria. - * - * @return array[] - */ - public function findBy(string $modelName, ?DomainInterface $criteria = null, array $fields = [], array $orders = [], int $limit = null, int $offset = null): array - { - return $this - ->getRepository($modelName) - ->findBy($criteria, $fields, $orders, $limit, $offset); - } - - /** - * Check if a record exists. - */ - public function exists(string $modelName, int $id): bool - { - return $this - ->getRepository($modelName) - ->exists($id); - } - - /** - * Count number of all records for the model. - */ - public function countAll(string $modelName): int - { - return $this - ->getRepository($modelName) - ->countAll(); - } - - /** - * Count number of records for a model and criteria. - */ - public function count(string $modelName, ?DomainInterface $criteria = null): int - { - return $this - ->getRepository($modelName) - ->count($criteria); - } - - public function getRepository(string $modelName): RecordRepository - { - if (!array_key_exists($modelName, $this->repositories)) { - $repository = new RecordRepository($this, $modelName); - $this->addRepository($repository); - - return $repository; - } - - return $this->repositories[$modelName]; - } - - public function setRepositories(array $repositories = []): self - { - $this->repositories = []; - - foreach ($repositories as $repository) { - $this->addRepository($repository); - } - - return $this; - } - - public function addRepository(RecordRepository $repository): self - { - $this->repositories[$repository->getModelName()] = $repository; - $repository->setRecordManager($this); - - return $this; - } - - public function createQueryBuilder(string $modelName = null): QueryBuilder - { - return new QueryBuilder($this, $modelName); - } - - public function createOrmQuery(string $name, string $method): OrmQuery - { - return new OrmQuery($this, $name, $method); - } - - public function createNativeQuery(string $name, string $method): NativeQuery - { - return new NativeQuery($this, $name, $method); - } - - /** - * @return mixed - */ - public function executeQuery(QueryInterface $query) - { - $options = $query->getOptions(); - - if (!$options) { - return $this->client->call($query->getName(), $query->getMethod(), $query->getParameters()); - } - - return $this->client->call($query->getName(), $query->getMethod(), $query->getParameters(), $options); - } - - public function getClient(): Client - { - return $this->client; - } - - public function getSchema(): Schema - { - return $this->schema; - } - - public function getRepositories(): array - { - return $this->repositories; - } -} diff --git a/src/DBAL/Repository/RecordNotFoundException.php b/src/DBAL/Repository/RecordNotFoundException.php deleted file mode 100644 index 4f3e9e8..0000000 --- a/src/DBAL/Repository/RecordNotFoundException.php +++ /dev/null @@ -1,34 +0,0 @@ -modelName = $modelName; - $this->id = $id; - - parent::__construct(sprintf('No record found for model "%s" with ID #%d.', $modelName, $id)); - } - - public function getModelName(): string - { - return $this->modelName; - } - - public function getId(): int - { - return $this->id; - } -} diff --git a/src/DBAL/Repository/RecordRepository.php b/src/DBAL/Repository/RecordRepository.php deleted file mode 100644 index 4d79918..0000000 --- a/src/DBAL/Repository/RecordRepository.php +++ /dev/null @@ -1,254 +0,0 @@ -recordManager = $recordManager; - $this->modelName = $modelName; - $recordManager->addRepository($this); - } - - /** - * Insert a new record. - * - * @throws InvalidArgumentException when $data is empty - * - * @return int the ID of the new record - */ - public function insert(array $data): int - { - if (!$data) { - throw new InvalidArgumentException('Data cannot be empty'); - } - - return $this - ->createQueryBuilder() - ->insert() - ->setValues($data) - ->getQuery() - ->execute(); - } - - /** - * Update record(s). - * - * NB: It is not currently possible to perform “computed” updates - * (where the value being set depends on an existing value of a record). - * - * @param array|int $ids - */ - public function update($ids, array $data = []): void - { - if (!$data) { - return; - } - - $this - ->createQueryBuilder() - ->update((array) $ids) - ->setValues($data) - ->getQuery() - ->execute(); - } - - /** - * Delete record(s). - * - * @param array|int $ids - */ - public function delete($ids): void - { - if (!$ids) { - return; - } - - $this - ->createQueryBuilder() - ->delete((array) $ids) - ->getQuery() - ->execute(); - } - - /** - * Search one ID of record by criteria. - */ - public function searchOne(?DomainInterface $criteria): ?int - { - return (int) $this - ->createQueryBuilder() - ->search() - ->where($criteria) - ->getQuery() - ->getOneOrNullScalarResult(); - } - - /** - * Search all ID of record(s). - * - * @return int[] - */ - public function searchAll(array $orders = [], int $limit = null, int $offset = null): array - { - return $this->search(null, $orders, $limit, $offset); - } - - /** - * Search ID of record(s) by criteria. - * - * @return int[] - */ - public function search(?DomainInterface $criteria = null, array $orders = [], int $limit = null, int $offset = null): array - { - /** @var int[] $result */ - $result = $this - ->createQueryBuilder() - ->search() - ->where($criteria) - ->setOrders($orders) - ->setFirstResult($offset) - ->setMaxResults($limit) - ->getQuery() - ->getScalarResult(); - - return $result; - } - - /** - * Find ONE record by ID. - * - * @throws RecordNotFoundException when the record was not found - */ - public function read(int $id, array $fields = []): array - { - $record = $this->find($id, $fields); - - if (!$record) { - throw new RecordNotFoundException($this->modelName, $id); - } - - return $record; - } - - /** - * Find ONE record by ID. - */ - public function find(int $id, array $fields = []): ?array - { - return $this->findOneBy($this->expr()->eq('id', $id), $fields); - } - - /** - * Find ONE record by criteria. - */ - public function findOneBy(?DomainInterface $criteria = null, array $fields = [], array $orders = [], int $offset = null): ?array - { - $result = $this->findBy($criteria, $fields, $orders, 1, $offset); - - return array_pop($result); - } - - /** - * Find all records. - * - * @return array[] - */ - public function findAll(array $fields = [], array $orders = [], int $limit = null, int $offset = null): array - { - return $this->findBy(null, $fields, $orders, $limit, $offset); - } - - /** - * Find record(s) by criteria. - * - * @return array[] - */ - public function findBy(?DomainInterface $criteria = null, array $fields = [], array $orders = [], int $limit = null, int $offset = null): array - { - return $this - ->createQueryBuilder() - ->select($fields) - ->where($criteria) - ->setOrders($orders) - ->setFirstResult($offset) - ->setMaxResults($limit) - ->getQuery() - ->getResult(); - } - - /** - * Check if a record exists. - */ - public function exists(int $id): bool - { - return 1 === $this->count($this->expr()->eq('id', $id)); - } - - /** - * Count number of all records for the model. - */ - public function countAll(): int - { - return $this->count(); - } - - /** - * Count number of records for a model and criteria. - */ - public function count(?DomainInterface $criteria = null): int - { - return $this - ->createQueryBuilder() - ->select() - ->where($criteria) - ->getQuery() - ->count(); - } - - public function createQueryBuilder(): QueryBuilder - { - return $this->recordManager - ->createQueryBuilder($this->modelName) - ->select(); - } - - public function setRecordManager(RecordManager $recordManager): self - { - $this->recordManager = $recordManager; - - return $this; - } - - public function getRecordManager(): RecordManager - { - return $this->recordManager; - } - - public function getModelName(): string - { - return $this->modelName; - } - - public function expr(): ExpressionBuilder - { - return $this->recordManager->getExpressionBuilder(); - } -} diff --git a/src/DBAL/Schema/Choice.php b/src/DBAL/Schema/Choice.php deleted file mode 100644 index aa0796f..0000000 --- a/src/DBAL/Schema/Choice.php +++ /dev/null @@ -1,43 +0,0 @@ -name = $name; - $this->value = $value; - $this->id = $id; - } - - public function getId(): ?int - { - return $this->id; - } - - public function getName(): string - { - return $this->name; - } - - public function getValue(): string - { - return $this->value; - } -} diff --git a/src/DBAL/Schema/Field.php b/src/DBAL/Schema/Field.php deleted file mode 100644 index 07d040c..0000000 --- a/src/DBAL/Schema/Field.php +++ /dev/null @@ -1,238 +0,0 @@ -id = (int) $data['id']; - $this->name = (string) $data['name']; - $this->type = (string) $data['ttype']; - $this->required = (bool) $data['required']; - $this->readOnly = (bool) $data['readonly']; - $this->displayName = $data['display_name'] ?? null; - $this->size = $data['size'] ?? null; - $this->selection = $data['selection'] ?? null; - $this->targetModelName = $data['relation'] ?? null; - $this->targetFieldName = $data['relation_field'] ?? null; - } - - public function getModel(): Model - { - return $this->model; - } - - public function setModel(Model $model): self - { - $this->model = $model; - - return $this; - } - - public function getId(): ?int - { - return $this->id; - } - - public function getName(): string - { - return $this->name; - } - - public function getType(): string - { - return $this->type; - } - - public function isRequired(): bool - { - return $this->required; - } - - public function isReadOnly(): bool - { - return $this->readOnly; - } - - public function getDisplayName(): string - { - return $this->displayName ?: $this->name; - } - - public function getSize(): ?int - { - return $this->size; - } - - public function getSelection(): ?Selection - { - return $this->selection; - } - - public function getTargetModelName(): ?string - { - return $this->targetModelName; - } - - public function getTargetFieldName(): ?string - { - return $this->targetFieldName; - } - - public function isIdentifier(): bool - { - return 'id' === $this->name; - } - - public function isBinary(): bool - { - return Field::T_BINARY === $this->type; - } - - public function isBoolean(): bool - { - return Field::T_BOOLEAN === $this->type; - } - - public function isInteger(): bool - { - return Field::T_INTEGER === $this->type; - } - - public function isFloat(): bool - { - return in_array($this->type, [self::T_FLOAT, self::T_MONETARY]); - } - - public function isNumber(): bool - { - return $this->isInteger() || $this->isFloat(); - } - - public function isString(): bool - { - return in_array($this->type, [self::T_CHAR, self::T_TEXT, self::T_HTML]); - } - - public function isDate(): bool - { - return in_array($this->type, [self::T_DATE, self::T_DATETIME], true); - } - - public function getDateFormat(): string - { - return Field::T_DATETIME === $this->type ? self::DATETIME_FORMAT : self::DATE_FORMAT; - } - - public function isSelection(): bool - { - return Field::T_SELECTION === $this->type; - } - - public function isSelectable(): bool - { - return null !== $this->selection; - } - - public function isAssociation(): bool - { - return in_array($this->type, [ - self::T_MANY_TO_ONE, - self::T_MANY_TO_MANY, - self::T_ONE_TO_MANY, - ], true); - } - - public function isSingleAssociation(): bool - { - return self::T_MANY_TO_ONE === $this->type; - } - - public function isMultipleAssociation(): bool - { - return in_array($this->type, [ - self::T_MANY_TO_MANY, - self::T_ONE_TO_MANY, - ], true); - } -} diff --git a/src/DBAL/Schema/Model.php b/src/DBAL/Schema/Model.php deleted file mode 100644 index e5fb201..0000000 --- a/src/DBAL/Schema/Model.php +++ /dev/null @@ -1,141 +0,0 @@ -schema = $schema; - $this->id = (int) $data['id']; - $this->name = (string) $data['model']; - $this->displayName = (string) $data['name']; - $this->transient = (bool) $data['transient']; - - foreach ($fields as $field) { - $this->addField($field); - } - } - - public function getSchema(): Schema - { - return $this->schema; - } - - public function getId(): int - { - return $this->id; - } - - public function getName(): string - { - return $this->name; - } - - public function getDisplayName(): string - { - return $this->displayName ?: $this->name; - } - - public function isTransient(): bool - { - return $this->transient; - } - - public function hasField(string $fieldName): bool - { - try { - $this->getField($fieldName); - } catch (SchemaException $exception) { - return false; - } - - return true; - } - - /** - * @throws SchemaException when the field was not found - */ - public function getField(string $fieldName): Field - { - $model = $this; - $fields = explode('.', $fieldName); - $lastKey = count($fields) - 1; - - foreach ($fields as $key => $subFieldName) { - $field = $model->getField($subFieldName); - - if ($lastKey === $key) { - break; - } - - $targetModel = $field->getTargetModelName(); - - if (!$targetModel) { - throw SchemaException::fieldNotFound($fieldName, $this); - } - - $model = $this->schema->getModel($targetModel); - } - - return $field; - } - - public function getFields(): array - { - return $this->fields; - } - - /** - * @return array - */ - public function getFieldNames(): array - { - $fieldNames = []; - - foreach ($this->fields as $field) { - $fieldNames[] = $field->getName(); - } - - return $fieldNames; - } - - /** - * @internal - */ - private function addField(Field $field): void - { - $field->setModel($this); - $this->fields[] = $field; - } -} diff --git a/src/DBAL/Schema/Schema.php b/src/DBAL/Schema/Schema.php deleted file mode 100644 index 966fc4c..0000000 --- a/src/DBAL/Schema/Schema.php +++ /dev/null @@ -1,114 +0,0 @@ -client = $client; - } - - public function getModel(string $modelName): Model - { - if (!$this->hasModel($modelName)) { - throw SchemaException::modelNotFound($modelName); - } - - if (!isset($this->loadedModels[$modelName])) { - $expr = $this->client->expr(); - $modelData = $this->client->call(self::IR_MODEL, OrmQuery::SEARCH_READ, $expr->normalizeDomains($expr->eq('model', $modelName))); - $this->loadedModels[$modelName] = $this->createModel($modelData[0]); - } - - return $this->loadedModels[$modelName]; - } - - public function hasModel(string $modelName): bool - { - return in_array($modelName, $this->getModelNames()); - } - - /** - * Gets all model names. - * - * @return string[] - */ - public function getModelNames(): array - { - if (!$this->modelNames) { - $this->modelNames = array_column($this->client->call(self::IR_MODEL, OrmQuery::SEARCH_READ, [[]], [ - 'fields' => ['model'], - ]), 'model'); - } - - return $this->modelNames; - } - - /** - * @internal - */ - private function createModel(array $modelData): Model - { - $expr = $this->client->expr(); - $fields = $this->client->call( - self::IR_MODEL_FIELDS, - OrmQuery::SEARCH_READ, - $expr->normalizeDomains($expr->eq('model_id', $modelData['id'])) - ); - - foreach ($fields as $key => $fieldData) { - $choices = []; - $selectionsIds = array_filter($fieldData['selection_ids'] ?? []); - - if (!empty($selectionsIds)) { - $choices = $this->client->call( - self::IR_MODEL_FIELD_SELECTION, - OrmQuery::SEARCH_READ, - $expr->normalizeDomains($expr->eq('field_id', $fieldData['id'])) - ); - - foreach ($choices as $index => $choice) { - if (is_array($choice)) { - $choices[$index] = new Choice((string) $choice['name'], $choice['value'], (int) $choice['id']); - } - } - } elseif (!empty($fieldData['selection'])) { - if (preg_match_all('#^\[\s*(\(\'(\w+)\'\,\s*\'(\w+)\'\)\s*\,?\s*)*\s*\]$#', trim($fieldData['selection']), $matches, PREG_SET_ORDER)) { - foreach ($matches as $match) { - if (isset($match[2], $match[3])) { - $choices[] = new Choice((string) $match[3], $match[2]); - } - } - } - } - - if ($choices) { - $fieldData['selection'] = $choices; - } - - $fields[$key] = new Field($fieldData); - } - - return new Model($this, $modelData, $fields); - } -} diff --git a/src/DBAL/Schema/SchemaException.php b/src/DBAL/Schema/SchemaException.php deleted file mode 100644 index 1f40d01..0000000 --- a/src/DBAL/Schema/SchemaException.php +++ /dev/null @@ -1,18 +0,0 @@ -getName())); - } - - public static function modelNotFound(string $modelName): self - { - return new self(sprintf('The model "%s" was not found on the database', $modelName)); - } -} diff --git a/src/DBAL/Schema/Selection.php b/src/DBAL/Schema/Selection.php deleted file mode 100644 index 260fd5b..0000000 --- a/src/DBAL/Schema/Selection.php +++ /dev/null @@ -1,67 +0,0 @@ -addChoice($choice); - } - } - - public function getIds(): array - { - $ids = []; - - foreach ($this->choices as $choice) { - $ids[] = $choice->getId(); - } - - return $ids; - } - - public function getNames(): array - { - $names = []; - - foreach ($this->choices as $choice) { - $names[] = $choice->getName(); - } - - return $names; - } - - public function getValues(): array - { - $values = []; - - foreach ($this->choices as $choice) { - $values[] = $choice->getValue(); - } - - return $values; - } - - public function getChoices(): array - { - return $this->choices; - } - - /** - * @internal - */ - private function addChoice(Choice $choice): void - { - $this->choices[] = $choice; - } -} diff --git a/src/Endpoint.php b/src/Endpoint.php deleted file mode 100644 index 73c7059..0000000 --- a/src/Endpoint.php +++ /dev/null @@ -1,58 +0,0 @@ -url = $url; - $this->client = new XmlRpcClient($url); - } - - /** - * @throws RequestException when request failed - * - * @return mixed - */ - public function call(string $method, array $args = []) - { - try { - return $this->client->call($method, $args); - } catch (XmlRpcRemoteException $exception) { - if (preg_match('#cannot marshal None unless allow_none is enabled#', $exception->getMessage())) { - return null; - } - - throw RemoteException::create($exception); - } catch (Throwable $exception) { - throw new RequestException($exception->getMessage(), $exception->getCode(), $exception); - } - } - - public function getUrl(): string - { - return $this->url; - } - - public function getClient(): XmlRpcClient - { - return $this->client; - } -} diff --git a/src/Enum/OdooRpcMethod.php b/src/Enum/OdooRpcMethod.php new file mode 100644 index 0000000..6099f4e --- /dev/null +++ b/src/Enum/OdooRpcMethod.php @@ -0,0 +1,24 @@ + + * + * @codeCoverageIgnore + */ +enum OdooRpcMethod: string +{ + case Login = 'login'; + case ExecuteKw = 'execute_kw'; + case Version = 'version'; +} diff --git a/src/Enum/OdooRpcService.php b/src/Enum/OdooRpcService.php new file mode 100644 index 0000000..74b0d9f --- /dev/null +++ b/src/Enum/OdooRpcService.php @@ -0,0 +1,23 @@ + + * + * @codeCoverageIgnore + */ +enum OdooRpcService: string +{ + case Common = 'common'; + case Object = 'object'; +} diff --git a/src/Exception/AuthenticationException.php b/src/Exception/AuthenticationException.php index 6727bd0..af91f0a 100644 --- a/src/Exception/AuthenticationException.php +++ b/src/Exception/AuthenticationException.php @@ -1,10 +1,22 @@ + */ class AuthenticationException extends RequestException { - public function __construct(\Throwable $previous = null) + public function __construct(?\Throwable $previous = null) { parent::__construct('Bad credentials', 0, $previous); } diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php index 6a56a89..e035d7b 100644 --- a/src/Exception/ExceptionInterface.php +++ b/src/Exception/ExceptionInterface.php @@ -1,7 +1,21 @@ + * + * @codeCoverageIgnore + */ interface ExceptionInterface extends \Throwable { } diff --git a/src/Exception/MissingConfigParameterException.php b/src/Exception/MissingConfigParameterException.php deleted file mode 100644 index 3ca66d5..0000000 --- a/src/Exception/MissingConfigParameterException.php +++ /dev/null @@ -1,7 +0,0 @@ - + * + * @phpstan-type ErrorArray array{code: int, message: string, data: array{debug: string}} + * @phpstan-type Payload array{error: ErrorArray} + * @phpstan-type RemoteTraceArray array, array{file: string, line: int, method: string, statement: string}> + */ class RemoteException extends RequestException { /** - * @var array + * @var RemoteTraceArray */ - protected $xmlTrace = []; + protected array $remoteTrace = []; - public static function create(XmlRemoteException $remoteException): self + /** + * @param mixed[] $payload + */ + public static function create(array $payload): self { - $errorCode = $remoteException->getCode(); - $errorMessage = $remoteException->getMessage(); + /** @var Payload $payload */ + $errorCode = $payload['error']['code'] ?? 0; + $errorMessage = $payload['error']['message'] ?? 'Unknown error.'; + $remoteTrace = trim($payload['error']['data']['debug']); - if (preg_match('#Traceback \(most recent call last\)#', $errorMessage)) { - $messages = array_filter(explode("\n", $errorMessage)); + if (preg_match('#'.preg_quote('Traceback (most recent call last):').'#', $remoteTrace)) { + $messages = array_filter(explode("\n", $remoteTrace)); foreach ($messages as $key => $message) { $messages[$key] = trim($message); @@ -46,7 +65,7 @@ public static function create(XmlRemoteException $remoteException): self } $exception = new self(implode("\n", $messageParts), $errorCode); - $exception->xmlTrace = array_reverse($trace); + $exception->remoteTrace = array_reverse($trace); return $exception; } @@ -54,8 +73,11 @@ public static function create(XmlRemoteException $remoteException): self return new self($errorMessage, $errorCode); } - public function getXmlTrace(): array + /** + * @return RemoteTraceArray + */ + public function getRemoteTrace(): array { - return $this->xmlTrace; + return $this->remoteTrace; } } diff --git a/src/Exception/RequestException.php b/src/Exception/RequestException.php index 088bc68..204c7dc 100644 --- a/src/Exception/RequestException.php +++ b/src/Exception/RequestException.php @@ -1,7 +1,19 @@ + */ class RequestException extends \RuntimeException implements ExceptionInterface { } diff --git a/src/Exception/TransportException.php b/src/Exception/TransportException.php new file mode 100644 index 0000000..9e8bba3 --- /dev/null +++ b/src/Exception/TransportException.php @@ -0,0 +1,19 @@ + + */ +class TransportException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/src/Metadata/Version.php b/src/Metadata/Version.php new file mode 100644 index 0000000..f24d189 --- /dev/null +++ b/src/Metadata/Version.php @@ -0,0 +1,97 @@ + + */ +readonly class Version +{ + public function __construct( + private int $majorVersion, + private int $minorVersion, + private int $patchVersion, + private string $buildName, + private string $buildIdentifier, + private string $buildVersion, + private int $protocolVersion, + ) { + } + + /** + * Creates the instance from Odoo response payload. + * + * @param mixed[] $payload + */ + public static function create(array $payload): self + { + /** @var int|string $protocolVersion */ + $protocolVersion = $payload['protocol_version']; + /** @var array{int, int, int, string, string, string} $infos */ + $infos = $payload['server_version_info']; + + return new self( + (int) $infos[0], + (int) $infos[1], + (int) $infos[2], + (string) $infos[3], + (string) $infos[4], + (string) $infos[5], + (int) $protocolVersion + ); + } + + public function __toString(): string + { + return $this->getName(); + } + + public function getName(): string + { + return \sprintf('%s.%s.%s+%s', $this->majorVersion, $this->minorVersion, $this->patchVersion, $this->buildName); + } + + public function getMajorVersion(): int + { + return $this->majorVersion; + } + + public function getMinorVersion(): int + { + return $this->minorVersion; + } + + public function getPatchVersion(): int + { + return $this->patchVersion; + } + + public function getBuildName(): string + { + return $this->buildName; + } + + public function getBuildIdentifier(): string + { + return $this->buildIdentifier; + } + + public function getBuildVersion(): string + { + return $this->buildVersion; + } + + public function getProtocolVersion(): int + { + return $this->protocolVersion; + } +} diff --git a/src/Transport/Client/JsonRpcHttpClient.php b/src/Transport/Client/JsonRpcHttpClient.php new file mode 100644 index 0000000..0753f38 --- /dev/null +++ b/src/Transport/Client/JsonRpcHttpClient.php @@ -0,0 +1,39 @@ +doRequest($url, $payload, $timeout); + } + + /** + * Extracted for testing / mocking. + * + * @codeCoverageIgnore + */ + protected function doRequest(string $url, string $payload, int $timeout): string|false + { + $context = stream_context_create([ + 'http' => [ + 'method' => 'POST', + 'timeout' => $timeout, + 'header' => 'Content-Type: application/json', + 'content' => $payload, + ], + ]); + + return file_get_contents($url, false, $context); + } +} diff --git a/src/Transport/Client/JsonRpcHttpClientInterface.php b/src/Transport/Client/JsonRpcHttpClientInterface.php new file mode 100644 index 0000000..8b8ecc1 --- /dev/null +++ b/src/Transport/Client/JsonRpcHttpClientInterface.php @@ -0,0 +1,20 @@ + + * @author Jules Sayer + */ +class JsonRpcTransport implements TransportInterface +{ + public const DEFAULT_ENDPOINT = '/jsonrpc'; + + private JsonRpcHttpClientInterface $httpClient; + + public function __construct( + private readonly Connection $connection, + ?JsonRpcHttpClientInterface $httpClient = null, + private readonly int $timeOut = TransportInterface::DEFAULT_TIMEOUT, + ) { + $this->httpClient = $httpClient ?: new JsonRpcHttpClient(); + } + + /** + * @param mixed[] $arguments + */ + public function request(string $service, string $method, array $arguments = []): mixed + { + $payload = (string) json_encode([ + 'jsonrpc' => '2.0', + 'method' => 'call', + 'params' => [ + 'service' => $service, + 'method' => $method, + 'args' => $arguments, + ], + 'id' => uniqid('odoo_jsonrpc'), + ]); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new TransportException(\sprintf('Failed to encode data to JSON: %s', json_last_error_msg())); + } + + $endpointUrl = $this->connection->getUrl().self::DEFAULT_ENDPOINT; + $response = $this->httpClient->post($endpointUrl, $payload, $this->timeOut); + + if (false === $response) { + throw new TransportException('JSON RPC request failed - Unable to get response.'); + } + + $data = (array) json_decode($response, true); + if (JSON_ERROR_NONE !== json_last_error()) { + throw new TransportException(\sprintf('Failed to decode JSON data: %s', json_last_error_msg())); + } + + if (\is_array($data['error'] ?? null)) { + throw RemoteException::create($data); + } + + return $data['result'] ?? null; + } +} diff --git a/src/Transport/TransportInterface.php b/src/Transport/TransportInterface.php new file mode 100644 index 0000000..1fbcdae --- /dev/null +++ b/src/Transport/TransportInterface.php @@ -0,0 +1,30 @@ +reflector = new Reflector(); - } - - /** - * @throws ReflectionException - */ - protected function createObjectTester(object $object): ObjectTester - { - return new ObjectTester($this, $object); - } -} diff --git a/tests/ClientTest.php b/tests/ClientTest.php new file mode 100644 index 0000000..4da05ad --- /dev/null +++ b/tests/ClientTest.php @@ -0,0 +1,170 @@ +connection = $this->createMock(Connection::class); + $this->transport = $this->createMock(TransportInterface::class); + + $this->connection->method('getDatabase')->willReturn('odoo_db'); + $this->connection->method('getUsername')->willReturn('admin'); + $this->connection->method('getPassword')->willReturn('secret'); + } + + public function testAuthenticateSuccess(): void + { + $this->transport + ->expects(self::once()) + ->method('request') + ->with( + OdooRpcService::Common->value, + OdooRpcMethod::Login->value, + ['odoo_db', 'admin', 'secret'] + ) + ->willReturn(42) + ; + + $client = new Client($this->connection, $this->transport); + $uid = $client->authenticate(); + + self::assertSame(42, $uid); + self::assertSame(42, $client->getUid()); + } + + public function testAuthenticateFailureThrowsAuthenticationException(): void + { + $this->transport + ->method('request') + ->willReturn(null) + ; + + $client = new Client($this->connection, $this->transport); + $this->expectException(AuthenticationException::class); + $client->authenticate(); + } + + public function testRequestWithoutAuthenticationThrowsRequestException(): void + { + $client = new Client($this->connection, $this->transport); + + $this->expectException(RequestException::class); + $this->expectExceptionMessage('You must authenticate before making requests.'); + + $client->request( + OdooRpcService::Object->value, + OdooRpcMethod::ExecuteKw->value, + 'odoo_db', + 1, + 'secret' + ); + } + + public function testExecuteKwAuthenticatesAndExecutesRequest(): void + { + $this->transport + ->expects(self::exactly(2)) + ->method('request') + ->willReturnOnConsecutiveCalls( + 7, // authenticate() + ['result' => true] // execute_kw + ) + ; + + $client = new Client($this->connection, $this->transport); + $result = $client->executeKw( + 'res.partner', + 'search_read', + [['is_company', '=', true]], + ['limit' => 10] + ); + + self::assertSame(['result' => true], $result); + self::assertSame(7, $client->getUid()); + } + + public function testRequestAfterAuthenticationUsesCachedUid(): void + { + $this->transport + ->expects(self::exactly(2)) + ->method('request') + ->willReturnOnConsecutiveCalls( + 5, // authenticate() + 'ok' // request() + ) + ; + + $client = new Client($this->connection, $this->transport); + $client->authenticate(); + $result = $client->request( + OdooRpcService::Object->value, + OdooRpcMethod::ExecuteKw->value, + 'odoo_db', + 5, + 'secret', + 'model', + 'method' + ); + + self::assertSame('ok', $result); + } + + public function testWithTransportReturnsNewInstance(): void + { + $newTransport = $this->createMock(TransportInterface::class); + + $client = new Client($this->connection, $this->transport); + $newClient = $client->withTransport($newTransport); + + self::assertNotSame($client, $newClient); + self::assertSame($newTransport, $newClient->getTransport()); + } + + public function testWithLoggerReturnsNewInstanceAndLoggerIsUsed(): void + { + $logger = $this->createMock(LoggerInterface::class); + $logger->expects(self::once()) + ->method('info') + ->with( + self::stringContains('Odoo request'), + self::arrayHasKey('service') + ) + ; + + $transport = $this->createMock(TransportInterface::class); + $transport->method('request')->willReturn(7); + + $client = new Client($this->connection, $transport, $logger); + $client->authenticate(); + } +} diff --git a/tests/ConnectionTest.php b/tests/ConnectionTest.php new file mode 100644 index 0000000..8f27701 --- /dev/null +++ b/tests/ConnectionTest.php @@ -0,0 +1,127 @@ +getHost()); + self::assertSame('user', $conn->getUsername()); + self::assertSame('pass', $conn->getPassword()); + self::assertSame('db', $conn->getDatabase()); + self::assertSame('http', $conn->getScheme()); + + self::assertStringContainsString('http://user:pass@host/db', (string) $conn); + self::assertStringContainsString('http://host', $conn->getUrl()); + self::assertNotEmpty($conn->getIdentifier()); + } + + public function testCreateFromConfigSuccess(): void + { + $config = [ + 'host' => 'myhost', + 'username' => 'myuser', + 'password' => 'mypass', + 'database' => 'mydb', + 'scheme' => 'https', + ]; + + $conn = Connection::createFromConfig($config); + + self::assertSame('myhost', $conn->getHost()); + self::assertSame('myuser', $conn->getUsername()); + self::assertSame('mypass', $conn->getPassword()); + self::assertSame('mydb', $conn->getDatabase()); + self::assertSame('https', $conn->getScheme()); + } + + public function testCreateFromConfigDefaultsSchemeToHttps(): void + { + $config = [ + 'host' => 'h', + 'username' => 'u', + 'password' => 'p', + 'database' => 'd', + ]; + + $conn = Connection::createFromConfig($config); + + self::assertSame('https', $conn->getScheme()); + } + + public function testCreateFromConfigInvalidConfigThrows(): void + { + $this->expectException(InvalidArgumentException::class); + Connection::createFromConfig([]); + } + + public function testCreateFromConfigUnsupportedSchemeThrows(): void + { + $this->expectException(InvalidArgumentException::class); + + Connection::createFromConfig([ + 'host' => 'h', + 'username' => 'u', + 'password' => 'p', + 'database' => 'd', + 'scheme' => 'ftp', + ]); + } + + public function testCreateFromDsnSuccess(): void + { + $dsn = 'https://user:pass@host/db'; + $conn = Connection::createFromDsn($dsn); + + self::assertSame('host', $conn->getHost()); + self::assertSame('user', $conn->getUsername()); + self::assertSame('pass', $conn->getPassword()); + self::assertSame('db', $conn->getDatabase()); + self::assertSame('https', $conn->getScheme()); + } + + public function testCreateFromDsnInvalidThrows(): void + { + $this->expectException(\InvalidArgumentException::class); + + Connection::createFromDsn('invalid-dsn'); + } + + public function testToStringAndGetUrl(): void + { + $conn = new Connection('host', 'user', 'pass', 'db', 'https'); + + $str = (string) $conn; + self::assertStringContainsString('https://user:pass@host/db', $str); + self::assertSame('https://host', $conn->getUrl()); + } + + public function testGetIdentifierIsStable(): void + { + $conn1 = new Connection('host', 'user', 'pass', 'db'); + $conn2 = new Connection('host', 'user', 'pass', 'db'); + + self::assertSame($conn1->getIdentifier(), $conn2->getIdentifier()); + } +} diff --git a/tests/Expression/AbstractDomainTest.php b/tests/Expression/AbstractDomainTest.php deleted file mode 100644 index 50cffaf..0000000 --- a/tests/Expression/AbstractDomainTest.php +++ /dev/null @@ -1,26 +0,0 @@ -createObjectTester($comparison) - ->assertPropertyAccessorsAndMutators('fieldName', 'bar') - ->assertPropertyAccessorsAndMutators('operator', Comparison::NOT_EQUAL_TO) - ->assertPropertyAccessorsAndMutators('value', 'mixed') - ; - } - - /** - * @covers ::toArray - */ - public function testToArray(): void - { - $comparison = new Comparison('foo', Comparison::EQUAL_TO, 'bar'); - - $this->assertEquals(['foo', '=', 'bar'], $comparison->toArray()); - } -} diff --git a/tests/Expression/CompositeDomainTest.php b/tests/Expression/CompositeDomainTest.php deleted file mode 100644 index 1974cbc..0000000 --- a/tests/Expression/CompositeDomainTest.php +++ /dev/null @@ -1,145 +0,0 @@ -createMock(DomainInterface::class); - - $this - ->createObjectTester($domain) - ->assertPropertyAccessorsAndMutators('operator', CompositeDomain::OR) - ->assertPropertyAccessorsAndMutators('domains', $fakeDomain, [ - 'is_collection' => true, - 'adder' => ['name' => 'add'], - 'remover' => ['name' => 'remove'], - 'hasser' => ['name' => 'has'], - ]) - ; - } - - /** - * Data provider for the test for method ::toArray(). - */ - public function provideToArrayDataSet(): array - { - $domainA = $this->createFakeDomain('A'); - $domainB = $this->createFakeDomain('B'); - $domainC = $this->createFakeDomain('C'); - $domainD = $this->createFakeDomain('D'); - $domainE = $this->createFakeDomain('E'); - $domainF = $this->createFakeDomain('F'); - $domainG = $this->createFakeDomain('G'); - $domainH = $this->createFakeDomain('H'); - - $data = [ - // [ , , ], - [ // 0 - CompositeDomain::AND, [], [], - ], - [ // 1 - CompositeDomain::AND, [$domainA], - $domainA->toArray(), - ], - [ // 2 - CompositeDomain::AND, [$domainA, $domainB], - ['&', $domainA->toArray(), $domainB->toArray()], - ], - [ // 3 - CompositeDomain::AND, [$domainA, $domainB, $domainC], - ['&', $domainA->toArray(), '&', $domainB->toArray(), $domainC->toArray()], - ], - [ // 4 - CompositeDomain::OR, [], [], - ], - [ // 5 - CompositeDomain::OR, [$domainA], - $domainA->toArray(), - ], - [ // 6 - CompositeDomain::OR, [$domainA, $domainB], - ['|', $domainA->toArray(), $domainB->toArray()], - ], - [ // 7 - CompositeDomain::OR, [$domainA, $domainB, $domainC], - ['|', $domainA->toArray(), '|', $domainB->toArray(), $domainC->toArray()], - ], - [ // 8 - CompositeDomain::NOT, [], [], - ], - [ // 9 - CompositeDomain::NOT, [$domainA], - ['!', $domainA->toArray()], - ], - [ // 10 - CompositeDomain::NOT, [$domainA, $domainB], - ['!', '&', $domainA->toArray(), $domainB->toArray()], - ], - [ // 11 - CompositeDomain::NOT, [$domainA, $domainB, $domainC], - ['!', '&', $domainA->toArray(), '&', $domainB->toArray(), $domainC->toArray()], - ], - ]; - - /** - * @see https://www.odoo.com/fr_FR/forum/aide-1/question/domain-notation-using-multiple-and-nested-and-2170 - */ - $orXA = new CompositeDomain(CompositeDomain::OR, [$domainA, $domainB]); - $orXB = new CompositeDomain(CompositeDomain::OR, [$domainC, $domainD, $domainE]); - $expectedResult = ['&', '|', ['A'], ['B'], '|', ['C'], '|', ['D'], ['E']]; - $data[] = [CompositeDomain::AND, [$orXA, $orXB], $expectedResult]; - - // #13 Final test - $orXA = new CompositeDomain(CompositeDomain::OR, [$domainA, $domainB]); - $orXB = new CompositeDomain(CompositeDomain::OR, [$domainC, $domainD, $domainE]); - $orXC = new CompositeDomain(CompositeDomain::OR, [$domainF, $domainG, $domainH]); - $expectedResult = ['&', '|', ['A'], ['B'], '&', '|', ['C'], '|', ['D'], ['E'], '|', ['F'], '|', ['G'], ['H']]; - $data[] = [CompositeDomain::AND, [$orXA, $orXB, $orXC], $expectedResult]; - - return $data; - } - - /** - * @covers ::toArray - * @dataProvider provideToArrayDataSet - * - * @param mixed $expectedResult - */ - public function testToArray(string $operator, array $domains = [], $expectedResult = null, string $message = ''): void - { - $domain = new CompositeDomain($operator, $domains); - $this->assertEquals($expectedResult, $domain->toArray(), $message); - } - - /** - * @param mixed $expression - */ - protected function createFakeDomain($expression): DomainInterface - { - $fakeDomain = $this->createMock(DomainInterface::class); - $fakeDomain - ->method('toArray') - ->willReturn(is_array($expression) ? $expression : (array) $expression); - - return $fakeDomain; - } -} diff --git a/tests/Metadata/VersionTest.php b/tests/Metadata/VersionTest.php new file mode 100644 index 0000000..2f05905 --- /dev/null +++ b/tests/Metadata/VersionTest.php @@ -0,0 +1,78 @@ +getMajorVersion()); + self::assertSame(2, $version->getMinorVersion()); + self::assertSame(3, $version->getPatchVersion()); + self::assertSame('nightly', $version->getBuildName()); + self::assertSame('abc123', $version->getBuildIdentifier()); + self::assertSame('15.2.3+nightly', $version->getBuildVersion()); + self::assertSame(2, $version->getProtocolVersion()); + } + + public function testCreateFromPayload(): void + { + $payload = [ + 'protocol_version' => '3', + 'server_version_info' => [16, 1, 4, 'rc', 'xyz789', '16.1.4+rc'], + ]; + + $version = Version::create($payload); + + self::assertSame(16, $version->getMajorVersion()); + self::assertSame(1, $version->getMinorVersion()); + self::assertSame(4, $version->getPatchVersion()); + self::assertSame('rc', $version->getBuildName()); + self::assertSame('xyz789', $version->getBuildIdentifier()); + self::assertSame('16.1.4+rc', $version->getBuildVersion()); + self::assertSame(3, $version->getProtocolVersion()); + } + + public function testToStringReturnsName(): void + { + $version = new Version( + majorVersion: 15, + minorVersion: 2, + patchVersion: 3, + buildName: 'nightly', + buildIdentifier: 'abc123', + buildVersion: '15.2.3+nightly', + protocolVersion: 2 + ); + + self::assertSame('15.2.3+nightly', $version->__toString()); + self::assertSame('15.2.3+nightly', $version->getName()); + } +} diff --git a/tests/Resources/jsonrpc_endpoints/json_error/jsonrpc b/tests/Resources/jsonrpc_endpoints/json_error/jsonrpc new file mode 100644 index 0000000..4b34730 --- /dev/null +++ b/tests/Resources/jsonrpc_endpoints/json_error/jsonrpc @@ -0,0 +1,3 @@ +{ + "result": "{"error":"true"}" +} \ No newline at end of file diff --git a/tests/Resources/jsonrpc_endpoints/remote_error/jsonrpc b/tests/Resources/jsonrpc_endpoints/remote_error/jsonrpc new file mode 100644 index 0000000..a39652f --- /dev/null +++ b/tests/Resources/jsonrpc_endpoints/remote_error/jsonrpc @@ -0,0 +1,9 @@ +{ + "error": { + "code": 123, + "message": "Odoo error", + "data": { + "debug": "foo" + } + } +} diff --git a/tests/Resources/jsonrpc_endpoints/success/jsonrpc b/tests/Resources/jsonrpc_endpoints/success/jsonrpc new file mode 100644 index 0000000..ae002c1 --- /dev/null +++ b/tests/Resources/jsonrpc_endpoints/success/jsonrpc @@ -0,0 +1,5 @@ +{ + "result": { + "success":"true" + } +} \ No newline at end of file diff --git a/tests/Transport/Client/JsonRpcHttpClientTest.php b/tests/Transport/Client/JsonRpcHttpClientTest.php new file mode 100644 index 0000000..61b9e5c --- /dev/null +++ b/tests/Transport/Client/JsonRpcHttpClientTest.php @@ -0,0 +1,45 @@ +getMockBuilder(JsonRpcHttpClient::class) + ->onlyMethods(['doRequest']) + ->getMock() + ; + + $url = 'https://example.com/jsonrpc'; + $payload = '{"jsonrpc":"2.0","method":"ping"}'; + $timeout = 5; + + $client->expects(self::once()) + ->method('doRequest') + ->with($url, $payload, $timeout) + ->willReturn('{"result":"pong"}') + ; + + $result = $client->post($url, $payload, $timeout); + + self::assertSame('{"result":"pong"}', $result); + } +} diff --git a/tests/Transport/JsonRpcTransportTest.php b/tests/Transport/JsonRpcTransportTest.php new file mode 100644 index 0000000..47d178b --- /dev/null +++ b/tests/Transport/JsonRpcTransportTest.php @@ -0,0 +1,95 @@ +connection = $this->createMock(Connection::class); + $this->connection->method('getUrl')->willReturn($this->baseUrl); + } + + public function testRequestReturnsResult(): void + { + $httpClient = $this->createMock(JsonRpcHttpClientInterface::class); + $httpClient->method('post') + ->willReturn(json_encode(['result' => 'success'])) + ; + + $transport = new JsonRpcTransport($this->connection, $httpClient); + + $result = $transport->request('service', 'method'); + self::assertSame('success', $result); + } + + public function testRequestThrowsTransportExceptionOnFalse(): void + { + $httpClient = $this->createMock(JsonRpcHttpClientInterface::class); + $httpClient->method('post')->willReturn(false); + + $transport = new JsonRpcTransport($this->connection, $httpClient); + + $this->expectException(TransportException::class); + $transport->request('service', 'method'); + } + + public function testRequestThrowsRemoteExceptionOnError(): void + { + $httpClient = $this->createMock(JsonRpcHttpClientInterface::class); + $httpClient->method('post') + ->willReturn(json_encode([ + 'error' => [ + 'code' => 123, + 'message' => 'Fail', + 'data' => [ + 'debug' => 'Traceback (most recent call last):'."\n". + 'File "/app/test.py", line 10, in '."\n". + 'print("Fail")', + ], + ], + ])) + ; + + $transport = new JsonRpcTransport($this->connection, $httpClient); + + $this->expectException(RemoteException::class); + $transport->request('service', 'method'); + } + + public function testRequestThrowsTransportExceptionOnInvalidJson(): void + { + $httpClient = $this->createMock(JsonRpcHttpClientInterface::class); + $httpClient->method('post')->willReturn('{"invalidJson":'); // JSON invalide + + $transport = new JsonRpcTransport($this->connection, $httpClient); + + $this->expectException(TransportException::class); + $transport->request('service', 'method'); + } +} diff --git a/tests/Utils/Debugger.php b/tests/Utils/Debugger.php deleted file mode 100644 index 92de05f..0000000 --- a/tests/Utils/Debugger.php +++ /dev/null @@ -1,26 +0,0 @@ - null, - self::VALUE => null, - self::RESULT => null, - self::MESSAGE => null, - self::IS_FLUENT => false, - self::IS_COLLECTION => false, - ]; - - /** - * @throws ReflectionException - */ - public function __construct(TestCase $testCase, object $object, array $defaultContext = []) - { - parent::__construct($testCase); - - $this->reflector = new Reflector(); - $this->debugger = new Debugger(); - $this->setObject($object); - $this->defaultContext = array_merge($this->defaultContext, $defaultContext); - } - - /** - * Test the accessors of a property (setter and getter). - * This test also checks if the return value of the getter is equal to the value registered with the setter. - * - * @param mixed $value - */ - public function assertPropertyAccessorsAndMutators(string $propertyName, $value, array $context = []): self - { - $context = $this->getContext($context); - $context[self::VALUE] = $value; - $context[self::MESSAGE] = sprintf('Asserting accessors and mutators for property %s::$%s', $this->class->getShortName(), $propertyName); - - if ($context[self::IS_COLLECTION]) { - $adderContext = $this->getContext($context['adder'] ?? [], $context); - $adder = $this->assertAdder($propertyName, $value, $adderContext); - - $hasserContext = $this->getContext($context['hasser'] ?? [], $context); - $hasser = $this->assertHasser($propertyName, $value, $hasserContext); - - $removerContext = $this->getContext($context['remover'] ?? [], $context); - $remover = $this->assertRemover($propertyName, $value, $removerContext); - } - - $setterContext = $this->getContext($context['setter'] ?? [], $context); - $setter = $this->assertSetter($propertyName, $value, $setterContext); - - $getterContext = $this->getContext($context['getter'] ?? [], $context); - $getter = $this->assertGetter($propertyName, $getterContext); - - return $this; - } - - /** - * @param mixed $value - */ - public function assertHasser(string $propertyName, $value, array $context = []): ?ReflectionMethod - { - $context[self::VALUE] = $value; - $context[self::IS_FLUENT] = false; - $context[self::MESSAGE] = sprintf('Asserting hasser for property %s::$%s', - $this->class->getShortName(), - $propertyName - ); - - return $this->assertPropertyMethod($propertyName, self::ACCESSOR_HAS, $context); - } - - public function assertGetter(string $propertyName, array $context = []): ?ReflectionMethod - { - $context[self::IS_FLUENT] = $context[self::IS_FLUENT] ?? false; - $context[self::MESSAGE] = sprintf('Asserting getter for property %s::$%s', - $this->class->getShortName(), - $propertyName - ); - - return $this->assertPropertyMethod($propertyName, self::ACCESSOR_GET, $context); - } - - /** - * @param mixed $value - */ - public function assertAdder(string $propertyName, $value, array $context = []): ?ReflectionMethod - { - $context[self::VALUE] = $value; - $context[self::IS_FLUENT] = $context[self::IS_FLUENT] ?? true; - $context[self::MESSAGE] = sprintf('Asserting adder for property %s::$%s', - $this->class->getShortName(), - $propertyName - ); - - return $this->assertPropertyMethod($propertyName, self::MUTATOR_ADD, $context); - } - - /** - * @param mixed $value - */ - public function assertRemover(string $propertyName, $value, array $context = []): ?ReflectionMethod - { - $context[self::VALUE] = $value; - $context[self::IS_FLUENT] = $context[self::IS_FLUENT] ?? true; - $context[self::MESSAGE] = sprintf('Asserting remover for property %s::$%s', - $this->class->getShortName(), - $propertyName - ); - - return $this->assertPropertyMethod($propertyName, self::MUTATOR_REMOVE, $context); - } - - /** - * Test and return the setter of a property. - * - * @param mixed $value - */ - public function assertSetter(string $propertyName, $value = null, array $context = []): ?ReflectionMethod - { - $context[self::VALUE] = $value; - $context[self::IS_FLUENT] = $context[self::IS_FLUENT] ?? true; - $context[self::MESSAGE] = sprintf('Asserting setter for property %s::$%s', - $this->class->getShortName(), - $propertyName - ); - - return $this->assertPropertyMethod($propertyName, self::MUTATOR_SET, $context); - } - - /** - * Test and return the specific method of a property. - */ - public function assertPropertyMethod(string $propertyName, string $prefix, array $context = []): ?ReflectionMethod - { - $context = $this->getContext($context); - - try { - $property = $this->reflector->getProperty($this->class, $propertyName); - } catch (ReflectionException $e) { - $this->testCase::fail($this->getContextErrorMessage('The property was not found', $context)); - - return null; - } - - $context[self::NAME] = (string) ($context[self::NAME] ?? null); - $methodName = $context[self::NAME] ?: $this->getPropertyMethodName($property->getName(), $prefix); - $class = $property->getDeclaringClass(); - $tested = []; - - if (!$context[self::NAME] && $context[self::IS_COLLECTION]) { - $methodNames = $this->getSingularNames($property->getName(), $prefix); - - foreach ($methodNames as $value) { - if ($class->hasMethod($value)) { - $methodName = $value; - break; - } - - $tested[] = $value; - } - } else { - $tested[] = $methodName; - } - - try { - $method = $class->getMethod($methodName); - } catch (ReflectionException $e) { - $errorMessage = sprintf('None of methods "%s()" was found', implode('"(), "', $tested)); - $this->testCase::fail($this->getContextErrorMessage($errorMessage, $context)); - - return null; - } - - $this->testCase->addToAssertionCount(1); - - $args = self::MUTATOR_SET === $prefix && $context[self::IS_COLLECTION] ? [$context[self::VALUE]] : $context[self::VALUE]; - $args = self::ACCESSOR_GET === $prefix ? [] : [$args]; - $result = $method->invokeArgs($this->object, $args); - - if ((bool) ($context[self::IS_FLUENT] ?? false)) { - $this->testCase::assertEquals($this->object, $result, $this->getContextErrorMessage(sprintf( - 'The method is fluent and should return the object instance' - ), $context)); - - return $method; - } - - $propertyValue = $property->getValue($this->object); - - switch ($prefix) { - case self::ACCESSOR_GET: - $this->testCase::assertEquals($result, $propertyValue); - break; - - case self::MUTATOR_SET: - $expectedValue = $context[self::IS_COLLECTION] ? [$context[self::VALUE]] : $context[self::VALUE]; - $this->testCase::assertEquals($expectedValue, $propertyValue, $this->getContextErrorMessage( - 'The property value is not equal to the value set', - $context - )); - break; - - case self::MUTATOR_ADD: - case self::MUTATOR_REMOVE: - case self::ACCESSOR_HAS: - if (!is_iterable($propertyValue)) { - $this->testCase::fail($this->getContextErrorMessage(sprintf( - 'The property collection should be iterable, %s declared', - $this->debugger->debugType($propertyValue) - ), $context)); - - return null; - } - - $hasValue = false; - - foreach ($propertyValue as $value) { - if ($value === $context[self::VALUE]) { - $hasValue = true; - break; - } - } - - switch ($prefix) { - case self::ACCESSOR_HAS: - $this->testCase::assertEquals($result, $hasValue, $this->getContextErrorMessage( - sprintf('The property collection %s the value but the hasser returns %s', - $hasValue ? 'contains' : 'does not contain', - $this->debugger->debugBool($result) - ), - $context - )); - break; - case self::MUTATOR_ADD: - $this->testCase::assertTrue($hasValue, $this->getContextErrorMessage( - 'The adder was called but the property collection does not contain the added value', - $context - )); - - return $method; - - case self::MUTATOR_REMOVE: - $this->testCase::assertFalse($hasValue, $this->getContextErrorMessage( - 'The property collection still contains the removed value', - $context - )); - - return $method; - } - break; - } - - return $method; - } - - public function getContextErrorMessage(string $message, array $context = []): string - { - $context = $this->getContext($context); - - return sprintf('%s%s', $context[self::MESSAGE] ? sprintf('[%s] ', $context[self::MESSAGE]) : '', $message); - } - - /** - * @return string[] - */ - public function getSingularNames(string $name, string $prefix = null): array - { - $names = (array) Inflector::singularize($name); - - if ($prefix) { - foreach ($names as $key => $value) { - $names[$key] = $this->getPropertyMethodName($value, $prefix); - } - } - - return $names; - } - - public function getPropertyMethodName(string $propertyName, string $prefix = ''): string - { - $propertyName = preg_replace('#([^A-Za-z]+)#', '', $propertyName); - - return sprintf('%s%s', $prefix, $prefix ? ucfirst($propertyName) : $propertyName); - } - - public function getObject(): object - { - return $this->object; - } - - /** - * @throws ReflectionException - */ - public function setObject(object $object): self - { - $this->object = $object; - $this->class = $this->reflector->getClass($object); - - return $this; - } - - public function getClass(): ReflectionClass - { - return $this->class; - } - - public function getReflector(): Reflector - { - return $this->reflector; - } - - public function setReflector(Reflector $reflector): self - { - $this->reflector = $reflector; - - return $this; - } - - public function getDebugger(): Debugger - { - return $this->debugger; - } - - public function setDebugger(Debugger $debugger): self - { - $this->debugger = $debugger; - - return $this; - } - - public function getDefaultContext(): array - { - return $this->defaultContext; - } - - public function setDefaultContext(array $defaultContext): self - { - $this->defaultContext = $defaultContext; - - return $this; - } - - public function getContext(array $context = [], array $defaultContext = []): array - { - return array_merge($defaultContext ?: $this->defaultContext, $context); - } -} diff --git a/tests/Utils/Reflector.php b/tests/Utils/Reflector.php deleted file mode 100644 index 0617bd7..0000000 --- a/tests/Utils/Reflector.php +++ /dev/null @@ -1,86 +0,0 @@ -getProperty($object, $propertyName)->getValue($object); - } - - /** - * @param mixed $value - * - * @throws ReflectionException - */ - public function setObjectValue(object $object, string $propertyName, $value): ReflectionProperty - { - $property = $this->getProperty($object, $propertyName); - - $property->setValue($object, $value); - - return $property; - } - - /** - * @param object|string $objectOrClass - * - * @throws ReflectionException - */ - public function getMethod($objectOrClass, string $methodName, bool $setAccessible = true): ?ReflectionMethod - { - $class = $this->getClass($objectOrClass); - $method = $class->getMethod($methodName); - - if ($setAccessible) { - $method->setAccessible(true); - } - - return $method; - } - - /** - * @param object|string $objectOrClass - * - * @throws ReflectionException - */ - public function getProperty($objectOrClass, string $propertyName, bool $setAccessible = true): ?ReflectionProperty - { - $class = $this->getClass($objectOrClass); - $property = $class->getProperty($propertyName); - - if ($setAccessible) { - $property->setAccessible(true); - } - - return $property; - } - - /** - * @param object|string $objectOrClass - * - * @throws ReflectionException - */ - public function getClass($objectOrClass): ReflectionClass - { - if (is_string($objectOrClass)) { - /* @var class-string $class */ - $class = $objectOrClass; - - return new ReflectionClass($class); - } - - return $objectOrClass instanceof ReflectionClass ? $objectOrClass : new ReflectionClass($objectOrClass); - } -} diff --git a/tests/Utils/TestDecorator.php b/tests/Utils/TestDecorator.php deleted file mode 100644 index f7d8ace..0000000 --- a/tests/Utils/TestDecorator.php +++ /dev/null @@ -1,33 +0,0 @@ -testCase = $testCase; - } - - public function getTestCase(): TestCase - { - return $this->testCase; - } - - public function setTestCase(TestCase $testCase): self - { - $this->testCase = $testCase; - - return $this; - } -}