diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..cffc922 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake . --impure diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..f129799 --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,31 @@ +name: PHPStan + +on: + push: + branches: + - main + - github-actions + paths: + - '**.php' + - 'phpstan.neon.dist' + - '.github/workflows/phpstan.yml' + +jobs: + phpstan: + name: phpstan + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + coverage: none + + - name: Install composer dependencies + uses: ramsey/composer-install@v3 + + - name: Run PHPStan + run: ./vendor/bin/phpstan --error-format=github diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..f918ca8 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,69 @@ +name: run-tests + +on: + push: + branches: + - main + - github-actions +jobs: + test: + runs-on: ubuntu-22.04 + env: + BROADCAST_DRIVER: log + CACHE_DRIVER: redis + QUEUE_CONNECTION: redis + SESSION_DRIVER: redis + DB_CONNECTION: testing + APP_KEY: base64:2fl+Ktvkfl+Fuz3Qp/A76G2RTiGVA/ZjKZaz6fiiM10= + APP_ENV: testing + BCRYPT_ROUNDS: 10 + MAIL_MAILER: array + TELESCOPE_ENABLED: false + + # Docs: https://docs.github.com/en/actions/using-containerized-services + services: + elasticsearch: + image: elasticsearch:7.17.3 + env: + discovery.type: single-node + ES_JAVA_OPTS: '-Xms512m -Xmx512m' + ports: + - 9200:9200 + options: --health-cmd="curl http://localhost:9200/_cluster/health" --health-interval=10s --health-timeout=5s --health-retries=5 + + redis: + image: redis + ports: + - 6379/tcp + options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 + + steps: + - name: Checkout 🛎 + uses: actions/checkout@v4 + + - name: Verify Elasticsearch connection 🧩 + run: | + curl -X GET "localhost:${{ job.services.elasticsearch.ports['9200'] }}/_cluster/health?pretty=true" + + - name: Setup PHP 🏗 + uses: shivammathur/setup-php@v2 + with: + php-version: 8.2 + tools: composer:v2 + coverage: xdebug + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo, mysql + + - name: Install Project Dependencies 💻 + run: | + composer install --no-interaction --prefer-dist --optimize-autoloader + + - name: List Installed Dependencies 📦 + run: composer show -D + + - name: Run tests 🧪 + run: | + ./vendor/bin/pest --version + ./vendor/bin/pest + env: + REDIS_PORT: ${{ job.services.redis.ports['6379'] }} + ELASTICSEARCH_PORT: ${{ job.services.elasticsearch.ports['9200'] }} diff --git a/.gitignore b/.gitignore index 262d48a..1b120b0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,30 @@ -/vendor -/.phpunit.cache +.env +.env.* + +#composer +vendor + +# Snow-blower +!secrets/.env* +.sb* +secrets.nix + +# pre-commit +.pre-commit-config.yaml + +# direnv +.direnv + +.phpunit* + + +.idea +.phpunit.cache +build +composer.lock +coverage +docs +phpunit.xml +phpstan.neon +testbench.yaml +node_modules \ No newline at end of file diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php new file mode 100644 index 0000000..ff37e37 --- /dev/null +++ b/.php-cs-fixer.php @@ -0,0 +1,239 @@ + true, + 'array_syntax' => ['syntax' => 'short'], + 'binary_operator_spaces' => [ + 'default' => 'single_space', + ], + 'blank_line_after_namespace' => true, + 'blank_line_after_opening_tag' => true, + 'blank_line_before_statement' => [ + 'statements' => [ + 'continue', + 'return', + ], + ], + 'blank_line_between_import_groups' => true, + 'blank_lines_before_namespace' => true, + 'braces_position' => [ + 'control_structures_opening_brace' => 'same_line', + 'functions_opening_brace' => 'next_line_unless_newline_at_signature_end', + 'anonymous_functions_opening_brace' => 'same_line', + 'classes_opening_brace' => 'next_line_unless_newline_at_signature_end', + 'anonymous_classes_opening_brace' => 'next_line_unless_newline_at_signature_end', + 'allow_single_line_empty_anonymous_classes' => false, + 'allow_single_line_anonymous_functions' => false, + ], + 'cast_spaces' => true, + 'class_attributes_separation' => [ + 'elements' => [ + 'const' => 'one', + 'method' => 'one', + 'property' => 'one', + 'trait_import' => 'none', + ], + ], + 'class_definition' => [ + 'multi_line_extends_each_single_line' => true, + 'single_item_single_line' => true, + 'single_line' => true, + ], + 'class_reference_name_casing' => true, + 'clean_namespace' => true, + 'compact_nullable_type_declaration' => true, + 'concat_space' => [ + 'spacing' => 'none', + ], + 'constant_case' => ['case' => 'lower'], + 'control_structure_braces' => true, + 'control_structure_continuation_position' => [ + 'position' => 'same_line', + ], + 'declare_equal_normalize' => true, + 'declare_parentheses' => true, + 'elseif' => true, + 'encoding' => true, + 'full_opening_tag' => true, + 'fully_qualified_strict_types' => false, + 'function_declaration' => true, + 'general_phpdoc_tag_rename' => true, + 'heredoc_to_nowdoc' => true, + 'include' => true, + 'increment_style' => ['style' => 'post'], + 'indentation_type' => true, + 'integer_literal_case' => true, + 'lambda_not_used_import' => true, + 'line_ending' => true, + 'linebreak_after_opening_tag' => true, + 'list_syntax' => true, + 'lowercase_cast' => true, + 'lowercase_keywords' => true, + 'lowercase_static_reference' => true, + 'magic_constant_casing' => true, + 'magic_method_casing' => true, + 'method_argument_space' => [ + 'on_multiline' => 'ignore', + ], + 'method_chaining_indentation' => true, + 'multiline_whitespace_before_semicolons' => [ + 'strategy' => 'no_multi_line', + ], + 'native_function_casing' => true, + 'native_type_declaration_casing' => true, + 'no_alias_functions' => true, + 'no_alias_language_construct_call' => true, + 'no_alternative_syntax' => true, + 'no_binary_string' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_closing_tag' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => [ + 'tokens' => [ + 'extra', + 'throw', + 'use', + ], + ], + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => [ + 'use' => 'echo', + ], + 'no_multiline_whitespace_around_double_arrow' => true, + 'no_multiple_statements_per_line' => true, + 'no_short_bool_cast' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_space_around_double_colon' => true, + 'no_spaces_after_function_name' => true, + 'no_spaces_around_offset' => [ + 'positions' => ['inside', 'outside'], + ], + 'no_superfluous_phpdoc_tags' => [ + 'allow_mixed' => true, + 'allow_unused_params' => true, + ], + 'no_trailing_comma_in_singleline' => true, + 'no_trailing_whitespace' => true, + 'no_trailing_whitespace_in_comment' => true, + 'no_unneeded_control_parentheses' => [ + 'statements' => ['break', 'clone', 'continue', 'echo_print', 'return', 'switch_case', 'yield'], + ], + 'no_unneeded_braces' => true, + 'no_unreachable_default_argument_value' => true, + 'no_unset_cast' => true, + 'no_unused_imports' => true, + 'no_useless_return' => true, + 'no_whitespace_before_comma_in_array' => true, + 'no_whitespace_in_blank_line' => true, + 'normalize_index_brace' => true, + 'not_operator_with_successor_space' => true, + 'nullable_type_declaration' => true, + 'nullable_type_declaration_for_default_null_value' => true, + 'object_operator_without_whitespace' => true, + 'ordered_imports' => ['sort_algorithm' => 'alpha', 'imports_order' => ['const', 'class', 'function']], + 'ordered_interfaces' => true, + 'ordered_traits' => true, + 'phpdoc_align' => [ + 'align' => 'left', + 'spacing' => [ + 'param' => 2, + ], + ], + 'phpdoc_indent' => true, + 'phpdoc_inline_tag_normalizer' => true, + 'phpdoc_no_access' => true, + 'phpdoc_no_package' => true, + 'phpdoc_no_useless_inheritdoc' => true, + 'phpdoc_order' => [ + 'order' => ['param', 'return', 'throws'], + ], + 'phpdoc_scalar' => true, + 'phpdoc_separation' => [ + 'groups' => [ + ['deprecated', 'link', 'see', 'since'], + ['author', 'copyright', 'license'], + ['category', 'package', 'subpackage'], + ['property', 'property-read', 'property-write'], + ['param', 'return'], + ], + ], + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_summary' => false, + 'phpdoc_tag_type' => [ + 'tags' => [ + 'inheritdoc' => 'inline', + ], + ], + 'phpdoc_to_comment' => false, + 'phpdoc_trim' => true, + 'phpdoc_types' => true, + 'phpdoc_var_without_name' => true, + 'psr_autoloading' => false, + 'return_type_declaration' => ['space_before' => 'none'], + 'self_accessor' => false, + 'self_static_accessor' => true, + 'short_scalar_cast' => true, + 'simplified_null_return' => false, + 'single_blank_line_at_eof' => true, + 'single_class_element_per_statement' => [ + 'elements' => ['const', 'property'], + ], + 'single_import_per_statement' => true, + 'single_line_after_imports' => true, + 'single_line_comment_style' => [ + 'comment_types' => ['hash'], + ], + 'single_line_empty_body' => true, + 'single_quote' => true, + 'single_space_around_construct' => true, + 'space_after_semicolon' => true, + 'spaces_inside_parentheses' => true, + 'standardize_not_equals' => true, + 'statement_indentation' => true, + 'switch_case_semicolon_to_colon' => true, + 'switch_case_space' => true, + 'ternary_operator_spaces' => true, + 'trailing_comma_in_multiline' => ['elements' => ['arrays']], + 'trim_array_spaces' => true, + 'type_declaration_spaces' => true, + 'types_spaces' => true, + 'unary_operator_spaces' => true, + 'visibility_required' => [ + 'elements' => ['method', 'property'], + ], + 'whitespace_after_comma_in_array' => true, + 'yoda_style' => [ + 'always_move_variable' => false, + 'equal' => false, + 'identical' => false, + 'less_and_greater' => false, + ], + 'declare_strict_types' => true, + 'explicit_string_variable' => true, + ]; + + $finder = Finder::create() + ->in([ + __DIR__.'/src', + __DIR__.'/tests', + ]) + ->name('*.php') + ->notName('*.blade.php') + ->ignoreDotFiles(true) + ->ignoreVCS(true); + + $config = new Config(); + + return $config->setFinder($finder) + ->setRules($rules) + ->setRiskyAllowed(true) + ->setUsingCache(true); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9a4b252 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,37 @@ +# Contribution Guide + +## Setting up the project + +### Nix Setup (With Flakes) + +Pull down this project locally. + +when you enter the directory if you have direnv installed run: + +```sh +direnv allow +``` + +If you do not run + +```sh +nix develop --impure +``` + +this will pull down all dependencies, install hooks and formatters etc. If done correctly you should see a screen like this: + +![img.png](assets/contributeing-install-success.png) + +### Development + +During development, you will need to start a local elasticsearch instance. To start elasticsearch run: + +```sh +just up +``` + +This will start process-compose and an elastic search instance. + +![img.png](assets/contributing-just-up.png) + +You can now open a new terminal window and run tests using pest. For conviance purposes the nix shell has the command `p` linked to run `./vendor/bin/pest` and `pf` linked to run `./vendor/bin/pest --filter "$@"`. diff --git a/composer.json b/composer.json index 4e186ba..025de5e 100644 --- a/composer.json +++ b/composer.json @@ -24,22 +24,42 @@ "illuminate/container": "^10.0|^11.0", "illuminate/database": "^10.0|^11.0", "illuminate/events": "^10.0|^11.0", - "elasticsearch/elasticsearch": "^8.12" + "elasticsearch/elasticsearch": "^8.15" }, "require-dev": { - "phpunit/phpunit": "^10.3", - "orchestra/testbench": "^8.0", + "orchestra/testbench": "^9.0.0||^8.22.0", "mockery/mockery": "^1.4.4", - "doctrine/coding-standard": "12.0.x-dev" + "doctrine/coding-standard": "12.0.x-dev", + "pestphp/pest": "^2.34", + "pestphp/pest-plugin-laravel": "^2.4", + "laravel/pint": "^1.14", + "nunomaduro/collision": "^8.1.1||^7.10.0", + "larastan/larastan": "^2.9", + "pestphp/pest-plugin-arch": "^2.7", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpstan/phpstan-phpunit": "^1.3" }, "autoload-dev": { "psr-4": { - "PDPhilip\\Elasticsearch\\Tests\\": "tests/" + "PDPhilip\\Elasticsearch\\Tests\\": "tests/", + "Workbench\\App\\": "workbench/app/", + "Workbench\\Database\\Factories\\": "workbench/database/factories/", + "Workbench\\Database\\Seeders\\": "workbench/database/seeders/" } }, "autoload": { "psr-4": { - "PDPhilip\\Elasticsearch\\": "src/" + "PDPhilip\\Elasticsearch\\": "src/", + "PDPhilip\\Elasticsearch\\Tests\\": "tests" + } + }, + "config": { + "allow-plugins": { + "pestphp/pest-plugin": true, + "dealerdirect/phpcodesniffer-composer-installer": true, + "php-http/discovery": true, + "phpstan/extension-installer": true } }, "extra": { @@ -48,5 +68,25 @@ "PDPhilip\\Elasticsearch\\ElasticServiceProvider" ] } + }, + "scripts": { + "post-autoload-dump": [ + "@clear", + "@prepare" + ], + "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi", + "prepare": "@php vendor/bin/testbench package:discover --ansi", + "build": "@php vendor/bin/testbench workbench:build --ansi", + "serve": [ + "Composer\\Config::disableProcessTimeout", + "@build", + "@php vendor/bin/testbench serve" + ], + "lint": [ + "@php vendor/bin/phpstan analyse" + ], + "test": [ + "@php vendor/bin/pest" + ] } -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..a559927 --- /dev/null +++ b/composer.lock @@ -0,0 +1,11144 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "0eaadb4bf1ba861097f30c8a88d88bfe", + "packages": [ + { + "name": "brick/math", + "version": "0.12.1", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "5.16.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.12.1" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2023-11-29T23:19:16+00:00" + }, + { + "name": "carbonphp/carbon-doctrine-types", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/dbal": "<4.0.0 || >=5.0.0" + }, + "require-dev": { + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2024-02-09T16:56:22+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" + }, + "time": "2024-07-08T12:26:09+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.0.10", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^11.0", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^8.5 || ^9.5", + "vimeo/psalm": "^4.25 || ^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "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" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.0.10" + }, + "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%2Finflector", + "type": "tidelift" + } + ], + "time": "2024-02-18T20:23:39+00:00" + }, + { + "name": "doctrine/lexer", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@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", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/3.0.1" + }, + "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" + } + ], + "time": "2024-02-05T11:56:58+00:00" + }, + { + "name": "dragonmantank/cron-expression", + "version": "v3.3.3", + "source": { + "type": "git", + "url": "https://github.com/dragonmantank/cron-expression.git", + "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", + "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "webmozart/assert": "^1.0" + }, + "replace": { + "mtdowling/cron-expression": "^1.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-webmozart-assert": "^1.0", + "phpunit/phpunit": "^7.0|^8.0|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Tankersley", + "email": "chris@ctankersley.com", + "homepage": "https://github.com/dragonmantank" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ], + "support": { + "issues": "https://github.com/dragonmantank/cron-expression/issues", + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.3" + }, + "funding": [ + { + "url": "https://github.com/dragonmantank", + "type": "github" + } + ], + "time": "2023-08-10T19:36:49+00:00" + }, + { + "name": "egulias/email-validator", + "version": "4.0.2", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ebaaf5be6c0286928352e054f2d5125608e5405e", + "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" + }, + "require-dev": { + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/4.0.2" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2023-10-06T06:47:41+00:00" + }, + { + "name": "elastic/transport", + "version": "v8.10.0", + "source": { + "type": "git", + "url": "https://github.com/elastic/elastic-transport-php.git", + "reference": "8be37d679637545e50b1cea9f8ee903888783021" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/elastic/elastic-transport-php/zipball/8be37d679637545e50b1cea9f8ee903888783021", + "reference": "8be37d679637545e50b1cea9f8ee903888783021", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0", + "open-telemetry/api": "^1.0", + "php": "^7.4 || ^8.0", + "php-http/discovery": "^1.14", + "php-http/httplug": "^2.3", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "nyholm/psr7": "^1.5", + "open-telemetry/sdk": "^1.0", + "php-http/mock-client": "^1.5", + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9.5", + "symfony/http-client": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Elastic\\Transport\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "HTTP transport PHP library for Elastic products", + "keywords": [ + "PSR_17", + "elastic", + "http", + "psr-18", + "psr-7", + "transport" + ], + "support": { + "issues": "https://github.com/elastic/elastic-transport-php/issues", + "source": "https://github.com/elastic/elastic-transport-php/tree/v8.10.0" + }, + "time": "2024-08-14T08:55:07+00:00" + }, + { + "name": "elasticsearch/elasticsearch", + "version": "v8.15.0", + "source": { + "type": "git", + "url": "https://github.com/elastic/elasticsearch-php.git", + "reference": "34c2444fa8d4c3e6c8b009bd8dea90bca007203b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/34c2444fa8d4c3e6c8b009bd8dea90bca007203b", + "reference": "34c2444fa8d4c3e6c8b009bd8dea90bca007203b", + "shasum": "" + }, + "require": { + "elastic/transport": "^8.10", + "guzzlehttp/guzzle": "^7.0", + "php": "^7.4 || ^8.0", + "psr/http-client": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "psr/log": "^1|^2|^3" + }, + "require-dev": { + "ext-yaml": "*", + "ext-zip": "*", + "mockery/mockery": "^1.5", + "nyholm/psr7": "^1.5", + "php-http/message-factory": "^1.0", + "php-http/mock-client": "^1.5", + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9.5", + "psr/http-factory": "^1.0", + "symfony/finder": "~4.0", + "symfony/http-client": "^5.0|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Elastic\\Elasticsearch\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP Client for Elasticsearch", + "keywords": [ + "client", + "elastic", + "elasticsearch", + "search" + ], + "support": { + "issues": "https://github.com/elastic/elasticsearch-php/issues", + "source": "https://github.com/elastic/elasticsearch-php/tree/v8.15.0" + }, + "time": "2024-08-14T14:32:50+00:00" + }, + { + "name": "fruitcake/php-cors", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/fruitcake/php-cors.git", + "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/3d158f36e7875e2f040f37bc0573956240a5a38b", + "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "symfony/http-foundation": "^4.4|^5.4|^6|^7" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "Fruitcake\\Cors\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fruitcake", + "homepage": "https://fruitcake.nl" + }, + { + "name": "Barryvdh", + "email": "barryvdh@gmail.com" + } + ], + "description": "Cross-origin resource sharing library for the Symfony HttpFoundation", + "homepage": "https://github.com/fruitcake/php-cors", + "keywords": [ + "cors", + "laravel", + "symfony" + ], + "support": { + "issues": "https://github.com/fruitcake/php-cors/issues", + "source": "https://github.com/fruitcake/php-cors/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2023-10-12T05:21:21+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:45:45+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.9.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2024-07-24T11:22:20+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", + "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2024-07-18T10:29:17+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.7.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.7.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2024-07-18T11:15:46+00:00" + }, + { + "name": "guzzlehttp/uri-template", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/uri-template.git", + "reference": "ecea8feef63bd4fef1f037ecb288386999ecc11c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/ecea8feef63bd4fef1f037ecb288386999ecc11c", + "reference": "ecea8feef63bd4fef1f037ecb288386999ecc11c", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "uri-template/tests": "1.0.0" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\UriTemplate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + } + ], + "description": "A polyfill class for uri_template of PHP", + "keywords": [ + "guzzlehttp", + "uri-template" + ], + "support": { + "issues": "https://github.com/guzzle/uri-template/issues", + "source": "https://github.com/guzzle/uri-template/tree/v1.0.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/uri-template", + "type": "tidelift" + } + ], + "time": "2023-12-03T19:50:20+00:00" + }, + { + "name": "laravel/framework", + "version": "v11.22.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "868c75beacc47d0f361b919bbc155c0b619bf3d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/868c75beacc47d0f361b919bbc155c0b619bf3d5", + "reference": "868c75beacc47d0f361b919bbc155c0b619bf3d5", + "shasum": "" + }, + "require": { + "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", + "composer-runtime-api": "^2.2", + "doctrine/inflector": "^2.0.5", + "dragonmantank/cron-expression": "^3.3.2", + "egulias/email-validator": "^3.2.1|^4.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-session": "*", + "ext-tokenizer": "*", + "fruitcake/php-cors": "^1.3", + "guzzlehttp/guzzle": "^7.8", + "guzzlehttp/uri-template": "^1.0", + "laravel/prompts": "^0.1.18", + "laravel/serializable-closure": "^1.3", + "league/commonmark": "^2.2.1", + "league/flysystem": "^3.8.0", + "monolog/monolog": "^3.0", + "nesbot/carbon": "^2.72.2|^3.0", + "nunomaduro/termwind": "^2.0", + "php": "^8.2", + "psr/container": "^1.1.1|^2.0.1", + "psr/log": "^1.0|^2.0|^3.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "ramsey/uuid": "^4.7", + "symfony/console": "^7.0", + "symfony/error-handler": "^7.0", + "symfony/finder": "^7.0", + "symfony/http-foundation": "^7.0", + "symfony/http-kernel": "^7.0", + "symfony/mailer": "^7.0", + "symfony/mime": "^7.0", + "symfony/polyfill-php83": "^1.28", + "symfony/process": "^7.0", + "symfony/routing": "^7.0", + "symfony/uid": "^7.0", + "symfony/var-dumper": "^7.0", + "tijsverkoyen/css-to-inline-styles": "^2.2.5", + "vlucas/phpdotenv": "^5.4.1", + "voku/portable-ascii": "^2.0" + }, + "conflict": { + "mockery/mockery": "1.6.8", + "tightenco/collect": "<5.5.33" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "psr/log-implementation": "1.0|2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", + "illuminate/bus": "self.version", + "illuminate/cache": "self.version", + "illuminate/collections": "self.version", + "illuminate/conditionable": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/contracts": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/log": "self.version", + "illuminate/macroable": "self.version", + "illuminate/mail": "self.version", + "illuminate/notifications": "self.version", + "illuminate/pagination": "self.version", + "illuminate/pipeline": "self.version", + "illuminate/process": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/testing": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version", + "spatie/once": "*" + }, + "require-dev": { + "ably/ably-php": "^1.0", + "aws/aws-sdk-php": "^3.235.5", + "ext-gmp": "*", + "fakerphp/faker": "^1.23", + "league/flysystem-aws-s3-v3": "^3.0", + "league/flysystem-ftp": "^3.0", + "league/flysystem-path-prefixing": "^3.3", + "league/flysystem-read-only": "^3.3", + "league/flysystem-sftp-v3": "^3.0", + "mockery/mockery": "^1.6", + "nyholm/psr7": "^1.2", + "orchestra/testbench-core": "^9.1.5", + "pda/pheanstalk": "^5.0", + "phpstan/phpstan": "^1.11.5", + "phpunit/phpunit": "^10.5|^11.0", + "predis/predis": "^2.0.2", + "resend/resend-php": "^0.10.0", + "symfony/cache": "^7.0", + "symfony/http-client": "^7.0", + "symfony/psr-http-message-bridge": "^7.0" + }, + "suggest": { + "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.235.5).", + "brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).", + "ext-apcu": "Required to use the APC cache driver.", + "ext-fileinfo": "Required to use the Filesystem class.", + "ext-ftp": "Required to use the Flysystem FTP driver.", + "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", + "ext-memcached": "Required to use the memcache cache driver.", + "ext-pcntl": "Required to use all features of the queue worker and console signal trapping.", + "ext-pdo": "Required to use all database features.", + "ext-posix": "Required to use all features of the queue worker.", + "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0|^6.0).", + "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", + "filp/whoops": "Required for friendly error pages in development (^2.14.3).", + "laravel/tinker": "Required to use the tinker console command (^2.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.0).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.0).", + "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.3).", + "league/flysystem-read-only": "Required to use read-only disks (^3.3)", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.0).", + "mockery/mockery": "Required to use mocking (^1.6).", + "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).", + "phpunit/phpunit": "Required to use assertions and run tests (^10.5|^11.0).", + "predis/predis": "Required to use the predis connector (^2.0.2).", + "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", + "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^7.0).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.0).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.0).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.0).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.0).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "autoload": { + "files": [ + "src/Illuminate/Collections/helpers.php", + "src/Illuminate/Events/functions.php", + "src/Illuminate/Filesystem/functions.php", + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate/", + "Illuminate\\Support\\": [ + "src/Illuminate/Macroable/", + "src/Illuminate/Collections/", + "src/Illuminate/Conditionable/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Laravel Framework.", + "homepage": "https://laravel.com", + "keywords": [ + "framework", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-09-03T15:27:15+00:00" + }, + { + "name": "laravel/prompts", + "version": "v0.1.25", + "source": { + "type": "git", + "url": "https://github.com/laravel/prompts.git", + "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/prompts/zipball/7b4029a84c37cb2725fc7f011586e2997040bc95", + "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "illuminate/collections": "^10.0|^11.0", + "php": "^8.1", + "symfony/console": "^6.2|^7.0" + }, + "conflict": { + "illuminate/console": ">=10.17.0 <10.25.0", + "laravel/framework": ">=10.17.0 <10.25.0" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "pestphp/pest": "^2.3", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-mockery": "^1.1" + }, + "suggest": { + "ext-pcntl": "Required for the spinner to be animated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.1.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Laravel\\Prompts\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Add beautiful and user-friendly forms to your command-line applications.", + "support": { + "issues": "https://github.com/laravel/prompts/issues", + "source": "https://github.com/laravel/prompts/tree/v0.1.25" + }, + "time": "2024-08-12T22:06:33+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v1.3.4", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/61b87392d986dc49ad5ef64e75b1ff5fee24ef81", + "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "illuminate/support": "^8.0|^9.0|^10.0|^11.0", + "nesbot/carbon": "^2.61|^3.0", + "pestphp/pest": "^1.21.3", + "phpstan/phpstan": "^1.8.2", + "symfony/var-dumper": "^5.4.11|^6.2.0|^7.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2024-08-02T07:48:17+00:00" + }, + { + "name": "league/commonmark", + "version": "2.5.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "b650144166dfa7703e62a22e493b853b58d874b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/b650144166dfa7703e62a22e493b853b58d874b0", + "reference": "b650144166dfa7703e62a22e493b853b58d874b0", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4 || ^2.0", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0 || ^7.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 || ^7.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.6-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2024-08-16T11:46:16+00:00" + }, + { + "name": "league/config", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2022-12-11T20:36:23+00:00" + }, + { + "name": "league/flysystem", + "version": "3.28.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c", + "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c", + "shasum": "" + }, + "require": { + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.295.10", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-mongodb": "^1.3", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", + "microsoft/azure-storage-blob": "^1.1", + "mongodb/mongodb": "^1.2", + "phpseclib/phpseclib": "^3.0.36", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "File storage abstraction for PHP", + "keywords": [ + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/3.28.0" + }, + "time": "2024-05-22T10:09:12+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.28.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/13f22ea8be526ea58c2ddff9e158ef7c296e4f40", + "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-local/tree/3.28.0" + }, + "time": "2024-05-06T20:05:52+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.15.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2024-01-28T23:22:08+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.7.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f4393b648b78a5408747de94fca38beb5f7e9ef8", + "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-strict-rules": "^1.4", + "phpunit/phpunit": "^10.5.17", + "predis/predis": "^1.1 || ^2", + "ruflin/elastica": "^7", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.7.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2024-06-28T09:40:51+00:00" + }, + { + "name": "nesbot/carbon", + "version": "3.8.0", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "bbd3eef89af8ba66a3aa7952b5439168fbcc529f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/bbd3eef89af8ba66a3aa7952b5439168fbcc529f", + "reference": "bbd3eef89af8ba66a3aa7952b5439168fbcc529f", + "shasum": "" + }, + "require": { + "carbonphp/carbon-doctrine-types": "*", + "ext-json": "*", + "php": "^8.1", + "psr/clock": "^1.0", + "symfony/clock": "^6.3 || ^7.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation": "^4.4.18 || ^5.2.1|| ^6.0 || ^7.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.57.2", + "kylekatarnls/multi-tester": "^2.5.3", + "ondrejmirtes/better-reflection": "^6.25.0.4", + "phpmd/phpmd": "^2.15.0", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan": "^1.11.2", + "phpunit/phpunit": "^10.5.20", + "squizlabs/php_codesniffer": "^3.9.0" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev", + "dev-2.x": "2.x-dev" + }, + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/briannesbitt/Carbon/issues", + "source": "https://github.com/briannesbitt/Carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2024-08-19T06:22:39+00:00" + }, + { + "name": "nette/schema", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", + "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", + "shasum": "" + }, + "require": { + "nette/utils": "^4.0", + "php": "8.1 - 8.3" + }, + "require-dev": { + "nette/tester": "^2.4", + "phpstan/phpstan-nette": "^1.0", + "tracy/tracy": "^2.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.3.0" + }, + "time": "2023-12-11T11:54:22+00:00" + }, + { + "name": "nette/utils", + "version": "v4.0.5", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", + "shasum": "" + }, + "require": { + "php": "8.0 - 8.4" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "dev-master", + "nette/tester": "^2.5", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.0.5" + }, + "time": "2024-08-07T15:39:19+00:00" + }, + { + "name": "nunomaduro/termwind", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "58c4c58cf23df7f498daeb97092e34f5259feb6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/58c4c58cf23df7f498daeb97092e34f5259feb6a", + "reference": "58c4c58cf23df7f498daeb97092e34f5259feb6a", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.2", + "symfony/console": "^7.0.4" + }, + "require-dev": { + "ergebnis/phpstan-rules": "^2.2.0", + "illuminate/console": "^11.0.0", + "laravel/pint": "^1.14.0", + "mockery/mockery": "^1.6.7", + "pestphp/pest": "^2.34.1", + "phpstan/phpstan": "^1.10.59", + "phpstan/phpstan-strict-rules": "^1.5.2", + "symfony/var-dumper": "^7.0.4", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Termwind\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Its like Tailwind CSS, but for the console.", + "keywords": [ + "cli", + "console", + "css", + "package", + "php", + "style" + ], + "support": { + "issues": "https://github.com/nunomaduro/termwind/issues", + "source": "https://github.com/nunomaduro/termwind/tree/v2.0.1" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://github.com/xiCO2k", + "type": "github" + } + ], + "time": "2024-03-06T16:17:14+00:00" + }, + { + "name": "open-telemetry/api", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/api.git", + "reference": "87de95d926f46262885d0d390060c095af13e2e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/87de95d926f46262885d0d390060c095af13e2e5", + "reference": "87de95d926f46262885d0d390060c095af13e2e5", + "shasum": "" + }, + "require": { + "open-telemetry/context": "^1.0", + "php": "^7.4 || ^8.0", + "psr/log": "^1.1|^2.0|^3.0", + "symfony/polyfill-php80": "^1.26", + "symfony/polyfill-php81": "^1.26", + "symfony/polyfill-php82": "^1.26" + }, + "conflict": { + "open-telemetry/sdk": "<=1.0.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.0.x-dev" + } + }, + "autoload": { + "files": [ + "Trace/functions.php" + ], + "psr-4": { + "OpenTelemetry\\API\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "API for OpenTelemetry PHP.", + "keywords": [ + "Metrics", + "api", + "apm", + "logging", + "opentelemetry", + "otel", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2024-02-06T01:32:25+00:00" + }, + { + "name": "open-telemetry/context", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/context.git", + "reference": "e9d254a7c89885e63fd2fde54e31e81aaaf52b7c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/e9d254a7c89885e63fd2fde54e31e81aaaf52b7c", + "reference": "e9d254a7c89885e63fd2fde54e31e81aaaf52b7c", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "symfony/polyfill-php80": "^1.26", + "symfony/polyfill-php81": "^1.26", + "symfony/polyfill-php82": "^1.26" + }, + "suggest": { + "ext-ffi": "To allow context switching in Fibers" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.0.x-dev" + } + }, + "autoload": { + "files": [ + "fiber/initialize_fiber_handler.php" + ], + "psr-4": { + "OpenTelemetry\\Context\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "Context implementation for OpenTelemetry PHP.", + "keywords": [ + "Context", + "opentelemetry", + "otel" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2024-01-13T05:50:44+00:00" + }, + { + "name": "php-http/discovery", + "version": "1.19.4", + "source": { + "type": "git", + "url": "https://github.com/php-http/discovery.git", + "reference": "0700efda8d7526335132360167315fdab3aeb599" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/discovery/zipball/0700efda8d7526335132360167315fdab3aeb599", + "reference": "0700efda8d7526335132360167315fdab3aeb599", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "nyholm/psr7": "<1.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "*", + "psr/http-factory-implementation": "*", + "psr/http-message-implementation": "*" + }, + "require-dev": { + "composer/composer": "^1.0.2|^2.0", + "graham-campbell/phpspec-skip-example-extension": "^5.0", + "php-http/httplug": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", + "sebastian/comparator": "^3.0.5 || ^4.0.8", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" + }, + "type": "composer-plugin", + "extra": { + "class": "Http\\Discovery\\Composer\\Plugin", + "plugin-optional": true + }, + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" + }, + "exclude-from-classmap": [ + "src/Composer/Plugin.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", + "homepage": "http://php-http.org", + "keywords": [ + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr17", + "psr7" + ], + "support": { + "issues": "https://github.com/php-http/discovery/issues", + "source": "https://github.com/php-http/discovery/tree/1.19.4" + }, + "time": "2024-03-29T13:00:05+00:00" + }, + { + "name": "php-http/httplug", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/httplug.git", + "reference": "625ad742c360c8ac580fcc647a1541d29e257f67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/httplug/zipball/625ad742c360c8ac580fcc647a1541d29e257f67", + "reference": "625ad742c360c8ac580fcc647a1541d29e257f67", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "php-http/promise": "^1.1", + "psr/http-client": "^1.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "require-dev": { + "friends-of-phpspec/phpspec-code-coverage": "^4.1 || ^5.0 || ^6.0", + "phpspec/phpspec": "^5.1 || ^6.0 || ^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eric GELOEN", + "email": "geloen.eric@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "HTTPlug, the HTTP client abstraction for PHP", + "homepage": "http://httplug.io", + "keywords": [ + "client", + "http" + ], + "support": { + "issues": "https://github.com/php-http/httplug/issues", + "source": "https://github.com/php-http/httplug/tree/2.4.0" + }, + "time": "2023-04-14T15:10:03+00:00" + }, + { + "name": "php-http/promise", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/php-http/promise.git", + "reference": "fc85b1fba37c169a69a07ef0d5a8075770cc1f83" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/promise/zipball/fc85b1fba37c169a69a07ef0d5a8075770cc1f83", + "reference": "fc85b1fba37c169a69a07ef0d5a8075770cc1f83", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "friends-of-phpspec/phpspec-code-coverage": "^4.3.2 || ^6.3", + "phpspec/phpspec": "^5.1.2 || ^6.2 || ^7.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Http\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Joel Wurtz", + "email": "joel.wurtz@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Promise used for asynchronous HTTP requests", + "homepage": "http://httplug.io", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/php-http/promise/issues", + "source": "https://github.com/php-http/promise/tree/1.3.1" + }, + "time": "2024-03-15T13:55:21+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.3", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:41:07+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "79dff0b268932c640297f5208d6298f71855c03e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/79dff0b268932c640297f5208d6298f71855c03e", + "reference": "79dff0b268932c640297f5208d6298f71855c03e", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.1" + }, + "time": "2024-08-21T13:31:24+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "ramsey/collection", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.28.3", + "fakerphp/faker": "^1.21", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^1.0", + "mockery/mockery": "^1.5", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpcsstandards/phpcsutils": "^1.0.0-rc1", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.18.4", + "ramsey/coding-standard": "^2.0.3", + "ramsey/conventional-commits": "^1.3", + "vimeo/psalm": "^5.4" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", + "type": "tidelift" + } + ], + "time": "2022-12-31T21:50:55+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.7.6", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", + "ext-json": "*", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.10", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.8", + "ergebnis/composer-normalize": "^2.15", + "mockery/mockery": "^1.3", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.2", + "php-mock/php-mock-mockery": "^1.3", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^8.5 || ^9", + "ramsey/composer-repl": "^1.4", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.9" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.7.6" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" + } + ], + "time": "2024-04-27T21:32:50+00:00" + }, + { + "name": "symfony/clock", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/3dfc8b084853586de51dd1441c6242c76a28cbe7", + "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, + { + "name": "symfony/console", + "version": "v7.1.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/1eed7af6961d763e7832e874d7f9b21c3ea9c111", + "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-08-15T22:48:53+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/1c7cee86c6f812896af54434f8ce29c8d94f9ff4", + "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "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": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v7.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "432bb369952795c61ca1def65e078c4a80dad13c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/432bb369952795c61ca1def65e078c4a80dad13c", + "reference": "432bb369952795c61ca1def65e078c4a80dad13c", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^6.4|^7.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-26T13:02:51+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.1.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "d95bbf319f7d052082fb7af147e0f835a695e823" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/d95bbf319f7d052082fb7af147e0f835a695e823", + "reference": "d95bbf319f7d052082fb7af147e0f835a695e823", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-08-13T14:28:19+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v7.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f602d5c17d1fa02f8019ace2687d9d136b7f4a1a", + "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4" + }, + "require-dev": { + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-26T12:41:01+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v7.1.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "6efcbd1b3f444f631c386504fc83eeca25963747" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6efcbd1b3f444f631c386504fc83eeca25963747", + "reference": "6efcbd1b3f444f631c386504fc83eeca25963747", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.0.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^7.1", + "symfony/routing": "^6.4|^7.0", + "symfony/serializer": "^7.1", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "twig/twig": "^3.0.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-08-30T17:02:28+00:00" + }, + { + "name": "symfony/mailer", + "version": "v7.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailer.git", + "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/8fcff0af9043c8f8a8e229437cea363e282f9aee", + "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.1.10|^3|^4", + "php": ">=8.2", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/twig-bridge": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps sending emails", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailer/tree/v7.1.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-28T08:00:31+00:00" + }, + { + "name": "symfony/mime", + "version": "v7.1.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "ccaa6c2503db867f472a587291e764d6a1e58758" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/ccaa6c2503db867f472a587291e764d6a1e58758", + "reference": "ccaa6c2503db867f472a587291e764d6a1e58758", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-08-13T14:28:19+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a6e83bdeb3c84391d1dfe16f42e40727ce524a5c", + "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-19T12:30:46+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "10112722600777e02d2745716b70c5db4ca70442" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/10112722600777e02d2745716b70c5db4ca70442", + "reference": "10112722600777e02d2745716b70c5db4ca70442", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-19T12:30:46+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af", + "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-19T12:30:46+00:00" + }, + { + "name": "symfony/polyfill-php82", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php82.git", + "reference": "77ff49780f56906788a88974867ed68bc49fae5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/77ff49780f56906788a88974867ed68bc49fae5b", + "reference": "77ff49780f56906788a88974867ed68bc49fae5b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php82\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php82/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-19T12:30:46+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", + "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-19T12:35:24+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/2ba1f33797470debcda07fe9dce20a0003df18e9", + "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/process", + "version": "v7.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca", + "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-26T12:44:47+00:00" + }, + { + "name": "symfony/routing", + "version": "v7.1.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "1500aee0094a3ce1c92626ed8cf3c2037e86f5a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/1500aee0094a3ce1c92626ed8cf3c2037e86f5a7", + "reference": "1500aee0094a3ce1c92626ed8cf3c2037e86f5a7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-08-29T08:16:25+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/string", + "version": "v7.1.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/6cd670a6d968eaeb1c77c2e76091c45c56bc367b", + "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-08-12T09:59:40+00:00" + }, + { + "name": "symfony/translation", + "version": "v7.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/8d5e50c813ba2859a6dfc99a0765c550507934a1", + "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5|^3.0" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.18|^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-26T12:41:01+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/uid", + "version": "v7.1.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "82177535395109075cdb45a70533aa3d7a521cdf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/82177535395109075cdb45a70533aa3d7a521cdf", + "reference": "82177535395109075cdb45a70533aa3d7a521cdf", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-08-12T09:59:40+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v7.1.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "a5fa7481b199090964d6fd5dab6294d5a870c7aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/a5fa7481b199090964d6fd5dab6294d5a870c7aa", + "reference": "a5fa7481b199090964d6fd5dab6294d5a870c7aa", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "twig/twig": "^3.0.4" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-08-30T16:12:47+00:00" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "v2.2.7", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "83ee6f38df0a63106a9e4536e3060458b74ccedb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/83ee6f38df0a63106a9e4536e3060458b74ccedb", + "reference": "83ee6f38df0a63106a9e4536e3060458b74ccedb", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": "^5.5 || ^7.0 || ^8.0", + "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^7.5 || ^8.5.21 || ^9.5.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "TijsVerkoyen\\CssToInlineStyles\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Tijs Verkoyen", + "email": "css_to_inline_styles@verkoyen.eu", + "role": "Developer" + } + ], + "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", + "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", + "support": { + "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.2.7" + }, + "time": "2023-12-08T13:03:43+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.1", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.3", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3", + "symfony/polyfill-ctype": "^1.24", + "symfony/polyfill-mbstring": "^1.24", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:52:34+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "b56450eed252f6801410d810c8e1727224ae0743" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b56450eed252f6801410d810c8e1727224ae0743", + "reference": "b56450eed252f6801410d810c8e1727224ae0743", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "http://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.0.1" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2022-03-08T17:03:00+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + } + ], + "packages-dev": [ + { + "name": "brianium/paratest", + "version": "v7.4.3", + "source": { + "type": "git", + "url": "https://github.com/paratestphp/paratest.git", + "reference": "64fcfd0e28a6b8078a19dbf9127be2ee645b92ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/64fcfd0e28a6b8078a19dbf9127be2ee645b92ec", + "reference": "64fcfd0e28a6b8078a19dbf9127be2ee645b92ec", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-simplexml": "*", + "fidry/cpu-core-counter": "^1.1.0", + "jean85/pretty-package-versions": "^2.0.5", + "php": "~8.2.0 || ~8.3.0", + "phpunit/php-code-coverage": "^10.1.11 || ^11.0.0", + "phpunit/php-file-iterator": "^4.1.0 || ^5.0.0", + "phpunit/php-timer": "^6.0.0 || ^7.0.0", + "phpunit/phpunit": "^10.5.9 || ^11.0.3", + "sebastian/environment": "^6.0.1 || ^7.0.0", + "symfony/console": "^6.4.3 || ^7.0.3", + "symfony/process": "^6.4.3 || ^7.0.3" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0.0", + "ext-pcov": "*", + "ext-posix": "*", + "phpstan/phpstan": "^1.10.58", + "phpstan/phpstan-deprecation-rules": "^1.1.4", + "phpstan/phpstan-phpunit": "^1.3.15", + "phpstan/phpstan-strict-rules": "^1.5.2", + "squizlabs/php_codesniffer": "^3.9.0", + "symfony/filesystem": "^6.4.3 || ^7.0.3" + }, + "bin": [ + "bin/paratest", + "bin/paratest.bat", + "bin/paratest_for_phpstorm" + ], + "type": "library", + "autoload": { + "psr-4": { + "ParaTest\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Scaturro", + "email": "scaturrob@gmail.com", + "role": "Developer" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com", + "role": "Developer" + } + ], + "description": "Parallel testing for PHP", + "homepage": "https://github.com/paratestphp/paratest", + "keywords": [ + "concurrent", + "parallel", + "phpunit", + "testing" + ], + "support": { + "issues": "https://github.com/paratestphp/paratest/issues", + "source": "https://github.com/paratestphp/paratest/tree/v7.4.3" + }, + "funding": [ + { + "url": "https://github.com/sponsors/Slamdunk", + "type": "github" + }, + { + "url": "https://paypal.me/filippotessarotto", + "type": "paypal" + } + ], + "time": "2024-02-20T07:24:02+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.2", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-07-12T11:35:52+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "4be43904336affa5c2f70744a348312336afd0da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", + "reference": "4be43904336affa5c2f70744a348312336afd0da", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "time": "2023-01-05T11:28:13+00:00" + }, + { + "name": "doctrine/coding-standard", + "version": "12.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/doctrine/coding-standard.git", + "reference": "3e88327e4bb74e5538787642a59a45919376e0a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/coding-standard/zipball/3e88327e4bb74e5538787642a59a45919376e0a9", + "reference": "3e88327e4bb74e5538787642a59a45919376e0a9", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0.0", + "php": "^7.2 || ^8.0", + "slevomat/coding-standard": "^8.11", + "squizlabs/php_codesniffer": "^3.7" + }, + "default-branch": true, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Müller", + "email": "st.mueller@dzh-online.de" + } + ], + "description": "The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/coding-standard.html", + "keywords": [ + "checks", + "code", + "coding", + "cs", + "dev", + "doctrine", + "rules", + "sniffer", + "sniffs", + "standard", + "style" + ], + "support": { + "issues": "https://github.com/doctrine/coding-standard/issues", + "source": "https://github.com/doctrine/coding-standard/tree/12.0.x" + }, + "time": "2023-04-28T07:19:07+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpstan/phpstan": "1.4.10 || 1.10.15", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "0.18.4", + "psr/log": "^1 || ^2 || ^3", + "vimeo/psalm": "4.30.0 || 5.12.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.3" + }, + "time": "2024-01-30T19:34:25+00:00" + }, + { + "name": "fakerphp/faker", + "version": "v1.23.1", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b", + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" + }, + "time": "2024-01-02T13:46:09+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "8520451a140d3f46ac33042715115e290cf5785f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", + "reference": "8520451a140d3f46ac33042715115e290cf5785f", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2024-08-06T10:04:20+00:00" + }, + { + "name": "filp/whoops", + "version": "2.15.4", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546", + "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.15.4" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2023-11-03T12:00:00+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + }, + "time": "2020-07-09T08:09:16+00:00" + }, + { + "name": "jean85/pretty-package-versions", + "version": "2.0.6", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4", + "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^7.5|^8.5|^9.4", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A library to get pretty versions strings of installed dependencies", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.6" + }, + "time": "2024-03-08T09:58:59+00:00" + }, + { + "name": "larastan/larastan", + "version": "v2.9.8", + "source": { + "type": "git", + "url": "https://github.com/larastan/larastan.git", + "reference": "340badd89b0eb5bddbc503a4829c08cf9a2819d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/larastan/larastan/zipball/340badd89b0eb5bddbc503a4829c08cf9a2819d7", + "reference": "340badd89b0eb5bddbc503a4829c08cf9a2819d7", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/console": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/container": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/contracts": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/database": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/http": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/pipeline": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/support": "^9.52.16 || ^10.28.0 || ^11.0", + "php": "^8.0.2", + "phpmyadmin/sql-parser": "^5.9.0", + "phpstan/phpstan": "^1.11.2" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0", + "nikic/php-parser": "^4.19.1", + "orchestra/canvas": "^7.11.1 || ^8.11.0 || ^9.0.2", + "orchestra/testbench": "^7.33.0 || ^8.13.0 || ^9.0.3", + "phpunit/phpunit": "^9.6.13 || ^10.5.16" + }, + "suggest": { + "orchestra/testbench": "Using Larastan for analysing a package needs Testbench" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Larastan\\Larastan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Can Vural", + "email": "can9119@gmail.com" + }, + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Larastan - Discover bugs in your code without running it. A phpstan/phpstan wrapper for Laravel", + "keywords": [ + "PHPStan", + "code analyse", + "code analysis", + "larastan", + "laravel", + "package", + "php", + "static analysis" + ], + "support": { + "issues": "https://github.com/larastan/larastan/issues", + "source": "https://github.com/larastan/larastan/tree/v2.9.8" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/canvural", + "type": "github" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2024-07-06T17:46:02+00:00" + }, + { + "name": "laravel/pint", + "version": "v1.17.3", + "source": { + "type": "git", + "url": "https://github.com/laravel/pint.git", + "reference": "9d77be916e145864f10788bb94531d03e1f7b482" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pint/zipball/9d77be916e145864f10788bb94531d03e1f7b482", + "reference": "9d77be916e145864f10788bb94531d03e1f7b482", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "ext-tokenizer": "*", + "ext-xml": "*", + "php": "^8.1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.64.0", + "illuminate/view": "^10.48.20", + "larastan/larastan": "^2.9.8", + "laravel-zero/framework": "^10.4.0", + "mockery/mockery": "^1.6.12", + "nunomaduro/termwind": "^1.15.1", + "pestphp/pest": "^2.35.1" + }, + "bin": [ + "builds/pint" + ], + "type": "project", + "autoload": { + "psr-4": { + "App\\": "app/", + "Database\\Seeders\\": "database/seeders/", + "Database\\Factories\\": "database/factories/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "An opinionated code formatter for PHP.", + "homepage": "https://laravel.com", + "keywords": [ + "format", + "formatter", + "lint", + "linter", + "php" + ], + "support": { + "issues": "https://github.com/laravel/pint/issues", + "source": "https://github.com/laravel/pint" + }, + "time": "2024-09-03T15:00:28+00:00" + }, + { + "name": "laravel/tinker", + "version": "v2.9.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/tinker.git", + "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/tinker/zipball/502e0fe3f0415d06d5db1f83a472f0f3b754bafe", + "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe", + "shasum": "" + }, + "require": { + "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "php": "^7.2.5|^8.0", + "psy/psysh": "^0.11.1|^0.12.0", + "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0" + }, + "require-dev": { + "mockery/mockery": "~1.3.3|^1.4.2", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.5.8|^9.3.3" + }, + "suggest": { + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0|^11.0)." + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Tinker\\TinkerServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Tinker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Powerful REPL for the Laravel framework.", + "keywords": [ + "REPL", + "Tinker", + "laravel", + "psysh" + ], + "support": { + "issues": "https://github.com/laravel/tinker/issues", + "source": "https://github.com/laravel/tinker/tree/v2.9.0" + }, + "time": "2024-01-04T16:10:04+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.6.12", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" + }, + "type": "library", + "autoload": { + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" + }, + "time": "2024-05-16T03:13:13+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.12.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2024-06-12T14:39:25+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.1.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" + }, + "time": "2024-07-01T20:03:41+00:00" + }, + { + "name": "nunomaduro/collision", + "version": "v8.4.0", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/e7d1aa8ed753f63fa816932bbc89678238843b4a", + "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.15.4", + "nunomaduro/termwind": "^2.0.1", + "php": "^8.2.0", + "symfony/console": "^7.1.3" + }, + "conflict": { + "laravel/framework": "<11.0.0 || >=12.0.0", + "phpunit/phpunit": "<10.5.1 || >=12.0.0" + }, + "require-dev": { + "larastan/larastan": "^2.9.8", + "laravel/framework": "^11.19.0", + "laravel/pint": "^1.17.1", + "laravel/sail": "^1.31.0", + "laravel/sanctum": "^4.0.2", + "laravel/tinker": "^2.9.0", + "orchestra/testbench-core": "^9.2.3", + "pestphp/pest": "^2.35.0 || ^3.0.0", + "sebastian/environment": "^6.1.0 || ^7.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" + ] + }, + "branch-alias": { + "dev-8.x": "8.x-dev" + } + }, + "autoload": { + "files": [ + "./src/Adapters/Phpunit/Autoload.php" + ], + "psr-4": { + "NunoMaduro\\Collision\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Cli error handling for console/command-line PHP applications.", + "keywords": [ + "artisan", + "cli", + "command-line", + "console", + "error", + "handling", + "laravel", + "laravel-zero", + "php", + "symfony" + ], + "support": { + "issues": "https://github.com/nunomaduro/collision/issues", + "source": "https://github.com/nunomaduro/collision" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2024-08-03T15:32:23+00:00" + }, + { + "name": "orchestra/canvas", + "version": "v9.1.1", + "source": { + "type": "git", + "url": "https://github.com/orchestral/canvas.git", + "reference": "c49867fac16b6286bf2b8360088620e697a2ea92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/orchestral/canvas/zipball/c49867fac16b6286bf2b8360088620e697a2ea92", + "reference": "c49867fac16b6286bf2b8360088620e697a2ea92", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2", + "composer/semver": "^3.0", + "illuminate/console": "^11.20", + "illuminate/database": "^11.20", + "illuminate/filesystem": "^11.20", + "illuminate/support": "^11.20", + "orchestra/canvas-core": "^9.0", + "orchestra/testbench-core": "^9.2", + "php": "^8.2", + "symfony/polyfill-php83": "^1.28", + "symfony/yaml": "^7.0" + }, + "require-dev": { + "laravel/framework": "^11.20", + "laravel/pint": "^1.17", + "mockery/mockery": "^1.6", + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^11.0", + "spatie/laravel-ray": "^1.35" + }, + "bin": [ + "canvas" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.0-dev" + }, + "laravel": { + "providers": [ + "Orchestra\\Canvas\\LaravelServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Orchestra\\Canvas\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Mior Muhammad Zaki", + "email": "crynobone@gmail.com" + } + ], + "description": "Code Generators for Laravel Applications and Packages", + "support": { + "issues": "https://github.com/orchestral/canvas/issues", + "source": "https://github.com/orchestral/canvas/tree/v9.1.1" + }, + "time": "2024-08-06T17:20:26+00:00" + }, + { + "name": "orchestra/canvas-core", + "version": "v9.0.0", + "source": { + "type": "git", + "url": "https://github.com/orchestral/canvas-core.git", + "reference": "3a29eecf324fe02e3e5628e422314b5cd1a80e48" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/orchestral/canvas-core/zipball/3a29eecf324fe02e3e5628e422314b5cd1a80e48", + "reference": "3a29eecf324fe02e3e5628e422314b5cd1a80e48", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2", + "composer/semver": "^3.0", + "illuminate/console": "^11.0", + "illuminate/filesystem": "^11.0", + "php": "^8.2", + "symfony/polyfill-php83": "^1.28" + }, + "require-dev": { + "laravel/framework": "^11.0", + "laravel/pint": "^1.6", + "mockery/mockery": "^1.5.1", + "orchestra/testbench-core": "^9.0", + "phpstan/phpstan": "^1.10.6", + "phpunit/phpunit": "^10.1", + "symfony/yaml": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.0-dev" + }, + "laravel": { + "providers": [ + "Orchestra\\Canvas\\Core\\LaravelServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Orchestra\\Canvas\\Core\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Mior Muhammad Zaki", + "email": "crynobone@gmail.com" + } + ], + "description": "Code Generators Builder for Laravel Applications and Packages", + "support": { + "issues": "https://github.com/orchestral/canvas/issues", + "source": "https://github.com/orchestral/canvas-core/tree/v9.0.0" + }, + "time": "2024-03-06T10:00:21+00:00" + }, + { + "name": "orchestra/testbench", + "version": "v9.4.0", + "source": { + "type": "git", + "url": "https://github.com/orchestral/testbench.git", + "reference": "f79c16b4cc9d22e3a71f8a2bc28326de392ff6aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/orchestral/testbench/zipball/f79c16b4cc9d22e3a71f8a2bc28326de392ff6aa", + "reference": "f79c16b4cc9d22e3a71f8a2bc28326de392ff6aa", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2", + "fakerphp/faker": "^1.23", + "laravel/framework": "^11.11", + "mockery/mockery": "^1.6", + "orchestra/testbench-core": "^9.4", + "orchestra/workbench": "^9.5", + "php": "^8.2", + "phpunit/phpunit": "^10.5 || ^11.0.1", + "symfony/process": "^7.0", + "symfony/yaml": "^7.0", + "vlucas/phpdotenv": "^5.4.1" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mior Muhammad Zaki", + "email": "crynobone@gmail.com", + "homepage": "https://github.com/crynobone" + } + ], + "description": "Laravel Testing Helper for Packages Development", + "homepage": "https://packages.tools/testbench/", + "keywords": [ + "BDD", + "TDD", + "dev", + "laravel", + "laravel-packages", + "testing" + ], + "support": { + "issues": "https://github.com/orchestral/testbench/issues", + "source": "https://github.com/orchestral/testbench/tree/v9.4.0" + }, + "time": "2024-08-26T05:10:07+00:00" + }, + { + "name": "orchestra/testbench-core", + "version": "v9.4.0", + "source": { + "type": "git", + "url": "https://github.com/orchestral/testbench-core.git", + "reference": "422827e195741ca397408eced09ca473ebbb4086" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/422827e195741ca397408eced09ca473ebbb4086", + "reference": "422827e195741ca397408eced09ca473ebbb4086", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2", + "php": "^8.2", + "symfony/polyfill-php83": "^1.28" + }, + "conflict": { + "brianium/paratest": "<7.3.0 || >=8.0.0", + "laravel/framework": "<11.11.0 || >=12.0.0", + "laravel/serializable-closure": "<1.3.0 || >=2.0.0", + "nunomaduro/collision": "<8.0.0 || >=9.0.0", + "phpunit/phpunit": "<10.5.0 || 11.0.0 || >=11.4.0" + }, + "require-dev": { + "fakerphp/faker": "^1.23", + "laravel/framework": "^11.11", + "laravel/pint": "^1.17", + "mockery/mockery": "^1.6", + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^10.5 || ^11.0.1", + "spatie/laravel-ray": "^1.35", + "symfony/process": "^7.0", + "symfony/yaml": "^7.0", + "vlucas/phpdotenv": "^5.4.1" + }, + "suggest": { + "brianium/paratest": "Allow using parallel tresting (^7.3).", + "ext-pcntl": "Required to use all features of the console signal trapping.", + "fakerphp/faker": "Allow using Faker for testing (^1.23).", + "laravel/framework": "Required for testing (^11.11).", + "mockery/mockery": "Allow using Mockery for testing (^1.6).", + "nunomaduro/collision": "Allow using Laravel style tests output and parallel testing (^8.0).", + "orchestra/testbench-dusk": "Allow using Laravel Dusk for testing (^9.0).", + "phpunit/phpunit": "Allow using PHPUnit for testing (^10.5 || ^11.0).", + "symfony/process": "Required to use Orchestra\\Testbench\\remote function (^7.0).", + "symfony/yaml": "Required for Testbench CLI (^7.0).", + "vlucas/phpdotenv": "Required for Testbench CLI (^5.4.1)." + }, + "bin": [ + "testbench" + ], + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Orchestra\\Testbench\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mior Muhammad Zaki", + "email": "crynobone@gmail.com", + "homepage": "https://github.com/crynobone" + } + ], + "description": "Testing Helper for Laravel Development", + "homepage": "https://packages.tools/testbench", + "keywords": [ + "BDD", + "TDD", + "dev", + "laravel", + "laravel-packages", + "testing" + ], + "support": { + "issues": "https://github.com/orchestral/testbench/issues", + "source": "https://github.com/orchestral/testbench-core" + }, + "time": "2024-08-26T05:01:33+00:00" + }, + { + "name": "orchestra/workbench", + "version": "v9.6.0", + "source": { + "type": "git", + "url": "https://github.com/orchestral/workbench.git", + "reference": "4bb12d505f24b450d1693e88faddc44a1c835907" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/orchestral/workbench/zipball/4bb12d505f24b450d1693e88faddc44a1c835907", + "reference": "4bb12d505f24b450d1693e88faddc44a1c835907", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2", + "fakerphp/faker": "^1.23", + "laravel/framework": "^11.11", + "laravel/tinker": "^2.9", + "nunomaduro/collision": "^8.0", + "orchestra/canvas": "^9.1", + "orchestra/testbench-core": "^9.4", + "php": "^8.1", + "spatie/laravel-ray": "^1.35", + "symfony/polyfill-php83": "^1.28", + "symfony/yaml": "^7.0" + }, + "require-dev": { + "laravel/pint": "^1.17", + "mockery/mockery": "^1.6", + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^10.5 || ^11.0", + "symfony/process": "^7.0" + }, + "suggest": { + "ext-pcntl": "Required to use all features of the console signal trapping." + }, + "type": "library", + "autoload": { + "psr-4": { + "Orchestra\\Workbench\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mior Muhammad Zaki", + "email": "crynobone@gmail.com" + } + ], + "description": "Workbench Companion for Laravel Packages Development", + "keywords": [ + "dev", + "laravel", + "laravel-packages", + "testing" + ], + "support": { + "issues": "https://github.com/orchestral/workbench/issues", + "source": "https://github.com/orchestral/workbench/tree/v9.6.0" + }, + "time": "2024-08-26T05:38:42+00:00" + }, + { + "name": "pestphp/pest", + "version": "v2.35.1", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest.git", + "reference": "b13acb630df52c06123588d321823c31fc685545" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest/zipball/b13acb630df52c06123588d321823c31fc685545", + "reference": "b13acb630df52c06123588d321823c31fc685545", + "shasum": "" + }, + "require": { + "brianium/paratest": "^7.3.1", + "nunomaduro/collision": "^7.10.0|^8.4.0", + "nunomaduro/termwind": "^1.15.1|^2.0.1", + "pestphp/pest-plugin": "^2.1.1", + "pestphp/pest-plugin-arch": "^2.7.0", + "php": "^8.1.0", + "phpunit/phpunit": "^10.5.17" + }, + "conflict": { + "phpunit/phpunit": ">10.5.17", + "sebastian/exporter": "<5.1.0", + "webmozart/assert": "<1.11.0" + }, + "require-dev": { + "pestphp/pest-dev-tools": "^2.16.0", + "pestphp/pest-plugin-type-coverage": "^2.8.5", + "symfony/process": "^6.4.0|^7.1.3" + }, + "bin": [ + "bin/pest" + ], + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Plugins\\Bail", + "Pest\\Plugins\\Cache", + "Pest\\Plugins\\Coverage", + "Pest\\Plugins\\Init", + "Pest\\Plugins\\Environment", + "Pest\\Plugins\\Help", + "Pest\\Plugins\\Memory", + "Pest\\Plugins\\Only", + "Pest\\Plugins\\Printer", + "Pest\\Plugins\\ProcessIsolation", + "Pest\\Plugins\\Profile", + "Pest\\Plugins\\Retry", + "Pest\\Plugins\\Snapshot", + "Pest\\Plugins\\Verbose", + "Pest\\Plugins\\Version", + "Pest\\Plugins\\Parallel" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "files": [ + "src/Functions.php", + "src/Pest.php" + ], + "psr-4": { + "Pest\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "The elegant PHP Testing Framework.", + "keywords": [ + "framework", + "pest", + "php", + "test", + "testing", + "unit" + ], + "support": { + "issues": "https://github.com/pestphp/pest/issues", + "source": "https://github.com/pestphp/pest/tree/v2.35.1" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2024-08-20T21:41:50+00:00" + }, + { + "name": "pestphp/pest-plugin", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin.git", + "reference": "e05d2859e08c2567ee38ce8b005d044e72648c0b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin/zipball/e05d2859e08c2567ee38ce8b005d044e72648c0b", + "reference": "e05d2859e08c2567ee38ce8b005d044e72648c0b", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0.0", + "composer-runtime-api": "^2.2.2", + "php": "^8.1" + }, + "conflict": { + "pestphp/pest": "<2.2.3" + }, + "require-dev": { + "composer/composer": "^2.5.8", + "pestphp/pest": "^2.16.0", + "pestphp/pest-dev-tools": "^2.16.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Pest\\Plugin\\Manager" + }, + "autoload": { + "psr-4": { + "Pest\\Plugin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Pest plugin manager", + "keywords": [ + "framework", + "manager", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin/tree/v2.1.1" + }, + "funding": [ + { + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2023-08-22T08:40:06+00:00" + }, + { + "name": "pestphp/pest-plugin-arch", + "version": "v2.7.0", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-arch.git", + "reference": "d23b2d7498475354522c3818c42ef355dca3fcda" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/d23b2d7498475354522c3818c42ef355dca3fcda", + "reference": "d23b2d7498475354522c3818c42ef355dca3fcda", + "shasum": "" + }, + "require": { + "nunomaduro/collision": "^7.10.0|^8.1.0", + "pestphp/pest-plugin": "^2.1.1", + "php": "^8.1", + "ta-tikoma/phpunit-architecture-test": "^0.8.4" + }, + "require-dev": { + "pestphp/pest": "^2.33.0", + "pestphp/pest-dev-tools": "^2.16.0" + }, + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Arch\\Plugin" + ] + } + }, + "autoload": { + "files": [ + "src/Autoload.php" + ], + "psr-4": { + "Pest\\Arch\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Arch plugin for Pest PHP.", + "keywords": [ + "arch", + "architecture", + "framework", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-arch/tree/v2.7.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2024-01-26T09:46:42+00:00" + }, + { + "name": "pestphp/pest-plugin-laravel", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-laravel.git", + "reference": "53df51169a7f9595e06839cce638c73e59ace5e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-laravel/zipball/53df51169a7f9595e06839cce638c73e59ace5e8", + "reference": "53df51169a7f9595e06839cce638c73e59ace5e8", + "shasum": "" + }, + "require": { + "laravel/framework": "^10.48.9|^11.5.0", + "pestphp/pest": "^2.34.7", + "php": "^8.1.0" + }, + "require-dev": { + "laravel/dusk": "^7.13.0", + "orchestra/testbench": "^8.22.3|^9.0.4", + "pestphp/pest-dev-tools": "^2.16.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Pest\\Laravel\\PestServiceProvider" + ] + }, + "pest": { + "plugins": [ + "Pest\\Laravel\\Plugin" + ] + } + }, + "autoload": { + "files": [ + "src/Autoload.php" + ], + "psr-4": { + "Pest\\Laravel\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Pest Laravel Plugin", + "keywords": [ + "framework", + "laravel", + "pest", + "php", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-laravel/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2024-04-27T10:41:54+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.4", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/33234b32dafa8eb69202f950a1fc92055ed76a86", + "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.4" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2023-09-08T09:24:21+00:00" + }, + { + "name": "php-di/php-di", + "version": "7.0.7", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", + "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", + "shasum": "" + }, + "require": { + "laravel/serializable-closure": "^1.0", + "php": ">=8.0", + "php-di/invoker": "^2.0", + "psr/container": "^1.1 || ^2.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3", + "friendsofphp/proxy-manager-lts": "^1", + "mnapoli/phpunit-easymock": "^1.3", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^4.6" + }, + "suggest": { + "friendsofphp/proxy-manager-lts": "Install it if you want to use lazy injection (version ^1)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/7.0.7" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2024-07-21T15:55:45+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.4.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.5", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1" + }, + "time": "2024-05-21T05:55:05+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "153ae662783729388a584b4361f2545e4d841e3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", + "reference": "153ae662783729388a584b4361f2545e4d841e3c", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.13" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" + }, + "time": "2024-02-23T11:10:43+00:00" + }, + { + "name": "phpmyadmin/sql-parser", + "version": "5.10.0", + "source": { + "type": "git", + "url": "https://github.com/phpmyadmin/sql-parser.git", + "reference": "91d980ab76c3f152481e367f62b921adc38af451" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/91d980ab76c3f152481e367f62b921adc38af451", + "reference": "91d980ab76c3f152481e367f62b921adc38af451", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "phpmyadmin/motranslator": "<3.0" + }, + "require-dev": { + "phpbench/phpbench": "^1.1", + "phpmyadmin/coding-standard": "^3.0", + "phpmyadmin/motranslator": "^4.0 || ^5.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.9.12", + "phpstan/phpstan-phpunit": "^1.3.3", + "phpunit/phpunit": "^8.5 || ^9.6", + "psalm/plugin-phpunit": "^0.16.1", + "vimeo/psalm": "^4.11", + "zumba/json-serializer": "~3.0.2" + }, + "suggest": { + "ext-mbstring": "For best performance", + "phpmyadmin/motranslator": "Translate messages to your favorite locale" + }, + "bin": [ + "bin/highlight-query", + "bin/lint-query", + "bin/sql-parser", + "bin/tokenize-query" + ], + "type": "library", + "autoload": { + "psr-4": { + "PhpMyAdmin\\SqlParser\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "The phpMyAdmin Team", + "email": "developers@phpmyadmin.net", + "homepage": "https://www.phpmyadmin.net/team/" + } + ], + "description": "A validating SQL lexer and parser with a focus on MySQL dialect.", + "homepage": "https://github.com/phpmyadmin/sql-parser", + "keywords": [ + "analysis", + "lexer", + "parser", + "query linter", + "sql", + "sql lexer", + "sql linter", + "sql parser", + "sql syntax highlighter", + "sql tokenizer" + ], + "support": { + "issues": "https://github.com/phpmyadmin/sql-parser/issues", + "source": "https://github.com/phpmyadmin/sql-parser" + }, + "funding": [ + { + "url": "https://www.phpmyadmin.net/donate/", + "type": "other" + } + ], + "time": "2024-08-29T20:56:34+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "46c8219b3fb0deb3fc08301e8f0797d321d17dcd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/46c8219b3fb0deb3fc08301e8f0797d321d17dcd", + "reference": "46c8219b3fb0deb3fc08301e8f0797d321d17dcd", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.4.2" + }, + "time": "2024-08-26T07:38:00+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.30.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "5ceb0e384997db59f38774bf79c2a6134252c08f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/5ceb0e384997db59f38774bf79c2a6134252c08f", + "reference": "5ceb0e384997db59f38774bf79c2a6134252c08f", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.0" + }, + "time": "2024-08-29T09:54:52+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.12.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2", + "reference": "d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2024-09-03T19:55:22+00:00" + }, + { + "name": "phpstan/phpstan-deprecation-rules", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", + "reference": "fa8cce7720fa782899a0aa97b6a41225d1bb7b26" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/fa8cce7720fa782899a0aa97b6a41225d1bb7b26", + "reference": "fa8cce7720fa782899a0aa97b6a41225d1bb7b26", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.11" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", + "support": { + "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.2.0" + }, + "time": "2024-04-20T06:39:48+00:00" + }, + { + "name": "phpstan/phpstan-phpunit", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-phpunit.git", + "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f3ea021866f4263f07ca3636bf22c64be9610c11", + "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.11" + }, + "conflict": { + "phpunit/phpunit": "<7.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-strict-rules": "^1.5.1", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPUnit extensions and rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-phpunit/issues", + "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.0" + }, + "time": "2024-04-20T06:39:00+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "10.1.16", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^10.1" + }, + "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": "10.1.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/10.1.16" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-22T04:31:57+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.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": "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/4.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T06:24:48+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "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": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:56:09+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.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": "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/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T14:07:24+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.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", + "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", + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:57:52+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "10.5.17", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "c1f736a473d21957ead7e94fcc029f571895abf5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c1f736a473d21957ead7e94fcc029f571895abf5", + "reference": "c1f736a473d21957ead7e94fcc029f571895abf5", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.5", + "phpunit/php-file-iterator": "^4.0", + "phpunit/php-invoker": "^4.0", + "phpunit/php-text-template": "^3.0", + "phpunit/php-timer": "^6.0", + "sebastian/cli-parser": "^2.0", + "sebastian/code-unit": "^2.0", + "sebastian/comparator": "^5.0", + "sebastian/diff": "^5.0", + "sebastian/environment": "^6.0", + "sebastian/exporter": "^5.1", + "sebastian/global-state": "^6.0.1", + "sebastian/object-enumerator": "^5.0", + "sebastian/recursion-context": "^5.0", + "sebastian/type": "^4.0", + "sebastian/version": "^4.0" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.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/10.5.17" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2024-04-05T04:39:01+00:00" + }, + { + "name": "psy/psysh", + "version": "v0.12.4", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "2fd717afa05341b4f8152547f142cd2f130f6818" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/2fd717afa05341b4f8152547f142cd2f130f6818", + "reference": "2fd717afa05341b4f8152547f142cd2f130f6818", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "nikic/php-parser": "^5.0 || ^4.0", + "php": "^8.0 || ^7.4", + "symfony/console": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" + }, + "conflict": { + "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2" + }, + "suggest": { + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-pdo-sqlite": "The doc command requires SQLite to work.", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." + }, + "bin": [ + "bin/psysh" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.12.x-dev" + }, + "bamarni-bin": { + "bin-links": false, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Psy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "An interactive shell for modern PHP.", + "homepage": "http://psysh.org", + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ], + "support": { + "issues": "https://github.com/bobthecow/psysh/issues", + "source": "https://github.com/bobthecow/psysh/tree/v0.12.4" + }, + "time": "2024-06-10T01:18:23+00:00" + }, + { + "name": "rector/rector", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "42a4aa23b48b4cfc8ebfeac2b570364e27744381" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/42a4aa23b48b4cfc8ebfeac2b570364e27744381", + "reference": "42a4aa23b48b4cfc8ebfeac2b570364e27744381", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.11.11" + }, + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" + }, + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "keywords": [ + "automation", + "dev", + "migration", + "refactoring" + ], + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/1.2.4" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2024-08-23T09:03:01+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.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/2.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:12:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.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", + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:58:43+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.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" + } + ], + "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", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:59:15+00:00" + }, + { + "name": "sebastian/comparator", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", + "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.4" + }, + "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" + }, + { + "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/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-12T06:03:08+00:00" + }, + { + "name": "sebastian/complexity", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "68ff824baeae169ec9f2137158ee529584553799" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.2-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/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:37:17+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" + }, + "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" + }, + { + "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/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:15:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "6.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "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/6.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-23T08:47:14+00:00" + }, + { + "name": "sebastian/exporter", + "version": "5.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "955288482d97c19a372d3f31006ab3f37da47adf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", + "reference": "955288482d97c19a372d3f31006ab3f37da47adf", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "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" + }, + { + "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/5.1.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:17:12+00:00" + }, + { + "name": "sebastian/global-state", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^10.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": "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/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:19:19+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.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/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:38:20+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "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", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:08:32+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.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" + } + ], + "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", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:06:18+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "05909fb5bc7df4c52992396d0116aed689f93712" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", + "reference": "05909fb5bc7df4c52992396d0116aed689f93712", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "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", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:05:40+00:00" + }, + { + "name": "sebastian/type", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.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": "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", + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:10:45+00:00" + }, + { + "name": "sebastian/version", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "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 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", + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-07T11:34:05+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "8.15.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "7d1d957421618a3803b593ec31ace470177d7817" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/7d1d957421618a3803b593ec31ace470177d7817", + "reference": "7d1d957421618a3803b593ec31ace470177d7817", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.23.1", + "squizlabs/php_codesniffer": "^3.9.0" + }, + "require-dev": { + "phing/phing": "2.17.4", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.10.60", + "phpstan/phpstan-deprecation-rules": "1.1.4", + "phpstan/phpstan-phpunit": "1.3.16", + "phpstan/phpstan-strict-rules": "1.5.2", + "phpunit/phpunit": "8.5.21|9.6.8|10.5.11" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "keywords": [ + "dev", + "phpcs" + ], + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/8.15.0" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2024-03-09T15:20:58+00:00" + }, + { + "name": "spatie/backtrace", + "version": "1.6.2", + "source": { + "type": "git", + "url": "https://github.com/spatie/backtrace.git", + "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/1a9a145b044677ae3424693f7b06479fc8c137a9", + "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "ext-json": "*", + "laravel/serializable-closure": "^1.3", + "phpunit/phpunit": "^9.3", + "spatie/phpunit-snapshot-assertions": "^4.2", + "symfony/var-dumper": "^5.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Backtrace\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van de Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "A better backtrace", + "homepage": "https://github.com/spatie/backtrace", + "keywords": [ + "Backtrace", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/backtrace/tree/1.6.2" + }, + "funding": [ + { + "url": "https://github.com/sponsors/spatie", + "type": "github" + }, + { + "url": "https://spatie.be/open-source/support-us", + "type": "other" + } + ], + "time": "2024-07-22T08:21:24+00:00" + }, + { + "name": "spatie/laravel-ray", + "version": "1.37.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-ray.git", + "reference": "c2bedfd1172648df2c80aaceb2541d70f1d9a5b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/c2bedfd1172648df2c80aaceb2541d70f1d9a5b9", + "reference": "c2bedfd1172648df2c80aaceb2541d70f1d9a5b9", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/contracts": "^7.20|^8.19|^9.0|^10.0|^11.0", + "illuminate/database": "^7.20|^8.19|^9.0|^10.0|^11.0", + "illuminate/queue": "^7.20|^8.19|^9.0|^10.0|^11.0", + "illuminate/support": "^7.20|^8.19|^9.0|^10.0|^11.0", + "php": "^7.4|^8.0", + "rector/rector": "^0.19.2|^1.0", + "spatie/backtrace": "^1.0", + "spatie/ray": "^1.41.1", + "symfony/stopwatch": "4.2|^5.1|^6.0|^7.0", + "zbateson/mail-mime-parser": "^1.3.1|^2.0|^3.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.3", + "laravel/framework": "^7.20|^8.19|^9.0|^10.0|^11.0", + "orchestra/testbench-core": "^5.0|^6.0|^7.0|^8.0|^9.0", + "pestphp/pest": "^1.22|^2.0", + "phpstan/phpstan": "^1.10.57", + "phpunit/phpunit": "^9.3|^10.1", + "spatie/pest-plugin-snapshots": "^1.1|^2.0", + "symfony/var-dumper": "^4.2|^5.1|^6.0|^7.0.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + }, + "laravel": { + "providers": [ + "Spatie\\LaravelRay\\RayServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\LaravelRay\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily debug Laravel apps", + "homepage": "https://github.com/spatie/laravel-ray", + "keywords": [ + "laravel-ray", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-ray/issues", + "source": "https://github.com/spatie/laravel-ray/tree/1.37.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/spatie", + "type": "github" + }, + { + "url": "https://spatie.be/open-source/support-us", + "type": "other" + } + ], + "time": "2024-07-12T12:35:17+00:00" + }, + { + "name": "spatie/macroable", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/macroable.git", + "reference": "ec2c320f932e730607aff8052c44183cf3ecb072" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/macroable/zipball/ec2c320f932e730607aff8052c44183cf3ecb072", + "reference": "ec2c320f932e730607aff8052c44183cf3ecb072", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.0|^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Macroable\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "A trait to dynamically add methods to a class", + "homepage": "https://github.com/spatie/macroable", + "keywords": [ + "macroable", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/macroable/issues", + "source": "https://github.com/spatie/macroable/tree/2.0.0" + }, + "time": "2021-03-26T22:39:02+00:00" + }, + { + "name": "spatie/ray", + "version": "1.41.2", + "source": { + "type": "git", + "url": "https://github.com/spatie/ray.git", + "reference": "c44f8cfbf82c69909b505de61d8d3f2d324e93fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/ray/zipball/c44f8cfbf82c69909b505de61d8d3f2d324e93fc", + "reference": "c44f8cfbf82c69909b505de61d8d3f2d324e93fc", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "php": "^7.3|^8.0", + "ramsey/uuid": "^3.0|^4.1", + "spatie/backtrace": "^1.1", + "spatie/macroable": "^1.0|^2.0", + "symfony/stopwatch": "^4.0|^5.1|^6.0|^7.0", + "symfony/var-dumper": "^4.2|^5.1|^6.0|^7.0.3" + }, + "require-dev": { + "illuminate/support": "6.x|^8.18|^9.0", + "nesbot/carbon": "^2.63", + "pestphp/pest": "^1.22", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.19.2", + "spatie/phpunit-snapshot-assertions": "^4.2", + "spatie/test-time": "^1.2" + }, + "bin": [ + "bin/remove-ray.sh" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Ray\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Debug with Ray to fix problems faster", + "homepage": "https://github.com/spatie/ray", + "keywords": [ + "ray", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/ray/issues", + "source": "https://github.com/spatie/ray/tree/1.41.2" + }, + "funding": [ + { + "url": "https://github.com/sponsors/spatie", + "type": "github" + }, + { + "url": "https://spatie.be/open-source/support-us", + "type": "other" + } + ], + "time": "2024-04-24T14:21:46+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.10.2", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/86e5f5dd9a840c46810ebe5ff1885581c42a3017", + "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-07-21T23:26:44+00:00" + }, + { + "name": "symfony/polyfill-iconv", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-iconv.git", + "reference": "c027e6a3c6aee334663ec21f5852e89738abc805" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/c027e6a3c6aee334663ec21f5852e89738abc805", + "reference": "c027e6a3c6aee334663ec21f5852e89738abc805", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-iconv": "*" + }, + "suggest": { + "ext-iconv": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Iconv\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Iconv extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "iconv", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d", + "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, + { + "name": "symfony/yaml", + "version": "v7.1.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "92e080b851c1c655c786a2da77f188f2dccd0f4b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/92e080b851c1c655c786a2da77f188f2dccd0f4b", + "reference": "92e080b851c1c655c786a2da77f188f2dccd0f4b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v7.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-08-12T09:59:40+00:00" + }, + { + "name": "ta-tikoma/phpunit-architecture-test", + "version": "0.8.4", + "source": { + "type": "git", + "url": "https://github.com/ta-tikoma/phpunit-architecture-test.git", + "reference": "89f0dea1cb0f0d5744d3ec1764a286af5e006636" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/89f0dea1cb0f0d5744d3ec1764a286af5e006636", + "reference": "89f0dea1cb0f0d5744d3ec1764a286af5e006636", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18.0 || ^5.0.0", + "php": "^8.1.0", + "phpdocumentor/reflection-docblock": "^5.3.0", + "phpunit/phpunit": "^10.5.5 || ^11.0.0", + "symfony/finder": "^6.4.0 || ^7.0.0" + }, + "require-dev": { + "laravel/pint": "^1.13.7", + "phpstan/phpstan": "^1.10.52" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPUnit\\Architecture\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ni Shi", + "email": "futik0ma011@gmail.com" + }, + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Methods for testing application architecture", + "keywords": [ + "architecture", + "phpunit", + "stucture", + "test", + "testing" + ], + "support": { + "issues": "https://github.com/ta-tikoma/phpunit-architecture-test/issues", + "source": "https://github.com/ta-tikoma/phpunit-architecture-test/tree/0.8.4" + }, + "time": "2024-01-05T14:10:56+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + }, + { + "name": "zbateson/mail-mime-parser", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/zbateson/mail-mime-parser.git", + "reference": "e0d4423fe27850c9dd301190767dbc421acc2f19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/e0d4423fe27850c9dd301190767dbc421acc2f19", + "reference": "e0d4423fe27850c9dd301190767dbc421acc2f19", + "shasum": "" + }, + "require": { + "guzzlehttp/psr7": "^2.5", + "php": ">=8.0", + "php-di/php-di": "^6.0|^7.0", + "psr/log": "^1|^2|^3", + "zbateson/mb-wrapper": "^2.0", + "zbateson/stream-decorators": "^2.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "*", + "monolog/monolog": "^2|^3", + "phpstan/phpstan": "*", + "phpunit/phpunit": "^9.6" + }, + "suggest": { + "ext-iconv": "For best support/performance", + "ext-mbstring": "For best support/performance" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZBateson\\MailMimeParser\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Zaahid Bateson" + }, + { + "name": "Contributors", + "homepage": "https://github.com/zbateson/mail-mime-parser/graphs/contributors" + } + ], + "description": "MIME email message parser", + "homepage": "https://mail-mime-parser.org", + "keywords": [ + "MimeMailParser", + "email", + "mail", + "mailparse", + "mime", + "mimeparse", + "parser", + "php-imap" + ], + "support": { + "docs": "https://mail-mime-parser.org/#usage-guide", + "issues": "https://github.com/zbateson/mail-mime-parser/issues", + "source": "https://github.com/zbateson/mail-mime-parser" + }, + "funding": [ + { + "url": "https://github.com/zbateson", + "type": "github" + } + ], + "time": "2024-08-10T18:44:09+00:00" + }, + { + "name": "zbateson/mb-wrapper", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/zbateson/mb-wrapper.git", + "reference": "9e4373a153585d12b6c621ac4a6bb143264d4619" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zbateson/mb-wrapper/zipball/9e4373a153585d12b6c621ac4a6bb143264d4619", + "reference": "9e4373a153585d12b6c621ac4a6bb143264d4619", + "shasum": "" + }, + "require": { + "php": ">=8.0", + "symfony/polyfill-iconv": "^1.9", + "symfony/polyfill-mbstring": "^1.9" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "*", + "phpstan/phpstan": "*", + "phpunit/phpunit": "<10.0" + }, + "suggest": { + "ext-iconv": "For best support/performance", + "ext-mbstring": "For best support/performance" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZBateson\\MbWrapper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Zaahid Bateson" + } + ], + "description": "Wrapper for mbstring with fallback to iconv for encoding conversion and string manipulation", + "keywords": [ + "charset", + "encoding", + "http", + "iconv", + "mail", + "mb", + "mb_convert_encoding", + "mbstring", + "mime", + "multibyte", + "string" + ], + "support": { + "issues": "https://github.com/zbateson/mb-wrapper/issues", + "source": "https://github.com/zbateson/mb-wrapper/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/zbateson", + "type": "github" + } + ], + "time": "2024-03-20T01:38:07+00:00" + }, + { + "name": "zbateson/stream-decorators", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/zbateson/stream-decorators.git", + "reference": "32a2a62fb0f26313395c996ebd658d33c3f9c4e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zbateson/stream-decorators/zipball/32a2a62fb0f26313395c996ebd658d33c3f9c4e5", + "reference": "32a2a62fb0f26313395c996ebd658d33c3f9c4e5", + "shasum": "" + }, + "require": { + "guzzlehttp/psr7": "^2.5", + "php": ">=8.0", + "zbateson/mb-wrapper": "^2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "*", + "phpstan/phpstan": "*", + "phpunit/phpunit": "^9.6|^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZBateson\\StreamDecorators\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Zaahid Bateson" + } + ], + "description": "PHP psr7 stream decorators for mime message part streams", + "keywords": [ + "base64", + "charset", + "decorators", + "mail", + "mime", + "psr7", + "quoted-printable", + "stream", + "uuencode" + ], + "support": { + "issues": "https://github.com/zbateson/stream-decorators/issues", + "source": "https://github.com/zbateson/stream-decorators/tree/2.1.1" + }, + "funding": [ + { + "url": "https://github.com/zbateson", + "type": "github" + } + ], + "time": "2024-04-29T21:42:39+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "doctrine/coding-standard": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.2" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..9bc2de6 --- /dev/null +++ b/flake.lock @@ -0,0 +1,576 @@ +{ + "nodes": { + "agenix": { + "inputs": { + "darwin": "darwin", + "home-manager": "home-manager", + "nixpkgs": "nixpkgs_2", + "systems": "systems" + }, + "locked": { + "lastModified": 1722339003, + "narHash": "sha256-ZeS51uJI30ehNkcZ4uKqT4ZDARPyqrHADSKAwv5vVCU=", + "owner": "ryantm", + "repo": "agenix", + "rev": "3f1dae074a12feb7327b4bf43cbac0d124488bb7", + "type": "github" + }, + "original": { + "owner": "ryantm", + "repo": "agenix", + "type": "github" + } + }, + "darwin": { + "inputs": { + "nixpkgs": [ + "snow-blower", + "agenix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1700795494, + "narHash": "sha256-gzGLZSiOhf155FW7262kdHo2YDeugp3VuIFb4/GGng0=", + "owner": "lnl7", + "repo": "nix-darwin", + "rev": "4b9b83d5a92e8c1fbfd8eb27eda375908c11ec4d", + "type": "github" + }, + "original": { + "owner": "lnl7", + "ref": "master", + "repo": "nix-darwin", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1719994518, + "narHash": "sha256-pQMhCCHyQGRzdfAkdJ4cIWiw+JNuWsTX7f0ZYSyz0VY=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "9227223f6d922fee3c7b190b2cc238a99527bbb7", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": [ + "snow-blower", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1719994518, + "narHash": "sha256-pQMhCCHyQGRzdfAkdJ4cIWiw+JNuWsTX7f0ZYSyz0VY=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "9227223f6d922fee3c7b190b2cc238a99527bbb7", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-root": { + "locked": { + "lastModified": 1713493429, + "narHash": "sha256-ztz8JQkI08tjKnsTpfLqzWoKFQF4JGu2LRz8bkdnYUk=", + "owner": "srid", + "repo": "flake-root", + "rev": "bc748b93b86ee76e2032eecda33440ceb2532fcd", + "type": "github" + }, + "original": { + "owner": "srid", + "repo": "flake-root", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1653893745, + "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "locked": { + "lastModified": 1653893745, + "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_3": { + "locked": { + "lastModified": 1653893745, + "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_4": { + "locked": { + "lastModified": 1653893745, + "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_5": { + "locked": { + "lastModified": 1653893745, + "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "snow-blower", + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1721042469, + "narHash": "sha256-6FPUl7HVtvRHCCBQne7Ylp4p+dpP3P/OYuzjztZ4s70=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "f451c19376071a90d8c58ab1a953c6e9840527fd", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "snow-blower", + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "home-manager": { + "inputs": { + "nixpkgs": [ + "snow-blower", + "agenix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1703113217, + "narHash": "sha256-7ulcXOk63TIT2lVDSExj7XzFx09LpdSAPtvgtM7yQPE=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "3bfaacf46133c037bb356193bd2f1765d9dc82c1", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, + "nixago": { + "inputs": { + "flake-utils": "flake-utils", + "nixago-exts": "nixago-exts", + "nixpkgs": [ + "snow-blower", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1714086354, + "narHash": "sha256-yKVQMxL9p7zCWUhnGhDzRVT8sDgHoI3V595lBK0C2YA=", + "owner": "nix-community", + "repo": "nixago", + "rev": "5133633e9fe6b144c8e00e3b212cdbd5a173b63d", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixago", + "type": "github" + } + }, + "nixago-exts": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixago": "nixago_2", + "nixpkgs": [ + "snow-blower", + "nixago", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1676070308, + "narHash": "sha256-QaJ65oc2l8iwQIGWUJ0EKjCeSuuCM/LqR8RauxZUUkc=", + "owner": "nix-community", + "repo": "nixago-extensions", + "rev": "e5380cb0456f4ea3c86cf94e3039eb856bf07d0b", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixago-extensions", + "type": "github" + } + }, + "nixago-exts_2": { + "inputs": { + "flake-utils": "flake-utils_4", + "nixago": "nixago_3", + "nixpkgs": [ + "snow-blower", + "nixago", + "nixago-exts", + "nixago", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1655508669, + "narHash": "sha256-BDDdo5dZQMmwNH/GNacy33nPBnCpSIydWFPZs0kkj/g=", + "owner": "nix-community", + "repo": "nixago-extensions", + "rev": "3022a932ce109258482ecc6568c163e8d0b426aa", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixago-extensions", + "type": "github" + } + }, + "nixago_2": { + "inputs": { + "flake-utils": "flake-utils_3", + "nixago-exts": "nixago-exts_2", + "nixpkgs": [ + "snow-blower", + "nixago", + "nixago-exts", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1676070010, + "narHash": "sha256-iYzJIWptE1EUD8VINAg66AAMUajizg8JUYN3oBmb8no=", + "owner": "nix-community", + "repo": "nixago", + "rev": "d480ba6c0c16e2c5c0bd2122852d6a0c9ad1ed0e", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "rename-config-data", + "repo": "nixago", + "type": "github" + } + }, + "nixago_3": { + "inputs": { + "flake-utils": "flake-utils_5", + "nixpkgs": [ + "snow-blower", + "nixago", + "nixago-exts", + "nixago", + "nixago-exts", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1655405483, + "narHash": "sha256-Crd49aZWNrpczlRTOwWGfwBMsTUoG9vlHDKQC7cx264=", + "owner": "nix-community", + "repo": "nixago", + "rev": "e6a9566c18063db5b120e69e048d3627414e327d", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixago", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1722185531, + "narHash": "sha256-veKR07psFoJjINLC8RK4DiLniGGMgF3QMlS4tb74S6k=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "52ec9ac3b12395ad677e8b62106f0b98c1f8569d", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1719876945, + "narHash": "sha256-Fm2rDDs86sHy0/1jxTOKB1118Q0O3Uc7EC0iXvXKpbI=", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/5daf0514482af3f97abaefc78a6606365c9108e2.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/5daf0514482af3f97abaefc78a6606365c9108e2.tar.gz" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1720386169, + "narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "194846768975b7ad2c4988bdb82572c00222c0d7", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1703013332, + "narHash": "sha256-+tFNwMvlXLbJZXiMHqYq77z/RfmpfpiI3yjL6o/Zo9M=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "54aac082a4d9bb5bbc5c4e899603abfb76a3f6d6", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1722185531, + "narHash": "sha256-veKR07psFoJjINLC8RK4DiLniGGMgF3QMlS4tb74S6k=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "52ec9ac3b12395ad677e8b62106f0b98c1f8569d", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_4": { + "locked": { + "lastModified": 1722185531, + "narHash": "sha256-veKR07psFoJjINLC8RK4DiLniGGMgF3QMlS4tb74S6k=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "52ec9ac3b12395ad677e8b62106f0b98c1f8569d", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "process-compose-flake": { + "locked": { + "lastModified": 1718031437, + "narHash": "sha256-+RrlkAVZx0QhyeHAGFJnjST+/7Dc3zsDU3zAKXoDXaI=", + "owner": "Platonic-Systems", + "repo": "process-compose-flake", + "rev": "9344fac44edced4c686721686a6ad904d067c546", + "type": "github" + }, + "original": { + "owner": "Platonic-Systems", + "repo": "process-compose-flake", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs", + "snow-blower": "snow-blower" + } + }, + "snow-blower": { + "inputs": { + "agenix": "agenix", + "flake-parts": "flake-parts_2", + "flake-root": "flake-root", + "git-hooks": "git-hooks", + "nixago": "nixago", + "nixpkgs": "nixpkgs_3", + "process-compose-flake": "process-compose-flake", + "systems": "systems_2", + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1722456547, + "narHash": "sha256-pudMUqtmALLZO/mpSPhxwhDdDIb6LTUqoLqJ1wHT4D4=", + "owner": "use-the-fork", + "repo": "snow-blower", + "rev": "94267dece6a9da3978f19be65bd739f275a649e4", + "type": "github" + }, + "original": { + "owner": "use-the-fork", + "repo": "snow-blower", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1689347949, + "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", + "owner": "nix-systems", + "repo": "default-linux", + "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default-linux", + "type": "github" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": "nixpkgs_4" + }, + "locked": { + "lastModified": 1718522839, + "narHash": "sha256-ULzoKzEaBOiLRtjeY3YoGFJMwWSKRYOic6VNw2UyTls=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "68eb1dc333ce82d0ab0c0357363ea17c31ea1f81", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..f72cca3 --- /dev/null +++ b/flake.nix @@ -0,0 +1,148 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + snow-blower.url = "github:use-the-fork/snow-blower"; + }; + + outputs = inputs: + inputs.snow-blower.mkSnowBlower { + inherit inputs; + perSystem = { + config, + lib, + ... + }: let + rootDir = config.snow-blower.paths.root; + serv = config.snow-blower.services; + lang = config.snow-blower.languages; + + # Refrences PHP and Composer later in this config. + composer = "${lang.php.packages.composer}/bin/composer"; + php = "${lang.php.package}/bin/php"; + + envKeys = builtins.attrNames config.snow-blower.env; + unsetEnv = builtins.concatStringsSep "\n" ( + map (key: "unset ${key}") envKeys + ); + in { + snow-blower = { + paths.src = ./.; + + # Conviance scripts + scripts = { + pf.exec = '' + ${unsetEnv} + ./vendor/bin/pest --filter "$@" + ''; + p.exec = '' + ${unsetEnv} + ./vendor/bin/pest + ''; + coverage.exec = '' + export XDEBUG_MODE=coverage + ./vendor/bin/pest --coverage + unset XDEBUG_MODE + ''; + + # swap a and artisan commands for testbench + a.exec = '' + ${unsetEnv} + ./vendor/bin/testbench "$@" + ''; + artisan.exec = '' + ${unsetEnv} + ./vendor/bin/testbench "$@" + ''; + }; + + languages = { + # the required version of PHP for this project. + php = { + enable = true; + version = "8.2"; + extensions = ["grpc" "redis" "imagick" "memcached" "xdebug"]; + ini = '' + memory_limit = 5G + max_execution_time = 90 + ''; + }; + }; + + services = { + # Elasticsearch service for testing + elasticsearch = { + enable = true; + }; + }; + + integrations = { + #Creates Changelogs based on commits + git-cliff.enable = true; + + treefmt = { + settings.formatter = { + # Laravel Pint Formating + "laravel-pint" = { + command = "${php}"; + options = [ + "${rootDir}/vendor/bin/pint" + #make it verbose + "-v" + "--repair" + ]; + includes = ["*.php"]; + }; + }; + + programs = { + #Nix Formater + alejandra.enable = true; + + #Format Markdown files. + mdformat.enable = true; + + #JS / CSS Formatting. + prettier = { + enable = true; + settings = { + trailingComma = "es5"; + semi = true; + singleQuote = true; + jsxSingleQuote = true; + bracketSpacing = true; + printWidth = 80; + tabWidth = 2; + endOfLine = "lf"; + }; + }; + }; + }; + + # Guess what this does. Go ahead Guess. + git-hooks.hooks = { + # run formatting on files that are being commited + treefmt.enable = true; + + # Code linting + phpstan.enable = false; + + #lets make sure there are no keys in the repo + detect-private-keys.enable = true; + + #fix line endings. + mixed-line-endings.enable = true; + }; + }; + + shell.interactive = [ + '' + if [[ ! -d vendor ]]; then + ${composer} install + fi + '' + ]; + }; + }; + }; +} diff --git a/justfile b/justfile new file mode 100644 index 0000000..4343761 --- /dev/null +++ b/justfile @@ -0,0 +1,5 @@ +import 'just-flake.just' + +# Display the list of recipes +default: + @just --list diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..e69de29 diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..7f25c7b --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,10 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: 5 + paths: + - src + tmpDir: build/phpstan + ignoreErrors: + - '#Return type .* of method .* should be compatible with return type .* of method#' diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..0455ff1 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,24 @@ + + + + + ./tests + + + + + + + + + + + + + + + + src/ + + + diff --git a/src/Collection/ElasticCollection.php b/src/Collection/ElasticCollection.php new file mode 100644 index 0000000..ea2f3d7 --- /dev/null +++ b/src/Collection/ElasticCollection.php @@ -0,0 +1,25 @@ + + */ +class ElasticCollection extends Collection +{ + use ElasticCollectionMeta; + + /** + * @param Arrayable|iterable|array|null $items + */ + public function __construct($items = []) + { + parent::__construct($items); + } +} diff --git a/src/Collection/ElasticCollectionMeta.php b/src/Collection/ElasticCollectionMeta.php new file mode 100644 index 0000000..e878cd6 --- /dev/null +++ b/src/Collection/ElasticCollectionMeta.php @@ -0,0 +1,58 @@ +meta = $meta; + } + + public function getQueryMeta(): QueryMetaData + { + return $this->meta; + } + + public function getQueryMetaAsArray(): array + { + return $this->meta->asArray(); + } + + public function getDsl(): array + { + return [ + 'query' => $this->meta->getQuery(), + 'dsl' => $this->meta->getDsl(), + ]; + } + + public function getTook(): int + { + return $this->meta->getTook(); + } + + public function getShards(): mixed + { + return $this->meta->getShards(); + } + + public function getTotal(): int + { + return $this->meta->getTotal(); + } + + public function getMaxScore(): string + { + return $this->meta->getMaxScore(); + } + + public function getResults(): array + { + return $this->meta->getResults(); + } +} diff --git a/src/Collection/ElasticResult.php b/src/Collection/ElasticResult.php new file mode 100644 index 0000000..0446470 --- /dev/null +++ b/src/Collection/ElasticResult.php @@ -0,0 +1,38 @@ +value = $value; + } + + public function setValue($value): void + { + $this->value = $value; + } + + public function getValue(): mixed + { + return $this->value; + } + + public function __invoke() + { + return $this->value; + } + + public function __toString() + { + return (string) $this->value; + } +} diff --git a/src/Collection/LazyElasticCollection.php b/src/Collection/LazyElasticCollection.php new file mode 100644 index 0000000..0878d64 --- /dev/null +++ b/src/Collection/LazyElasticCollection.php @@ -0,0 +1,16 @@ + + */ +class LazyElasticCollection extends LazyCollection +{ + use ElasticCollectionMeta; +} diff --git a/src/Connection.php b/src/Connection.php index a716a41..f824599 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -1,34 +1,77 @@ connectionName = $config['name']; $this->config = $config; @@ -37,17 +80,16 @@ public function __construct(array $config) $this->client = $this->buildConnection(); - $this->useDefaultPostProcessor(); + $this->postProcessor = new Query\Processor; $this->useDefaultSchemaGrammar(); $this->useDefaultQueryGrammar(); - } public function setOptions($config) { - if (!empty($config['index_prefix'])) { + if (! empty($config['index_prefix'])) { $this->indexPrefix = $config['index_prefix']; } if (isset($config['options']['allow_id_sort'])) { @@ -56,13 +98,13 @@ public function setOptions($config) if (isset($config['options']['ssl_verification'])) { $this->sslVerification = $config['options']['ssl_verification']; } - if (!empty($config['options']['retires'])) { + if (! empty($config['options']['retires'])) { $this->retires = $config['options']['retires']; } if (isset($config['options']['meta_header'])) { $this->elasticMetaHeader = $config['options']['meta_header']; } - if (!empty($config['error_log_index'])) { + if (! empty($config['error_log_index'])) { if ($this->indexPrefix) { $this->errorLoggingIndex = $this->indexPrefix.'_'.$config['error_log_index']; } else { @@ -71,42 +113,45 @@ public function setOptions($config) } } - public function getIndexPrefix(): string|null + protected function buildConnection(): Client { - return $this->indexPrefix; + $type = config('database.connections.elasticsearch.auth_type') ?? null; + $type = strtolower($type); + if (! in_array($type, ['http', 'cloud'])) { + throw new RuntimeException('Invalid [auth_type] in database config. Must be: http, cloud or api'); + } + + return $this->{'_'.$type.'Connection'}(); } - public function setIndexPrefix($newPrefix): void + public function getTablePrefix(): ?string { - $this->indexPrefix = $newPrefix; + return $this->getIndexPrefix(); } - - public function getTablePrefix(): string|null + public function getIndexPrefix(): ?string { - return $this->getIndexPrefix(); + return $this->indexPrefix; } - public function getErrorLoggingIndex(): string|bool + public function getPostProcessor(): Query\Processor { - return $this->errorLoggingIndex; + return $this->postProcessor; } - public function setIndex($index): string + public function setIndexPrefix($newPrefix): void { - $this->index = $index; - if ($this->indexPrefix) { - if (!(str_contains($this->index, $this->indexPrefix.'_'))) { - $this->index = $this->indexPrefix.'_'.$index; - } - } + $this->indexPrefix = $newPrefix; + } - return $this->getIndex(); + public function getErrorLoggingIndex(): ?string + { + return $this->errorLoggingIndex; } - public function getSchemaGrammar() + public function getSchemaGrammar(): Schema\Grammar { - return new Schema\Grammar($this); + return new Schema\Grammar; } public function getIndex(): string @@ -114,104 +159,114 @@ public function getIndex(): string return $this->index; } - public function setMaxSize($value) + public function setIndex($index): string { - $this->maxSize = $value; - } + $this->index = $index; + if ($this->indexPrefix) { + if (! (str_contains($this->index, $this->indexPrefix.'_'))) { + $this->index = $this->indexPrefix.'_'.$index; + } + } + return $this->getIndex(); + } public function table($table, $as = null) { - $query = new Query\Builder($this, new Query\Processor()); + $query = new Query\Builder($this, new Query\Processor); return $query->from($table); } /** - * @inheritdoc + * Override the default schema builder. */ - public function getSchemaBuilder() + public function getSchemaBuilder(): Schema\Builder { return new Schema\Builder($this); } - /** - * @inheritdoc + * {@inheritdoc} */ - public function disconnect() + public function disconnect(): void { unset($this->connection); } - /** - * @inheritdoc + * {@inheritdoc} */ public function getDriverName(): string { return 'elasticsearch'; } - /** - * @inheritdoc - */ - protected function getDefaultPostProcessor() + public function rebuildConnection(): void { - return new Query\Processor(); + $this->rebuild = true; } - /** - * @inheritdoc - */ - protected function getDefaultQueryGrammar() + public function getClient(): Client { - return new Query\Grammar(); + return $this->client; } - /** - * @inheritdoc - */ - protected function getDefaultSchemaGrammar() + public function getMaxSize(): int { - return new Schema\Grammar(); + return $this->maxSize; } - public function rebuildConnection() + public function setMaxSize($value): void { - $this->rebuild = true; + $this->maxSize = $value; } - public function getClient() + public function getAllowIdSort(): bool { - return $this->client; + return $this->allowIdSort; } - public function getMaxSize() + public function __call($method, $parameters) { - return $this->maxSize; + if (! $this->index) { + $this->index = $this->indexPrefix.'*'; + } + if ($this->rebuild) { + $this->client = $this->buildConnection(); + $this->rebuild = false; + } + $bridge = new Bridge($this); + + return $bridge->{'process'.Str::studly($method)}(...$parameters); } - public function getAllowIdSort() + /** + * {@inheritdoc} + */ + protected function getDefaultPostProcessor(): Query\Processor { - return $this->allowIdSort; + return new Query\Processor; } - //---------------------------------------------------------------------- // Connection Builder //---------------------------------------------------------------------- - protected function buildConnection(): Client + /** + * {@inheritdoc} + */ + protected function getDefaultQueryGrammar(): Query\Grammar { - $type = config('database.connections.elasticsearch.auth_type') ?? null; - $type = strtolower($type); - if (!in_array($type, ['http', 'cloud'])) { - throw new RuntimeException('Invalid [auth_type] in database config. Must be: http, cloud or api'); - } - - return $this->{'_'.$type.'Connection'}(); + return new Query\Grammar; + } + /** + * {@inheritdoc} + */ + protected function getDefaultSchemaGrammar(): Schema\Grammar + { + return new Schema\Grammar; } protected function _httpConnection(): Client @@ -233,27 +288,6 @@ protected function _httpConnection(): Client return $cb->build(); } - protected function _cloudConnection(): Client - { - $cloudId = config('database.connections.'.$this->connectionName.'.cloud_id') ?? null; - $username = config('database.connections.'.$this->connectionName.'.username') ?? null; - $pass = config('database.connections.'.$this->connectionName.'.password') ?? null; - $apiId = config('database.connections.'.$this->connectionName.'.api_id') ?? null; - $apiKey = config('database.connections.'.$this->connectionName.'.api_key') ?? null; - - $cb = ClientBuilder::create()->setElasticCloudId($cloudId); - $cb = $this->_builderOptions($cb); - if ($username && $pass) { - $cb->setBasicAuthentication($username, $pass); - } - if ($apiKey) { - $cb->setApiKey($apiKey, $apiId); - } - - - return $cb->build(); - } - protected function _builderOptions($cb) { $cb->setSSLVerification($this->sslVerification); @@ -282,22 +316,27 @@ protected function _builderOptions($cb) return $cb; } - //---------------------------------------------------------------------- // Dynamic call routing to DSL bridge //---------------------------------------------------------------------- - public function __call($method, $parameters) + protected function _cloudConnection(): Client { - if (!$this->index) { - $this->index = $this->indexPrefix.'*'; + $cloudId = config('database.connections.'.$this->connectionName.'.cloud_id') ?? null; + $username = config('database.connections.'.$this->connectionName.'.username') ?? null; + $pass = config('database.connections.'.$this->connectionName.'.password') ?? null; + $apiId = config('database.connections.'.$this->connectionName.'.api_id') ?? null; + $apiKey = config('database.connections.'.$this->connectionName.'.api_key') ?? null; + + $cb = ClientBuilder::create()->setElasticCloudId($cloudId); + $cb = $this->_builderOptions($cb); + if ($username && $pass) { + $cb->setBasicAuthentication($username, $pass); } - if ($this->rebuild) { - $this->client = $this->buildConnection(); - $this->rebuild = false; + if ($apiKey) { + $cb->setApiKey($apiKey, $apiId); } - $bridge = new Bridge($this); - return $bridge->{'process'.Str::studly($method)}(...$parameters); + return $cb->build(); } } diff --git a/src/DSL/Bridge.php b/src/DSL/Bridge.php index bf926ab..331716b 100644 --- a/src/DSL/Bridge.php +++ b/src/DSL/Bridge.php @@ -1,33 +1,35 @@ maxSize = $this->connection->getMaxSize(); $this->indexPrefix = $this->connection->getIndexPrefix(); $this->errorLogger = $this->connection->getErrorLoggingIndex(); - } - //---------------------------------------------------------------------- + //====================================================================== // PIT - //---------------------------------------------------------------------- + //====================================================================== /** * @throws QueryException @@ -50,34 +51,41 @@ public function __construct(Connection $connection) public function processOpenPit($keepAlive = '5m'): string { $params = [ - 'index' => $this->index, + 'index' => $this->index, 'keep_alive' => $keepAlive, ]; + $res = []; try { $process = $this->client->openPointInTime($params); $res = $process->asArray(); - if (!empty($res['id'])) { - return $res['id']; + if (empty($res['id'])) { + throw new Exception('Error on PIT creation. No ID returned.'); } - - throw new Exception('Error on PIT creation. No ID returned.'); } catch (Exception $e) { - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + return $res['id']; } /** * @throws QueryException + * @throws ParameterException */ - public function processPitFind($wheres, $options, $columns, $pitId, $searchAfter = false, $keepAlive = '5m') - { + public function processPitFind( + $wheres, + $options, + $columns, + $pitId, + $searchAfter = false, + $keepAlive = '5m' + ): Results { $params = $this->buildParams($this->index, $wheres, $options, $columns); unset($params['index']); $params['body']['pit'] = [ - 'id' => $pitId, + 'id' => $pitId, 'keep_alive' => $keepAlive, ]; if (empty($params['body']['sort'])) { @@ -89,47 +97,41 @@ public function processPitFind($wheres, $options, $columns, $pitId, $searchAfter if ($searchAfter) { $params['body']['search_after'] = $searchAfter; } + $process = []; try { $process = $this->client->search($params); - - return $this->_sanitizePitSearchResponse($process, $params, $this->_queryTag(__FUNCTION__)); - - } catch (Exception $e) { - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } - + return $this->_sanitizePitSearchResponse($process, $params, $this->_queryTag(__FUNCTION__)); } - /** * @throws QueryException */ public function processClosePit($id): bool { - $params = [ 'index' => $this->index, - 'body' => [ + 'body' => [ 'id' => $id, ], - ]; + $res = []; try { $process = $this->client->closePointInTime($params); $res = $process->asArray(); - - return $res['succeeded']; - } catch (Exception $e) { - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + + return $res['succeeded']; } - //---------------------------------------------------------------------- - // BYO Query - //---------------------------------------------------------------------- + //====================================================================== + // BYO Query + //====================================================================== /** * @throws Exception @@ -138,20 +140,20 @@ public function processSearchRaw($bodyParams, $returnRaw): Results { $params = [ 'index' => $this->index, - 'body' => $bodyParams, + 'body' => $bodyParams, ]; + $process = []; try { $process = $this->client->search($params); if ($returnRaw) { return $this->_return($process->asArray(), [], $params, $this->_queryTag(__FUNCTION__)); } - - return $this->_sanitizeSearchResponse($process, $params, $this->_queryTag(__FUNCTION__)); } catch (Exception $e) { - - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + + return $this->_sanitizeSearchResponse($process, $params, $this->_queryTag(__FUNCTION__)); } /** @@ -161,17 +163,16 @@ public function processAggregationRaw($bodyParams): Results { $params = [ 'index' => $this->index, - 'body' => $bodyParams, - + 'body' => $bodyParams, ]; + $process = []; try { $process = $this->client->search($params); - - return $this->_sanitizeRawAggsResponse($process, $params, $this->_queryTag(__FUNCTION__)); } catch (Exception $e) { - - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + + return $this->_sanitizeRawAggsResponse($process, $params, $this->_queryTag(__FUNCTION__)); } /** @@ -179,47 +180,45 @@ public function processAggregationRaw($bodyParams): Results */ public function processIndicesDsl($method, $params): Results { + $process = []; try { $process = $this->client->indices()->{$method}($params); - - return $this->_sanitizeSearchResponse($process, $params, $this->_queryTag(__FUNCTION__)); } catch (Exception $e) { - - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + + return $this->_sanitizeSearchResponse($process, $params, $this->_queryTag(__FUNCTION__)); } - //---------------------------------------------------------------------- + //====================================================================== // To DSL - //---------------------------------------------------------------------- + //====================================================================== - public function processToDsl($wheres, $options, $columns) + /** + * @throws QueryException + * @throws ParameterException + */ + public function processToDsl($wheres, $options, $columns): array { return $this->buildParams($this->index, $wheres, $options, $columns); } - public function processToDslForSearch($searchParams, $searchOptions, $wheres, $opts, $fields, $cols) - { - return $this->buildSearchParams($this->index, $searchParams, $searchOptions, $wheres, $opts, $fields, $cols); - } - /** * @throws ParameterException + * @throws QueryException */ - public function processShowQuery($wheres, $options, $columns) + public function processToDslForSearch($searchParams, $searchOptions, $wheres, $opts, $fields, $cols): array { - $params = $this->buildParams($this->index, $wheres, $options, $columns); - - return $params['body'] ?? null; + return $this->buildSearchParams($this->index, $searchParams, $searchOptions, $wheres, $opts, $fields, $cols); } - - //---------------------------------------------------------------------- - // Read Queries - //---------------------------------------------------------------------- + //====================================================================== + // Find/Search Queries + //====================================================================== /** * @throws QueryException + * @throws ParameterException */ public function processFind($wheres, $options, $columns): Results { @@ -230,47 +229,48 @@ public function processFind($wheres, $options, $columns): Results /** * @throws QueryException + * @throws ParameterException */ - public function processSearch($searchParams, $searchOptions, $wheres, $opts, $fields, $cols) + public function processSearch($searchParams, $searchOptions, $wheres, $opts, $fields, $cols): Results { $params = $this->buildSearchParams($this->index, $searchParams, $searchOptions, $wheres, $opts, $fields, $cols); return $this->_returnSearch($params, __FUNCTION__); - } /** * @throws QueryException */ - protected function _returnSearch($params, $source) + protected function _returnSearch($params, $source): Results { if (empty($params['size'])) { $params['size'] = $this->maxSize; } + $process = []; try { - $process = $this->client->search($params); - - return $this->_sanitizeSearchResponse($process, $params, $this->_queryTag($source)); - } catch (Exception $e) { - - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + + return $this->_sanitizeSearchResponse($process, $params, $this->_queryTag($source)); } + //---------------------------------------------------------------------- + // Distinct + //---------------------------------------------------------------------- /** * @throws QueryException * @throws ParameterException */ public function processDistinct($wheres, $options, $columns, $includeDocCount = false): Results { - if ($columns && !is_array($columns)) { + if ($columns && ! is_array($columns)) { $columns = [$columns]; } $sort = $options['sort'] ?? []; - $skip = $options['skip'] ?? false; - $limit = $options['limit'] ?? false; + $skip = $options['skip'] ?? 0; + $limit = $options['limit'] ?? 0; unset($options['sort']); unset($options['skip']); unset($options['limit']); @@ -281,34 +281,26 @@ public function processDistinct($wheres, $options, $columns, $includeDocCount = $sort = [$sortField => $sortDir]; } - $params = $this->buildParams($this->index, $wheres, $options); + $data = []; + $response = []; try { - $params['body']['aggs'] = $this->createNestedAggs($columns, $sort); - $response = $this->client->search($params); - - - $data = []; - if (!empty($response['aggregations'])) { + if (! empty($response['aggregations'])) { $data = $this->_sanitizeDistinctResponse($response['aggregations'], $columns, $includeDocCount); } - //process limit and skip from all results if ($skip || $limit) { $data = array_slice($data, $skip, $limit); } - - return $this->_return($data, $response, $params, $this->_queryTag(__FUNCTION__)); } catch (Exception $e) { - - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + return $this->_return($data, $response, $params, $this->_queryTag(__FUNCTION__)); } - //---------------------------------------------------------------------- // Write Queries //---------------------------------------------------------------------- @@ -332,26 +324,112 @@ public function processSave($data, $refresh): Results $params = [ 'index' => $this->index, - 'body' => $data, + 'body' => $data, ]; if ($id) { $params['id'] = $id; - } if ($refresh) { $params['refresh'] = $refresh; } - + $response = []; + $savedData = []; try { $response = $this->client->index($params); $savedData = ['_id' => $response['_id']] + $data; - - return $this->_return($savedData, $response, $params, $this->_queryTag(__FUNCTION__)); } catch (Exception $e) { - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); + } + + return $this->_return($savedData, $response, $params, $this->_queryTag(__FUNCTION__)); + } + + /** + * Allows us to use the Bulk API. + * Such speed! + * + * More Info: + * - https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/indexing_documents.html#_bulk_indexing + * - https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html + * + * @throws QueryException + */ + public function processInsertBulk(array $records, bool $returnData = false, string|bool|null $refresh = null): array + { + $params = ['body' => []]; + + // If we don't want to wait for elastic to refresh this needs to be set. + if ($refresh) { + $params['refresh'] = $refresh; } + // Create action/metadata pairs + foreach ($records as $data) { + $recordHeader['_index'] = $this->index; + + if (isset($data['_id'])) { + $recordHeader['_id'] = $data['_id']; + unset($data['_id']); + } + if (isset($data['_index'])) { + unset($data['_index']); + } + if (isset($data['_meta'])) { + unset($data['_meta']); + } + + $params['body'][] = [ + 'index' => $recordHeader, + ]; + $params['body'][] = $data; + } + + $finalResponse = [ + 'hasErrors' => false, + 'total' => 0, + 'took' => 0, + 'success' => 0, + 'created' => 0, + 'modified' => 0, + 'failed' => 0, + 'data' => [], + 'error_bag' => [], + ]; + try { + $response = $this->client->bulk($params); + $finalResponse['hasErrors'] = $response['errors']; + $finalResponse['took'] = $response['took']; + foreach ($response['items'] as $count => $hit) { + $finalResponse['total']++; + $payload = $params['body'][($count * 2) + 1]; + + if (! empty($hit['index']['error'])) { + $finalResponse['failed']++; + $finalResponse['error_bag'][] = [ + 'error' => $hit['index']['error'], + 'payload' => $payload, + ]; + } else { + $finalResponse['success']++; + $finalResponse['success']++; + if ($hit['index']['result'] === 'created') { + $finalResponse['created']++; + } else { + $finalResponse['modified']++; + } + + if ($returnData) { + $id = $hit['index']['_id']; + $record = ['_id' => $id] + $payload; + $finalResponse['data'][] = $record; + } + } + } + } catch (Exception $e) { + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); + } + return $finalResponse; } /** @@ -373,7 +451,7 @@ public function processUpdateMany($wheres, $newValues, $options, $refresh = null $resultData = []; $data = $this->processFind($wheres, $options, []); - if (!empty($data->data)) { + if (! empty($data->data)) { foreach ($data->data as $currentData) { foreach ($newValues as $field => $value) { @@ -393,7 +471,6 @@ public function processUpdateMany($wheres, $newValues, $options, $refresh = null $params['queryOptions'] = $options; $params['updateValues'] = $newValues; - return $this->_return($resultData, $resultMeta, $params, $this->_queryTag(__FUNCTION__)); } @@ -413,14 +490,14 @@ public function processIncrementMany($wheres, $newValues, $options, $refresh): R $resultMeta['failed'] = 0; $resultData = []; $data = $this->processFind($wheres, $options, []); - if (!empty($data->data)) { + if (! empty($data->data)) { foreach ($data->data as $currentData) { $currentValue = $currentData[$incField] ?? 0; $currentValue += $newValues['inc'][$incField]; - $currentData[$incField] = (int)$currentValue; + $currentData[$incField] = (int) $currentValue; - if (!empty($newValues['set'])) { + if (! empty($newValues['set'])) { foreach ($newValues['set'] as $field => $value) { $currentData[$field] = $value; } @@ -438,11 +515,9 @@ public function processIncrementMany($wheres, $newValues, $options, $refresh): R $params['queryOptions'] = $options; $params['updateValues'] = $newValues; - return $this->_return($resultData, $resultMeta, $params, $this->_queryTag(__FUNCTION__)); } - //---------------------------------------------------------------------- // Delete Queries //---------------------------------------------------------------------- @@ -456,7 +531,7 @@ public function processDeleteAll($wheres, $options = []): Results if (isset($wheres['_id'])) { $params = [ 'index' => $this->index, - 'id' => $wheres['_id'], + 'id' => $wheres['_id'], ]; try { $responseObject = $this->client->delete($params); @@ -465,41 +540,22 @@ public function processDeleteAll($wheres, $options = []): Results return $this->_return($response['deleteCount'], $response, $params, $this->_queryTag(__FUNCTION__)); } catch (Exception $e) { - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } } + $response = []; $params = $this->buildParams($this->index, $wheres, $options); try { $responseObject = $this->client->deleteByQuery($params); $response = $responseObject->asArray(); $response['deleteCount'] = $response['deleted'] ?? 0; - - return $this->_return($response['deleteCount'], $response, $params, $this->_queryTag(__FUNCTION__)); } catch (Exception $e) { - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + return $this->_return($response['deleteCount'], $response, $params, $this->_queryTag(__FUNCTION__)); } - public function processScript($id, $script) - { -// $params = [ -// 'id' => $id, -// 'index' => $this->index, -// ]; -// if ($script) { -// $params['body']['script']['source'] = $script; -// } -// -// $response = $this->client->update($params); -// -// $n = new self($this->index); -// $find = $n->processFind($id); - -// return $this->_return($find->data, $response, $params, $this->_queryTag(__FUNCTION__)); - } - - //---------------------------------------------------------------------- // Index administration //---------------------------------------------------------------------- @@ -523,67 +579,48 @@ public function processGetIndices($all): array public function processIndexExists($index): bool { $params = ['index' => $index]; - try { $test = $this->client->indices()->exists($params); - - return $test->getStatusCode() == 200; } catch (Exception $e) { return false; } + return $test->getStatusCode() == 200; } - /** * @throws QueryException */ - public function processIndexMappings($index): mixed - { - $params = ['index' => $index]; - try { - $responseObject = $this->client->indices()->getMapping($params); - $response = $responseObject->asArray(); - $result = $this->_return($response, $response, $params, $this->_queryTag(__FUNCTION__)); - - return $result->data; - } catch (Exception $e) { - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); - } - } - - /** - * @throws QueryException - */ - public function processIndexSettings($index): mixed + public function processIndexSettings($index): array { $params = ['index' => $index]; + $response = []; try { $response = $this->client->indices()->getSettings($params); $result = $this->_return($response, $response, $params, $this->_queryTag(__FUNCTION__)); - - return $result->data->asArray(); + $response = $result->data->asArray(); } catch (Exception $e) { - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + + return $response; } /** * @throws QueryException */ - public function processIndexCreate($settings) + public function processIndexCreate($settings): bool { $params = $this->buildIndexMap($this->index, $settings); + $created = false; try { $response = $this->client->indices()->create($params); - - $result = $this->_return(true, $response, $params, $this->_queryTag(__FUNCTION__)); - - return true; + $created = $response->asArray(); } catch (Exception $e) { - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + return ! empty($created); } /** @@ -593,15 +630,12 @@ public function processIndexDelete(): bool { $params = ['index' => $this->index]; try { - $response = $this->client->indices()->delete($params); - $this->_return(true, $response, $params, $this->_queryTag(__FUNCTION__)); - - return true; + $this->client->indices()->delete($params); } catch (Exception $e) { - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } - + return true; } /** @@ -619,12 +653,11 @@ public function processIndexModify($settings): bool try { $response = $this->client->indices()->putMapping($params); $result = $this->_return(true, $response, $params, $this->_queryTag(__FUNCTION__)); - - return true; } catch (Exception $e) { - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + return true; } /** @@ -639,26 +672,27 @@ public function processReIndex($oldIndex, $newIndex): Results } $params['body']['source']['index'] = $oldIndex; $params['body']['dest']['index'] = $newIndex; + $resultData = []; + $result = []; try { $response = $this->client->reindex($params); $result = $response->asArray(); $resultData = [ - 'took' => $result['took'], - 'total' => $result['total'], - 'created' => $result['created'], - 'updated' => $result['updated'], - 'deleted' => $result['deleted'], - 'batches' => $result['batches'], + 'took' => $result['took'], + 'total' => $result['total'], + 'created' => $result['created'], + 'updated' => $result['updated'], + 'deleted' => $result['deleted'], + 'batches' => $result['batches'], 'version_conflicts' => $result['version_conflicts'], - 'noops' => $result['noops'], - 'retries' => $result['retries'], + 'noops' => $result['noops'], + 'retries' => $result['retries'], ]; - - return $this->_return($resultData, $result, $params, $this->_queryTag(__FUNCTION__)); - } catch (Exception $e) { - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + + return $this->_return($resultData, $result, $params, $this->_queryTag(__FUNCTION__)); } /** @@ -672,44 +706,36 @@ public function processIndexAnalyzerSettings($settings): bool $response = $this->client->indices()->putSettings($params); $result = $this->_return(true, $response, $params, $this->_queryTag(__FUNCTION__)); $this->client->indices()->open(['index' => $this->index]); - - return true; } catch (Exception $e) { - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } - } + return true; + } //---------------------------------------------------------------------- // Aggregates //---------------------------------------------------------------------- - - - public function processMultipleAggregate($functions, $wheres, $options, $column) + /** + * @throws QueryException + * @throws ParameterException + */ + public function processMultipleAggregate($functions, $wheres, $options, $column): Results { $params = $this->buildParams($this->index, $wheres, $options); + $process = []; try { $params['body']['aggs'] = ParameterBuilder::multipleAggregations($functions, $column); $process = $this->client->search($params); - - return $this->_return($process['aggregations'] ?? [], $process, $params, $this->_queryTag(__FUNCTION__)); - } catch (Exception $e) { - - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } - } + return $this->_return($process['aggregations'] ?? [], $process, $params, $this->_queryTag(__FUNCTION__)); + } /** * Aggregate entry point - * - * @param $function - * @param $wheres - * @param $options - * @param $columns - * - * @return mixed */ public function processAggregate($function, $wheres, $options, $columns): Results { @@ -723,15 +749,14 @@ public function processAggregate($function, $wheres, $options, $columns): Result public function _countAggregate($wheres, $options, $columns): Results { $params = $this->buildParams($this->index, $wheres); + $process = []; try { $process = $this->client->count($params); - - return $this->_return($process['count'] ?? 0, $process, $params, $this->_queryTag(__FUNCTION__)); } catch (Exception $e) { - - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + return $this->_return($process['count'] ?? 0, $process, $params, $this->_queryTag(__FUNCTION__)); } /** @@ -744,18 +769,17 @@ private function _maxAggregate($wheres, $options, $columns): Results if (is_array($columns[0])) { $columns = $columns[0]; } + $process = []; try { foreach ($columns as $column) { $params['body']['aggs']['max_'.$column] = ParameterBuilder::maxAggregation($column); } $process = $this->client->search($params); - - return $this->_sanitizeAggsResponse($process, $params, $this->_queryTag(__FUNCTION__)); - } catch (Exception $e) { - - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + + return $this->_sanitizeAggsResponse($process, $params, $this->_queryTag(__FUNCTION__)); } /** @@ -768,16 +792,17 @@ private function _minAggregate($wheres, $options, $columns): Results if (is_array($columns[0])) { $columns = $columns[0]; } + $process = []; try { foreach ($columns as $column) { $params['body']['aggs']['min_'.$column] = ParameterBuilder::minAggregation($column); } $process = $this->client->search($params); - - return $this->_sanitizeAggsResponse($process, $params, $this->_queryTag(__FUNCTION__)); } catch (Exception $e) { - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + + return $this->_sanitizeAggsResponse($process, $params, $this->_queryTag(__FUNCTION__)); } /** @@ -790,19 +815,17 @@ private function _sumAggregate($wheres, $options, $columns): Results if (is_array($columns[0])) { $columns = $columns[0]; } + $process = []; try { foreach ($columns as $column) { $params['body']['aggs']['sum_'.$column] = ParameterBuilder::sumAggregation($column); } $process = $this->client->search($params); - - return $this->_sanitizeAggsResponse($process, $params, $this->_queryTag(__FUNCTION__)); - } catch (Exception $e) { - - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + return $this->_sanitizeAggsResponse($process, $params, $this->_queryTag(__FUNCTION__)); } /** @@ -815,17 +838,17 @@ private function _avgAggregate($wheres, $options, $columns): Results if (is_array($columns[0])) { $columns = $columns[0]; } + $process = []; try { foreach ($columns as $column) { $params['body']['aggs']['avg_'.$column] = ParameterBuilder::avgAggregation($column); } $process = $this->client->search($params); - - return $this->_sanitizeAggsResponse($process, $params, $this->_queryTag(__FUNCTION__)); - } catch (Exception $e) { - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + + return $this->_sanitizeAggsResponse($process, $params, $this->_queryTag(__FUNCTION__)); } /** @@ -835,42 +858,34 @@ private function _avgAggregate($wheres, $options, $columns): Results private function _matrixAggregate($wheres, $options, $columns): Results { $params = $this->buildParams($this->index, $wheres, $options); + $process = []; try { $params['body']['aggs']['statistics'] = ParameterBuilder::matrixAggregation($columns); $process = $this->client->search($params); - - return $this->_return($process['aggregations']['statistics'] ?? [], $process, $params, $this->_queryTag(__FUNCTION__)); } catch (Exception $e) { - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + return $this->_return($process['aggregations']['statistics'] ?? [], $process, $params, $this->_queryTag(__FUNCTION__)); } - /** - * @throws QueryException - */ - public function parseRequiredKeywordMapping($field) + private function _sanitizeAggsResponse($response, $params, $queryTag): Results { - $mappings = $this->processIndexMappings($this->index); - $map = reset($mappings); - if (!empty($map['mappings']['properties'][$field])) { - $fieldMap = $map['mappings']['properties'][$field]; - if (!empty($fieldMap['type']) && $fieldMap['type'] === 'keyword') { - //primary Map is field. Use as is - return $field; - } - if (!empty($fieldMap['fields']['keyword'])) { - return $field.'.keyword'; - } - } + $meta['timed_out'] = $response['timed_out']; + $meta['total'] = $response['hits']['total']['value'] ?? 0; + $meta['max_score'] = $response['hits']['max_score'] ?? 0; + $meta['sorts'] = []; - return false; + $aggs = $response['aggregations']; + $data = (count($aggs) === 1) ? reset($aggs)['value'] ?? 0 : array_map(fn ($value + ) => $value['value'] ?? 0, $aggs); + return $this->_return($data, $meta, $params, $queryTag); } - //---------------------------------------------------------------------- + //====================================================================== // Distinct Aggregates - //---------------------------------------------------------------------- + //====================================================================== public function processDistinctAggregate($function, $wheres, $options, $columns): Results { @@ -884,82 +899,78 @@ public function processDistinctAggregate($function, $wheres, $options, $columns) private function _countDistinctAggregate($wheres, $options, $columns): Results { $params = $this->buildParams($this->index, $wheres); + $count = 0; + $meta = []; try { $process = $this->processDistinct($wheres, $options, $columns); $count = count($process->data); - - return $this->_return($count, $process->getMetaData(), $params, $this->_queryTag(__FUNCTION__)); + $meta = $process->getMetaData(); } catch (Exception $e) { - - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + return $this->_return($count, $meta, $params, $this->_queryTag(__FUNCTION__)); } - /** * @throws ParameterException * @throws QueryException */ - private function _minDistinctAggregate($wheres, $options, $columns): Results + private function _maxDistinctAggregate($wheres, $options, $columns): Results { $params = $this->buildParams($this->index, $wheres); + $max = 0; + $meta = []; try { $process = $this->processDistinct($wheres, $options, $columns); - $min = 0; - $hasBeenSet = false; - if (!empty($process->data)) { + if (! empty($process->data)) { foreach ($process->data as $datum) { - if (!empty($datum[$columns[0]]) && is_numeric($datum[$columns[0]])) { - if (!$hasBeenSet) { - $min = $datum[$columns[0]]; - $hasBeenSet = true; - } else { - $min = min($min, $datum[$columns[0]]); - } - + if (! empty($datum[$columns[0]]) && is_numeric($datum[$columns[0]])) { + $max = max($max, $datum[$columns[0]]); } } } - - return $this->_return($min, $process->getMetaData(), $params, $this->_queryTag(__FUNCTION__)); + $meta = $process->getMetaData(); } catch (Exception $e) { - - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + return $this->_return($max, $meta, $params, $this->_queryTag(__FUNCTION__)); } /** * @throws ParameterException * @throws QueryException */ - private function _maxDistinctAggregate($wheres, $options, $columns): Results + private function _minDistinctAggregate($wheres, $options, $columns): Results { $params = $this->buildParams($this->index, $wheres); + $min = 0; + $meta = []; try { $process = $this->processDistinct($wheres, $options, $columns); - - $max = 0; - if (!empty($process->data)) { + $hasBeenSet = false; + if (! empty($process->data)) { foreach ($process->data as $datum) { - if (!empty($datum[$columns[0]]) && is_numeric($datum[$columns[0]])) { - $max = max($max, $datum[$columns[0]]); + if (! empty($datum[$columns[0]]) && is_numeric($datum[$columns[0]])) { + if (! $hasBeenSet) { + $min = $datum[$columns[0]]; + $hasBeenSet = true; + } else { + $min = min($min, $datum[$columns[0]]); + } } } } - - - return $this->_return($max, $process->getMetaData(), $params, $this->_queryTag(__FUNCTION__)); + $meta = $process->getMetaData(); } catch (Exception $e) { - - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + return $this->_return($min, $meta, $params, $this->_queryTag(__FUNCTION__)); } - /** * @throws ParameterException * @throws QueryException @@ -967,40 +978,42 @@ private function _maxDistinctAggregate($wheres, $options, $columns): Results private function _sumDistinctAggregate($wheres, $options, $columns): Results { $params = $this->buildParams($this->index, $wheres); + $sum = 0; + $meta = []; try { $process = $this->processDistinct($wheres, $options, $columns); - $sum = 0; - if (!empty($process->data)) { + if (! empty($process->data)) { foreach ($process->data as $datum) { - if (!empty($datum[$columns[0]]) && is_numeric($datum[$columns[0]])) { + if (! empty($datum[$columns[0]]) && is_numeric($datum[$columns[0]])) { $sum += $datum[$columns[0]]; } } } - - return $this->_return($sum, $process->getMetaData(), $params, $this->_queryTag(__FUNCTION__)); + $meta = $process->getMetaData(); } catch (Exception $e) { - - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); } + return $this->_return($sum, $meta, $params, $this->_queryTag(__FUNCTION__)); } /** * @throws ParameterException * @throws QueryException */ - private function _avgDistinctAggregate($wheres, $options, $columns) + private function _avgDistinctAggregate($wheres, $options, $columns): Results { $params = $this->buildParams($this->index, $wheres); + $sum = 0; + $count = 0; + $avg = 0; + $meta = []; try { $process = $this->processDistinct($wheres, $options, $columns); - $sum = 0; - $count = 0; - $avg = 0; - if (!empty($process->data)) { + + if (! empty($process->data)) { foreach ($process->data as $datum) { - if (!empty($datum[$columns[0]]) && is_numeric($datum[$columns[0]])) { + if (! empty($datum[$columns[0]]) && is_numeric($datum[$columns[0]])) { $count++; $sum += $datum[$columns[0]]; } @@ -1009,73 +1022,158 @@ private function _avgDistinctAggregate($wheres, $options, $columns) if ($count > 0) { $avg = $sum / $count; } + $meta = $process->getMetaData(); + } catch (Exception $e) { + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); + } + return $this->_return($avg, $meta, $params, $this->_queryTag(__FUNCTION__)); + } - return $this->_return($avg, $process->getMetaData(), $params, $this->_queryTag(__FUNCTION__)); - } catch (Exception $e) { + /** + * @throws QueryException + */ + private function _matrixDistinctAggregate($wheres, $options, $columns) + { + $this->_throwError(new Exception('Matrix distinct aggregate not supported', 500), [], $this->_queryTag(__FUNCTION__)); + } + + //====================================================================== + // Helpers + //====================================================================== - $this->throwError($e, $params, $this->_queryTag(__FUNCTION__)); + /** + * @throws QueryException + */ + public function parseRequiredKeywordMapping($field): ?string + { + $mappings = $this->processIndexMappings($this->index); + $map = reset($mappings); + if (! empty($map['mappings']['properties'][$field])) { + $fieldMap = $map['mappings']['properties'][$field]; + if (! empty($fieldMap['type']) && $fieldMap['type'] === 'keyword') { + //primary Map is field. Use as is + return $field; + } + if (! empty($fieldMap['fields']['keyword'])) { + return $field.'.keyword'; + } } + return null; } /** * @throws QueryException */ - private function _matrixDistinctAggregate($wheres, $options, $columns): Results + public function processIndexMappings($index): array { - $this->throwError(new Exception('Matrix distinct aggregate not supported', 500), [], $this->_queryTag(__FUNCTION__)); + $params = ['index' => $index]; + $result = []; + try { + $responseObject = $this->client->indices()->getMapping($params); + $response = $responseObject->asArray(); + $result = $this->_return($response, $response, $params, $this->_queryTag(__FUNCTION__)); + } catch (Exception $e) { + $this->_throwError($e, $params, $this->_queryTag(__FUNCTION__)); + } + + return $result->data; } //====================================================================== // Private & Sanitization methods //====================================================================== + private function _return($data, $meta, $params, $queryTag): Results + { + if (is_object($meta)) { + $metaAsArray = []; + if (method_exists($meta, 'asArray')) { + $metaAsArray = $meta->asArray(); + } + $results = new Results($data, $metaAsArray, $params, $queryTag); + } else { + $results = new Results($data, $meta, $params, $queryTag); + } - private function _queryTag($function) + return $results; + } + + private function _queryTag($function): string { return str_replace('process', '', $function); } - private function _sanitizeSearchResponse($response, $params, $queryTag) + private function _sanitizePitSearchResponse($response, $params, $queryTag) { + $meta['timed_out'] = $response['timed_out']; + $meta['total'] = $response['hits']['total']['value'] ?? 0; + $meta['max_score'] = $response['hits']['max_score'] ?? 0; + $meta['sort'] = null; + $data = []; + if (! empty($response['hits']['hits'])) { + foreach ($response['hits']['hits'] as $hit) { + $datum = []; + $datum['_index'] = $hit['_index']; + $datum['_id'] = $hit['_id']; + if (! empty($hit['_source'])) { + foreach ($hit['_source'] as $key => $value) { + $datum[$key] = $value; + } + } + if (! empty($hit['sort'][0])) { + $meta['sort'] = $hit['sort']; + } + $data[] = $datum; + } + } + + return $this->_return($data, $meta, $params, $queryTag); + } + + private function _sanitizeSearchResponse($response, $params, $queryTag) + { $meta['took'] = $response['took'] ?? 0; $meta['timed_out'] = $response['timed_out']; $meta['total'] = $response['hits']['total']['value'] ?? 0; $meta['max_score'] = $response['hits']['max_score'] ?? 0; $meta['shards'] = $response['_shards'] ?? []; $data = []; - if (!empty($response['hits']['hits'])) { + if (! empty($response['hits']['hits'])) { foreach ($response['hits']['hits'] as $hit) { $datum = []; $datum['_index'] = $hit['_index']; $datum['_id'] = $hit['_id']; - if (!empty($hit['_source'])) { + if (! empty($hit['_source'])) { foreach ($hit['_source'] as $key => $value) { $datum[$key] = $value; } - } - if (!empty($hit['inner_hits'])) { + if (! empty($hit['inner_hits'])) { foreach ($hit['inner_hits'] as $innerKey => $innerHit) { $datum[$innerKey] = $this->_filterInnerHits($innerHit); } } //Meta data - if (!empty($hit['highlight'])) { + if (! empty($hit['highlight'])) { $datum['_meta']['highlights'] = $this->_sanitizeHighlights($hit['highlight']); } $datum['_meta']['_index'] = $hit['_index']; $datum['_meta']['_id'] = $hit['_id']; - if (!empty($hit['_score'])) { + if (! empty($hit['_score'])) { $datum['_meta']['_score'] = $hit['_score']; } $datum['_meta']['_query'] = $meta; - + // If we are sorting we need to store it to be able to pass it on in the search after. + if (! empty($hit['sort'])) { + $datum['_meta']['sort'] = $hit['sort']; + } + $datum['_meta'] = $this->_attachStashedMeta($datum['_meta']); $data[] = $datum; } } @@ -1083,36 +1181,50 @@ private function _sanitizeSearchResponse($response, $params, $queryTag) return $this->_return($data, $meta, $params, $queryTag); } - private function _sanitizeHighlights($highlights) + private function _sanitizeDistinctResponse($response, $columns, $includeDocCount): array { - //remove keyword results - foreach ($highlights as $field => $vals) { - if (str_contains($field, '.keyword')) { - $cleanField = str_replace('.keyword', '', $field); - if (isset($highlights[$cleanField])) { - unset($highlights[$field]); - } else { - $highlights[$cleanField] = $vals; - } - } + $keys = []; + foreach ($columns as $column) { + $keys[] = 'by_'.$column; } - return $highlights; + return $this->_processBuckets($columns, $keys, $response, 0, $includeDocCount); } - public function _sanitizeAggsResponse($response, $params, $queryTag) + private function _processBuckets($columns, $keys, $response, $index, $includeDocCount, $currentData = []): array { - $meta['timed_out'] = $response['timed_out']; - $meta['total'] = $response['hits']['total']['value'] ?? 0; - $meta['max_score'] = $response['hits']['max_score'] ?? 0; - $meta['sorts'] = []; + $data = []; + if (! empty($response[$keys[$index]]['buckets'])) { + foreach ($response[$keys[$index]]['buckets'] as $res) { - $aggs = $response['aggregations']; - $data = (count($aggs) === 1) - ? reset($aggs)['value'] ?? 0 - : array_map(fn($value) => $value['value'] ?? 0, $aggs); + $datum = $currentData; - return $this->_return($data, $meta, $params, $queryTag); + $col = $columns[$index]; + if (str_contains($col, '.keyword')) { + $col = str_replace('.keyword', '', $col); + } + + $datum[$col] = $res['key']; + + if ($includeDocCount) { + $datum[$col.'_count'] = $res['doc_count']; + } + + if (isset($columns[$index + 1])) { + $nestedData = $this->_processBuckets($columns, $keys, $res, $index + 1, $includeDocCount, $datum); + + if (! empty($nestedData)) { + $data = array_merge($data, $nestedData); + } else { + $data[] = $datum; + } + } else { + $data[] = $datum; + } + } + } + + return $data; } private function _sanitizeRawAggsResponse($response, $params, $queryTag) @@ -1122,7 +1234,7 @@ private function _sanitizeRawAggsResponse($response, $params, $queryTag) $meta['max_score'] = $response['hits']['max_score'] ?? 0; $meta['sorts'] = []; $data = []; - if (!empty($response['aggregations'])) { + if (! empty($response['aggregations'])) { foreach ($response['aggregations'] as $key => $values) { $data[$key] = $this->_formatAggs($key, $values)[$key]; } @@ -1131,24 +1243,21 @@ private function _sanitizeRawAggsResponse($response, $params, $queryTag) return $this->_return($data, $meta, $params, $queryTag); } - private function _formatAggs($key, $values) + private function _sanitizeHighlights($highlights) { - $data[$key] = []; - $aggTypes = ['buckets', 'values']; - - foreach ($values as $subKey => $value) { - if (in_array($subKey, $aggTypes)) { - $data[$key] = $this->_formatAggs($subKey, $value)[$subKey]; - } elseif (is_array($value)) { - $data[$key][$subKey] = $this->_formatAggs($subKey, $value)[$subKey]; - } else { - $data[$key][$subKey] = $value; + //remove keyword results + foreach ($highlights as $field => $vals) { + if (str_contains($field, '.keyword')) { + $cleanField = str_replace('.keyword', '', $field); + if (isset($highlights[$cleanField])) { + unset($highlights[$field]); + } else { + $highlights[$cleanField] = $vals; + } } - } - return $data; - + return $highlights; } private function _filterInnerHits($innerHit) @@ -1156,7 +1265,7 @@ private function _filterInnerHits($innerHit) $hits = []; foreach ($innerHit['hits']['hits'] as $inner) { $innerDatum = []; - if (!empty($inner['_source'])) { + if (! empty($inner['_source'])) { foreach ($inner['_source'] as $innerSourceKey => $innerSourceValue) { $innerDatum[$innerSourceKey] = $innerSourceValue; } @@ -1167,113 +1276,32 @@ private function _filterInnerHits($innerHit) return $hits; } - private function _sanitizePitSearchResponse($response, $params, $queryTag) - { - - $meta['timed_out'] = $response['timed_out']; - $meta['total'] = $response['hits']['total']['value'] ?? 0; - $meta['max_score'] = $response['hits']['max_score'] ?? 0; - $meta['last_sort'] = null; - $data = []; - if (!empty($response['hits']['hits'])) { - foreach ($response['hits']['hits'] as $hit) { - $datum = []; - $datum['_index'] = $hit['_index']; - $datum['_id'] = $hit['_id']; - if (!empty($hit['_source'])) { - foreach ($hit['_source'] as $key => $value) { - $datum[$key] = $value; - } - } - if (!empty($hit['sort'][0])) { - $meta['last_sort'] = $hit['sort']; - } - $data[] = $datum; - - } - } - - return $this->_return($data, $meta, $params, $queryTag); - } - - - private function _parseSort($sort, $sortParams) - { - $sortValues = []; - foreach ($sort as $key => $value) { - $sortValues[array_key_first($sortParams[$key])] = $value; - } - - return $sortValues; - } - - private function _sanitizeDistinctResponse($response, $columns, $includeDocCount) - { - $keys = []; - foreach ($columns as $column) { - $keys[] = 'by_'.$column; - } - - return $this->processBuckets($columns, $keys, $response, 0, $includeDocCount); - - } - - private function processBuckets($columns, $keys, $response, $index, $includeDocCount, $currentData = []) + private function _formatAggs($key, $values) { - $data = []; - if (!empty($response[$keys[$index]]['buckets'])) { - foreach ($response[$keys[$index]]['buckets'] as $res) { - - $datum = $currentData; - - $col = $columns[$index]; - if (str_contains($col, '.keyword')) { - $col = str_replace('.keyword', '', $col); - } - - $datum[$col] = $res['key']; - - if ($includeDocCount) { - $datum[$col.'_count'] = $res['doc_count']; - } - - if (isset($columns[$index + 1])) { - $nestedData = $this->processBuckets($columns, $keys, $res, $index + 1, $includeDocCount, $datum); + $data[$key] = []; + $aggTypes = ['buckets', 'values']; - if (!empty($nestedData)) { - $data = array_merge($data, $nestedData); - } else { - $data[] = $datum; - } - } else { - $data[] = $datum; - } + foreach ($values as $subKey => $value) { + if (in_array($subKey, $aggTypes)) { + $data[$key] = $this->_formatAggs($subKey, $value)[$subKey]; + } elseif (is_array($value)) { + $data[$key][$subKey] = $this->_formatAggs($subKey, $value)[$subKey]; + } else { + $data[$key][$subKey] = $value; } } return $data; } - private function _return($data, $meta, $params, $queryTag): Results - { - if (is_object($meta)) { - $metaAsArray = []; - if (method_exists($meta, 'asArray')) { - $metaAsArray = $meta->asArray(); - } - $results = new Results($data, $metaAsArray, $params, $queryTag); - } else { - $results = new Results($data, $meta, $params, $queryTag); - } - - return $results; - } - + //====================================================================== + // Error and logging + //====================================================================== /** * @throws QueryException */ - private function throwError(Exception $exception, $params, $queryTag): QueryException + private function _throwError(Exception $exception, $params, $queryTag): QueryException { $previous = get_class($exception); $errorMsg = $exception->getMessage(); @@ -1283,15 +1311,15 @@ private function throwError(Exception $exception, $params, $queryTag): QueryExce $error = new Results([], [], $params, $queryTag); $error->setError($errorMsg, $errorCode); - $meta = $error->getMetaData(); + $meta = $error->getMetaDataAsArray(); $details = [ - 'error' => $meta['error']['msg'], - 'details' => $meta['error']['data'], - 'code' => $errorCode, + 'error' => $meta['error']['msg'], + 'details' => $meta['error']['data'], + 'code' => $errorCode, 'exception' => $previous, - 'query' => $queryTag, - 'params' => $params, - 'original' => $errorMsg, + 'query' => $queryTag, + 'params' => $params, + 'original' => $errorMsg, ]; if ($this->errorLogger) { $this->_logQuery($error, $details); @@ -1304,11 +1332,11 @@ private function _logQuery(Results $results, $details) { $body = $results->getLogFormattedMetaData(); if ($details) { - $body['details'] = (array)$details; + $body['details'] = (array) $details; } $params = [ 'index' => $this->errorLogger, - 'body' => $body, + 'body' => $body, ]; try { $this->client->index($params); @@ -1317,4 +1345,31 @@ private function _logQuery(Results $results, $details) } } + //---------------------------------------------------------------------- + // Meta Stasher + //---------------------------------------------------------------------- + + private function _stashMeta($meta): void + { + $this->stashedMeta = $meta; + } + + private function _attachStashedMeta($meta): mixed + { + if (! empty($this->stashedMeta)) { + $meta = array_merge($meta, $this->stashedMeta); + } + + return $meta; + } + + // private function _parseSort($sort, $sortParams): array + // { + // $sortValues = []; + // foreach ($sort as $key => $value) { + // $sortValues[array_key_first($sortParams[$key])] = $value; + // } + // + // return $sortValues; + // } } diff --git a/src/DSL/IndexInterpreter.php b/src/DSL/IndexInterpreter.php index 3f2effd..e59eeaa 100644 --- a/src/DSL/IndexInterpreter.php +++ b/src/DSL/IndexInterpreter.php @@ -1,5 +1,7 @@ $value) { $params['body']['mappings'][$key] = $value; } } - if (!empty($raw['properties'])) { + if (! empty($raw['properties'])) { $properties = []; foreach ($raw['properties'] as $prop) { $field = $prop['field']; unset($prop['field']); - if (!empty($properties[$field])) { + if (! empty($properties[$field])) { $type = $prop['type']; foreach ($prop as $key => $value) { $properties[$field]['fields'][$type][Str::snake($key)] = $value; @@ -37,7 +39,7 @@ public function buildIndexMap($index, $raw): array } } } - if (!empty($properties)) { + if (! empty($properties)) { $params['body']['mappings']['properties'] = $properties; } } @@ -69,14 +71,13 @@ public function buildAnalyzerSettings($index, $raw): array return $params; } - public function catIndices($data, $all = false): array { - if (!$all && $data) { + if (! $all && $data) { $indices = $data; $data = []; foreach ($indices as $index) { - if (!(str_starts_with($index['index'], "."))) { + if (! (str_starts_with($index['index'], '.'))) { $data[] = $index; } } @@ -97,5 +98,4 @@ public function cleanData($data): array return $data; } - } diff --git a/src/DSL/ParameterBuilder.php b/src/DSL/ParameterBuilder.php index 1e99c52..e4fff58 100644 --- a/src/DSL/ParameterBuilder.php +++ b/src/DSL/ParameterBuilder.php @@ -1,5 +1,7 @@ [ - 'match_all' => new \stdClass(), + 'match_all' => new \stdClass, ], ]; } - public static function queryStringQuery($string): array - { - return [ - 'query' => [ - 'query_string' => [ - 'query' => $string, - ], - ], - ]; - } + // public static function queryStringQuery($string): array + // { + // return [ + // 'query' => [ + // 'query_string' => [ + // 'query' => $string, + // ], + // ], + // ]; + // } public static function query($dsl): array { @@ -31,24 +33,23 @@ public static function query($dsl): array ]; } - public static function fieldSort($field, $payload, $allowId = false): array { - if ($field === '_id' && !$allowId) { + if ($field === '_id' && ! $allowId) { return []; } - if (!empty($payload['is_geo'])) { + if (! empty($payload['is_geo'])) { return self::fieldSortGeo($field, $payload); } - if (!empty($payload['is_nested'])) { + if (! empty($payload['is_nested'])) { return self::filterNested($field, $payload); } $sort = []; $sort['order'] = $payload['order'] ?? 'asc'; - if (!empty($payload['mode'])) { + if (! empty($payload['mode'])) { $sort['mode'] = $payload['mode']; } - if (!empty($payload['missing'])) { + if (! empty($payload['missing'])) { $sort['missing'] = $payload['missing']; } @@ -64,10 +65,10 @@ public static function fieldSortGeo($field, $payload): array $sort['order'] = $payload['order'] ?? 'asc'; $sort['unit'] = $payload['unit'] ?? 'km'; - if (!empty($payload['mode'])) { + if (! empty($payload['mode'])) { $sort['mode'] = $payload['mode']; } - if (!empty($payload['type'])) { + if (! empty($payload['type'])) { $sort['distance_type'] = $payload['type']; } @@ -76,25 +77,58 @@ public static function fieldSortGeo($field, $payload): array ]; } - public static function filterNested($field, $payload) + public static function filterNested($field, $payload): array { $sort = []; $pathParts = explode('.', $field); $path = $pathParts[0]; $sort['order'] = $payload['order'] ?? 'asc'; - if (!empty($payload['mode'])) { + if (! empty($payload['mode'])) { $sort['mode'] = $payload['mode']; } $sort['nested'] = [ 'path' => $path, ]; - return [ $field => $sort, ]; } + public static function multipleAggregations($aggregations, $field): array + { + $aggs = []; + foreach ($aggregations as $aggregation) { + switch ($aggregation) { + case 'max': + $aggs['max_'.$field] = self::maxAggregation($field); + break; + case 'min': + $aggs['min_'.$field] = self::minAggregation($field); + break; + case 'avg': + $aggs['avg_'.$field] = self::avgAggregation($field); + break; + case 'sum': + $aggs['sum_'.$field] = self::sumAggregation($field); + break; + case 'matrix': + $aggs['matrix_'.$field] = self::matrixAggregation([$field]); + break; + case 'count': + $aggs['count_'.$field] = [ + 'value_count' => [ + 'field' => $field, + ], + ]; + break; + } + } + + return $aggs; + + } + public static function maxAggregation($field): array { return [ @@ -139,40 +173,4 @@ public static function matrixAggregation(array $fields): array ], ]; } - - - public static function multipleAggregations($aggregations, $field) - { - $aggs = []; - foreach ($aggregations as $aggregation) { - switch ($aggregation) { - case 'max': - $aggs['max_'.$field] = self::maxAggregation($field); - break; - case 'min': - $aggs['min_'.$field] = self::minAggregation($field); - break; - case 'avg': - $aggs['avg_'.$field] = self::avgAggregation($field); - break; - case 'sum': - $aggs['sum_'.$field] = self::sumAggregation($field); - break; - case 'matrix': - $aggs['matrix_'.$field] = self::matrixAggregation([$field]); - break; - case 'count': - $aggs['count_'.$field] = [ - 'value_count' => [ - 'field' => $field, - ], - ]; - break; - } - } - - return $aggs; - - } - -} \ No newline at end of file +} diff --git a/src/DSL/QueryBuilder.php b/src/DSL/QueryBuilder.php index a4c6754..f8d4cbc 100644 --- a/src/DSL/QueryBuilder.php +++ b/src/DSL/QueryBuilder.php @@ -1,22 +1,24 @@ _clearAndStashMeta($searchOptions); + $options = $this->_clearAndStashMeta($options); $params = []; if ($index) { $params['index'] = $index; @@ -47,7 +51,7 @@ public function buildSearchParams($index, $searchQuery, $searchOptions, $wheres } } - if (!empty($searchOptions['highlight'])) { + if (! empty($searchOptions['highlight'])) { $params['body']['highlight'] = $searchOptions['highlight']; unset($searchOptions['highlight']); } @@ -70,7 +74,7 @@ public function buildSearchParams($index, $searchQuery, $searchOptions, $wheres if ($opts) { foreach ($opts as $key => $value) { if (isset($params[$key])) { - $params[$key] = array_merge($params[$key], $opts[$key]); + $params[$key] = array_merge($params[$key], $value); } else { $params[$key] = $value; } @@ -91,6 +95,7 @@ public function buildSearchParams($index, $searchQuery, $searchOptions, $wheres */ public function buildParams($index, $wheres, $options = [], $columns = [], $_id = null): array { + $options = $this->_clearAndStashMeta($options); if ($index) { $params = [ 'index' => $index, @@ -123,20 +128,17 @@ public function buildParams($index, $wheres, $options = [], $columns = [], $_id return $params; } - - public function createNestedAggs($columns, $sort) + public function createNestedAggs($columns, $sort): array { $aggs = []; $terms = [ 'terms' => [ 'field' => $columns[0], - 'size' => 10000, + 'size' => 10000, ], ]; if (isset($sort['_count'])) { - if (!isset($terms['terms']['order'])) { - $terms['terms']['order'] = []; - } + $terms['terms']['order'] = []; if ($sort['_count'] == 'asc') { $terms['terms']['order'][] = ['_count' => 'asc']; } else { @@ -158,19 +160,18 @@ public function createNestedAggs($columns, $sort) return $aggs; } - public function addSearchToWheres($wheres, $queryString): array { $clause = ['_' => ['search' => $queryString]]; - if (!$wheres) { + if (! $wheres) { return $clause; } - if (!empty($wheres['and'])) { + if (! empty($wheres['and'])) { $wheres['and'][] = $clause; return $wheres; } - if (!empty($wheres['or'])) { + if (! empty($wheres['or'])) { $newOrs = []; foreach ($wheres['or'] as $cond) { $cond['and'][] = $clause; @@ -184,38 +185,34 @@ public function addSearchToWheres($wheres, $queryString): array return ['and' => [$wheres, $clause]]; } - //---------------------------------------------------------------------- // Parsers //---------------------------------------------------------------------- - public function _escape($value): string - { - $specialChars = ['"', '\\', '~', '^', '/']; - foreach ($specialChars as $char) { - $value = str_replace($char, "\\".$char, $value); - } - if (str_starts_with($value, '-')) { - $value = '\\'.$value; - } - - return $value; - } - /** * @throws ParameterException * @throws QueryException */ private function _buildQuery($wheres): array { - if (!$wheres) { + if (! $wheres) { return ParameterBuilder::matchAll(); } + $dsl = $this->_convertWheresToDSL($wheres); return ParameterBuilder::query($dsl); } + private function _clearAndStashMeta($options): array + { + if (! empty($options['_meta'])) { + $this->_stashMeta($options['_meta']); + unset($options['_meta']); + } + + return $options; + } /** * @throws ParameterException @@ -230,7 +227,7 @@ public function _convertWheresToDSL($wheres, $parentField = false): array $dsl['bool']['must'] = []; foreach ($conditions as $condition) { $parsedCondition = $this->_parseCondition($condition, $parentField); - if (!empty($parsedCondition)) { + if (! empty($parsedCondition)) { $dsl['bool']['must'][] = $parsedCondition; } } @@ -242,12 +239,12 @@ public function _convertWheresToDSL($wheres, $parentField = false): array foreach ($conditionGroup as $subConditions) { foreach ($subConditions as $subCondition) { $parsedCondition = $this->_parseCondition($subCondition, $parentField); - if (!empty($parsedCondition)) { + if (! empty($parsedCondition)) { $boolClause['bool']['must'][] = $parsedCondition; } } } - if (!empty($boolClause['bool']['must'])) { + if (! empty($boolClause['bool']['must'])) { $dsl['bool']['should'][] = $boolClause; } } @@ -268,15 +265,14 @@ private function _parseCondition($condition, $parentField = null): array { $field = key($condition); if ($parentField) { - if (!str_starts_with($field, $parentField.'.')) { + if (! str_starts_with($field, $parentField.'.')) { $field = $parentField.'.'.$field; } } $value = current($condition); - - if (!is_array($value)) { + if (! is_array($value)) { return ['match' => [$field => $value]]; } else { @@ -328,7 +324,7 @@ private function _parseCondition($condition, $parentField = null): array break; case 'in': $keywordField = $this->parseRequiredKeywordMapping($field); - if (!$keywordField) { + if (! $keywordField) { $queryPart = ['terms' => [$field => $operand]]; } else { $queryPart = ['terms' => [$keywordField => $operand]]; @@ -337,7 +333,7 @@ private function _parseCondition($condition, $parentField = null): array break; case 'nin': $keywordField = $this->parseRequiredKeywordMapping($field); - if (!$keywordField) { + if (! $keywordField) { $queryPart = ['bool' => ['must_not' => ['terms' => [$field => $operand]]]]; } else { $queryPart = ['bool' => ['must_not' => ['terms' => [$keywordField => $operand]]]]; @@ -358,7 +354,7 @@ private function _parseCondition($condition, $parentField = null): array break; case 'exact': $keywordField = $this->parseRequiredKeywordMapping($field); - if (!$keywordField) { + if (! $keywordField) { throw new ParameterException('Field ['.$field.'] is not a keyword field which is required for the [exact] operator.'); } $queryPart = ['term' => [$keywordField => $operand]]; @@ -370,8 +366,8 @@ private function _parseCondition($condition, $parentField = null): array case 'nested': $queryPart = [ 'nested' => [ - 'path' => $field, - 'query' => $this->_convertWheresToDSL($operand['wheres'], $field), + 'path' => $field, + 'query' => $this->_convertWheresToDSL($operand['wheres'], $field), 'score_mode' => $operand['score_mode'], ], ]; @@ -382,8 +378,8 @@ private function _parseCondition($condition, $parentField = null): array 'must_not' => [ [ 'nested' => [ - 'path' => $field, - 'query' => $this->_convertWheresToDSL($operand['wheres']), + 'path' => $field, + 'query' => $this->_convertWheresToDSL($operand['wheres']), 'score_mode' => $operand['score_mode'], ], ], @@ -394,24 +390,24 @@ private function _parseCondition($condition, $parentField = null): array break; case 'innerNested': $options = $this->_buildNestedOptions($operand['options'], $field); - if (!$options) { + if (! $options) { $options['size'] = 100; } $query = ParameterBuilder::matchAll()['query']; - if (!empty($operand['wheres'])) { + if (! empty($operand['wheres'])) { $query = $this->_convertWheresToDSL($operand['wheres'], $field); } $queryPart = [ 'nested' => [ - 'path' => $field, - 'query' => $query, + 'path' => $field, + 'query' => $query, 'inner_hits' => $options, ], ]; break; default: - abort('400', 'Invalid operator ['.$operator.'] provided for condition.'); + abort(400, 'Invalid operator ['.$operator.'] provided for condition.'); } return $queryPart; @@ -427,11 +423,17 @@ private function _buildOptions($options): array if ($options) { foreach ($options as $key => $value) { switch ($key) { + case 'prev_search_after': + $return['_meta']['prev_search_after'] = $value; + break; + case 'search_after': + $return['body']['search_after'] = $value; + break; case 'limit': $return['size'] = $value; break; case 'sort': - if (!isset($return['body']['sort'])) { + if (! isset($return['body']['sort'])) { $return['body']['sort'] = []; } foreach ($value as $field => $sortPayload) { @@ -470,20 +472,20 @@ private function _buildOptions($options): array /** * @throws ParameterException */ - private function _buildNestedOptions($options, $field) + private function _buildNestedOptions($options, $field): array { $options = $this->_buildOptions($options); - if (!empty($options['body'])) { + if (! empty($options['body'])) { $body = $options['body']; unset($options['body']); $options = array_merge($options, $body); } - if (!empty($options['sort'])) { + if (! empty($options['sort'])) { //ensure that the sort field is prefixed with the nested field $sorts = []; foreach ($options['sort'] as $sort) { foreach ($sort as $sortField => $sortPayload) { - if (!str_starts_with($sortField, $field.'.')) { + if (! str_starts_with($sortField, $field.'.')) { $sortField = $field.'.'.$sortField; } $sorts[] = [$sortField => $sortPayload]; @@ -501,13 +503,13 @@ public function _parseFilter($filterType, $filterPayload): void switch ($filterType) { case 'filterGeoBox': self::$filter['filter']['geo_bounding_box'][$filterPayload['field']] = [ - 'top_left' => $filterPayload['topLeft'], + 'top_left' => $filterPayload['topLeft'], 'bottom_right' => $filterPayload['bottomRight'], ]; break; case 'filterGeoPoint': self::$filter['filter']['geo_distance'] = [ - 'distance' => $filterPayload['distance'], + 'distance' => $filterPayload['distance'], $filterPayload['field'] => [ 'lat' => $filterPayload['geoPoint'][0], 'lon' => $filterPayload['geoPoint'][1], @@ -518,8 +520,7 @@ public function _parseFilter($filterType, $filterPayload): void } } - - public function _parseFilterParameter($params, $filer) + public function _parseFilterParameter($params, $filer): array { $body = $params['body']; $currentQuery = $body['query']; @@ -527,7 +528,7 @@ public function _parseFilterParameter($params, $filer) $filteredBody = [ 'query' => [ 'bool' => [ - 'must' => [ + 'must' => [ $currentQuery, ], 'filter' => $filer['filter'], diff --git a/src/DSL/Results.php b/src/DSL/Results.php index 63c80fd..1afb8c5 100644 --- a/src/DSL/Results.php +++ b/src/DSL/Results.php @@ -1,112 +1,83 @@ data = $data; - $this->_meta = ['query' => $queryTag] + $meta; - $this->_meta['params'] = $params; - $this->_meta['_id'] = $data['_id'] ?? null; - $this->_meta['success'] = true; - + $this->_meta = new QueryMetaData($meta); + $this->_meta->setQuery($queryTag); + $this->_meta->setSuccess(); + $this->_meta->setDsl($params); + if (! empty($data['_id'])) { + $this->_meta->setId($data['_id']); + } + if (! empty($meta['deleteCount'])) { + $this->_meta->setDeleted($meta['deleteCount']); + } + if (! empty($meta['modified'])) { + $this->_meta->setModified($meta['modified']); + } + if (! empty($meta['failed'])) { + $this->_meta->setFailed($meta['failed']); + } } public function setError($error, $errorCode): void { - $details = $this->_decodeError($error); - $this->_meta['error']['msg'] = $details['msg']; - $this->_meta['error']['data'] = $details['data']; - $this->_meta['error']['code'] = $errorCode; - $this->_meta['success'] = false; - $this->errorMessage = $error; - + $this->_meta->parseAndSetError($error, $errorCode); } - private function _decodeError($error) + public function isSuccessful(): bool { - $return['msg'] = $error; - $return['data'] = []; - $jsonStartPos = strpos($error, ': ') + 2; - $response = ($error); - $title = substr($response, 0, $jsonStartPos); - $jsonString = substr($response, $jsonStartPos); - $errorArray = json_decode($jsonString, true); - - if (json_last_error() === JSON_ERROR_NONE) { - $errorReason = $errorArray['error']['reason'] ?? null; - if (!$errorReason) { - return $return; - } - $return['msg'] = $title.$errorReason; - $cause = $errorArray['error']['root_cause'][0]['reason'] ?? null; - if ($cause) { - $return['msg'] .= ' - '.$cause; - } - - $return['data'] = $errorArray; - - } - - return $return; + return $this->_meta->isSuccessful(); } - public function isSuccessful(): bool + public function getMetaData(): QueryMetaData { - return $this->_meta['success'] ?? false; + return $this->_meta; } - public function getMetaData(): array + public function getMetaDataAsArray(): array { - return $this->_meta; + return $this->_meta->asArray(); } public function getLogFormattedMetaData(): array { $return = []; - foreach ($this->_meta as $key => $value) { + $meta = $this->getMetaDataAsArray(); + foreach ($meta as $key => $value) { $return['logged_'.$key] = $value; } return $return; } - public function getInsertedId(): string|null + public function getInsertedId(): mixed { - return $this->_meta['_id'] ?? null; + return $this->_meta->getId(); } - public function getModifiedCount(): int { - return $this->_meta['modified'] ?? 0; + return $this->_meta->getModified(); } public function getDeletedCount(): int { - return $this->_meta['deleted'] ?? 0; - } - - private function _isJson($string): bool - { - json_decode($string); - - return (json_last_error() == JSON_ERROR_NONE); + return $this->_meta->getDeleted(); } - } diff --git a/src/DSL/exceptions/ParameterException.php b/src/DSL/exceptions/ParameterException.php index 71fc429..d87679a 100644 --- a/src/DSL/exceptions/ParameterException.php +++ b/src/DSL/exceptions/ParameterException.php @@ -1,22 +1,24 @@ _details = $details; } - public function getDetails() + public function getDetails(): array { return $this->_details; } -} \ No newline at end of file +} diff --git a/src/DSL/exceptions/QueryException.php b/src/DSL/exceptions/QueryException.php index 8809b52..0f4d60d 100644 --- a/src/DSL/exceptions/QueryException.php +++ b/src/DSL/exceptions/QueryException.php @@ -1,22 +1,24 @@ _details = $details; } - public function getDetails() + public function getDetails(): array { return $this->_details; } -} \ No newline at end of file +} diff --git a/src/ElasticServiceProvider.php b/src/ElasticServiceProvider.php index b1ba245..0c08196 100644 --- a/src/ElasticServiceProvider.php +++ b/src/ElasticServiceProvider.php @@ -1,5 +1,7 @@ app['db']); Model::setEventDispatcher($this->app['events']); @@ -19,7 +21,7 @@ public function boot() /** * Register the service provider. */ - public function register() + public function register(): void { // Add database driver. $this->app->resolving('db', function ($db) { diff --git a/src/Eloquent/Builder.php b/src/Eloquent/Builder.php index b5bcc18..123d9c1 100644 --- a/src/Eloquent/Builder.php +++ b/src/Eloquent/Builder.php @@ -1,14 +1,31 @@ */ protected $passthru = [ 'aggregate', @@ -57,117 +74,152 @@ class Builder extends BaseEloquentBuilder 'search', 'todsl', 'agg', + 'insertwithoutrefresh', ]; - /** - * @inheritDoc + * @inerhitDoc */ - public function getConnection() + public function getConnection(): ConnectionInterface { return $this->query->getConnection(); } /** - * @inerhitDoc + * Override the default getModels + * + * @return array */ - public function getModels($columns = ['*']) + public function getModels($columns = ['*']): array { - $data = $this->query->get($columns); + $meta = $data->getQueryMeta(); $results = $this->model->hydrate($data->all())->all(); - return ['results' => $results]; - + return [ + 'results' => $results, + 'meta' => $meta, + ]; } - /** - * @see getModels($columns = ['*']) - */ - public function searchModels($columns = ['*']) + public function getModel(): Model { - - $data = $this->query->search($columns); - $results = $this->model->hydrate($data->all())->all(); - - return ['results' => $results]; - + return $this->model; } /** - * @inerhitDoc + * @param string[] $columns + * @return TCollection */ - public function get($columns = ['*']) + public function get($columns = ['*']): ElasticCollection { $builder = $this->applyScopes(); $fetch = $builder->getModels($columns); + $meta = $fetch['meta']; if (count($models = $fetch['results']) > 0) { $models = $builder->eagerLoadRelations($models); } + $elasticCollection = $builder->getModel()->newCollection($models); - return $builder->getModel()->newCollection($models); + $elasticCollection->setQueryMeta($meta); + return $elasticCollection; } /** - * @see get($columns = ['*']) + * Hydrate the models from the given array. */ - public function search($columns = ['*']) + public function hydrate(array $items): ElasticCollection { - $builder = $this->applyScopes(); - $fetch = $builder->searchModels($columns); - if (count($models = $fetch['results']) > 0) { - $models = $builder->eagerLoadRelations($models); - } + $instance = $this->newModelInstance(); - return $builder->getModel()->newCollection($models); + return $instance->newCollection(array_map(function ($item) use ($items, $instance) { + $recordIndex = null; + if (is_array($item)) { + $recordIndex = ! empty($item['_index']) ? $item['_index'] : null; + if ($recordIndex) { + unset($item['_index']); + } + } + $meta = []; + if (isset($item['_meta'])) { + $meta = $item['_meta']; + unset($item['_meta']); + } + $instance->setMeta($meta); + $model = $instance->newFromBuilder($item); + if ($recordIndex) { + $model->setRecordIndex($recordIndex); + $model->setIndex($recordIndex); + } + if ($meta) { + $model->setMeta($meta); + } + if (count($items) > 1) { + $model->preventsLazyLoading = Model::preventsLazyLoading(); + } + + return $model; + }, $items)); } /** - * @param array $values - * - * @return array + * @see getModels($columns = ['*']) */ - protected function addUpdatedAtColumn(array $values) + public function searchModels($columns = ['*']): array { - if (!$this->model->usesTimestamps() || $this->model->getUpdatedAtColumn() === null) { - return $values; - } - $column = $this->model->getUpdatedAtColumn(); - $values = array_merge([$column => $this->model->freshTimestampString()], $values); + $data = $this->query->search($columns); + $results = $this->model->hydrate($data->all())->all(); - return $values; + return ['results' => $results]; } + /** + * @see get($columns = ['*']) + */ + public function search($columns = ['*']): Collection + { + $builder = $this->applyScopes(); + $fetch = $builder->searchModels($columns); + if (count($models = $fetch['results']) > 0) { + $models = $builder->eagerLoadRelations($models); + } + + return $builder->getModel()->newCollection($models); + } - public function firstOrCreate(array $attributes = [], array $values = []) + public function firstOrCreate(array $attributes = [], array $values = []): Model { $instance = $this->_instanceBuilder($attributes); - if (!is_null($instance)) { + if (! is_null($instance)) { return $instance; } return $this->create(array_merge($attributes, $values)); } - - /** - * - * Fast create method for 'write and forget' - * - * @param array $attributes - * - * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Support\HigherOrderTapProxy|mixed|Builder - */ - public function createWithoutRefresh(array $attributes = []) + private function _instanceBuilder(array $attributes = []) { - return tap($this->newModelInstance($attributes), function ($instance) { - $instance->saveWithoutRefresh(); - }); + $instance = clone $this; + + foreach ($attributes as $field => $value) { + $method = is_string($value) ? 'whereExact' : 'where'; + + if (is_array($value)) { + foreach ($value as $v) { + $specificMethod = is_string($v) ? 'whereExact' : 'where'; + $instance = $instance->$specificMethod($field, $v); + } + } else { + $instance = $instance->$method($field, $value); + } + } + + return $instance->first(); } - public function updateWithoutRefresh(array $attributes = []) + public function updateWithoutRefresh(array $attributes = []): int { $query = $this->toBase(); $query->setRefresh(false); @@ -175,11 +227,21 @@ public function updateWithoutRefresh(array $attributes = []) return $query->update($this->addUpdatedAtColumn($attributes)); } + protected function addUpdatedAtColumn(array $values): array + { + if (! $this->model->usesTimestamps() || $this->model->getUpdatedAtColumn() === null) { + return $values; + } + + $column = $this->model->getUpdatedAtColumn(); + + return array_merge([$column => $this->model->freshTimestampString()], $values); + } public function firstOrCreateWithoutRefresh(array $attributes = [], array $values = []) { $instance = $this->_instanceBuilder($attributes); - if (!is_null($instance)) { + if (! is_null($instance)) { return $instance; } @@ -187,10 +249,29 @@ public function firstOrCreateWithoutRefresh(array $attributes = [], array $value } /** - * @inheritdoc + * Fast create method for 'write and forget' */ - public function chunkById($count, callable $callback, $column = '_id', $alias = null, $keepAlive = '5m') - { + public function createWithoutRefresh(array $attributes = [] + ): \Illuminate\Database\Eloquent\Model|\Illuminate\Support\HigherOrderTapProxy|null|Builder { + return tap($this->newModelInstance($attributes), function ($instance) { + $instance->saveWithoutRefresh(); + }); + } + + //---------------------------------------------------------------------- + // ES Filters + //---------------------------------------------------------------------- + + /** + * {@inheritdoc} + */ + public function chunkById( + mixed $count, + callable $callback, + mixed $column = '_id', + mixed $alias = null, + string $keepAlive = '5m' + ): bool { $column ??= $this->defaultKeyName(); $alias ??= $column; //remove sort @@ -198,8 +279,6 @@ public function chunkById($count, callable $callback, $column = '_id', $alias = if ($column === '_id') { //Use PIT - - return $this->_chunkByPit($count, $callback, $keepAlive); } else { $lastId = null; @@ -212,10 +291,10 @@ public function chunkById($count, callable $callback, $column = '_id', $alias = break; } if ($callback($results, $page) === false) { - return false; + return true; } $aliasClean = $alias; - if (substr($aliasClean, -8) == '.keyword') { + if (str_ends_with($aliasClean, '.keyword')) { $aliasClean = substr($aliasClean, 0, -8); } $lastId = data_get($results->last(), $aliasClean); @@ -228,335 +307,290 @@ public function chunkById($count, callable $callback, $column = '_id', $alias = $page++; } while ($countResults == $count); - - return true; } - + return true; } - - public function chunk($count, callable $callback, $keepAlive = '5m') + private function _chunkByPit(mixed $count, callable $callback, string $keepAlive = '5m'): bool { - //default to using PIT - return $this->_chunkByPit($count, $callback, $keepAlive); - } + $pitId = $this->query->openPit($keepAlive); + $searchAfter = null; + $page = 1; + do { + $clone = clone $this; + $search = $clone->query->pitFind($count, $pitId, $searchAfter, $keepAlive); + $meta = $search->getMetaData(); + $searchAfter = $meta->getSort(); + $results = $this->hydrate($search->data); + $countResults = $results->count(); + + if ($countResults == 0) { + break; + } + if ($callback($results, $page) === false) { + return true; + } + unset($results); + + $page++; + } while ($countResults == $count); + + $this->query->closePit($pitId); + + return true; + } //---------------------------------------------------------------------- - // ES Filters + // ES Search query builders //---------------------------------------------------------------------- - /** - * @param string $field - * @param array $topLeft - * @param array $bottomRight - * - * @return $this - */ - public function filterGeoBox(string $field, array $topLeft, array $bottomRight) + public function chunk(mixed $count, callable $callback, string $keepAlive = '5m'): bool + { + //default to using PIT + return $this->_chunkByPit($count, $callback, $keepAlive); + } + + public function filterGeoBox(string $field, array $topLeft, array $bottomRight): self { $this->query->filterGeoBox($field, $topLeft, $bottomRight); return $this; } - /** - * @param string $field - * @param string $distance - * @param array $geoPoint - * - * @return $this - */ - public function filterGeoPoint(string $field, string $distance, array $geoPoint) + public function filterGeoPoint(string $field, string $distance, array $geoPoint): self { $this->query->filterGeoPoint($field, $distance, $geoPoint); return $this; } - //---------------------------------------------------------------------- - // ES Search query builders - //---------------------------------------------------------------------- - - /** - * @param string $term - * @param int|null $boostFactor - * - * @return $this - */ - public function term(string $term, int $boostFactor = null) + public function term(string $term, ?int $boostFactor = null): self { $this->query->searchQuery($term, $boostFactor); return $this; } - /** - * @param string $term - * @param int|null $boostFactor - * - * @return $this - */ - public function andTerm(string $term, int $boostFactor = null) + public function andTerm(string $term, ?int $boostFactor = null): self { $this->query->searchQuery($term, $boostFactor, 'AND'); return $this; } - /** - * @param string $term - * @param int|null $boostFactor - * - * @return $this - */ - public function orTerm(string $term, int $boostFactor = null) + public function orTerm(string $term, ?int $boostFactor = null): self { $this->query->searchQuery($term, $boostFactor, 'OR'); return $this; } - /** - * @param string $term - * @param int|null $boostFactor - * - * @return $this - */ - public function fuzzyTerm(string $term, int $boostFactor = null) + public function fuzzyTerm(string $term, ?int $boostFactor = null): self { $this->query->searchQuery($term, $boostFactor, null, 'fuzzy'); return $this; } - /** - * @param string $term - * @param int|null $boostFactor - * - * @return $this - */ - public function andFuzzyTerm(string $term, int $boostFactor = null) + public function andFuzzyTerm(string $term, ?int $boostFactor = null): self { $this->query->searchQuery($term, $boostFactor, 'AND', 'fuzzy'); return $this; } - /** - * @param string $term - * @param int|null $boostFactor - * - * @return $this - */ - public function orFuzzyTerm(string $term, int $boostFactor = null) + public function orFuzzyTerm(string $term, ?int $boostFactor = null): self { $this->query->searchQuery($term, $boostFactor, 'OR', 'fuzzy'); return $this; } - /** - * @param string $regEx - * @param int|null $boostFactor - * - * @return $this - */ - public function regEx(string $regEx, int $boostFactor = null) + public function regEx(string $regEx, ?int $boostFactor = null): self { $this->query->searchQuery($regEx, $boostFactor, null, 'regex'); return $this; } - /** - * @param string $regEx - * @param int|null $boostFactor - * - * @return $this - */ - public function andRegEx(string $regEx, int $boostFactor = null) + public function andRegEx(string $regEx, ?int $boostFactor = null): self { $this->query->searchQuery($regEx, $boostFactor, 'AND', 'regex'); return $this; } - /** - * @param string $regEx - * @param int|null $boostFactor - * - * @return $this - */ - public function orRegEx(string $regEx, int $boostFactor = null) + public function orRegEx(string $regEx, ?int $boostFactor = null): self { $this->query->searchQuery($regEx, $boostFactor, 'OR', 'regex'); return $this; } - - public function phrase(string $term, int $boostFactor = null) + public function phrase(string $term, ?int $boostFactor = null): self { $this->query->searchQuery($term, $boostFactor, null, 'phrase'); return $this; } - public function andPhrase(string $term, int $boostFactor = null) + public function andPhrase(string $term, ?int $boostFactor = null): self { $this->query->searchQuery($term, $boostFactor, 'AND', 'phrase'); return $this; } - public function orPhrase(string $term, int $boostFactor = null) + public function orPhrase(string $term, ?int $boostFactor = null): self { $this->query->searchQuery($term, $boostFactor, 'OR', 'phrase'); return $this; } - /** - * @param $value - * - * @return $this - */ - public function minShouldMatch($value) + public function minShouldMatch($value): self { $this->query->minShouldMatch($value); return $this; } - /** - * @param float $value - * - * @return $this - */ - public function minScore(float $value) + public function minScore(float $value): self { $this->query->minScore($value); return $this; } - /** - * @param string $field - * @param int|null $boostFactor - * - * @return $this - */ - public function field(string $field, int $boostFactor = null) + // Elastic type paginator that uses the search_after instead of limiting to Max results. + + public function field(string $field, ?int $boostFactor = null): self { $this->query->searchField($field, $boostFactor); return $this; } - /** - * @param array $fields - * - * @return $this - */ - public function fields(array $fields) + public function fields(array $fields): self { $this->query->searchFields($fields); return $this; } - public function hydrate(array $items) + //---------------------------------------------------------------------- + // Inherited as is but typed + //---------------------------------------------------------------------- + /** + * Create a new instance of the model being queried. + * + * @param array $attributes + */ + public function newModelInstance($attributes = []): Model { - $instance = $this->newModelInstance(); - - return $instance->newCollection(array_map(function ($item) use ($items, $instance) { - $recordIndex = null; - if (is_array($item)) { - $recordIndex = !empty($item['_index']) ? $item['_index'] : null; - if ($recordIndex) { - unset($item['_index']); - } - } - $meta = []; - if (isset($item['_meta'])) { - $meta = $item['_meta']; - unset($item['_meta']); - } - $model = $instance->newFromBuilder($item); - if ($recordIndex) { - $model->setRecordIndex($recordIndex); - $model->setIndex($recordIndex); - - } - if ($meta) { - $model->setMeta($meta); - } - if (count($items) > 1) { - $model->preventsLazyLoading = Model::preventsLazyLoading(); - } + return $this->model->newInstance($attributes)->setConnection($this->query->getConnection()->getName()); + } - return $model; - }, $items)); + /** + * Override the default schema builder. + */ + public function toBase(): QueryBuilder + { + return $this->applyScopes()->getQuery(); } + public function create(array $attributes = []): Model + { + return tap($this->newModelInstance($attributes), function ($instance) { + $instance->save(); + }); + } + public function getQuery(): QueryBuilder + { + return $this->query; + } //---------------------------------------------------------------------- // Private methods //---------------------------------------------------------------------- - private function _instanceBuilder(array $attributes = []) + /** + * Using Laravel base method name rather + * + * @throws MissingOrderException|BindingResolutionException + */ + public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null): SearchAfterPaginator { - $instance = clone $this; + if (empty($this->query->orders)) { + //try set created_at & updated_at + if (! $this->inferSort()) { + throw new MissingOrderException; + } + } elseif (count($this->query->orders) === 1) { + //try set a tie-breaker with created_at & updated_at + $this->inferSort(); + } - foreach ($attributes as $field => $value) { - $method = is_string($value) ? 'whereExact' : 'where'; + if (! $cursor instanceof Cursor) { + $cursor = is_string($cursor) ? Cursor::fromEncoded($cursor) : CursorPaginator::resolveCurrentCursor('cursor', $cursor); + } - if (is_array($value)) { - foreach ($value as $v) { - $specificMethod = is_string($v) ? 'whereExact' : 'where'; - $instance = $instance->$specificMethod($field, $v); - } - } else { - $instance = $instance->$method($field, $value); - } + $this->query->limit($perPage); + $cursorPayload = $this->query->initCursor($cursor); + $age = time() - $cursorPayload['ts']; + $ttl = 300; //5 minutes + if ($age > $ttl) { + // cursor is older than 5m, let's refresh it + $clone = $this->clone(); + $cursorPayload['records'] = $clone->count(); + $cursorPayload['pages'] = (int) ceil($cursorPayload['records'] / $perPage); + $cursorPayload['ts'] = time(); } + if ($cursorPayload['next_sort'] && ! in_array($cursorPayload['next_sort'], $cursorPayload['sort_history'])) { + $cursorPayload['sort_history'][] = $cursorPayload['next_sort']; + } + $this->query->cursor = $cursorPayload; + $search = $this->get($columns); - return $instance->first(); + return $this->searchAfterPaginator($search, $perPage, $cursor, [ + 'path' => Paginator::resolveCurrentPath(), + 'cursorName' => 'cursor', + 'records' => $cursorPayload['records'], + 'totalPages' => $cursorPayload['pages'], + 'currentPage' => $cursorPayload['page'], + ]); } - - private function _chunkByPit($count, callable $callback, $keepAlive = '5m') + protected function inferSort(): bool { - $pitId = $this->query->openPit($keepAlive); + $found = false; + $indexMappings = $this->query->getIndexMappings(); + $mappings = reset($indexMappings); + $fields = $mappings['mappings']['properties']; + if (! empty($fields['created_at'])) { + $this->query->orderBy('created_at'); - $searchAfter = null; - $page = 1; - do { - $clone = clone $this; - $search = $clone->query->pitFind($count, $pitId, $searchAfter, $keepAlive); - $meta = $search->getMetaData(); - $searchAfter = $meta['last_sort']; - $results = $this->hydrate($search->data); - $countResults = $results->count(); - - if ($countResults == 0) { - break; - } - - if ($callback($results, $page) === false) { - return false; - } + $found = true; + } + if (! empty($fields['updated_at'])) { + $this->query->orderBy('updated_at'); - unset($results); + $found = true; + } - $page++; - } while ($countResults == $count); + return $found; + } - $this->query->closePit($pitId); + /** + * @throws BindingResolutionException + */ + protected function searchAfterPaginator($items, $perPage, $cursor, $options) + { + return Container::getInstance()->makeWith(SearchAfterPaginator::class, compact('items', 'perPage', 'cursor', 'options')); } } diff --git a/src/Eloquent/Docs/ModelDocs.php b/src/Eloquent/Docs/ModelDocs.php index b996b5e..4b75774 100644 --- a/src/Eloquent/Docs/ModelDocs.php +++ b/src/Eloquent/Docs/ModelDocs.php @@ -1,66 +1,89 @@ ', string|array $postTag = '', $globalOptions = []) - * - * @method $this deleteIndexIfExists() + * @method static $this andPhrase(string $term, $boostFactor = null) + * @method static $this orPhrase(string $term, $boostFactor = null) + * @method static $this minShouldMatch(int $value) + * @method static $this minScore(float $value) + * @method static $this field(string $field, int $boostFactor = null) + * @method static $this fields(array $fields) + * @method static int|array sum(array|string $columns) + * @method static int|array min(array|string $columns) + * @method static int|array max(array|string $columns) + * @method static int|array avg(array|string $columns) + * @method static array getModels(array $columns = ['*']) + * @method static array searchModels(array $columns = ['*']) + * @method static ElasticCollection get(array $columns = ['*']) + * @method static Model|null first(array $columns = ['*']) + * @method static ElasticCollection search(array $columns = ['*']) + * @method static array toDsl(array $columns = ['*']) + * @method static mixed agg(array $functions, $column) + * @method static $this where(array|Closure|Expression|string $column, $operator = null, $value = null, $boolean = 'and') + * @method static $this whereDate($column, $operator = null, $value = null, $boolean = 'and') + * @method static $this whereTimestamp($column, $operator = null, $value = null, $boolean = 'and') + * @method static $this whereIn(string $column, array $values) + * @method static $this whereExact(string $column, string $value) + * @method static $this wherePhrase(string $column, string $value) + * @method static $this wherePhrasePrefix(string $column, string $value) + * @method static $this filterGeoBox(string $column, array $topLeftCoords, array $bottomRightCoords) + * @method static $this filterGeoPoint(string $column, string $distance, array $point) + * @method static $this whereRegex(string $column, string $regex) + * @method static $this whereNestedObject(string $column, Callable $callback, string $scoreType = 'avg') + * @method static $this whereNotNestedObject(string $column, Callable $callback, string $scoreType = 'avg') + * @method static $this firstOrCreate(array $attributes, array $values = []) + * @method static $this firstOrCreateWithoutRefresh(array $attributes, array $values = []) + * @method static $this orderBy(string $column, string $direction = 'asc', string $mode = null, array $missing = '_last') + * @method static $this orderByDesc(string $column, string $mode = null, array $missing = '_last') + * @method static $this orderByGeo(string $column, array $pin, $direction = 'asc', $unit = 'km', $mode = null, $type = 'arc') + * @method static $this orderByGeoDesc(string $column, array $pin, $unit = 'km', $mode = null, $type = 'arc') + * @method static $this orderByNested(string $column, string $direction = 'asc', string $mode = null) + * @method static bool chunk(mixed $count, callable $callback, string $keepAlive = '5m') + * @method static bool chunkById(mixed $count, callable $callback, $column = '_id', $alias = null, $keepAlive = '5m') + * @method static $this queryNested(string $column, Callable $callback) + * @method static array rawSearch(array $bodyParams, bool $returnRaw = false) + * @method static array rawAggregation(array $bodyParams) + * @method static $this highlight(array $fields = [], string|array $preTag = '', string|array $postTag = '', $globalOptions = []) + * @method static bool deleteIndexIfExists() + * @method static bool deleteIndex() + * @method static bool createIndex(array $settings = []) + * @method static array getIndexMappings() + * @method static array getIndexSettings() + * @method static bool indexExists() + * @method static LengthAwarePaginator paginate(int $perPage = 15, array $columns = ['*'], string $pageName = 'page', ?int $page = null, ?int $total = null) + * @method static SearchAfterPaginator cursorPaginate(int|null $perPage = null, array $columns = [], string $cursorName = 'cursor', ?Cursor $cursor = null) + * @method static string getQualifiedKeyName() + * @method static string getConnection() + * @method static void truncate() + * @method static ElasticCollection insert($values, $returnData = null): + * @method static ElasticCollection insertWithoutRefresh($values, $returnData = null) * + * @property object $search_highlights + * @property object $with_highlights + * @property array $search_highlights_as_array * - * @mixin \Illuminate\Database\Query\Builder + * @mixin Builder */ -trait ModelDocs -{ -} +trait ModelDocs {} diff --git a/src/Eloquent/HasCollection.php b/src/Eloquent/HasCollection.php new file mode 100644 index 0000000..44c3e58 --- /dev/null +++ b/src/Eloquent/HasCollection.php @@ -0,0 +1,19 @@ + $models + * @return \PDPhilip\Elasticsearch\Collection\ElasticCollection; + */ + public function newCollection(array $models = []): ElasticCollection + { + return new ElasticCollection($models); + } +} diff --git a/src/Eloquent/HybridRelations.php b/src/Eloquent/HybridRelations.php index 167b452..f9d572d 100644 --- a/src/Eloquent/HybridRelations.php +++ b/src/Eloquent/HybridRelations.php @@ -1,70 +1,67 @@ getForeignKey(); + $foreignKey = $foreignKey ?: $this->getForeignKey(); $instance = new $related; - $localKey = $localKey ? : $this->getKeyName(); + $localKey = $localKey ?: $this->getKeyName(); return new HasOne($instance->newQuery(), $this, $foreignKey, $localKey); } /** - * @inheritDoc + * {@inheritDoc} */ - public function morphOne($related, $name, $type = null, $id = null, $localKey = null) + public function morphOne($related, $name, $type = null, $id = null, $localKey = null): MorphOne { $instance = new $related; [$type, $id] = $this->getMorphs($name, $type, $id); - $localKey = $localKey ? : $this->getKeyName(); + $localKey = $localKey ?: $this->getKeyName(); return new MorphOne($instance->newQuery(), $this, $type, $id, $localKey); - } /** - * @inheritDoc + * {@inheritDoc} */ - public function hasMany($related, $foreignKey = null, $localKey = null) + public function hasMany($related, $foreignKey = null, $localKey = null): HasMany { - $foreignKey = $foreignKey ? : $this->getForeignKey(); + $foreignKey = $foreignKey ?: $this->getForeignKey(); $instance = new $related; - $localKey = $localKey ? : $this->getKeyName(); + $localKey = $localKey ?: $this->getKeyName(); return new HasMany($instance->newQuery(), $this, $foreignKey, $localKey); } /** - * @inheritDoc + * {@inheritDoc} */ - public function morphMany($related, $name, $type = null, $id = null, $localKey = null) + public function morphMany($related, $name, $type = null, $id = null, $localKey = null): MorphMany { $instance = new $related; @@ -73,24 +70,23 @@ public function morphMany($related, $name, $type = null, $id = null, $localKey = $table = $instance->getTable(); - $localKey = $localKey ? : $this->getKeyName(); + $localKey = $localKey ?: $this->getKeyName(); return new MorphMany($instance->newQuery(), $this, $type, $id, $localKey); } /** - * @inheritDoc + * {@inheritDoc} */ - public function belongsTo($related, $foreignKey = null, $otherKey = null, $relation = null) + public function belongsTo($related, $foreignKey = null, $otherKey = null, $relation = null): BelongsTo { if ($relation === null) { - [$current, $caller] = debug_backtrace(false, 2); + [$current, $caller] = debug_backtrace(0, 2); $relation = $caller['function']; } - if ($foreignKey === null) { $foreignKey = Str::snake($relation).'_id'; } @@ -99,15 +95,15 @@ public function belongsTo($related, $foreignKey = null, $otherKey = null, $relat $query = $instance->newQuery(); - $otherKey = $otherKey ? : $instance->getKeyName(); + $otherKey = $otherKey ?: $instance->getKeyName(); return new BelongsTo($query, $this, $foreignKey, $otherKey, $relation); } /** - * @inheritDoc + * {@inheritDoc} */ - public function morphTo($name = null, $type = null, $id = null, $ownerKey = null) + public function morphTo($name = null, $type = null, $id = null, $ownerKey = null): MorphTo { if ($name === null) { [$current, $caller] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); @@ -118,9 +114,7 @@ public function morphTo($name = null, $type = null, $id = null, $ownerKey = null [$type, $id] = $this->getMorphs($name, $type, $id); if (($class = $this->$type) === null) { - return new MorphTo( - $this->newQuery(), $this, $id, $ownerKey, $type, $name - ); + return new MorphTo($this->newQuery(), $this, $id, $ownerKey, $type, $name); } $class = $this->getActualClassNameForMorph($class); @@ -129,45 +123,25 @@ public function morphTo($name = null, $type = null, $id = null, $ownerKey = null $ownerKey = $ownerKey ?? $instance->getKeyName(); - return new MorphTo( - $instance->newQuery(), $this, $id, $ownerKey, $type, $name - ); + return new MorphTo($instance->newQuery(), $this, $id, $ownerKey, $type, $name); } /** - * @inheritDoc + * {@inheritdoc} */ - public function belongsToMany($related, $collection = null, $foreignKey = null, $otherKey = null, $parentKey = null, $relatedKey = null, $relation = null) + public function newEloquentBuilder($query): EloquentBuilder|Builder { - - if ($relation === null) { - $relation = $this->guessBelongsToManyRelation(); - } - - - if (!is_subclass_of($related, ParentModel::class)) { - return parent::belongsToMany($related, $collection, $foreignKey, $otherKey, $parentKey, $relatedKey, $relation); - } - - $foreignKey = $foreignKey ? : $this->getForeignKey().'s'; - $instance = new $related; - $otherKey = $otherKey ? : $instance->getForeignKey().'s'; - - if ($collection === null) { - $collection = $instance->getTable(); + if (is_subclass_of($this, ParentModel::class)) { + return new Builder($query); } - $query = $instance->newQuery(); - - return new BelongsToMany($query, $this, $collection, $foreignKey, $otherKey, $parentKey ? : $this->getKeyName(), $relatedKey ? : $instance->getKeyName(), $relation - ); + return new EloquentBuilder($query); } /** - * @inheritDoc + * {@inheritDoc} */ - - protected function guessBelongsToManyRelation() + protected function guessBelongsToManyRelation(): string { if (method_exists($this, 'getBelongsToManyCaller')) { return $this->getBelongsToManyCaller(); @@ -175,16 +149,4 @@ protected function guessBelongsToManyRelation() return parent::guessBelongsToManyRelation(); } - - /** - * @inheritdoc - */ - public function newEloquentBuilder($query) - { - if (is_subclass_of($this, ParentModel::class)) { - return new Builder($query); - } - - return new EloquentBuilder($query); - } } diff --git a/src/Eloquent/Model.php b/src/Eloquent/Model.php index 50d940f..cabbfc2 100644 --- a/src/Eloquent/Model.php +++ b/src/Eloquent/Model.php @@ -1,5 +1,7 @@ forcePrimaryKey(); } + public function forcePrimaryKey(): void + { + $this->primaryKey = '_id'; + } - public function setIndex($index = null) + public function getRecordIndex(): ?string { - if ($index) { - return $this->index = $index; - } - $this->index = $this->index ?? $this->getTable(); - unset($this->table); + return $this->recordIndex; } public function setRecordIndex($recordIndex = null) @@ -62,11 +70,9 @@ public function setRecordIndex($recordIndex = null) return $this->recordIndex = $this->index; } - public function getRecordIndex() - { - return $this->recordIndex; - } - + /** + * {@inheritdoc} + */ public function setTable($index) { $this->index = $index; @@ -75,22 +81,10 @@ public function setTable($index) return $this; } - - public function forcePrimaryKey() - { - $this->primaryKey = '_id'; - } - - - public function getMaxSize() - { - return static::MAX_SIZE; - } - public function getIdAttribute($value = null) { // If no value for id, then set ES's _id - if (!$value && array_key_exists('_id', $this->attributes)) { + if (! $value && array_key_exists('_id', $this->attributes)) { $value = $this->attributes['_id']; } @@ -98,116 +92,104 @@ public function getIdAttribute($value = null) } /** - * @inheritdoc + * {@inheritdoc} */ - public function getQualifiedKeyName() + public function getQualifiedKeyName(): string { return $this->getKeyName(); } - /** - * @inheritdoc - */ - public function fromDateTime($value) + public function getMeta(): ModelMetaData { - return parent::asDateTime($value); + return $this->_meta; } - /** - * @inheritdoc - */ - protected function asDateTime($value) + public function getMetaAsArray(): array { - - return parent::asDateTime($value); + return $this->_meta->asArray(); } - /** - * @inheritdoc - */ - public function getDateFormat() + public function setMeta($meta): static { - return $this->dateFormat ? : 'Y-m-d H:i:s'; - } - - public function setMeta($meta) - { - $this->_meta = $meta; + $this->_meta = new ModelMetaData($meta); return $this; } - public function getMeta() - { - return (object)$this->_meta; - } - - public function getSearchHighlightsAttribute() + public function getSearchHighlightsAttribute(): ?object { - if (!empty($this->_meta['highlights'])) { - $data = []; - $this->_mergeFlatKeysIntoNestedArray($data, $this->_meta['highlights']); - - return (object)$data; - } - - return null; + return $this->_meta->parseHighlights(); } - public function getSearchHighlightsAsArrayAttribute() + public function getSearchHighlightsAsArrayAttribute(): array { - if (!empty($this->_meta['highlights'])) { - return $this->_meta['highlights']; - } - - return []; + return $this->_meta->getHighlights(); } - public function getWithHighlightsAttribute() + public function getWithHighlightsAttribute(): object { $data = $this->attributes; - $mutators = array_values(array_diff($this->getMutatedAttributes(), ['id', 'search_highlights', 'search_highlights_as_array', 'with_highlights'])); + $mutators = array_values(array_diff($this->getMutatedAttributes(), [ + 'id', + 'search_highlights', + 'search_highlights_as_array', + 'with_highlights', + ])); if ($mutators) { foreach ($mutators as $mutator) { $data[$mutator] = $this->{$mutator}; } } - if (!empty($this->_meta['highlights'])) { - $this->_mergeFlatKeysIntoNestedArray($data, $this->_meta['highlights']); - } - return (object)$data; + return (object) $this->_meta->parseHighlights($data); } /** - * @inheritdoc + * {@inheritdoc} */ - public function freshTimestamp() + public function freshTimestamp(): string { -// return Carbon::now()->toIso8601String(); + // return Carbon::now()->toIso8601String(); return Carbon::now()->format($this->getDateFormat()); } - public function getIndex() + /** + * {@inheritdoc} + */ + public function getDateFormat(): string { - return $this->index ? : parent::getTable(); + return $this->dateFormat ?: 'Y-m-d H:i:s'; + } + + public function getIndex(): string + { + return $this->index ?: parent::getTable(); + } + + public function setIndex($index = null) + { + if ($index) { + return $this->index = $index; + } + $this->index = $this->index ?? $this->getTable(); + unset($this->table); } /** - * @inheritdoc + * {@inheritdoc} */ - public function getTable() + public function getTable(): string { return $this->getIndex(); } /** - * @inheritdoc + * {@inheritdoc} */ - public function getAttribute($key) + public function getAttribute($key): mixed { - if (!$key) { - return; + if (! $key) { + return null; } // Dot notation support. @@ -219,22 +201,9 @@ public function getAttribute($key) } /** - * @inheritdoc + * {@inheritdoc} */ - protected function getAttributeFromArray($key) - { - // Support keys in dot notation. - if (Str::contains($key, '.')) { - return Arr::get($this->attributes, $key); - } - - return parent::getAttributeFromArray($key); - } - - /** - * @inheritdoc - */ - public function setAttribute($key, $value) + public function setAttribute($key, $value): mixed { if (Str::contains($key, '.')) { @@ -244,26 +213,40 @@ public function setAttribute($key, $value) Arr::set($this->attributes, $key, $value); - return; + return null; } return parent::setAttribute($key, $value); } + public function fromDateTime(mixed $value): Carbon + { + return parent::asDateTime($value); + } + /** - * @inheritdoc + * {@inheritdoc} */ - public function getCasts() + protected function asDateTime($value): Carbon + { + + return parent::asDateTime($value); + } + + /** + * {@inheritdoc} + */ + public function getCasts(): array { return $this->casts; } /** - * @inheritdoc + * {@inheritdoc} */ - public function originalIsEquivalent($key) + public function originalIsEquivalent($key): bool { - if (!array_key_exists($key, $this->original)) { + if (! array_key_exists($key, $this->original)) { return false; } @@ -274,33 +257,68 @@ public function originalIsEquivalent($key) return true; } - if (null === $attribute) { + if ($attribute === null) { return false; } if ($this->hasCast($key, static::$primitiveCastTypes)) { - return $this->castAttribute($key, $attribute) === - $this->castAttribute($key, $original); + return $this->castAttribute($key, $attribute) === $this->castAttribute($key, $original); } - return is_numeric($attribute) && is_numeric($original) && strcmp((string)$attribute, (string)$original) === 0; + return is_numeric($attribute) && is_numeric($original) && strcmp((string) $attribute, (string) $original) === 0; } + /** + * {@inheritdoc} + */ + public function getForeignKey(): string + { + return Str::snake(class_basename($this)).'_'.ltrim($this->primaryKey, '_'); + } + + /** + * {@inheritdoc} + */ + public function newEloquentBuilder($query): Builder + { + $builder = new Builder($query); + + return $builder; + } + + public function saveWithoutRefresh(array $options = []): bool + { + $this->mergeAttributesFromCachedCasts(); + + $query = $this->newModelQuery(); + //@phpstan-ignore-next-line + $query->setRefresh(false); + + if ($this->exists) { + //@phpstan-ignore-next-line + $saved = ! $this->isDirty() || $this->performUpdate($query); + } else { + //@phpstan-ignore-next-line + $saved = $this->performInsert($query); + } + + if ($saved) { + $this->finishSave($options); + } + + return $saved; + } /** * Append one or more values to the underlying attribute value and sync with original. - * - * @param string $column - * @param array $values - * @param bool $unique */ - protected function pushAttributeValues($column, array $values, $unique = false) + protected function pushAttributeValues(string $column, array $values, bool $unique = false): void { - $current = $this->getAttributeFromArray($column) ? : []; + $current = $this->getAttributeFromArray($column) ?: []; foreach ($values as $value) { // Don't add duplicate values when we only want unique values. - if ($unique && (!is_array($current) || in_array($value, $current))) { + if ($unique && (! is_array($current) || in_array($value, $current))) { continue; } @@ -312,15 +330,25 @@ protected function pushAttributeValues($column, array $values, $unique = false) $this->syncOriginalAttribute($column); } + /** + * {@inheritdoc} + */ + protected function getAttributeFromArray($key): mixed + { + // Support keys in dot notation. + if (Str::contains($key, '.')) { + return Arr::get($this->attributes, $key); + } + + return parent::getAttributeFromArray($key); + } + /** * Remove one or more values to the underlying attribute value and sync with original. - * - * @param string $column - * @param array $values */ - protected function pullAttributeValues($column, array $values) + protected function pullAttributeValues(string $column, array $values): void { - $current = $this->getAttributeFromArray($column) ? : []; + $current = $this->getAttributeFromArray($column) ?: []; if (is_array($current)) { foreach ($values as $value) { @@ -337,144 +365,88 @@ protected function pullAttributeValues($column, array $values) $this->syncOriginalAttribute($column); } - /** - * @inheritdoc - */ - public function getForeignKey() + protected function newBaseQueryBuilder(): QueryBuilder { - return Str::snake(class_basename($this)).'_'.ltrim($this->primaryKey, '_'); - } + /** @phpstan-var Connection $connection */ + $connection = $this->getConnection(); + $connection->setIndex($this->getTable()); + $connection->setMaxSize($this->getMaxSize()); - /** - * Set the parent relation. - * - * @param \Illuminate\Database\Eloquent\Relations\Relation $relation - */ - public function setParentRelation(Relation $relation) - { - $this->parentRelation = $relation; + return new QueryBuilder($connection, $connection->getPostProcessor()); } /** - * Get the parent relation. + * Get the database connection instance. * - * @return \Illuminate\Database\Eloquent\Relations\Relation - */ - public function getParentRelation() - { - return $this->parentRelation; - } - - /** - * @inheritdoc - */ - public function newEloquentBuilder($query) - { - $builder = new Builder($query); - - return $builder; - } - - /** - * @inheritdoc + * + * @throws \RuntimeException */ - protected function newBaseQueryBuilder() + public function getConnection(): Connection { - $connection = $this->getConnection(); - if (!($connection instanceof Connection)) { + $connection = clone static::resolveConnection($this->getConnectionName()); + if (! ($connection instanceof Connection)) { $config = $connection->getConfig() ?? null; - if (!empty($config['driver'])) { + if (! empty($config['driver'])) { throw new RuntimeException('Invalid connection settings; expected "elasticsearch", got "'.$config['driver'].'"'); } else { throw new RuntimeException('Invalid connection settings; expected "elasticsearch"'); } } - $connection->setIndex($this->getTable()); - $connection->setMaxSize($this->getMaxSize()); - + return $connection; + } - return new QueryBuilder($connection, $connection->getPostProcessor()); + public function getMaxSize(): int + { + return static::MAX_SIZE; } /** - * @inheritdoc + * {@inheritdoc} */ - protected function removeTableFromKey($key) + protected function removeTableFromKey($key): string { return $key; } - /** * Get loaded relations for the instance without parent. - * - * @return array */ - protected function getRelationsWithoutParent() + protected function getRelationsWithoutParent(): array { $relations = $this->getRelations(); - if ($parentRelation = $this->getParentRelation()) { + $parentRelation = $this->getParentRelation(); + if ($parentRelation instanceof Relation) { + //@phpstan-ignore-next-line unset($relations[$parentRelation->getQualifiedForeignKeyName()]); } return $relations; } - - protected function isGuardableColumn($key) + /** + * Get the parent relation. + */ + public function getParentRelation(): ?Relation { - return true; + return $this->parentRelation ?? null; } - - public function saveWithoutRefresh(array $options = []) + /** + * Set the parent relation. + */ + public function setParentRelation(Relation $relation): void { - $this->mergeAttributesFromCachedCasts(); - - $query = $this->newModelQuery(); - $query->setRefresh(false); - - if ($this->exists) { - $saved = $this->isDirty() ? $this->performUpdate($query) : true; - } else { - $saved = $this->performInsert($query); - } - - if ($saved) { - $this->finishSave($options); - } - - return $saved; + $this->parentRelation = $relation; } - //---------------------------------------------------------------------- // Helpers //---------------------------------------------------------------------- - protected function _mergeFlatKeysIntoNestedArray(&$data, $attrs) - { - foreach ($attrs as $key => $value) { - if ($value) { - $value = implode('......', $value); - $parts = explode('.', $key); - $current = &$data; - - foreach ($parts as $partIndex => $part) { - if ($partIndex === count($parts) - 1) { - $current[$part] = $value; - } else { - if (!isset($current[$part]) || !is_array($current[$part])) { - $current[$part] = []; - } - $current = &$current[$part]; - } - } - } - - } + protected function isGuardableColumn($key): bool + { + return true; } - } diff --git a/src/Eloquent/SoftDeletes.php b/src/Eloquent/SoftDeletes.php index 4d8df3d..05ddece 100644 --- a/src/Eloquent/SoftDeletes.php +++ b/src/Eloquent/SoftDeletes.php @@ -1,5 +1,7 @@ getDeletedAtColumn(); } @@ -19,20 +21,19 @@ public function getQualifiedDeletedAtColumn() * Requires more testing */ - -// protected function runSoftDelete() -// { -// $query = $this->setKeysForSaveQuery($this->newModelQuery()); -// $time = $this->freshTimestamp(); -// $columns = [$this->getDeletedAtColumn() => $this->fromDateTime($time)]; -// $this->{$this->getDeletedAtColumn()} = $time; -// if ($this->usesTimestamps() && !is_null($this->getUpdatedAtColumn())) { -// $this->{$this->getUpdatedAtColumn()} = $time; -// $columns[$this->getUpdatedAtColumn()] = $this->fromDateTime($time); -// } -// $query->updateWithoutRefresh($columns); -// $this->syncOriginalAttributes(array_keys($columns)); -// $this->fireModelEvent('trashed', false); -// } + // protected function runSoftDelete() + // { + // $query = $this->setKeysForSaveQuery($this->newModelQuery()); + // $time = $this->freshTimestamp(); + // $columns = [$this->getDeletedAtColumn() => $this->fromDateTime($time)]; + // $this->{$this->getDeletedAtColumn()} = $time; + // if ($this->usesTimestamps() && !is_null($this->getUpdatedAtColumn())) { + // $this->{$this->getUpdatedAtColumn()} = $time; + // $columns[$this->getUpdatedAtColumn()] = $this->fromDateTime($time); + // } + // $query->updateWithoutRefresh($columns); + // $this->syncOriginalAttributes(array_keys($columns)); + // $this->fireModelEvent('trashed', false); + // } } diff --git a/src/Exceptions/MissingOrderException.php b/src/Exceptions/MissingOrderException.php new file mode 100644 index 0000000..52e14e0 --- /dev/null +++ b/src/Exceptions/MissingOrderException.php @@ -0,0 +1,24 @@ +json([ + 'error' => $this->getMessage(), + ], 400); + } +} diff --git a/src/Helpers/QueriesRelationships.php b/src/Helpers/QueriesRelationships.php index 891c0a7..fc3ba38 100644 --- a/src/Helpers/QueriesRelationships.php +++ b/src/Helpers/QueriesRelationships.php @@ -1,7 +1,6 @@ =', $count = 1, $boolean = 'and', Closure $callback = null) - { + public function has( + $relation, + $operator = '>=', + $count = 1, + $boolean = 'and', + ?Closure $callback = null + ): Builder|static { if (is_string($relation)) { - if (strpos($relation, '.') !== false) { + if (str_contains($relation, '.')) { return $this->hasNested($relation, $operator, $count, $boolean, $callback); } @@ -39,6 +42,7 @@ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', C // If this is a hybrid relation then we can not use a normal whereExists() query that relies on a subquery // We need to use a `whereIn` query + //@phpstan-ignore-next-line if ($this->getModel() instanceof Model || $this->isAcrossConnections($relation)) { return $this->addHybridHas($relation, $operator, $count, $boolean, $callback); } @@ -46,13 +50,10 @@ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', C // If we only need to check for the existence of the relation, then we can optimize // the subquery to only run a "where exists" clause instead of this full "count" // clause. This will make these queries run much faster compared with a count. - $method = $this->canUseExistsForExistenceCheck($operator, $count) - ? 'getRelationExistenceQuery' - : 'getRelationExistenceCountQuery'; + //@phpstan-ignore-next-line + $method = $this->canUseExistsForExistenceCheck($operator, $count) ? 'getRelationExistenceQuery' : 'getRelationExistenceCountQuery'; - $hasQuery = $relation->{$method}( - $relation->getRelated()->newQuery(), $this - ); + $hasQuery = $relation->{$method}($relation->getRelated()->newQuery(), $this); // Next we will call any given callback as an "anonymous" scope so they can get the // proper logical grouping of the where clauses if needed by this Eloquent query @@ -61,17 +62,10 @@ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', C $hasQuery->callScope($callback); } - return $this->addHasWhere( - $hasQuery, $relation, $operator, $count, $boolean - ); + return $this->addHasWhere($hasQuery, $relation, $operator, $count, $boolean); } - /** - * @param Relation $relation - * - * @return bool - */ - protected function isAcrossConnections(Relation $relation) + protected function isAcrossConnections(Relation $relation): bool { return $relation->getParent()->getConnectionName() !== $relation->getRelated()->getConnectionName(); } @@ -79,17 +73,16 @@ protected function isAcrossConnections(Relation $relation) /** * Compare across databases. * - * @param Relation $relation - * @param string $operator - * @param int $count - * @param string $boolean - * @param Closure|null $callback * - * @return mixed * @throws Exception */ - public function addHybridHas(Relation $relation, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null) - { + public function addHybridHas( + Relation $relation, + string $operator = '>=', + int $count = 1, + string $boolean = 'and', + ?Closure $callback = null + ): mixed { $hasQuery = $relation->getQuery(); if ($callback) { $hasQuery->callScope($callback); @@ -99,7 +92,7 @@ public function addHybridHas(Relation $relation, $operator = '>=', $count = 1, $ $not = in_array($operator, ['<', '<=', '!=']); // If we are comparing to 0, we need an additional $not flip. if ($count == 0) { - $not = !$not; + $not = ! $not; } $relations = $hasQuery->pluck($this->getHasCompareKey($relation)); @@ -109,31 +102,20 @@ public function addHybridHas(Relation $relation, $operator = '>=', $count = 1, $ return $this->whereIn($this->getRelatedConstraintKey($relation), $relatedIds, $boolean, $not); } - /** - * @param Relation $relation - * - * @return string - */ - protected function getHasCompareKey(Relation $relation) + protected function getHasCompareKey(Relation $relation): string { if (method_exists($relation, 'getHasCompareKey')) { return $relation->getHasCompareKey(); } + //@phpstan-ignore-next-line return $relation instanceof HasOneOrMany ? $relation->getForeignKeyName() : $relation->getOwnerKeyName(); } - /** - * @param $relations - * @param $operator - * @param $count - * - * @return array - */ - protected function getConstrainedRelatedIds($relations, $operator, $count) + protected function getConstrainedRelatedIds($relations, $operator, $count): array { $relationCount = array_count_values(array_map(function ($id) { - return (string)$id; // Convert Back ObjectIds to Strings + return (string) $id; // Convert Back ObjectIds to Strings }, is_array($relations) ? $relations : $relations->flatten()->toArray())); // Remove unwanted related objects based on the operator and count. $relationCount = array_filter($relationCount, function ($counted) use ($count, $operator) { @@ -161,12 +143,11 @@ protected function getConstrainedRelatedIds($relations, $operator, $count) /** * Returns key we are constraining this parent model's query with. * - * @param Relation $relation * - * @return string + * * @throws Exception */ - protected function getRelatedConstraintKey(Relation $relation) + protected function getRelatedConstraintKey(Relation $relation): string { if ($relation instanceof HasOneOrMany) { return $relation->getLocalKeyName(); @@ -176,7 +157,7 @@ protected function getRelatedConstraintKey(Relation $relation) return $relation->getForeignKeyName(); } - if ($relation instanceof BelongsToMany && !$this->isAcrossConnections($relation)) { + if ($relation instanceof BelongsToMany && ! $this->isAcrossConnections($relation)) { return $this->model->getKeyName(); } diff --git a/src/Helpers/Utilities.php b/src/Helpers/Utilities.php new file mode 100644 index 0000000..44bfe8d --- /dev/null +++ b/src/Helpers/Utilities.php @@ -0,0 +1,21 @@ +sort = $meta['score']; + } + if (isset($meta['index'])) { + $this->sort = $meta['index']; + } + if (isset($meta['sort'])) { + $this->sort = $meta['sort']; + } + if (isset($meta['cursor'])) { + $this->cursor = $meta['cursor']; + } + if (isset($meta['_id'])) { + $this->_id = $meta['_id']; + } + if (isset($meta['_query'])) { + $this->_query = $meta['_query']; + } + if (isset($meta['highlights'])) { + $this->highlights = $meta['highlights']; + } + } + + //---------------------------------------------------------------------- + // Getters + //---------------------------------------------------------------------- + + public function getIndex() + { + return $this->index; + } + + public function getScore() + { + return $this->score; + } + + public function getId(): mixed + { + return $this->_id ?? null; + } + + public function getSort(): ?array + { + return $this->sort; + } + + public function getCursor(): ?array + { + return $this->cursor; + } + + public function getQuery() + { + return $this->_query; + } + + public function getHighlights(): array + { + return $this->highlights ?? []; + } + + public function parseHighlights($data = []): ?object + { + if ($this->highlights) { + $this->_mergeFlatKeysIntoNestedArray($data, $this->highlights); + + return (object) $data; + } + + return null; + } + + public function asArray() + { + return [ + 'score' => $this->score, + 'index' => $this->index, + '_id' => $this->_id, + 'sort' => $this->sort, + 'cursor' => $this->cursor, + '_query' => $this->_query, + 'highlights' => $this->highlights, + ]; + } + + //---------------------------------------------------------------------- + // Setters + //---------------------------------------------------------------------- + public function setId($id): void + { + $this->_id = $id; + } + + public function setSort(array $sort): void + { + $this->sort = $sort; + } + + public function setCursor(array $cursor): void + { + $this->cursor = $cursor; + } + + //---------------------------------------------------------------------- + // Helpers + //---------------------------------------------------------------------- + + protected function _mergeFlatKeysIntoNestedArray(&$data, $attrs): void + { + foreach ($attrs as $key => $value) { + if ($value) { + $value = implode('......', $value); + $parts = explode('.', $key); + $current = &$data; + + foreach ($parts as $partIndex => $part) { + if ($partIndex === count($parts) - 1) { + $current[$part] = $value; + } else { + if (! isset($current[$part]) || ! is_array($current[$part])) { + $current[$part] = []; + } + $current = &$current[$part]; + } + } + } + } + } +} diff --git a/src/Meta/QueryMetaData.php b/src/Meta/QueryMetaData.php new file mode 100644 index 0000000..a55a22a --- /dev/null +++ b/src/Meta/QueryMetaData.php @@ -0,0 +1,333 @@ +timed_out = $meta['timed_out'] ?? false; + unset($meta['timed_out']); + if (isset($meta['took'])) { + $this->took = $meta['took']; + unset($meta['took']); + } + if (isset($meta['total'])) { + $this->total = $meta['total']; + unset($meta['total']); + } + if (isset($meta['max_score'])) { + $this->max_score = $meta['max_score']; + unset($meta['max_score']); + } + if (isset($meta['total'])) { + $this->total = $meta['total']; + unset($meta['total']); + } + if (isset($meta['shards'])) { + $this->shards = $meta['shards']; + unset($meta['shards']); + } + if (isset($meta['sort'])) { + $this->sort = $meta['sort']; + unset($meta['sort']); + } + if (isset($meta['cursor'])) { + $this->cursor = $meta['cursor']; + unset($meta['cursor']); + } + if (isset($meta['_id'])) { + $this->_id = $meta['_id']; + unset($meta['_id']); + } + if ($meta) { + $this->_meta = $meta; + } + } + + //---------------------------------------------------------------------- + // Getters + //---------------------------------------------------------------------- + + public function getId(): mixed + { + return $this->_id ?? null; + } + + public function getModified(): int + { + return $this->getResults('modified') ?? 0; + } + + public function getDeleted(): int + { + return $this->getResults('deleted') ?? 0; + } + + public function getCreated(): int + { + return $this->getResults('created') ?? 0; + } + + public function isSuccessful(): bool + { + return $this->success; + } + + public function getSort(): ?array + { + return $this->sort; + } + + public function getCursor(): ?array + { + return $this->cursor; + } + + public function getQuery(): string + { + return $this->query; + } + + public function getDsl(): array + { + return $this->dsl; + } + + public function getTook(): int + { + return $this->took; + } + + public function getTotal(): int + { + return $this->total; + } + + public function getMaxScore(): string + { + return $this->max_score; + } + + public function getShards(): mixed + { + return $this->shards; + } + + public function getErrorMessage(): string + { + return $this->errorMessage; + } + + public function getError(): array + { + return $this->error; + } + + public function asArray(): array + { + $return = [ + 'query' => $this->query, + 'success' => $this->success, + 'timed_out' => $this->timed_out, + 'took' => $this->took, + 'total' => $this->total, + ]; + if ($this->max_score) { + $return['max_score'] = $this->max_score; + } + if ($this->shards) { + $return['shards'] = $this->shards; + } + if ($this->dsl) { + $return['dsl'] = $this->dsl; + } + if ($this->_id) { + $return['_id'] = $this->_id; + } + if ($this->results) { + foreach ($this->results as $key => $value) { + $return[$key] = $value; + } + } + if ($this->error) { + $return['error'] = $this->error; + $return['errorMessage'] = $this->errorMessage; + } + if ($this->sort) { + $return['sort'] = $this->sort; + } + if ($this->cursor) { + $return['cursor'] = $this->cursor; + } + if ($this->_meta) { + $return['_meta'] = $this->_meta; + } + + return $return; + } + + public function getResults($key = null) + { + if ($key) { + return $this->results[$key] ?? null; + } + + return $this->results; + } + + //---------------------------------------------------------------------- + // Setters + //---------------------------------------------------------------------- + public function setId($id): void + { + $this->_id = $id; + } + + public function setTook(int $took): void + { + $this->took = $took; + } + + public function setTotal(int $total): void + { + $this->total = $total; + } + + public function setQuery($query): void + { + $this->query = $query; + } + + public function setSuccess(): void + { + $this->success = true; + } + + public function setResult($key, $value): void + { + $this->results[$key] = $value; + } + + public function setModified(int $count): void + { + $this->setResult('modified', $count); + } + + public function setCreated(int $count): void + { + $this->setResult('created', $count); + } + + public function setDeleted(int $count): void + { + $this->setResult('deleted', $count); + } + + public function setFailed(int $count): void + { + $this->setResult('failed', $count); + } + + public function setSort(array $sort): void + { + $this->sort = $sort; + } + + public function setCursor(array $cursor): void + { + $this->cursor = $cursor; + } + + public function setDsl($params) + { + $this->dsl = $params; + } + + public function setError(array $error, string $errorMessage = ''): void + { + $this->success = false; + $this->error = $error; + $this->errorMessage = $errorMessage; + } + + public function parseAndSetError($error, $errorCode) + { + $errorMessage = $error; + $this->success = false; + $details = $this->_decodeError($errorMessage); + $error = [ + 'msg' => $details['msg'], + 'data' => $details['data'], + 'code' => $errorCode, + ]; + $this->error = $error; + $this->errorMessage = $errorMessage; + } + + private function _decodeError($error): array + { + $return['msg'] = $error; + $return['data'] = []; + $jsonStartPos = strpos($error, ': ') + 2; + $response = ($error); + $title = substr($response, 0, $jsonStartPos); + $jsonString = substr($response, $jsonStartPos); + if ($this->_isJson($jsonString)) { + $errorArray = json_decode($jsonString, true); + } else { + $errorArray = [$jsonString]; + } + + if (json_last_error() === JSON_ERROR_NONE) { + $errorReason = $errorArray['error']['reason'] ?? null; + if (! $errorReason) { + return $return; + } + $return['msg'] = $title.$errorReason; + $cause = $errorArray['error']['root_cause'][0]['reason'] ?? null; + if ($cause) { + $return['msg'] .= ' - '.$cause; + } + + $return['data'] = $errorArray; + } + + return $return; + } + + private function _isJson($string): bool + { + return json_validate($string); + } +} diff --git a/src/Pagination/SearchAfterPaginator.php b/src/Pagination/SearchAfterPaginator.php new file mode 100644 index 0000000..248ef7c --- /dev/null +++ b/src/Pagination/SearchAfterPaginator.php @@ -0,0 +1,102 @@ +getMeta()->getCursor(); + $search_after = $item->getMeta()->getSort(); + $cursor['page']++; + $cursor['next_sort'] = $search_after; + + return $cursor; + } + + public function toArray(): array + { + return [ + 'data' => $this->items->toArray(), + 'path' => $this->path(), + 'per_page' => $this->perPage(), + 'next_cursor' => $this->nextCursor()?->encode(), + 'next_page_url' => $this->nextPageUrl(), + 'prev_cursor' => $this->previousCursor()?->encode(), + 'prev_page_url' => $this->previousPageUrl(), + 'current_page' => $this->currentPageNumber(), + 'total' => $this->totalRecords(), + 'from' => $this->showingFrom(), + 'to' => $this->showingTo(), + 'last_page' => $this->lastPage(), + + ]; + } + + public function currentPageNumber(): int + { + return $this->options['currentPage']; + } + + public function totalRecords(): int + { + return $this->options['records']; + } + + public function showingFrom(): int + { + $perPage = $this->perPage(); + $currentPage = $this->currentPageNumber(); + + return ($currentPage - 1) * $perPage + 1; + + } + + public function showingTo(): int + { + $records = count($this->items); + $currentPage = $this->currentPageNumber(); + $perPage = $this->perPage(); + + return (($currentPage - 1) * $perPage) + $records; + } + + public function lastPage(): int + { + return $this->options['totalPages']; + } + + // Builds the cursor for the previous page + public function previousCursor(): ?Cursor + { + if (! $this->cursor) { + return null; + } + $current = $this->cursor->toArray(); + if ($current['page'] < 2) { + return null; + } + $previousCursor = $current; + unset($previousCursor['_pointsToNextItems']); + $previousCursor['page']--; + $previousCursor['next_sort'] = array_pop($previousCursor['sort_history']); + + return new Cursor($previousCursor, false); + } + + protected function setItems($items): void + { + $this->items = $items instanceof Collection ? $items : Collection::make($items); + $this->hasMore = $this->options['currentPage'] < $this->options['totalPages']; + } +} diff --git a/src/Query/Builder.php b/src/Query/Builder.php index 1859c9e..e68ab82 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -1,40 +1,58 @@ ', '<=', '>=', '<>', '!=', '<=>', - 'like', 'like binary', 'not like', 'ilike', - '&', '|', '^', '<<', '>>', '&~', - 'rlike', 'not rlike', 'regexp', 'not regexp', - '~', '~*', '!~', '!~*', 'similar to', - 'not similar to', 'not ilike', '~~*', '!~~*', + '=', + '<', + '>', + '<=', + '>=', + '<>', + '!=', + '<=>', + 'like', + 'like binary', + 'not like', + 'ilike', + '&', + '|', + '^', + '<<', + '>>', + '&~', + 'rlike', + 'not rlike', + 'regexp', + 'not regexp', + '~', + '~*', + '!~', + '!~*', + 'similar to', + 'not similar to', + 'not ilike', + '~~*', + '!~~*', // @Elastic Search - 'exist', 'regex', + 'exist', + 'regex', ]; + protected string $index = ''; + + protected string|bool $refresh = 'wait_for'; + /** * Operator conversion. - * - * @var array */ - protected $conversion = [ - '=' => '=', + protected array $conversion = [ + '=' => '=', '!=' => 'ne', '<>' => 'ne', - '<' => 'lt', + '<' => 'lt', '<=' => 'lte', - '>' => 'gt', + '>' => 'gt', '>=' => 'gte', ]; /** - * @inheritdoc + * {@inheritdoc} */ public function __construct(Connection $connection, Processor $processor) { $this->grammar = new Grammar; $this->connection = $connection; $this->processor = $processor; + } + public function getProcessor(): Processor + { + return $this->processor; } + public function getConnection(): Connection + { + return $this->connection; + } - public function setRefresh($value) + public function setRefresh($value): void { $this->refresh = $value; } + public function initCursor($cursor): array + { + + $this->cursor = [ + 'page' => 1, + 'pages' => 0, + 'records' => 0, + 'sort_history' => [], + 'next_sort' => null, + 'ts' => 0, + ]; + + if (! empty($cursor)) { + $this->cursor = [ + 'page' => $cursor->parameter('page'), + 'pages' => $cursor->parameter('pages'), + 'records' => $cursor->parameter('records'), + 'sort_history' => $cursor->parameter('sort_history'), + 'next_sort' => $cursor->parameter('next_sort'), + 'ts' => $cursor->parameter('ts'), + ]; + } + + return $this->cursor; + } //---------------------------------------------------------------------- // Querying Executors //---------------------------------------------------------------------- /** - * @inheritdoc - */ - public function find($id, $columns = []) - { - return $this->where('_id', $id)->first($columns); - } - - /** - * @inheritdoc + * {@inheritdoc} */ public function value($column) { - $result = (array)$this->first([$column]); + $result = (array) $this->first([$column]); return Arr::get($result, $column); } /** - * @inheritdoc + * {@inheritdoc} */ - public function all($columns = []) + public function get($columns = []): ElasticCollection|LazyCollection { return $this->_processGet($columns); } /** - * @inheritdoc + * @return ElasticCollection|LazyElasticCollection|void */ - public function get($columns = []) + protected function _processGet(array|string $columns = [], bool $returnLazy = false) { - return $this->_processGet($columns); - } - /** - * @inheritdoc - */ - public function cursor($columns = []) - { - $result = $this->_processGet($columns, true); - if ($result instanceof LazyCollection) { - return $result; + $wheres = $this->compileWheres(); + $options = $this->compileOptions(); + $columns = $this->prepareColumns($columns); + + if ($this->groups) { + throw new RuntimeException('Groups are not used'); } - throw new RuntimeException('Query not compatible with cursor'); - } - /** - * @inheritdoc - */ - public function exists() - { - return $this->first() !== null; - } + if ($this->aggregate) { + $function = $this->aggregate['function']; + $aggColumns = $this->aggregate['columns']; + if (in_array('*', $aggColumns)) { + $aggColumns = null; + } + if ($aggColumns) { + $columns = $aggColumns; + } - /** - * @inheritdoc - */ - public function insert(array $values) - { - if (empty($values)) { - return true; - } + if ($this->distinctType) { + $totalResults = $this->connection->distinctAggregate($function, $wheres, $options, $columns); + } else { + $totalResults = $this->connection->aggregate($function, $wheres, $options, $columns); + } - if (!is_array(reset($values))) { - $values = [$values]; + if (! $totalResults->isSuccessful()) { + throw new RuntimeException($totalResults->errorMessage); + } + $results = [ + [ + '_id' => null, + 'aggregate' => $totalResults->data, + ], + ]; + $result = new ElasticCollection($results); + $result->setQueryMeta($totalResults->getMetaData()); + + // Return results + return $result; } - $allSuccess = true; - foreach ($values as $value) { - $result = $this->_processInsert($value, true); - if (!$result) { - $allSuccess = false; + if ($this->distinctType) { + if (empty($columns[0]) || $columns[0] == '*') { + throw new RuntimeException('Columns are required for term aggregation when using distinct()'); + } else { + + if ($this->distinctType == 2) { + $find = $this->connection->distinct($wheres, $options, $columns, true); + } else { + $find = $this->connection->distinct($wheres, $options, $columns); + } } + } else { + $find = $this->connection->find($wheres, $options, $columns); } - return $allSuccess; - } - - /** - * @inheritdoc - */ - public function insertGetId(array $values, $sequence = null) - { - //Also Model->save() - return $this->_processInsert($values, true); - } + //Else Normal find query + if ($find->isSuccessful()) { + $data = $find->data; + if ($returnLazy) { + if ($data) { + $lazy = LazyElasticCollection::make(function () use ($data) { + foreach ($data as $item) { + yield $item; + } + }); + $lazy->setQueryMeta($find->getMetaData()); - /** - * @inheritdoc - */ - public function update(array $values, array $options = []) - { - $this->_checkValues($values); + return $lazy; + } + } + $collection = new ElasticCollection($data); + $collection->setQueryMeta($find->getMetaData()); - return $this->_processUpdate($values, $options); + return $collection; + } else { + throw new RuntimeException('Error: '.$find->errorMessage); + } } - /** - * @inheritdoc - */ - public function increment($column, $amount = 1, $extra = [], $options = []) + protected function compileWheres(): array { - $values = ['inc' => [$column => $amount]]; - - if (!empty($extra)) { - $values['set'] = $extra; - } - - $this->where(function ($query) use ($column) { - $query->where($column, 'exists', false); + $wheres = $this->wheres ?: []; + $compiledWheres = []; + if ($wheres) { + if ($wheres[0]['boolean'] == 'or') { + throw new RuntimeException('Cannot start a query with an OR statement'); + } + if (count($wheres) == 1) { + return $this->{'_parseWhere'.$wheres[0]['type']}($wheres[0]); + } + $and = []; + $or = []; + foreach ($wheres as $where) { + if ($where['boolean'] == 'or') { + $or[] = $and; + //clear AND for the next bucket + $and = []; + } - $query->orWhereNotNull($column); - }); + $result = $this->{'_parseWhere'.$where['type']}($where); + $and[] = $result; + } + if ($or) { + //Add the last AND bucket + $or[] = $and; + foreach ($or as $and) { + $compiledWheres['or'][] = $this->_prepAndBucket($and); + } + } else { + $compiledWheres = $this->_prepAndBucket($and); + } + } - return $this->_processUpdate($values, $options, 'incrementMany'); + return $compiledWheres; } - /** - * @inheritdoc - */ - public function decrement($column, $amount = 1, $extra = [], $options = []) + private function _prepAndBucket($andData): array { - return $this->increment($column, -1 * $amount, $extra, $options); - } + $data = []; + foreach ($andData as $key => $ops) { + $data['and'][$key] = $ops; + } + return $data; + } - public function agg(array $functions, $column) + protected function compileOptions(): array { - if (is_array($column)) { - throw new RuntimeException('Column must be a string'); + $options = []; + if ($this->orders) { + $options['sort'] = $this->orders; } - $aggregateTypes = ['sum', 'avg', 'min', 'max', 'matrix', 'count']; - foreach ($functions as $function) { - if (!in_array($function, $aggregateTypes)) { - throw new RuntimeException('Invalid aggregate type: '.$function); + if ($this->offset) { + $options['skip'] = $this->offset; + } + if ($this->limit) { + $options['limit'] = $this->limit; + //Check if it's first() with no ordering, + //Set order to created_at -> asc for consistency + //TODO + } + if ($this->cursor) { + $options['_meta']['cursor'] = $this->cursor; + if (! empty($this->cursor['next_sort'])) { + $options['search_after'] = $this->cursor['next_sort']; } } - $wheres = $this->compileWheres(); - $options = $this->compileOptions(); - $results = $this->connection->multipleAggregate($functions, $wheres, $options, $column); + if ($this->previousSearchAfter) { + $options['prev_search_after'] = $this->previousSearchAfter; + } + if ($this->minScore) { + $options['minScore'] = $this->minScore; + } + if ($this->searchOptions) { + $options['searchOptions'] = $this->searchOptions; + } + if ($this->filters) { + $options['filters'] = $this->filters; + } - return $results->data ?? []; + return $options; } -// - - /** - * @inheritdoc - */ - public function forPageAfterId($perPage = 15, $lastId = 0, $column = '_id') + protected function prepareColumns($columns): array { - return parent::forPageAfterId($perPage, $lastId, $column); - } + $final = []; + if ($this->columns) { + foreach ($this->columns as $col) { + $final[] = $col; + } + } - /** - * @inheritdoc - */ - public function delete($id = null) - { + if ($columns) { + if (! is_array($columns)) { + $columns = [$columns]; + } - if ($id !== null) { - $this->where('_id', '=', $id); + foreach ($columns as $col) { + $final[] = $col; + } + } + if (! $final) { + return ['*']; } - return $this->_processDelete(); + $final = array_values(array_unique($final)); + if (($key = array_search('*', $final)) !== false) { + unset($final[$key]); + } + return $final; } /** - * @inheritdoc + * {@inheritdoc} */ - public function aggregate($function, $columns = []) + public function aggregate($function, $columns = []): mixed { $this->aggregate = compact('function', 'columns'); @@ -275,275 +403,208 @@ public function aggregate($function, $columns = []) $this->bindings['select'] = []; $results = $this->get($columns); - // Restore bindings after aggregate search - $this->aggregate = null; + $this->aggregate = []; $this->columns = $previousColumns; $this->bindings['select'] = $previousSelectBindings; if (isset($results[0])) { - $result = (array)$results[0]; + $result = (array) $results[0]; + $esResult = new ElasticResult(); + $esResult->setQueryMeta($results->getQueryMeta()); + $esResult->setValue($result['aggregate']); - return $result['aggregate']; + // For now we'll return the result as is, + // Later we'll return ElasticResult to get access to the meta + return $esResult->getValue(); } return null; } /** - * @param $column - * @param $callBack - * @param $scoreMode - * - * @return $this + * {@inheritdoc} */ - public function whereNestedObject($column, $callBack, $scoreMode = 'avg') + public function distinct($includeCount = false): static { - $boolean = 'and'; - $query = $this->newQuery(); - $callBack($query); - $wheres = $query->compileWheres(); - $this->wheres[] = [ - 'column' => $column, - 'type' => 'NestedObject', - 'wheres' => $wheres, - 'score_mode' => $scoreMode, - 'boolean' => $boolean, - ]; + $this->distinctType = 1; + if ($includeCount) { + $this->distinctType = 2; + } return $this; } /** - * @param $column - * @param $callBack - * @param $scoreMode - * - * @return $this + * {@inheritdoc} */ - public function whereNotNestedObject($column, $callBack, $scoreMode = 'avg') + public function find($id, $columns = []) { - $boolean = 'and'; - $query = $this->newQuery(); - $callBack($query); - $wheres = $query->compileWheres(); - $this->wheres[] = [ - 'column' => $column, - 'type' => 'NotNestedObject', - 'wheres' => $wheres, - 'score_mode' => $scoreMode, - 'boolean' => $boolean, - ]; - - return $this; + return $this->where('_id', $id)->first($columns); } /** - * @param $column - * @param $value - * - * @return $this + * {@inheritdoc} */ - public function wherePhrase($column, $value) + public function cursor($columns = []): LazyCollection { - $boolean = 'and'; - $this->wheres[] = [ - 'column' => $column, - 'type' => 'Basic', - 'value' => $value, - 'operator' => 'phrase', - 'boolean' => $boolean, - ]; - - return $this; + $result = $this->_processGet($columns, true); + if ($result instanceof LazyCollection) { + return $result; + } + throw new RuntimeException('Query not compatible with cursor'); } /** - * @param $column - * @param $value - * - * @return $this + * {@inheritdoc} */ - public function wherePhrasePrefix($column, $value) + public function exists(): bool { - $boolean = 'and'; - $this->wheres[] = [ - 'column' => $column, - 'type' => 'Basic', - 'value' => $value, - 'operator' => 'phrase_prefix', - 'boolean' => $boolean, - ]; - - return $this; + return $this->first() !== null; } /** - * @param $column - * @param $value - * - * @return $this + * {@inheritdoc} */ - public function whereExact($column, $value) + public function insert(array $values, $returnData = false): ElasticCollection { - $boolean = 'and'; - $this->wheres[] = [ - 'column' => $column, - 'type' => 'Basic', - 'value' => $value, - 'operator' => 'exact', - 'boolean' => $boolean, - ]; - - return $this; + return $this->_processInsert($values, $returnData, false); } - - /** - * @param $column - * @param $callBack - * - * @return $this + /* + * @see insert(array $values, $returnData = false) */ - public function queryNested($column, $callBack) - { - $boolean = 'and'; - $query = $this->newQuery(); - $callBack($query); - $wheres = $query->compileWheres(); - $options = $query->compileOptions(); - $this->wheres[] = [ - 'column' => $column, - 'type' => 'QueryNested', - 'wheres' => $wheres, - 'options' => $options, - 'boolean' => $boolean, - ]; - return $this; + public function insertWithoutRefresh(array $values, $returnData = false): ElasticCollection + { + return $this->_processInsert($values, $returnData, true); } - public function whereTimestamp($column, $operator = null, $value = null, $boolean = 'and') + protected function _processInsert(array $values, bool $returnData, bool $saveWithoutRefresh): ElasticCollection { - [$value, $operator] = $this->prepareValueAndOperator( - $value, $operator, func_num_args() === 2 - ); - if ($this->invalidOperator($operator)) { - [$value, $operator] = [$operator, '=']; - } - $this->wheres[] = [ - 'column' => $column, - 'type' => 'Timestamp', - 'value' => $value, - 'operator' => $operator, - 'boolean' => $boolean, + $response = [ + 'hasErrors' => false, + 'took' => 0, + 'total' => 0, + 'success' => 0, + 'created' => 0, + 'modified' => 0, + 'failed' => 0, + 'data' => [], + 'error_bag' => [], ]; + if (empty($values)) { + return $this->_parseBulkInsertResult($response, $returnData); + } - return $this; - } - + if ($saveWithoutRefresh) { + $this->refresh = false; + } - //---------------------------------------------------------------------- - // Query Processing (Connection API) - //---------------------------------------------------------------------- + if (! is_array(reset($values))) { + $values = [$values]; + } + $this->applyBeforeQueryCallbacks(); - /** - * @param array $columns - * @param false $returnLazy - * - * @return Collection|LazyCollection|void - */ - protected function _processGet($columns = [], $returnLazy = false) - { + collect($values)->chunk(1000)->each(callback: function ($chunk) use (&$response, $returnData) { + $result = $this->connection->insertBulk($chunk->toArray(), $returnData, $this->refresh); + if ((bool) $result['hasErrors']) { + $response['hasErrors'] = true; + } + $response['total'] += $result['total']; + $response['took'] += $result['took']; + $response['success'] += $result['success']; + $response['failed'] += $result['failed']; + $response['created'] += $result['created']; + $response['modified'] += $result['modified']; + $response['data'] = array_merge($response['data'], $result['data']); + + $response['error_bag'] = array_merge($response['error_bag'], $result['error_bag']); + }); - $wheres = $this->compileWheres(); - $options = $this->compileOptions(); - $columns = $this->prepareColumns($columns); + return $this->_parseBulkInsertResult($response, $returnData); - if ($this->groups) { - throw new RuntimeException('Groups are not used'); - } + } - if ($this->aggregate) { - $function = $this->aggregate['function']; - $aggColumns = $this->aggregate['columns']; - if (in_array('*', $aggColumns)) { - $aggColumns = null; + protected function _parseBulkInsertResult($response, $returnData): ElasticCollection + { + $result = new ElasticCollection($response['data']); + $result->setQueryMeta(new QueryMetaData([])); + $result->getQueryMeta()->setSuccess(); + $result->getQueryMeta()->setCreated($response['created']); + $result->getQueryMeta()->setModified($response['modified']); + $result->getQueryMeta()->setFailed($response['failed']); + $result->getQueryMeta()->setQuery('InsertBulk'); + $result->getQueryMeta()->setTook($response['took']); + $result->getQueryMeta()->setTotal($response['total']); + if ($response['hasErrors']) { + $errorMessage = 'Bulk insert failed for all values'; + if ($response['success'] > 0) { + $errorMessage = 'Bulk insert failed for some values'; } - if ($aggColumns) { - $columns = $aggColumns; - } + $result->getQueryMeta()->setError($response['error_bag'], $errorMessage); + } + if (! $returnData) { + $data = $result->getQueryMetaAsArray(); + unset($data['query']); + $response['data'] = $data; - if ($this->distinct) { - $totalResults = $this->connection->distinctAggregate($function, $wheres, $options, $columns); - } else { - $totalResults = $this->connection->aggregate($function, $wheres, $options, $columns); - } + return $this->_parseBulkInsertResult($response, true); + } - if (!$totalResults->isSuccessful()) { - throw new RuntimeException($totalResults->errorMessage); - } - $results = [ - [ - '_id' => null, - 'aggregate' => $totalResults->data, - ], - ]; + return $result; + } - // Return results - return new Collection($results); + /** + * {@inheritdoc} + */ + public function insertGetId(array $values, $sequence = null): int|array|string|null + { + $result = $this->connection->save($values, $this->refresh); + if ($result->isSuccessful()) { + // Return id + return $sequence ? $result->getInsertedId() : $result->data; } - if ($this->distinct) { - if (empty($columns[0]) || $columns[0] == '*') { - throw new RuntimeException('Columns are required for term aggregation when using distinct()'); - } else { - if ($this->distinct == 2) { - $find = $this->connection->distinct($wheres, $options, $columns, true); - } else { - $find = $this->connection->distinct($wheres, $options, $columns); - } + return null; + } - } + /** + * {@inheritdoc} + */ + public function update(array $values, array $options = []) + { + $this->_checkValues($values); - } else { - $find = $this->connection->find($wheres, $options, $columns); - } + return $this->_processUpdate($values, $options); + } - //Else Normal find query - if ($find->isSuccessful()) { - $data = $find->data; - if ($returnLazy) { - if ($data) { - return LazyCollection::make(function () use ($data) { - foreach ($data as $item) { - yield $item; - } - }); - } + private function _checkValues($values): true + { + unset($values['updated_at']); + unset($values['created_at']); + if (! $this->_isAssociative($values)) { + throw new RuntimeException('Invalid value format. Expected associative array, got sequential array'); + } - } + return true; + } - return new Collection($data); - } else { - throw new RuntimeException('Error: '.$find->errorMessage); + private function _isAssociative(array $arr): bool + { + if ($arr === []) { + return true; } + return array_keys($arr) !== range(0, count($arr) - 1); } - /** - * @param $query - * @param array $options - * @param string $method - * - * @return int - */ - protected function _processUpdate($values, array $options = [], $method = 'updateMany') + protected function _processUpdate($values, array $options = [], $method = 'updateMany'): int { // Update multiple items by default. - if (!array_key_exists('multiple', $options)) { + if (! array_key_exists('multiple', $options)) { $options['multiple'] = true; } $wheres = $this->compileWheres(); @@ -555,688 +616,402 @@ protected function _processUpdate($values, array $options = [], $method = 'updat return 0; } - /** - * @param array $values - * @param false $returnIdOnly - * - * @return null|string|array + * {@inheritdoc} */ - protected function _processInsert(array $values, $returnIdOnly = false) + public function decrement($column, $amount = 1, $extra = [], $options = []) { - $result = $this->connection->save($values, $this->refresh); - - if ($result->isSuccessful()) { - - // Return id - return $returnIdOnly ? $result->getInsertedId() : $result->data; - } - - return null; + return $this->increment($column, -1 * $amount, $extra, $options); } /** - * @return int + * {@inheritdoc} */ - protected function _processDelete() + public function increment($column, $amount = 1, $extra = [], $options = []) { - $wheres = $this->compileWheres(); - $options = $this->compileOptions(); - $result = $this->connection->deleteAll($wheres, $options); - if ($result->isSuccessful()) { - return $result->getDeletedCount(); + $values = ['inc' => [$column => $amount]]; + + if (! empty($extra)) { + $values['set'] = $extra; } - return 0; - } + $this->where(function ($query) use ($column) { + $query->where($column, 'exists', false); + $query->orWhereNotNull($column); + }); - //---------------------------------------------------------------------- - // Clause Operators - //---------------------------------------------------------------------- + return $this->_processUpdate($values, $options, 'incrementMany'); + } - /** - * @inheritdoc - */ - public function orderBy($column, $direction = 'asc', $mode = null, $missing = null) + public function agg(array $functions, $column) { - if (is_string($direction)) { - $direction = (strtolower($direction) == 'asc' ? 'asc' : 'desc'); + if (is_array($column)) { + throw new RuntimeException('Column must be a string'); } + $aggregateTypes = ['sum', 'avg', 'min', 'max', 'matrix', 'count']; + foreach ($functions as $function) { + if (! in_array($function, $aggregateTypes)) { + throw new RuntimeException('Invalid aggregate type: '.$function); + } + } + $wheres = $this->compileWheres(); + $options = $this->compileOptions(); - $this->orders[$column] = [ - 'order' => $direction, - 'mode' => $mode, - 'missing' => $missing, - ]; - -// dd($this->orders); + $results = $this->connection->multipleAggregate($functions, $wheres, $options, $column); - return $this; + return $results->data ?? []; } + //---------------------------------------------------------------------- + // Query Processing (Connection API) + //---------------------------------------------------------------------- + /** - * @inheritDoc + * {@inheritdoc} */ - public function orderByDesc($column, $mode = null, $missing = null) + public function forPageAfterId($perPage = 15, $lastId = 0, $column = '_id') { - return $this->orderBy($column, 'desc', $mode, $missing); + return parent::forPageAfterId($perPage, $lastId, $column); } /** - * @param $column - * @param $pin - * @param $direction @values: 'asc', 'desc' - * @param $unit @values: 'km', 'mi', 'm', 'ft' - * @param $mode @values: 'min', 'max', 'avg', 'sum' - * @param $type @values: 'arc', 'plane' - * * @return $this */ - public function orderByGeo($column, $pin, $direction = 'asc', $unit = 'km', $mode = null, $type = null) + public function whereNestedObject($column, $callBack, $scoreMode = 'avg'): static { - $this->orders[$column] = [ - 'is_geo' => true, - 'order' => $direction, - 'pin' => $pin, - 'unit' => $unit, - 'mode' => $mode, - 'type' => $type, + $boolean = 'and'; + $query = $this->newQuery(); + $callBack($query); + $wheres = $query->compileWheres(); + $this->wheres[] = [ + 'column' => $column, + 'type' => 'NestedObject', + 'wheres' => $wheres, + 'score_mode' => $scoreMode, + 'boolean' => $boolean, ]; return $this; } /** - * @param $column - * @param $pin - * @param $unit @values: 'km', 'mi', 'm', 'ft' - * @param $mode @values: 'min', 'max', 'avg', 'sum' - * @param $type @values: 'arc', 'plane' - * - * @return $this + * {@inheritdoc} */ - public function orderByGeoDesc($column, $pin, $unit = 'km', $mode = null, $type = null) + public function newQuery(): Builder { - return $this->orderByGeo($column, $pin, 'desc', $unit, $mode, $type); + return new self($this->connection, $this->processor); } - /** - * @param $column - * @param $direction - * @param $mode - * * @return $this */ - public function orderByNested($column, $direction = 'asc', $mode = null) + public function whereNotNestedObject($column, $callBack, $scoreMode = 'avg'): static { - $this->orders[$column] = [ - 'is_nested' => true, - 'order' => $direction, - 'mode' => $mode, - + $boolean = 'and'; + $query = $this->newQuery(); + $callBack($query); + $wheres = $query->compileWheres(); + $this->wheres[] = [ + 'column' => $column, + 'type' => 'NotNestedObject', + 'wheres' => $wheres, + 'score_mode' => $scoreMode, + 'boolean' => $boolean, ]; return $this; } + //---------------------------------------------------------------------- + // Clause Operators + //---------------------------------------------------------------------- - /** - * @inheritdoc - */ - public function whereBetween($column, iterable $values, $boolean = 'and', $not = false) + public function wherePhrase($column, $value): static { - $type = 'between'; - - $this->wheres[] = compact('column', 'type', 'boolean', 'values', 'not'); + $boolean = 'and'; + $this->wheres[] = [ + 'column' => $column, + 'type' => 'Basic', + 'value' => $value, + 'operator' => 'phrase', + 'boolean' => $boolean, + ]; return $this; } - /** - * @inheritdoc - */ - public function select($columns = ['*']) + public function wherePhrasePrefix($column, $value): static { - $columns = is_array($columns) ? $columns : [$columns]; - $this->columns = $columns; - - return $this; - } - - public function addSelect($column) - { - if (!is_array($column)) { - $column = [$column]; - } - - $currentColumns = $this->columns; - if ($currentColumns) { - return $this->select(array_merge($currentColumns, $column)); - } - - return $this->select($column); - - } - - /** - * @inheritdoc - */ - - public function distinct($includeCount = false) - { - $this->distinct = 1; - if ($includeCount) { - $this->distinct = 2; - } - - return $this; - } - - /** - * @param ...$groups - * - * GroupBy will be passed on to distinct - * - * @return $this|Builder - */ - public function groupBy(...$groups) - { - if (is_array($groups[0])) { - $groups = $groups[0]; - } - - $this->addSelect($groups); - $this->distinct = 1; - - return $this; - } - - //Filters - - public function filterGeoBox($field, $topLeft, $bottomRight) - { - $this->filters['filterGeoBox'] = [ - 'field' => $field, - 'topLeft' => $topLeft, - 'bottomRight' => $bottomRight, - ]; - } - - public function filterGeoPoint($field, $distance, $geoPoint) - { - $this->filters['filterGeoPoint'] = [ - 'field' => $field, - 'distance' => $distance, - 'geoPoint' => $geoPoint, - ]; - } - - //Regexs - - public function whereRegex($column, $expression) - { - $type = 'regex'; $boolean = 'and'; - $this->wheres[] = compact('column', 'type', 'expression', 'boolean'); - - return $this; - } - - public function orWhereRegex($column, $expression) - { - $type = 'regex'; - $boolean = 'or'; - $this->wheres[] = compact('column', 'type', 'expression', 'boolean'); - - return $this; - } - - /** - * @inheritdoc - */ - public function newQuery() - { - return new self($this->connection, $this->processor); - } - - protected function prepareColumns($columns) - { - $final = []; - if ($this->columns) { - foreach ($this->columns as $col) { - $final[] = $col; - } - - } - - if ($columns) { - if (!is_array($columns)) { - $columns = [$columns]; - } - - foreach ($columns as $col) { - $final[] = $col; - } - } - if (!$final) { - return ['*']; - } - - $final = array_values(array_unique($final)); - if (($key = array_search('*', $final)) !== false) { - unset($final[$key]); - } - - return $final; - - - } - - protected function compileOptions() - { - $options = []; - if ($this->orders) { - $options['sort'] = $this->orders; - } - if ($this->offset) { - $options['skip'] = $this->offset; - } - if ($this->limit) { - $options['limit'] = $this->limit; - //Check if it's first() with no ordering, - //Set order to created_at -> asc for consistency - //TODO - } - if ($this->minScore) { - $options['minScore'] = $this->minScore; - } - if ($this->searchOptions) { - $options['searchOptions'] = $this->searchOptions; - } - if ($this->filters) { - $options['filters'] = $this->filters; - } - - return $options; - } - - /** - * @return array - */ - protected function compileWheres() - { - $wheres = $this->wheres ? : []; - $compiledWheres = []; - if ($wheres) { - if ($wheres[0]['boolean'] == 'or') { - throw new RuntimeException('Cannot start a query with an OR statement'); - } - if (count($wheres) == 1) { - return $this->{'_parseWhere'.$wheres[0]['type']}($wheres[0]); - } - $and = []; - $or = []; - foreach ($wheres as $where) { - if ($where['boolean'] == 'or') { - $or[] = $and; - //clear AND for the next bucket - $and = []; - } - - $result = $this->{'_parseWhere'.$where['type']}($where); - $and[] = $result; - - } - if ($or) { - //Add the last AND bucket - $or[] = $and; - foreach ($or as $and) { - $compiledWheres['or'][] = $this->_prepAndBucket($and); - } - } else { - - $compiledWheres = $this->_prepAndBucket($and); - } - } - - return $compiledWheres; - } - - private function _prepAndBucket($andData) - { - $data = []; - foreach ($andData as $key => $ops) { - $data['and'][$key] = $ops; - } - - return $data; - } - - /** - * @param array $where - * - * @return array - */ - protected function _parseWhereBasic(array $where) - { - $operator = $where['operator']; - $column = $where['column']; - $value = $where['value']; - $boolean = $where['boolean'] ?? null; - if ($boolean === 'and not') { - $operator = '!='; - } - if ($boolean === 'or not') { - $operator = '!='; - } - if ($operator === 'not like') { - $operator = 'not_like'; - } - - if (!isset($operator) || $operator == '=') { - $query = [$column => $value]; - } elseif (array_key_exists($operator, $this->conversion)) { - $query = [$column => [$this->conversion[$operator] => $value]]; - } else { - if (is_callable($column)) { - throw new RuntimeException('Invalid closure for where clause'); - } - $query = [$column => [$operator => $value]]; - } - - return $query; - } - - /** - * @param array $where - * - * @return mixed - */ - protected function _parseWhereNested(array $where) - { - - $boolean = $where['boolean']; -// if ($boolean !== 'and') { -// throw new RuntimeException('Nested where clause with boolean other than "and" is not supported'); -// } - if ($boolean === 'and not') { - $boolean = 'not'; - } - $must = match ($boolean) { - 'and' => 'must', - 'not', 'or not' => 'must_not', - 'or' => 'should', - default => throw new RuntimeException($boolean.' is not supported for parameter grouping'), - }; - - $query = $where['query']; - $wheres = $query->compileWheres(); - - return [ - $must => ['group' => ['wheres' => $wheres]], - ]; - - - } - - protected function _parseWhereQueryNested(array $where) - { - return [ - $where['column'] => [ - 'innerNested' => [ - 'wheres' => $where['wheres'], - 'options' => $where['options'], - ], - ], + $this->wheres[] = [ + 'column' => $column, + 'type' => 'Basic', + 'value' => $value, + 'operator' => 'phrase_prefix', + 'boolean' => $boolean, ]; - } - - /** - * @param array $where - * - * @return array - */ - protected function _parseWhereIn(array $where) - { - $column = $where['column']; - $values = $where['values']; - - return [$column => ['in' => array_values($values)]]; - } - - - /** - * @param array $where - * - * @return array - */ - protected function _parseWhereNotIn(array $where) - { - $column = $where['column']; - $values = $where['values']; - - return [$column => ['nin' => array_values($values)]]; - } - - /** - * @param array $where - * - * @return array - */ - protected function _parseWhereNull(array $where) - { - $where['operator'] = 'not_exists'; - $where['value'] = null; - - return $this->_parseWhereBasic($where); - } - - /** - * @param array $where - * - * @return array - */ - protected function _parseWhereNotNull(array $where) - { - $where['operator'] = 'exists'; - $where['value'] = null; - return $this->_parseWhereBasic($where); + return $this; } /** - * @param array $where - * - * @return array + * @return $this */ - protected function _parseWhereBetween(array $where) + public function whereExact($column, $value): static { - $not = $where['not'] ?? false; - $values = $where['values']; - $column = $where['column']; - - if ($not) { - return [ - $column => [ - 'not_between' => [$values[0], $values[1]], - ], - ]; - } - - return [ - $column => [ - 'between' => [$values[0], $values[1]], - ], + $boolean = 'and'; + $this->wheres[] = [ + 'column' => $column, + 'type' => 'Basic', + 'value' => $value, + 'operator' => 'exact', + 'boolean' => $boolean, ]; + + return $this; } /** - * @param array $where - * - * @return array + * @return $this */ - protected function _parseWhereDate(array $where) + public function queryNested($column, $callBack): static { - //return a normal where clause - return $this->_parseWhereBasic($where); + $boolean = 'and'; + $query = $this->newQuery(); + $callBack($query); + $wheres = $query->compileWheres(); + $options = $query->compileOptions(); + $this->wheres[] = [ + 'column' => $column, + 'type' => 'QueryNested', + 'wheres' => $wheres, + 'options' => $options, + 'boolean' => $boolean, + ]; + + return $this; } - protected function _parseWhereTimestamp(array $where) + public function whereTimestamp($column, $operator = null, $value = null, $boolean = 'and'): static { - $where['value'] = $this->_formatTimestamp($where['value']); - - return $this->_parseWhereBasic($where); + [$value, $operator] = $this->prepareValueAndOperator($value, $operator, func_num_args() === 2); + if ($this->invalidOperator($operator)) { + [$value, $operator] = [$operator, '=']; + } + $this->wheres[] = [ + 'column' => $column, + 'type' => 'Timestamp', + 'value' => $value, + 'operator' => $operator, + 'boolean' => $boolean, + ]; + return $this; } /** - * @param array $where - * - * @return array + * {@inheritDoc} */ - protected function _parseWhereMonth(array $where) + public function orderByDesc($column, $mode = null, $missing = null): static { - throw new LogicException('whereMonth clause is not available yet'); - + return $this->orderBy($column, 'desc', $mode, $missing); } /** - * @param array $where - * - * @return array + * {@inheritdoc} */ - protected function _parseWhereDay(array $where) + public function orderBy($column, $direction = 'asc', $mode = null, $missing = null): static { - throw new LogicException('whereDay clause is not available yet'); + if (is_string($direction)) { + $direction = (strtolower($direction) == 'asc' ? 'asc' : 'desc'); + } + $this->orders[$column] = [ + 'order' => $direction, + 'mode' => $mode, + 'missing' => $missing, + ]; + + return $this; } /** - * @param array $where - * - * @return array + * @param $unit @values: 'km', 'mi', 'm', 'ft' + * @param $mode @values: 'min', 'max', 'avg', 'sum' + * @param $type @values: 'arc', 'plane' + * @return $this */ - protected function _parseWhereYear(array $where) + public function orderByGeoDesc($column, $pin, $unit = 'km', $mode = null, $type = null): static { - throw new LogicException('whereYear clause is not available yet'); - + return $this->orderByGeo($column, $pin, 'desc', $unit, $mode, $type); } /** - * @param array $where - * - * @return array + * @param string $direction @values: 'asc', 'desc' + * @param string $unit @values: 'km', 'mi', 'm', 'ft' + * @param $mode @values: 'min', 'max', 'avg', 'sum' + * @param $type @values: 'arc', 'plane' + * @return $this */ - protected function _parseWhereTime(array $where) - { - throw new LogicException('whereTime clause is not available yet'); + public function orderByGeo( + $column, + $pin, + string $direction = 'asc', + string $unit = 'km', + ?string $mode = null, + ?string $type = null + ): static { + $this->orders[$column] = [ + 'is_geo' => true, + 'order' => $direction, + 'pin' => $pin, + 'unit' => $unit, + 'mode' => $mode, + 'type' => $type, + ]; + return $this; } /** - * @param array $where - * - * @return mixed + * @return $this */ - protected function _parseWhereRaw(array $where) + public function orderByNested($column, $direction = 'asc', $mode = null): static { - throw new LogicException('whereRaw clause is not available yet'); + $this->orders[$column] = [ + 'is_nested' => true, + 'order' => $direction, + 'mode' => $mode, + + ]; + return $this; } - public function _parseWhereExists(array $where) + //Filters + + /** + * {@inheritdoc} + */ + public function whereBetween($column, iterable $values, $boolean = 'and', $not = false): static { - throw new LogicException('SQL type "where exists" query is not valid for Elasticsearch. Use whereNotNull() or whereNull() to query the existence of a field'); + $type = 'between'; + + $this->wheres[] = compact('column', 'type', 'boolean', 'values', 'not'); + + return $this; } - public function _parseWhereNotExists(array $where) + public function groupBy(...$groups): Builder { - throw new LogicException('SQL type "where exists" query is not valid for Elasticsearch. Use whereNotNull() or whereNull() to query the existence of a field'); + if (is_array($groups[0])) { + $groups = $groups[0]; + } + + $this->addSelect($groups); + $this->distinctType = 1; + + return $this; } + //Regexs - /** - * @param array $where - * - * @return mixed - */ - protected function _parseWhereRegex(array $where) + public function addSelect($column): static { - $value = $where['expression']; - $column = $where['column']; + if (! is_array($column)) { + $column = [$column]; + } - return [$column => ['regex' => $value]]; + $currentColumns = $this->columns; + if ($currentColumns) { + return $this->select(array_merge($currentColumns, $column)); + } + return $this->select($column); } /** - * @param array $where - * - * @return array[] + * {@inheritdoc} */ - protected function _parseWhereNestedObject(array $where) + public function select($columns = ['*']): static { - $wheres = $where['wheres']; - $column = $where['column']; - $scoreMode = $where['score_mode']; + $columns = is_array($columns) ? $columns : [$columns]; + $this->columns = $columns; + return $this; + } - return [ - $column => ['nested' => ['wheres' => $wheres, 'score_mode' => $scoreMode]], + public function filterGeoBox($field, $topLeft, $bottomRight): void + { + $this->filters['filterGeoBox'] = [ + 'field' => $field, + 'topLeft' => $topLeft, + 'bottomRight' => $bottomRight, ]; } + public function filterGeoPoint($field, $distance, $geoPoint): void + { + $this->filters['filterGeoPoint'] = [ + 'field' => $field, + 'distance' => $distance, + 'geoPoint' => $geoPoint, + ]; + } - /** - * @param array $where - * - * @return array[] - */ - protected function _parseWhereNotNestedObject(array $where) + public function whereRegex($column, $expression): static { - $wheres = $where['wheres']; - $column = $where['column']; - $scoreMode = $where['score_mode']; + $type = 'regex'; + $boolean = 'and'; + $this->wheres[] = compact('column', 'type', 'expression', 'boolean'); + return $this; + } - return [ - $column => ['not_nested' => ['wheres' => $wheres, 'score_mode' => $scoreMode]], - ]; + public function orWhereRegex($column, $expression): static + { + $type = 'regex'; + $boolean = 'or'; + $this->wheres[] = compact('column', 'type', 'expression', 'boolean'); + + return $this; + } + + public function _parseWhereExists(array $where) + { + throw new LogicException('SQL type "where exists" query is not valid for Elasticsearch. Use whereNotNull() or whereNull() to query the existence of a field'); } + public function _parseWhereNotExists(array $where) + { + throw new LogicException('SQL type "where exists" query is not valid for Elasticsearch. Use whereNotNull() or whereNull() to query the existence of a field'); + } /** * Set custom options for the query. * - * @param array $options * * @return $this */ - public function options(array $options) + public function options(array $options): static { $this->options = $options; return $this; } - - //---------------------------------------------------------------------- - // Collection bindings - //---------------------------------------------------------------------- - /** - * @inheritdoc + * {@inheritdoc} */ - public function pluck($column, $key = null) + public function pluck($column, $key = null): Collection { $results = $this->get($key === null ? [$column] : [$column, $key]); // Convert ObjectID's to strings if ($key == '_id') { $results = $results->map(function ($item) { - $item['_id'] = (string)$item['_id']; + $item['_id'] = (string) $item['_id']; return $item; }); @@ -1247,14 +1022,10 @@ public function pluck($column, $key = null) return new Collection($p); } - //---------------------------------------------------------------------- - // Index/Schema - //---------------------------------------------------------------------- - /** - * @inheritdoc + * {@inheritdoc} */ - public function from($index, $as = null) + public function from($index, $as = null): static { if ($index) { @@ -1266,9 +1037,9 @@ public function from($index, $as = null) } /** - * @inheritdoc + * {@inheritdoc} */ - public function truncate() + public function truncate(): int { $result = $this->connection->deleteAll([]); @@ -1279,37 +1050,55 @@ public function truncate() return 0; } - public function deleteIndex() + public function deleteIndex(): bool { return Schema::connection($this->connection->getName())->delete($this->index); + } + /** + * {@inheritdoc} + */ + public function delete($id = null): int + { + + if ($id !== null) { + $this->where('_id', '=', $id); + } + + return $this->_processDelete(); } - public function deleteIndexIfExists() + protected function _processDelete(): int { - return Schema::connection($this->connection->getName())->deleteIfExists($this->index); + $wheres = $this->compileWheres(); + $options = $this->compileOptions(); + $result = $this->connection->deleteAll($wheres, $options); + if ($result->isSuccessful()) { + return $result->getDeletedCount(); + } + return 0; } - public function getIndexMappings() + public function deleteIndexIfExists(): bool { - return Schema::connection($this->connection->getName())->getMappings($this->index); + return Schema::connection($this->connection->getName())->deleteIfExists($this->index); } - public function getIndexSettings() + public function getIndexMappings(): array { - return Schema::connection($this->connection->getName())->getSettings($this->index); + return Schema::connection($this->connection->getName())->getMappings($this->index); } - public function indexExists() + public function getIndexSettings(): array { - return Schema::connection($this->connection->getName())->hasIndex($this->index); + return Schema::connection($this->connection->getName())->getSettings($this->index); } - public function createIndex() + public function createIndex(array $settings = []): bool { - if (!$this->indexExists()) { - $this->connection->indexCreate($this->index); + if (! $this->indexExists()) { + $this->connection->indexCreate($settings); return true; } @@ -1317,148 +1106,81 @@ public function createIndex() return false; } - public function rawSearch(array $bodyParams, $returnRaw = false) + public function indexExists(): bool + { + return Schema::connection($this->connection->getName())->hasIndex($this->index); + } + + public function rawSearch(array $bodyParams, $returnRaw = false): Collection { $find = $this->connection->searchRaw($bodyParams, $returnRaw); $data = $find->data; return new Collection($data); - } - public function rawAggregation(array $bodyParams) + public function rawAggregation(array $bodyParams): Collection { $find = $this->connection->aggregationRaw($bodyParams); $data = $find->data; return new Collection($data); - - } - //---------------------------------------------------------------------- - // Pagination overrides - //---------------------------------------------------------------------- - - - protected function runPaginationCountQuery($columns = ['*']) - { - if ($this->distinct) { - $clone = $this->cloneForPaginationCount(); - $currentCloneCols = $clone->columns; - if ($columns && $columns !== ['*']) { - $currentCloneCols = array_merge($currentCloneCols, $columns); - } - - return $clone->setAggregate('count', $currentCloneCols)->get()->all(); - } - - $without = $this->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset']; - - return $this->cloneWithout($without) - ->cloneWithoutBindings($this->unions ? ['order'] : ['select', 'order']) - ->setAggregate('count', $this->withoutSelectAliases($columns)) - ->get()->all(); - } - - public function toSql() - { - return $this->toDsl(); - } - - public function toDsl() - { - $wheres = $this->compileWheres(); - $options = $this->compileOptions(); - $columns = $this->prepareColumns([]); - if ($this->searchQuery) { - $searchParams = $this->searchQuery; - $searchOptions = $this->searchOptions; - $fields = $this->fields; - - return $this->connection->toDslForSearch($searchParams, $searchOptions, $wheres, $options, $fields, $columns); - } - - return $this->connection->toDsl($wheres, $options, $columns); - - - } - - //---------------------------------------------------------------------- - // Disabled features (for now) - //---------------------------------------------------------------------- - - /** - * @inheritdoc - */ - public function upsert(array $values, $uniqueBy, $update = null) - { - throw new LogicException('The upsert feature for Elasticsearch is currently not supported. Please use updateAll()'); - } - - - /** - * @inheritdoc - */ - public function groupByRaw($sql, array $bindings = []) - { - throw new LogicException('groupByRaw() is currently not supported'); } - - //---------------------------------------------------------------------- - // Helpers - //---------------------------------------------------------------------- - - private function _checkValues($values) - { - unset($values['updated_at']); - unset($values['created_at']); - if (!$this->_isAssociative($values)) { - throw new RuntimeException('Invalid value format. Expected associative array, got sequential array'); - } - - return true; + public function toSql(): array + { + return $this->toDsl(); } - private function _isAssociative(array $arr) + public function toDsl(): array { - if ([] === $arr) { - return true; + $wheres = $this->compileWheres(); + $options = $this->compileOptions(); + $columns = $this->prepareColumns([]); + if ($this->searchQuery) { + $searchParams = $this->searchQuery; + $searchOptions = $this->searchOptions; + $fields = $this->fields; + + return $this->connection->toDslForSearch($searchParams, $searchOptions, $wheres, $options, $fields, $columns); } - return array_keys($arr) !== range(0, count($arr) - 1); + return $this->connection->toDsl($wheres, $options, $columns); } - - //---------------------------------------------------------------------- - // ES query executors - //---------------------------------------------------------------------- - - public function query($columns = []) + /** + * {@inheritdoc} + */ + public function upsert(array $values, $uniqueBy, $update = null): int { - $wheres = $this->compileWheres(); - $options = $this->compileOptions(); + throw new LogicException('The upsert feature for Elasticsearch is currently not supported. Please use updateAll()'); + } - return $this->connection->showQuery($wheres, $options, $columns); + /** + * {@inheritdoc} + */ + public function groupByRaw($sql, array $bindings = []) + { + throw new LogicException('groupByRaw() is currently not supported'); } public function matrix($column) { - if (!is_array($column)) { + if (! is_array($column)) { $column = [$column]; } $result = $this->aggregate(__FUNCTION__, $column); - return $result ? : 0; + return $result ?: 0; } //---------------------------------------------------------------------- - // ES Search query methods + // Collection bindings //---------------------------------------------------------------------- - - public function searchQuery($term, $boostFactor = null, $clause = null, $type = 'term') + public function searchQuery($term, $boostFactor = null, $clause = null, $type = 'term'): void { - if (!$clause && !empty($this->searchQuery)) { + if (! $clause && ! empty($this->searchQuery)) { switch ($type) { case 'fuzzy': throw new RuntimeException('Incorrect query sequencing, fuzzyTerm() should only start the ORM chain'); @@ -1469,7 +1191,6 @@ public function searchQuery($term, $boostFactor = null, $clause = null, $type = default: throw new RuntimeException('Incorrect query sequencing, term() should only start the ORM chain'); } - } if ($clause && empty($this->searchQuery)) { switch ($type) { @@ -1482,7 +1203,6 @@ public function searchQuery($term, $boostFactor = null, $clause = null, $type = default: throw new RuntimeException('Incorrect query sequencing, andTerm()/orTerm() cannot start the ORM chain'); } - } switch ($type) { case 'fuzzy': @@ -1509,22 +1229,26 @@ public function searchQuery($term, $boostFactor = null, $clause = null, $type = } } - public function minShouldMatch($value) + //---------------------------------------------------------------------- + // Index/Schema + //---------------------------------------------------------------------- + + public function minShouldMatch($value): void { $this->searchOptions['minimum_should_match'] = $value; } - public function minScore($value) + public function minScore($value): void { $this->minScore = $value; } - public function boostField($field, $factor) + public function boostField($field, $factor): void { $this->fields[$field] = $factor ?? 1; } - public function searchFields(array $fields) + public function searchFields(array $fields): void { foreach ($fields as $field) { if (empty($this->fields[$field])) { @@ -1533,31 +1257,34 @@ public function searchFields(array $fields) } } - public function searchField($field, $boostFactor = null) + public function searchField($field, $boostFactor = null): void { $this->fields[$field] = $boostFactor ?? 1; } - public function highlight(array $fields = [], string|array $preTag = '', string|array $postTag = '', array $globalOptions = []) - { + public function highlight( + array $fields = [], + string|array $preTag = '', + string|array $postTag = '', + array $globalOptions = [] + ): void { $highlightFields = [ - '*' => (object)[], + '*' => (object) [], ]; - if (!empty($fields)) { + if (! empty($fields)) { $highlightFields = []; foreach ($fields as $field => $payload) { if (is_int($field)) { - $highlightFields[$payload] = (object)[]; + $highlightFields[$payload] = (object) []; } else { $highlightFields[$field] = $payload; } - } } - if (!is_array($preTag)) { + if (! is_array($preTag)) { $preTag = [$preTag]; } - if (!is_array($postTag)) { + if (! is_array($postTag)) { $postTag = [$postTag]; } @@ -1569,16 +1296,14 @@ public function highlight(array $fields = [], string|array $preTag = '', str $highlight['post_tags'] = $postTag; $highlight['fields'] = $highlightFields; - $this->searchOptions['highlight'] = $highlight; } - - public function search($columns = '*') + public function search($columns = '*'): Collection { $searchParams = $this->searchQuery; - if (!$searchParams) { + if (! $searchParams) { throw new RuntimeException('No search parameters. Add terms to search for.'); } $searchOptions = $this->searchOptions; @@ -1590,27 +1315,18 @@ public function search($columns = '*') if ($search->isSuccessful()) { $data = $search->data; - return new Collection($data); - - } else { throw new RuntimeException('Error: '.$search->errorMessage); } - - } - //---------------------------------------------------------------------- - // PIT API - //---------------------------------------------------------------------- - - public function openPit($keepAlive = '5m') + public function openPit($keepAlive = '5m'): string { return $this->connection->openPit($keepAlive); } - public function pitFind($count, $pitId, $after = null, $keepAlive = '5m') + public function pitFind(int $count, string $pitId, ?array $after = null, string $keepAlive = '5m'): Results { $wheres = $this->compileWheres(); $options = $this->compileOptions(); @@ -1620,37 +1336,269 @@ public function pitFind($count, $pitId, $after = null, $keepAlive = '5m') return $this->connection->pitFind($wheres, $options, $fields, $pitId, $after, $keepAlive); } - public function closePit($id) + public function closePit($id): bool { return $this->connection->closePit($id); } + //---------------------------------------------------------------------- + // Pagination overrides + //---------------------------------------------------------------------- + + protected function _parseWhereNested(array $where): array + { + + $boolean = $where['boolean']; + // if ($boolean !== 'and') { + // throw new RuntimeException('Nested where clause with boolean other than "and" is not supported'); + // } + if ($boolean === 'and not') { + $boolean = 'not'; + } + $must = match ($boolean) { + 'and' => 'must', + 'not', 'or not' => 'must_not', + 'or' => 'should', + default => throw new RuntimeException($boolean.' is not supported for parameter grouping'), + }; + + $query = $where['query']; + $wheres = $query->compileWheres(); + + return [ + $must => ['group' => ['wheres' => $wheres]], + ]; + } + + protected function _parseWhereQueryNested(array $where): array + { + return [ + $where['column'] => [ + 'innerNested' => [ + 'wheres' => $where['wheres'], + 'options' => $where['options'], + ], + ], + ]; + } + + protected function _parseWhereIn(array $where): array + { + $column = $where['column']; + $values = $where['values']; + + return [$column => ['in' => array_values($values)]]; + } + + protected function _parseWhereNotIn(array $where): array + { + $column = $where['column']; + $values = $where['values']; + + return [$column => ['nin' => array_values($values)]]; + } + + protected function _parseWhereNull(array $where): array + { + $where['operator'] = 'not_exists'; + $where['value'] = null; + return $this->_parseWhereBasic($where); + } //---------------------------------------------------------------------- // Helpers //---------------------------------------------------------------------- - private function _formatTimestamp($value) + protected function _parseWhereBasic(array $where): array + { + $operator = $where['operator']; + $column = $where['column']; + $value = $where['value']; + $boolean = $where['boolean'] ?? null; + if ($boolean === 'and not') { + $operator = '!='; + } + if ($boolean === 'or not') { + $operator = '!='; + } + if ($operator === 'not like') { + $operator = 'not_like'; + } + + if (! isset($operator) || $operator == '=') { + $query = [$column => $value]; + } elseif (array_key_exists($operator, $this->conversion)) { + $query = [$column => [$this->conversion[$operator] => $value]]; + } else { + if (is_callable($column)) { + throw new RuntimeException('Invalid closure for where clause'); + } + $query = [$column => [$operator => $value]]; + } + + return $query; + } + + /** + * @return array + */ + protected function _parseWhereNotNull(array $where) + { + $where['operator'] = 'exists'; + $where['value'] = null; + + return $this->_parseWhereBasic($where); + } + + //---------------------------------------------------------------------- + // ES query executors + //---------------------------------------------------------------------- + + protected function _parseWhereBetween(array $where): array + { + $not = $where['not'] ?? false; + $values = $where['values']; + $column = $where['column']; + + if ($not) { + return [ + $column => [ + 'not_between' => [$values[0], $values[1]], + ], + ]; + } + + return [ + $column => [ + 'between' => [$values[0], $values[1]], + ], + ]; + } + + protected function _parseWhereDate(array $where): array + { + //return a normal where clause + return $this->_parseWhereBasic($where); + } + + //---------------------------------------------------------------------- + // ES Search query methods + //---------------------------------------------------------------------- + + protected function _parseWhereTimestamp(array $where): array + { + $where['value'] = $this->_formatTimestamp($where['value']); + + return $this->_parseWhereBasic($where); + } + + private function _formatTimestamp($value): string|int { if (is_numeric($value)) { // Convert to integer in case it's a string - $value = (int)$value; + $value = (int) $value; // Check for milliseconds if ($value > 10000000000) { return $value; } // ES expects seconds as a string - return (string)Carbon::createFromTimestamp($value)->timestamp; + return (string) Carbon::createFromTimestamp($value)->timestamp; } // If it's not numeric, assume it's a date string and try to return TS as a string try { - return (string)Carbon::parse($value)->timestamp; + return (string) Carbon::parse($value)->timestamp; } catch (\Exception $e) { throw new LogicException('Invalid date or timestamp'); } } + protected function _parseWhereMonth(array $where): array + { + throw new LogicException('whereMonth clause is not available yet'); + } + + protected function _parseWhereDay(array $where): array + { + throw new LogicException('whereDay clause is not available yet'); + } + + protected function _parseWhereYear(array $where): array + { + throw new LogicException('whereYear clause is not available yet'); + } + + protected function _parseWhereTime(array $where): array + { + throw new LogicException('whereTime clause is not available yet'); + } + + protected function _parseWhereRaw(array $where): array + { + throw new LogicException('whereRaw clause is not available yet'); + } + + protected function _parseWhereRegex(array $where): array + { + $value = $where['expression']; + $column = $where['column']; + + return [$column => ['regex' => $value]]; + } + + //---------------------------------------------------------------------- + // PIT API + //---------------------------------------------------------------------- + + protected function _parseWhereNestedObject(array $where): array + { + $wheres = $where['wheres']; + $column = $where['column']; + $scoreMode = $where['score_mode']; + + return [ + $column => ['nested' => ['wheres' => $wheres, 'score_mode' => $scoreMode]], + ]; + } + + protected function _parseWhereNotNestedObject(array $where): array + { + $wheres = $where['wheres']; + $column = $where['column']; + $scoreMode = $where['score_mode']; + + return [ + $column => ['not_nested' => ['wheres' => $wheres, 'score_mode' => $scoreMode]], + ]; + } + + protected function runPaginationCountQuery($columns = ['*']): Closure|array + { + if ($this->distinctType) { + $clone = $this->cloneForPaginationCount(); + $currentCloneCols = $clone->columns; + if ($columns && $columns !== ['*']) { + $currentCloneCols = array_merge($currentCloneCols, $columns); + } + + return $clone->setAggregate('count', $currentCloneCols)->get()->all(); + } + + $without = $this->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset']; + + return $this->cloneWithout($without)->cloneWithoutBindings($this->unions ? ['order'] : [ + 'select', + 'order', + ])->setAggregate('count', $this->withoutSelectAliases($columns))->get()->all(); + } + + //---------------------------------------------------------------------- + // Helpers + //---------------------------------------------------------------------- + public function all($columns = []): Collection + { + return $this->_processGet($columns); + } } diff --git a/src/Query/Grammar.php b/src/Query/Grammar.php index 019c0b1..1c4962d 100644 --- a/src/Query/Grammar.php +++ b/src/Query/Grammar.php @@ -1,5 +1,7 @@ getOwnerKey(); + return $this->ownerKey; } /** - * @inheritdoc + * {@inheritdoc} */ - public function addConstraints() + public function addConstraints(): void { if (static::$constraints) { - $this->query->where($this->getOwnerKey(), '=', $this->parent->{$this->foreignKey}); + $this->query->where($this->ownerKey, '=', $this->parent->{$this->foreignKey}); } } /** - * @inheritdoc + * {@inheritdoc} */ - public function addEagerConstraints(array $models) + public function addEagerConstraints(array $models): void { - $key = $this->getOwnerKey(); - - $this->query->whereIn($key, $this->getEagerModelKeys($models)); + $this->query->whereIn($this->ownerKey, $this->getEagerModelKeys($models)); } /** - * @inheritdoc + * {@inheritdoc} */ - public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) + public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']): Builder { return $query; } - /** - * Get the owner key with backwards compatible support. - * - * @return string - */ - public function getOwnerKey() - { - return property_exists($this, 'ownerKey') ? $this->ownerKey : $this->otherKey; - } - - protected function whereInMethod(EloquentModel $model, $key) + protected function whereInMethod(EloquentModel $model, $key): string { return 'whereIn'; } diff --git a/src/Relations/BelongsToMany.php b/src/Relations/BelongsToMany.php index af27a04..4cecf34 100644 --- a/src/Relations/BelongsToMany.php +++ b/src/Relations/BelongsToMany.php @@ -1,13 +1,12 @@ foreignKey; + $foreignKey = $this->getHasCompareKey(); + + //@phpstan-ignore-next-line + return $query->select($foreignKey)->where($foreignKey, 'exists', true); } - + /** * Get the key for comparing against the parent key in "has" query. - * - * @return string */ - public function getHasCompareKey() + public function getHasCompareKey(): string { return $this->getForeignKeyName(); } /** - * @inheritdoc + * Get the plain foreign key. */ - public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) + public function getForeignKeyName(): string { - $foreignKey = $this->getHasCompareKey(); - - return $query->select($foreignKey)->where($foreignKey, 'exists', true); + return $this->foreignKey; } - - protected function whereInMethod(EloquentModel $model, $key) + protected function whereInMethod(EloquentModel $model, $key): string { return 'whereIn'; } diff --git a/src/Relations/HasOne.php b/src/Relations/HasOne.php index 9e08597..c6cb99e 100644 --- a/src/Relations/HasOne.php +++ b/src/Relations/HasOne.php @@ -1,5 +1,7 @@ foreignKey; + return $this->getForeignKeyName(); } - public function getHasCompareKey() + public function getForeignKeyName(): string { - return $this->getForeignKeyName(); + return $this->foreignKey; } /** - * @inheritdoc + * {@inheritdoc} */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { $foreignKey = $this->getForeignKeyName(); - + //@phpstan-ignore-next-line return $query->select($foreignKey)->where($foreignKey, 'exists', true); } - - protected function whereInMethod(EloquentModel $model, $key) + protected function whereInMethod(EloquentModel $model, $key): string { return 'whereIn'; } diff --git a/src/Relations/MorphMany.php b/src/Relations/MorphMany.php index d675bf7..0dafb69 100644 --- a/src/Relations/MorphMany.php +++ b/src/Relations/MorphMany.php @@ -1,5 +1,7 @@ query->where($this->getOwnerKey(), '=', $this->parent->{$this->foreignKey}); + $this->query->where($this->ownerKey, '=', $this->getForeignKeyFrom($this->parent)); } } - /** - * @inheritdoc - */ - protected function getResultsByType($type) + /** {@inheritdoc} */ + protected function getResultsByType($type): Collection { $instance = $this->createModelByType($type); @@ -30,13 +29,8 @@ protected function getResultsByType($type) return $query->whereIn($key, $this->gatherKeysByType($type, $instance->getKeyType()))->get(); } - - public function getOwnerKey() - { - return property_exists($this, 'ownerKey') ? $this->ownerKey : $this->otherKey; - } - protected function whereInMethod(EloquentModel $model, $key) + protected function whereInMethod(EloquentModel $model, $key): string { return 'whereIn'; } diff --git a/src/Schema/AnalyzerBlueprint.php b/src/Schema/AnalyzerBlueprint.php index 9ba02c8..967b8fa 100644 --- a/src/Schema/AnalyzerBlueprint.php +++ b/src/Schema/AnalyzerBlueprint.php @@ -1,22 +1,22 @@ addProperty('analyzer', $name); } - public function tokenizer($type): Definitions\AnalyzerPropertyDefinition + protected function addProperty($config, $name, array $parameters = []) { - return $this->addProperty('tokenizer', $type); + return $this->addPropertyDefinition(new Definitions\AnalyzerPropertyDefinition( + array_merge(compact('config', 'name'), $parameters) + )); } - public function charFilter($type): Definitions\AnalyzerPropertyDefinition + protected function addPropertyDefinition($definition) { - return $this->addProperty('char_filter', $type); + $this->parameters['analysis'][] = $definition; + + return $definition; } - public function filter($type): Definitions\AnalyzerPropertyDefinition + public function tokenizer($type): Definitions\AnalyzerPropertyDefinition { - return $this->addProperty('filter', $type); + return $this->addProperty('tokenizer', $type); } - //---------------------------------------------------------------------- // Definitions //---------------------------------------------------------------------- - protected function addProperty($config, $name, array $parameters = []) + public function charFilter($type): Definitions\AnalyzerPropertyDefinition { - return $this->addPropertyDefinition(new Definitions\AnalyzerPropertyDefinition( - array_merge(compact('config', 'name'), $parameters) - )); + return $this->addProperty('char_filter', $type); } - protected function addPropertyDefinition($definition) + public function filter($type): Definitions\AnalyzerPropertyDefinition { - $this->parameters['analysis'][] = $definition; - - return $definition; + return $this->addProperty('filter', $type); } //---------------------------------------------------------------------- // Builders //---------------------------------------------------------------------- - public function buildIndexAnalyzerSettings(Connection $connection) + public function buildIndexAnalyzerSettings(Connection $connection): bool { $connection->setIndex($this->index); if ($this->parameters) { $this->_formatParams(); $connection->indexAnalyzerSettings($this->parameters); } + + return false; } //---------------------------------------------------------------------- // Helpers //---------------------------------------------------------------------- - private function _formatParams() + private function _formatParams(): void { if ($this->parameters) { - if (!empty($this->parameters['analysis'])) { + if (! empty($this->parameters['analysis'])) { $properties = []; foreach ($this->parameters['analysis'] as $property) { if ($property instanceof Fluent) { @@ -98,5 +99,4 @@ private function _formatParams() } } } - } diff --git a/src/Schema/Builder.php b/src/Schema/Builder.php index 229f2d7..185f5ab 100644 --- a/src/Schema/Builder.php +++ b/src/Schema/Builder.php @@ -1,15 +1,17 @@ connection->getIndices(false); - } - - public function overridePrefix($value): Builder { $this->connection->setIndexPrefix($value); @@ -33,7 +29,14 @@ public function overridePrefix($value): Builder return $this; } - public function getIndex($index) + public function getSettings($index): array + { + $this->connection->setIndex($index); + + return $this->connection->indexSettings($this->connection->getIndex()); + } + + public function getIndex($index): array { if ($this->hasIndex($index)) { $this->connection->setIndex($index); @@ -45,24 +48,23 @@ public function getIndex($index) } - public function getMappings($index) + public function hasIndex($index): bool { - $this->connection->setIndex($index); + $index = $this->connection->setIndex($index); - return $this->connection->indexMappings($this->connection->getIndex()); + return $this->connection->indexExists($index); } - public function getSettings($index) + public function getIndices(): array { - $this->connection->setIndex($index); - - return $this->connection->indexSettings($this->connection->getIndex()); + return $this->connection->getIndices(false); } //---------------------------------------------------------------------- // Create Index //---------------------------------------------------------------------- - public function create($index, Closure $callback) + + public function create($index, Closure $callback): array { $this->builder('buildIndexCreate', tap(new IndexBlueprint($index), function ($blueprint) use ($callback) { $callback($blueprint); @@ -71,7 +73,16 @@ public function create($index, Closure $callback) return $this->getIndex($index); } - public function createIfNotExists($index, Closure $callback) + protected function builder($builder, IndexBlueprint $blueprint): void + { + $blueprint->{$builder}($this->connection); + } + + //---------------------------------------------------------------------- + // Reindex + //---------------------------------------------------------------------- + + public function createIfNotExists($index, Closure $callback): array { if ($this->hasIndex($index)) { return $this->getIndex($index); @@ -84,20 +95,19 @@ public function createIfNotExists($index, Closure $callback) } //---------------------------------------------------------------------- - // Reindex + // Modify Index //---------------------------------------------------------------------- - public function reIndex($from, $to) + public function reIndex($from, $to): Results { return $this->connection->reIndex($from, $to); } - //---------------------------------------------------------------------- - // Modify Index + // Delete Index //---------------------------------------------------------------------- - public function modify($index, Closure $callback) + public function modify($index, Closure $callback): array { $this->builder('buildIndexModify', tap(new IndexBlueprint($index), function ($blueprint) use ($callback) { $callback($blueprint); @@ -106,18 +116,18 @@ public function modify($index, Closure $callback) return $this->getIndex($index); } - //---------------------------------------------------------------------- - // Delete Index - //---------------------------------------------------------------------- - - public function delete($index) + public function delete($index): bool { $this->connection->setIndex($index); return $this->connection->indexDelete(); } - public function deleteIfExists($index) + //---------------------------------------------------------------------- + // Index template + //---------------------------------------------------------------------- + + public function deleteIfExists($index): bool { if ($this->hasIndex($index)) { $this->connection->setIndex($index); @@ -129,18 +139,19 @@ public function deleteIfExists($index) } //---------------------------------------------------------------------- - // Index template + // Analysers //---------------------------------------------------------------------- + public function createTemplate($name, Closure $callback) { //TODO } //---------------------------------------------------------------------- - // Analysers + // Index ops //---------------------------------------------------------------------- - public function setAnalyser($index, Closure $callback) + public function setAnalyser($index, Closure $callback): array { $this->analyzerBuilder('buildIndexAnalyzerSettings', tap(new AnalyzerBlueprint($index), function ($blueprint) use ($callback) { $callback($blueprint); @@ -149,11 +160,12 @@ public function setAnalyser($index, Closure $callback) return $this->getIndex($index); } - //---------------------------------------------------------------------- - // Index ops - //---------------------------------------------------------------------- + protected function analyzerBuilder($builder, AnalyzerBlueprint $blueprint): void + { + $blueprint->{$builder}($this->connection); + } - public function hasField($index, $field) + public function hasField($index, $field): bool { $index = $this->connection->setIndex($index); @@ -173,63 +185,22 @@ public function hasField($index, $field) } - public function hasFields($index, array $fields) - { - $index = $this->connection->setIndex($index); - - try { - $mappings = $this->getMappings($index); - $props = $mappings[$index]['mappings']['properties']; - $props = $this->_flattenFields($props); - $fileList = $this->_sanitizeFlatFields($props); - $allFound = true; - foreach ($fields as $field) { - if (!in_array($field, $fileList)) { - $allFound = false; - } - } - - return $allFound; - } catch (Exception $e) { - return false; - } - - } - - public function hasIndex($index) - { - $index = $this->connection->setIndex($index); - - return $this->connection->indexExists($index); - } - //---------------------------------------------------------------------- // Manual //---------------------------------------------------------------------- - public function dsl($method, $params) + public function getMappings($index): array { - return $this->connection->indicesDsl($method, $params); + $this->connection->setIndex($index); + + return $this->connection->indexMappings($this->connection->getIndex()); } //---------------------------------------------------------------------- // Helpers //---------------------------------------------------------------------- - function flatten($array, $prefix = '') - { - $result = array(); - foreach ($array as $key => $value) { - if (is_array($value)) { - $result = $result + flatten($value, $prefix.$key.'.'); - } else { - $result[$prefix.$key] = $value; - } - } - - return $result; - } - private function _flattenFields($array, $prefix = '') + private function _flattenFields($array, $prefix = ''): array { $result = []; @@ -244,7 +215,7 @@ private function _flattenFields($array, $prefix = '') return $result; } - private function _sanitizeFlatFields($flatFields) + private function _sanitizeFlatFields($flatFields): array { $fields = []; if ($flatFields) { @@ -264,25 +235,59 @@ private function _sanitizeFlatFields($flatFields) return $fields; } + public function hasFields($index, array $fields): bool + { + $index = $this->connection->setIndex($index); + + try { + $mappings = $this->getMappings($index); + $props = $mappings[$index]['mappings']['properties']; + $props = $this->_flattenFields($props); + $fileList = $this->_sanitizeFlatFields($props); + $allFound = true; + foreach ($fields as $field) { + if (! in_array($field, $fileList)) { + $allFound = false; + } + } + + return $allFound; + } catch (Exception $e) { + return false; + } + + } + //---------------------------------------------------------------------- // Internal Laravel init migration catchers // *Case for when ES is the only datasource //---------------------------------------------------------------------- - public function hasTable($table) + + public function dsl($method, $params): Results { - return $this->getIndex($table); + return $this->connection->indicesDsl($method, $params); } //---------------------------------------------------------------------- // Builders //---------------------------------------------------------------------- - protected function builder($builder, IndexBlueprint $blueprint) + + public function flatten($array, $prefix = ''): array { - $blueprint->{$builder}($this->connection); + $result = []; + foreach ($array as $key => $value) { + if (is_array($value)) { + $result = $result + $this->flatten($value, $prefix.$key.'.'); + } else { + $result[$prefix.$key] = $value; + } + } + + return $result; } - protected function analyzerBuilder($builder, AnalyzerBlueprint $blueprint) + public function hasTable($table): array { - $blueprint->{$builder}($this->connection); + return $this->getIndex($table); } } diff --git a/src/Schema/Definitions/AnalyzerPropertyDefinition.php b/src/Schema/Definitions/AnalyzerPropertyDefinition.php index 1300876..b68b32c 100644 --- a/src/Schema/Definitions/AnalyzerPropertyDefinition.php +++ b/src/Schema/Definitions/AnalyzerPropertyDefinition.php @@ -1,5 +1,7 @@ addField('text', $field); } + protected function addField($type, $field, array $parameters = []) + { + return $this->addFieldDefinition(new Definitions\FieldDefinition( + array_merge(compact('type', 'field'), $parameters) + )); + } + + protected function addFieldDefinition($definition) + { + $this->parameters['properties'][] = $definition; + + return $definition; + } + public function array($field): Definitions\FieldDefinition { return $this->addField('text', $field); } + //---------------------------------------------------------------------- + // Numeric Types + //---------------------------------------------------------------------- + public function boolean($field): Definitions\FieldDefinition { return $this->addField('boolean', $field); @@ -53,11 +68,6 @@ public function keyword($field): Definitions\FieldDefinition return $this->addField('keyword', $field); } - - //---------------------------------------------------------------------- - // Numeric Types - //---------------------------------------------------------------------- - public function long($field): Definitions\FieldDefinition { return $this->addField('long', $field); @@ -93,6 +103,8 @@ public function halfFloat($field): Definitions\FieldDefinition return $this->addField('half_float', $field); } + //---------------------------------------------------------------------- + public function scaledFloat($field, $scalingFactor = 100): Definitions\FieldDefinition { return $this->addField('scaled_float', $field, [ @@ -105,8 +117,6 @@ public function unsignedLong($field): Definitions\FieldDefinition return $this->addField('unsigned_long', $field); } - //---------------------------------------------------------------------- - public function date($field, $format = null): Definitions\FieldDefinition { if ($format) { @@ -147,40 +157,25 @@ public function settings($key, $value): void $this->parameters['settings'][$key] = $value; } - public function map($key, $value): void - { - $this->parameters['map'][$key] = $value; - } - - public function field($type, $field, array $parameters = []) - { - return $this->addField($type, $field, $parameters); - } - //---------------------------------------------------------------------- // Definitions //---------------------------------------------------------------------- - protected function addField($type, $field, array $parameters = []) + public function map($key, $value): void { - return $this->addFieldDefinition(new Definitions\FieldDefinition( - array_merge(compact('type', 'field'), $parameters) - )); + $this->parameters['map'][$key] = $value; } - protected function addFieldDefinition($definition) + public function field($type, $field, array $parameters = []) { - $this->parameters['properties'][] = $definition; - - return $definition; + return $this->addField($type, $field, $parameters); } //====================================================================== // Builders //====================================================================== - - public function buildIndexCreate(Connection $connection) + public function buildIndexCreate(Connection $connection): void { $connection->setIndex($this->index); if ($this->parameters) { @@ -189,55 +184,53 @@ public function buildIndexCreate(Connection $connection) } } - public function buildReIndex(Connection $connection) - { - return $connection->reIndex($this->index, $this->newIndex); - } - - public function buildIndexModify(Connection $connection) + private function _formatParams(): void { - $connection->setIndex($this->index); if ($this->parameters) { - $this->_formatParams(); - $connection->indexModify($this->parameters); + if (! empty($this->parameters['properties'])) { + $properties = []; + foreach ($this->parameters['properties'] as $property) { + if ($property instanceof Fluent) { + $properties[] = $property->toArray(); + } else { + $properties[] = $property; + } + } + $this->parameters['properties'] = $properties; + } } } + // public function buildReIndex(Connection $connection): void + // { + // return $connection->reIndex($this->index, $this->newIndex); + // } //---------------------------------------------------------------------- // Internal Laravel init migration catchers // *Case for when ES is the only datasource //---------------------------------------------------------------------- - public function increments($column) + public function buildIndexModify(Connection $connection): void { - return $this->addField('keyword', $column); + $connection->setIndex($this->index); + if ($this->parameters) { + $this->_formatParams(); + $connection->indexModify($this->parameters); + } } - public function string($column) + public function increments($column): Definitions\FieldDefinition { return $this->addField('keyword', $column); } - - //---------------------------------------------------------------------- // Helpers //---------------------------------------------------------------------- - private function _formatParams() + + public function string($column): Definitions\FieldDefinition { - if ($this->parameters) { - if (!empty($this->parameters['properties'])) { - $properties = []; - foreach ($this->parameters['properties'] as $property) { - if ($property instanceof Fluent) { - $properties[] = $property->toArray(); - } else { - $properties[] = $property; - } - } - $this->parameters['properties'] = $properties; - } - } + return $this->addField('keyword', $column); } } diff --git a/src/Schema/Schema.php b/src/Schema/Schema.php index a684d0c..fcfc798 100644 --- a/src/Schema/Schema.php +++ b/src/Schema/Schema.php @@ -1,8 +1,9 @@ connection($name)->getSchemaBuilder(); } - public static function on($name) - { - return static::connection($name); - } - /** * Get a schema builder instance for the default connection. - * - * @return Builder */ - protected static function getFacadeAccessor() + protected static function getFacadeAccessor(): Builder { return static::$app['db']->connection('elasticsearch')->getSchemaBuilder(); } -// public static function __callStatic($method, $args) { $instance = static::getFacadeAccessor(); return $instance->$method(...$args); } - } diff --git a/testbench.yaml b/testbench.yaml new file mode 100644 index 0000000..19ff6f9 --- /dev/null +++ b/testbench.yaml @@ -0,0 +1,21 @@ +providers: + # - Workbench\App\Providers\WorkbenchServiceProvider + +migrations: + - workbench/database/migrations + +seeders: + - Workbench\Database\Seeders\DatabaseSeeder + +workbench: + start: '/' + install: true + discovers: + web: true + api: false + commands: false + components: false + views: false + build: [] + assets: [] + sync: [] diff --git a/tests/ArchitectureTest.php b/tests/ArchitectureTest.php new file mode 100644 index 0000000..1e378a1 --- /dev/null +++ b/tests/ArchitectureTest.php @@ -0,0 +1,8 @@ +expect(['dd', 'dump', 'ray', 'var_dump']) + ->not->toBeUsed(); diff --git a/tests/Eloquent/AggregationTest.php b/tests/Eloquent/AggregationTest.php new file mode 100644 index 0000000..d6e427c --- /dev/null +++ b/tests/Eloquent/AggregationTest.php @@ -0,0 +1,43 @@ +state(['status' => 1])->count(3)->create(); + Product::factory()->state(['status' => 2])->count(2)->create(); + $statuses = Product::select('status')->distinct()->get(); + expect($statuses)->toHaveCount(2); +}); + +test('group products by status', function () { + Product::factory()->state(['status' => 1])->count(3)->create(); + Product::factory()->state(['status' => 2])->count(2)->create(); + $grouped = Product::groupBy('status')->get(); + expect($grouped)->toBeCollection() + ->and($grouped)->toHaveCount(2); +}); + +test('retrieve distinct products with multiple fields', function () { + Product::factory()->state(['status' => 1, 'color' => 'blue'])->create(); + Product::factory()->state(['status' => 1, 'color' => 'red'])->create(); + $products = Product::distinct()->get(['status', 'color']); + expect($products)->toHaveCount(2); +}); + +test('order products by the count of distinct status', function () { + Product::factory()->state(['status' => 1])->count(5)->create(); + Product::factory()->state(['status' => 2])->count(2)->create(); + $products = Product::select('status')->distinct()->orderBy('status_count')->get(); + expect($products->first()->status)->toEqual(1); // Assuming it orders with the least first +}); + +test('get distinct statuses with their counts', function () { + Product::factory()->state(['status' => 1])->count(5)->create(); + Product::factory()->state(['status' => 2])->count(3)->create(); + $statuses = Product::select('status')->distinct(true)->orderByDesc('status_count')->get(); + + expect($statuses->first()->status_count)->toEqual(5); +}); diff --git a/tests/Eloquent/ChunkingTest.php b/tests/Eloquent/ChunkingTest.php new file mode 100644 index 0000000..4e23bca --- /dev/null +++ b/tests/Eloquent/ChunkingTest.php @@ -0,0 +1,54 @@ +state(['price' => 50])->make(); + Product::insert($products->toArray()); + + Product::chunk(10, function ($products) { + foreach ($products as $product) { + $product->price *= 1.1; + $product->saveWithoutRefresh(); + } + }); + sleep(3); + + $updatedProduct = Product::first(); + expect($updatedProduct->price)->toBeGreaterThan(50); +}); + +test('process large dataset using basic chunking with extended keepAlive', function () { + $products = Product::factory(100)->state(['price' => 50])->make(); + Product::insert($products->toArray()); + + Product::chunk(1000, function ($products) { + foreach ($products as $product) { + $product->price *= 1.1; + $product->saveWithoutRefresh(); + } + }, '20m'); // Using an extended keepAlive period + sleep(3); + + $updatedProduct = Product::first(); + expect($updatedProduct->price)->toBeGreaterThan(50); +}); + +test('chunk by ID on a specific column with custom keepAlive', function () { + $products = Product::factory(100)->state(['price' => 50])->make(); + Product::insert($products->toArray()); + + // Assuming 'product_id' is a unique identifier in the dataset + Product::chunkById(1000, function ($products) { + foreach ($products as $product) { + $product->price *= 1.1; + $product->saveWithoutRefresh(); + } + }, 'product_id.keyword', null, '5m'); + sleep(3); + + $updatedProduct = Product::first(); + expect($updatedProduct->price)->toBeGreaterThan(50); +}); diff --git a/tests/Eloquent/DeletionTest.php b/tests/Eloquent/DeletionTest.php new file mode 100644 index 0000000..a5dd6e8 --- /dev/null +++ b/tests/Eloquent/DeletionTest.php @@ -0,0 +1,88 @@ +create(); + $retrieved = Product::find($product->_id); + $retrieved->delete(); + $deleted = Product::find($product->_id); + expect($deleted)->toBeNull(); +}); + +test('mass deletion of models where color is null', function () { + Product::factory(5)->state(['color' => null])->create(); + Product::factory(3)->state(['color' => 'blue'])->create(); + Product::whereNull('color')->delete(); + $products = Product::all(); + expect($products)->toHaveCount(3); +}); + +test('truncate all documents from an index', function () { + Product::factory(10)->create(); + Product::truncate(); + sleep(3); + + $products = Product::all(); + expect($products)->toBeEmpty(); +}); + +test('destroy a product by _id', function () { + $product = Product::factory()->create(); + Product::destroy($product->_id); + $deleted = Product::find($product->_id); + expect($deleted)->toBeNull(); +}); + +test('destroy multiple products by _ids', function () { + $product1 = Product::factory()->create(); + $product2 = Product::factory()->create(); + Product::destroy([$product1->_id, $product2->_id]); + $deleted1 = Product::find($product1->_id); + $deleted2 = Product::find($product2->_id); + expect($deleted1)->toBeNull() + ->and($deleted2)->toBeNull(); +}); + +test('soft deletes a product and restores it', function () { + $product = Product::factory()->create(); + $product->delete(); + $trashed = Product::withTrashed()->find($product->_id); + expect($trashed->trashed())->toBeTrue(); + $trashed->restore(); + $restored = Product::find($product->_id); + expect($restored->trashed())->toBeFalse(); +}); + +test('ensure deletion of models with a specific status', function () { + Product::factory(3)->state(['status' => 5])->create(); + Product::factory(2)->state(['status' => 1])->create(); + Product::where('status', 5)->delete(); + $remainingProducts = Product::all(); + expect($remainingProducts)->toHaveCount(2) + ->and($remainingProducts->pluck('status')->contains(5))->toBeFalse(); +}); + +test('delete multiple models by complex query', function () { + Product::factory()->state(['is_active' => true, 'color' => 'blue'])->create(); + Product::factory()->state(['is_active' => false, 'color' => 'blue'])->create(); + Product::where('is_active', true)->where('color', 'blue')->delete(); + $activeBlue = Product::where('is_active', true)->where('color', 'blue')->first(); + expect($activeBlue)->toBeNull(); + $inactiveBlue = Product::where('is_active', false)->where('color', 'blue')->first(); + expect($inactiveBlue)->not()->toBeNull(); +}); + +test('test soft deletion query visibility', function () { + $product = Product::factory()->create(); + $product->delete(); + $visibleProduct = Product::find($product->_id); + expect($visibleProduct)->toBeNull(); + $trashedProduct = Product::withTrashed()->find($product->_id); + expect($trashedProduct)->not()->toBeNull() + ->and($trashedProduct->trashed())->toBeTrue(); +}); diff --git a/tests/Eloquent/DynamicIndicesTest.php b/tests/Eloquent/DynamicIndicesTest.php new file mode 100644 index 0000000..4224bd8 --- /dev/null +++ b/tests/Eloquent/DynamicIndicesTest.php @@ -0,0 +1,82 @@ +each(function (string $index) { + Schema::deleteIfExists('page_hits_'.$index); + }); +}); + +test('retrieve page hits across dynamic indices', function () { + PageHit::factory()->count(15)->state(['page_id' => 1])->make()->each(function ($pageHit) { + $pageHit->setIndex('page_hits_'.$pageHit->date); + $pageHit->saveWithoutRefresh(); + }); + sleep(2); + + $pageHitsSearch = PageHit::where('page_id', 1)->get(); + expect($pageHitsSearch)->toHaveCount(15); + +}); + +test('create a page hit record with dynamic index', function () { + $pageHit = new PageHit; + $pageHit->ip = '192.168.1.1'; + $pageHit->page_id = 4; + $pageHit->date = '2021-01-01'; + $pageHit->setIndex('page_hits_'.$pageHit->date); + $pageHit->save(); + + $retrievedHits = PageHit::where('page_id', 4)->get(); + expect($retrievedHits)->toHaveCount(1) + ->and($retrievedHits->first()->ip)->toEqual('192.168.1.1'); +}); + +test('retrieve current record index', function () { + $pageHit = new PageHit; + $pageHit->ip = '192.168.1.100'; + $pageHit->page_id = 5; + $pageHit->date = '2021-01-01'; + $pageHit->setIndex('page_hits_'.$pageHit->date); + $pageHit->save(); + + $indexName = $pageHit->getRecordIndex(); + expect($indexName)->toEqual('page_hits_*'); +}); + +test('search within a specific dynamic index', function () { + $pageHit = new PageHit; + $pageHit->ip = '192.168.1.100'; + $pageHit->page_id = 3; + $pageHit->date = '2021-01-02'; + $pageHit->setIndex('page_hits_'.$pageHit->date); + $pageHit->save(); + + $pageHit = new PageHit; + $pageHit->ip = '192.168.1.100'; + $pageHit->page_id = 3; + $pageHit->date = '2021-01-01'; + $pageHit->setIndex('page_hits_'.$pageHit->date); + $pageHit->save(); + + $model = new PageHit; + $model->setIndex('page_hits_2021-01-01'); + + $pageHits = $model->where('page_id', 3)->get(); + expect($pageHits)->toHaveCount(1); +}); diff --git a/tests/Eloquent/ElasticsearchSpecificTest.php b/tests/Eloquent/ElasticsearchSpecificTest.php new file mode 100644 index 0000000..98222ff --- /dev/null +++ b/tests/Eloquent/ElasticsearchSpecificTest.php @@ -0,0 +1,115 @@ +count(3)->state(['status' => 7, 'manufacturer' => ['location' => ['lat' => 5, 'lon' => 5]]])->create(); + Product::factory()->count(2)->state(['status' => 7, 'manufacturer' => ['location' => ['lat' => 15, 'lon' => -15]]])->create(); + $topLeft = [-10, 10]; + $bottomRight = [10, -10]; + $products = Product::where('status', 7)->filterGeoBox('manufacturer.location', $topLeft, $bottomRight)->get(); + expect($products)->toHaveCount(3); // Expecting only the first three within the box +}); + +test('filter products close to a specific point', function () { + + $first = Product::factory()->state(['status' => 7])->create(); + Product::factory()->count(5)->state(['status' => 7])->create(); + $first->manufacturer = ['location' => ['lat' => 0, 'lon' => 0]]; + $first->save(); + $point = [0, 0]; + $distance = '1m'; + $products = Product::where('status', 7)->filterGeoPoint('manufacturer.location', $distance, $point)->get(); + expect($products)->toHaveCount(1); + +}); + +test('search for products by exact name', function () { + Product::factory()->state(['name' => 'John Smith'])->create(); + + $products = Product::whereExact('name', 'John Smith')->get(); + expect($products->first()->name)->toEqual('John Smith'); +}); + +test('search for products by phrase in description', function () { + Product::factory()->state(['description' => 'loves espressos'])->create(); + + $products = Product::wherePhrase('description', 'loves espressos')->get(); + expect($products->first()->description)->toContain('loves espressos'); +}); + +test('search for products where description starts with a prefix', function () { + Product::factory()->state(['description' => 'loves espresso beans'])->create(); + + $products = Product::wherePhrasePrefix('description', 'loves es')->get(); + expect($products->first()->description)->toContain('loves espresso beans'); +}); + +test('query products by timestamp', function () { + $timestamp = 1713911889521; + Product::factory()->state(['last_order_ts' => $timestamp])->create(); + + $products = Product::whereTimestamp('last_order_ts', '<=', $timestamp)->get(); + expect($products)->toHaveCount(1); +}); + +test('search for products using regex on color', function () { + Product::factory()->state(['color' => 'blue'])->create(); + Product::factory()->state(['color' => 'black'])->create(); + + $regexProducts = Product::whereRegex('color', 'bl(ue)?(ack)?')->get(); + expect($regexProducts)->toHaveCount(2); +}); + +test('execute raw DSL query on products', function () { + Product::factory()->state(['color' => 'silver'])->create(); + + $bodyParams = [ + 'query' => [ + 'match' => [ + 'color' => 'silver', + ], + ], + ]; + $products = Product::rawSearch($bodyParams); + expect($products)->toHaveCount(1); +}); + +test('perform raw aggregation query', function () { + Product::factory()->state(['price' => 50])->create(); + Product::factory()->state(['price' => 300])->create(); + Product::factory()->state(['price' => 700])->create(); + Product::factory()->state(['price' => 1200])->create(); + + $bodyParams = [ + 'aggs' => [ + 'price_ranges' => [ + 'range' => [ + 'field' => 'price', + 'ranges' => [ + ['to' => 100], + ['from' => 100, 'to' => 500], + ['from' => 500, 'to' => 1000], + ['from' => 1000], + ], + + ], + ], + ], + ]; + $priceBuckets = Product::rawAggregation($bodyParams); + expect($priceBuckets['price_ranges'][0]['doc_count'])->toBe(1) + ->and($priceBuckets['price_ranges'][1]['doc_count'])->toBe(1) + ->and($priceBuckets['price_ranges'][2]['doc_count'])->toBe(1) + ->and($priceBuckets['price_ranges'][3]['doc_count'])->toBe(1); +}); + +test('convert query to DSL', function () { + $dslQuery = Product::where('price', '>', 100)->toDSL(); + + expect($dslQuery)->toBeArray() + ->and($dslQuery['body']['query']['bool']['must'][0]['range'])->toBeArray(); +}); diff --git a/tests/Eloquent/FullTextSearchTest.php b/tests/Eloquent/FullTextSearchTest.php new file mode 100644 index 0000000..76121ca --- /dev/null +++ b/tests/Eloquent/FullTextSearchTest.php @@ -0,0 +1,67 @@ +create([ + 'name' => 'Espresso Machine', + 'description' => 'Automatic espresso machine with fine control over brew temperature.', + 'manufacturer' => [ + 'name' => 'Coffee Inc.', + 'location' => ['lat' => 40.7128, 'lon' => -74.0060], + ], + ]); +}); + +test('term search across all fields', function () { + $results = Product::term('Espresso')->search(); + expect($results)->toHaveCount(1); +}); + +test('phrase search across all fields', function () { + $results = Product::phrase('Espresso Machine')->search(); + expect($results)->toHaveCount(1); +}); + +test('combining multiple terms with logical operators', function () { + $results = Product::term('Espresso')->orTerm('Machine')->andTerm('Automatic')->search(); + expect($results)->toHaveCount(1); +}); + +test('boosting terms in search', function () { + $results = Product::term('Espresso', 2)->orTerm('Brew')->search(); + expect($results)->toHaveCount(1); +}); + +test('limiting search to specific fields', function () { + $results = Product::term('Espresso')->fields(['name', 'description'])->search(); + expect($results)->toHaveCount(1); +}); + +test('minimum should match in search', function () { + $results = Product::term('Espresso')->orTerm('Brew')->orTerm('Machine')->minShouldMatch(2)->search(); + expect($results)->toHaveCount(1); +}); + +test('minimum score for search results', function () { + $results = Product::term('Espresso')->minScore(0.1)->search(); + expect($results)->toHaveCount(1); +}); + +test('fuzzy searches for similar terms', function () { + $results = Product::fuzzyTerm('espreso')->orFuzzyTerm('mchine')->search(); + expect($results)->toHaveCount(1); +}); + +test('regex search on product fields', function () { + $results = Product::regEx('espresso*')->search(); + expect($results)->toHaveCount(1); +}); + +test('highlighting search results', function () { + $results = Product::term('Espresso')->highlight(['description'], '', '')->search(); + $highlighted = $results->first()->searchHighlights->description ?? []; + expect($highlighted)->toContain(''); +}); diff --git a/tests/Eloquent/InsertTest.php b/tests/Eloquent/InsertTest.php new file mode 100644 index 0000000..88fd664 --- /dev/null +++ b/tests/Eloquent/InsertTest.php @@ -0,0 +1,23 @@ +make(); + $result = Product::insert($products->toArray(), true); + + expect($result)->toBeInstanceOf(ElasticCollection::class) + ->and($result->getQueryMetaAsArray())->toBeArray(); +}); + +test('bulk insert without refresh', function () { + $products = Product::factory(1000)->make(); + $result = Product::insertWithoutRefresh($products->toArray()); + expect($result)->toBeInstanceOf(ElasticCollection::class) + ->and($result->getQueryMetaAsArray())->toBeArray(); + sleep(2); + expect(Product::count())->toBe(1000); +}); diff --git a/tests/Eloquent/NestedTest.php b/tests/Eloquent/NestedTest.php new file mode 100644 index 0000000..1134408 --- /dev/null +++ b/tests/Eloquent/NestedTest.php @@ -0,0 +1,85 @@ +create([ + 'comments' => [ + ['name' => 'John Doe', 'country' => 'Peru', 'likes' => 5], + ['name' => 'Jane Smith', 'country' => 'USA', 'likes' => 3] + ] + ]); + + $posts = BlogPost::whereNestedObject('comments', function ($query) { + $query->where('country', 'Peru')->where('likes', 5); + })->get(); + + expect($posts)->toHaveCount(1) + ->and($posts->first()->comments[0]['country'])->toEqual('Peru') + ->and($posts->first()->comments[0]['likes'])->toEqual(5); + }); + + test('exclude blog posts with comments from a specific country', function () { + BlogPost::factory()->create([ + 'comments' => [ + ['name' => 'John Doe', 'country' => 'Peru', 'likes' => 5] + ] + ]); + + $posts = BlogPost::whereNotNestedObject('comments', function ($query) { + $query->where('country', 'Peru'); + })->get(); + + expect($posts->isNotEmpty())->toBeTrue(); + }); + + test('order blog posts by comments likes descending', function () { + BlogPost::factory()->create([ + 'status' => 1, + 'comments' => [ + ['name' => 'John Doe', 'country' => 'Peru', 'likes' => 5], + ['name' => 'Jane Smith', 'country' => 'USA', 'likes' => 8] + ] + ]); + + // FIXME: @pdphilip I can't get this to sort for the life of me not sure what I am doing wrong. + $posts = BlogPost::where('status', 1)->orderByNested('comments.likes', 'desc', 'sum')->get(); + expect($posts->first()->comments[0]['likes'])->toEqual(8); + })->todo(); + + test('filter blog posts by comments from Switzerland ordered by likes', function () { + BlogPost::factory()->create([ + 'status' => 5, + 'comments' => [ + ['name' => 'April Von', 'country' => 'Switzerland', 'likes' => 10], + ['name' => 'Mabelle Schinner', 'country' => 'Switzerland', 'likes' => 7] + ] + ]); + + $post = BlogPost::where('status', 5)->queryNested('comments', function ($query) { + $query->where('country', 'Switzerland')->orderBy('likes'); + })->first(); + + expect($post->comments[0]['name'])->toEqual('Mabelle Schinner') + ->and($post->comments[0]['likes'])->toEqual(7) + ->and($post->comments[1]['likes'])->toEqual(10); + }); + + test('filter comments with likes greater than or equal to 5, limit 2', function () { + BlogPost::factory()->create([ + 'status' => 5, + 'comments' => [ + ['name' => 'Damaris Ondricka', 'country' => 'Peru', 'likes' => 5], + ['name' => 'April Von', 'country' => 'Switzerland', 'likes' => 10], + ['name' => 'Third Comment', 'country' => 'USA', 'likes' => 2] + ] + ]); + + $post = BlogPost::where('status', 5)->queryNested('comments', function ($query) { + $query->where('likes', '>=', 5)->limit(2); + })->first(); + + expect($post->comments)->toHaveCount(2) + ->and($post->comments[0]['likes'])->toBeGreaterThanOrEqual(5); + }); diff --git a/tests/Eloquent/OrderAndPaginationTest.php b/tests/Eloquent/OrderAndPaginationTest.php new file mode 100644 index 0000000..38c488f --- /dev/null +++ b/tests/Eloquent/OrderAndPaginationTest.php @@ -0,0 +1,79 @@ +pluck($key)->toArray(); + for ($i = 0; $i < count($values) - 1; $i++) { + if ($descending) { + if ($values[$i] < $values[$i + 1]) { + return false; + } + } else { + if ($values[$i] > $values[$i + 1]) { + return false; + } + } + } + + return true; +} + +test('products are ordered by status', function () { + $products = Product::factory(50)->make(); + Product::insert($products->toArray()); + + $products = Product::orderBy('status')->get(); + expect(isSorted($products, 'status'))->toBeTrue(); +}); + +// not sure why this is failing +test('products are ordered by created_at descending', function () { + + $products = Product::factory(10)->make(); + Product::insert($products->toArray()); + + $products = Product::orderBy('created_at', 'desc')->get(); + expect(isSorted($products, 'created_at', true))->toBeTrue(); +})->todo(); + +test('products are ordered by name using keyword subfield', function () { + $products = Product::factory(50)->make(); + Product::insert($products->toArray()); + + $products = Product::orderBy('name.keyword')->get(); + expect(isSorted($products, 'name'))->toBeTrue(); +}); + +test('products are paginated', function () { + $products = Product::factory(50)->make(); + Product::insert($products->toArray()); + + $products = Product::where('is_active', true)->paginate(10); + expect($products)->toHaveCount(10); +}); + +test('sort products by color with missing values treated as first', function () { + Product::factory()->state(['color' => null])->create(); + Product::factory()->state(['color' => 'blue'])->create(); + $products = Product::orderBy('color', 'desc', null, '_first')->get(); + expect($products->first()->color)->toBeNull(); +}); + +test('sort products by geographic location closest to London', function () { + Product::factory()->state(['manufacturer' => ['location' => ['lat' => 51.50853, 'lon' => -0.12574]]])->create(); // London + $products = Product::orderByGeo('manufacturer.location', [-0.12574, 51.50853])->get(); + expect(! empty($products))->toBeTrue(); +})->todo(); + +test('sort products by geographic location farthest from Paris using multiple points and plane type', function () { + Product::factory()->state(['manufacturer' => ['location' => ['lat' => 48.85341, 'lon' => 2.3488]]])->create(); // Paris + $products = Product::orderByGeo('manufacturer.location', [[2.3488, 48.85341], [-0.12574, 51.50853]], 'desc', 'km', 'avg', 'plane')->get(); + expect(! empty($products))->toBeTrue(); +})->todo(); diff --git a/tests/Eloquent/QueryingTest.php b/tests/Eloquent/QueryingTest.php new file mode 100644 index 0000000..c2645fc --- /dev/null +++ b/tests/Eloquent/QueryingTest.php @@ -0,0 +1,126 @@ +count(5)->create(); + $products = Product::all(); + expect($products)->toBeCollection(); +}); + +test('find a product by primary key', function () { + $product = Product::factory()->create(); + $found = Product::find($product->_id); + expect($found)->toBeInstanceOf(Product::class); +}); + +test('fail to find a product and get null', function () { + $product = Product::find('nonexistent'); + expect($product)->toBeNull(); +}); + +test('fail to find a product and get exception', function () { + Product::findOrFail('nonexistent'); +})->throws(Exception::class); + +test('retrieve first product by status', function () { + $product = Product::factory()->state(['status' => 1])->create(); + $found = Product::where('status', 1)->first(); + expect($found)->toBeInstanceOf(Product::class) + ->and($found->status)->toEqual(1); +}); + +test('retrieve and count products using where condition', function () { + Product::factory(5)->state(['status' => 1])->create(); + $products = Product::where('status', 1)->get(); + expect($products)->toHaveCount(5); +}); + +test('exclude products with specific status using whereNot', function () { + Product::factory()->state(['status' => 1])->create(); + Product::factory()->state(['status' => 2])->create(); + $products = Product::whereNot('status', 1)->get(); + expect($products->first()->status)->not()->toEqual(1); +}); + +test('chain multiple conditions', function () { + Product::factory()->state(['is_active' => true, 'in_stock' => 50])->create(); + $products = Product::where('is_active', true)->where('in_stock', '<=', 50)->get(); + expect($products)->toHaveCount(1); +}); + +test('use OR conditions', function () { + Product::factory()->state(['is_active' => false, 'in_stock' => 150])->create(); + $products = Product::where('is_active', false)->orWhere('in_stock', '>=', 100)->get(); + expect($products)->toHaveCount(1); +}); + +test('check inclusion with whereIn', function () { + Product::factory(3)->state(['status' => 1])->create(); + Product::factory(2)->state(['status' => 5])->create(); + $products = Product::whereIn('status', [1, 5])->get(); + expect($products)->toHaveCount(5); +}); + +test('check exclusion with whereNotIn', function () { + Product::factory()->state(['color' => 'red'])->create(); + Product::factory()->state(['color' => 'green'])->create(); + $products = Product::whereNotIn('color', ['red', 'green'])->get(); + expect($products)->toBeEmpty(); +}); + +test('query non-existent color field', function () { + $products = Product::whereNull('color')->get(); + expect($products)->toBeCollection(); +}); + +test('query products where color field exists', function () { + Product::factory()->state(['color' => 'blue'])->create(); + $products = Product::whereNotNull('color')->get(); + expect($products->first()->color)->toEqual('blue'); +}); + +test('filter products based on a date range', function () { + Product::factory()->state(['created_at' => now()->subDays(5)])->create(); + $products = Product::whereBetween('created_at', [now()->subWeek(), now()])->get(); + expect($products)->toHaveCount(1); +}); + +test('retrieve products with no stock', function () { + Product::factory()->state(['in_stock' => 0])->create(); + $products = Product::where('in_stock', 0)->get(); + expect($products)->toHaveCount(1); +}); + +test('calculate average orders correctly', function () { + Product::factory()->state(['order_values' => [10, 20, 30]])->create(); + $product = Product::first(); + expect($product->getAvgOrdersAttribute())->toEqual(20); +}); + +test('search for products with partial text match', function () { + Product::factory()->state(['name' => 'Black Coffee'])->create(); + $products = Product::where('name', 'like', 'bl')->orderBy('name.keyword')->get(); + expect($products)->toHaveCount(1) + ->and($products->first()->name)->toEqual('Black Coffee'); +}); + +test('complex query chaining', function () { + Product::factory()->state(['type' => 'coffee', 'is_approved' => true])->create(); + Product::factory()->state(['type' => 'tea', 'is_approved' => false])->create(); + $products = Product::where('type', 'coffee') + ->where('is_approved', true) + ->orWhere('type', 'tea') + ->where('is_approved', false) + ->get(); + expect($products)->toHaveCount(2); +}); + +test('date query on product creation', function () { + Product::factory()->state(['created_at' => now()->subDay()])->create(); + $products = Product::whereDate('created_at', now()->subDay()->toDateString())->get(); + expect($products)->toHaveCount(1); +}); diff --git a/tests/Eloquent/RelationshipTest.php b/tests/Eloquent/RelationshipTest.php new file mode 100644 index 0000000..fb221bf --- /dev/null +++ b/tests/Eloquent/RelationshipTest.php @@ -0,0 +1,45 @@ +create(); + $logs = CompanyLog::factory(3)->create(['company_id' => $company->_id]); + $fetchedLogs = $company->companyLogs; + + expect($fetchedLogs)->toHaveCount(3) + ->and($fetchedLogs->first())->toBeInstanceOf(CompanyLog::class); + }); + + test('company has one company profile', function () { + $company = Company::factory()->create(); + $profile = CompanyProfile::factory()->create(['company_id' => $company->_id]); + $fetchedProfile = $company->companyProfile; + + expect($fetchedProfile)->toBeInstanceOf(CompanyProfile::class) + ->and($fetchedProfile->_id)->toEqual($profile->_id); + }); + + test('company has one avatar using morphOne', function () { + $company = Company::factory()->create(); + $avatar = Avatar::factory()->create(['imageable_id' => $company->_id, 'imageable_type' => Company::class]); + $fetchedAvatar = $company->avatar; + + expect($fetchedAvatar)->toBeInstanceOf(Avatar::class) + ->and($fetchedAvatar->_id)->toEqual($avatar->_id); + }); + + test('company has many photos using morphMany', function () { + $company = Company::factory()->create(); + $photos = Photo::factory(5)->create(['photoable_id' => $company->_id, 'photoable_type' => Company::class]); + $fetchedPhotos = $company->photos; + + expect($fetchedPhotos)->toHaveCount(5) + ->and($fetchedPhotos->first())->toBeInstanceOf(Photo::class); + }); diff --git a/tests/Eloquent/SaveTest.php b/tests/Eloquent/SaveTest.php new file mode 100644 index 0000000..ac9492d --- /dev/null +++ b/tests/Eloquent/SaveTest.php @@ -0,0 +1,121 @@ +name = 'New Product'; + $product->price = 199.99; + $product->status = 1; + $product->save(); + + $found = Product::first(); + expect($found)->toBeInstanceOf(Product::class) + ->and($found->name)->toEqual('New Product') + ->and($found->price)->toEqual(199.99); +}); + +test('create a new product using mass assignment', function () { + Product::create([ + 'name' => 'Mass Assigned Product', + 'price' => 299.99, + 'status' => 1, + ]); + + $found = Product::first(); + expect($found)->toBeInstanceOf(Product::class) + ->and($found->name)->toEqual('Mass Assigned Product') + ->and($found->price)->toEqual(299.99); +}); + +test('update a product attribute and save', function () { + $product = Product::factory()->create(['status' => 1]); + $product->status = 2; + $product->save(); + + $updated = Product::find($product->_id); + expect($updated->status)->toEqual(2); +}); + +test('mass update products matching a condition', function () { + Product::factory(5)->state(['status' => 1])->create(); + $updates = Product::where('status', 1)->update(['status' => 4]); + + $updatedCount = Product::where('status', 4)->count(); + expect($updates)->toEqual(5) + ->and($updatedCount)->toEqual(5); +}); + +test('save product without waiting for index refresh', function () { + $product = new Product(); + $product->name = 'Fast Save Product'; + $product->status = 1; + $product->saveWithoutRefresh(); + + // Note: Can't directly test the non-wait state, this would typically be tested with integration tests + expect($product->wasRecentlyCreated)->toBeTrue(); +}); + +test('first or create product based on unique attributes', function () { + Product::factory()->create(['name' => 'Unique Product', 'status' => 1]); + + $product = Product::firstOrCreate( + ['name' => 'Unique Product', 'status' => 1], + ['price' => 99.99] + ); + + expect($product->wasRecentlyCreated)->toBeFalse() + ->and($product->name)->toEqual('Unique Product'); +}); + +test('first or create without refresh', function () { + $product = Product::firstOrCreateWithoutRefresh( + ['name' => 'Non-Refresh Product', 'status' => 1], + ['price' => 109.99] + ); + + // Note: Similar to the fast save test, the non-refresh state is an integration aspect + expect($product->wasRecentlyCreated)->toBeTrue() + ->and($product->name)->toEqual('Non-Refresh Product'); +}); + +test('validate saving a model with a unique constraint on name', function () { + Product::create(['name' => 'Unique Gadget', 'price' => 100]); + $duplicateProductAttempt = Product::firstOrCreate( + ['name' => 'Unique Gadget'], + ['price' => 200] + ); + + // Assert it didn't overwrite the existing product + expect($duplicateProductAttempt->price)->toEqual(100) + // Ensure no duplicate was created + ->and(Product::count())->toEqual(1); + +}); + +test('ensure save without refresh accurately models elastic behavior', function () { + $product = new Product(); + $product->name = 'Delayed Visibility Product'; + $product->price = 150; + $product->saveWithoutRefresh(); + + $foundImmediately = Product::where('name', 'Delayed Visibility Product')->first(); + expect($foundImmediately)->toBeNull(); // Not immediately available +}); + +test('query using firstOrCreate to simulate inventory addition', function () { + Product::factory()->create(['name' => 'Gadget', 'status' => 1]); + + $newOrExistingProduct = Product::firstOrCreate( + ['name' => 'Gadget'], + ['status' => 1, 'price' => 99.99] + ); + + expect($newOrExistingProduct->wasRecentlyCreated)->toBeFalse() + // Price won't be 99.99 if it already existed + ->and($newOrExistingProduct->price)->not()->toEqual(99.99); + +}); diff --git a/tests/Eloquent/SearchAfterPaginationTest.php b/tests/Eloquent/SearchAfterPaginationTest.php new file mode 100644 index 0000000..dd8af08 --- /dev/null +++ b/tests/Eloquent/SearchAfterPaginationTest.php @@ -0,0 +1,87 @@ +push([ + 'title' => fake()->name(), + 'slug' => fake()->uuid(), + 'content' => fake()->realTextBetween(5, 15), + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]); + } + + Post::insert($collectionToInsert->toArray()); + + $perPage = 100; + $totalFetched = 0; + $totalProducts = Post::count(); + + // Fetch the first page of posts + $paginator = Post::orderBy('slug.keyword')->cursorPaginate($perPage)->withQueryString(); + + do { + + // Count the number of posts fetched in the current page + $totalFetched += $paginator->count(); + + // Move to the next page if possible + if ($paginator->hasMorePages()) { + $cursor = $paginator->nextCursor(); + $paginator = Post::orderBy('slug.keyword')->cursorPaginate($perPage, ['*'], 'cursor', $cursor)->withQueryString(); + } + } while ($paginator->hasMorePages()); + + // Include the last page count if not empty + $totalFetched += $paginator->count(); + + // Check if all products were fetched + expect($totalFetched)->toEqual($totalProducts); + +}); + +it('can paginate a small amount of records', function () { + + Post::truncate(); + + //Generate a massive amount of data to paginate over. + $collectionToInsert = collect([]); + $numberOfEntries = 100; + for ($i = 1; $i <= $numberOfEntries; $i++) { + $collectionToInsert->push([ + 'title' => fake()->name(), + 'slug' => fake()->uuid(), + 'content' => fake()->realTextBetween(5, 15), + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]); + } + Post::insert($collectionToInsert->toArray()); + + // Fetch the first page of posts + $paginator = Post::orderBy('slug.keyword')->cursorPaginate(200)->withQueryString(); + + expect($paginator->hasMorePages())->toBeFalse() + ->and($paginator->count())->toBe(100); + +}); + +test('throws an exception when there is no ordering search_after', function () { + + // Fetch the first page of posts + StaticPage::cursorPaginate(100)->withQueryString(); + +})->throws(MissingOrderException::class); diff --git a/tests/ModelTest.php b/tests/ModelTest.php new file mode 100644 index 0000000..2190bda --- /dev/null +++ b/tests/ModelTest.php @@ -0,0 +1,444 @@ +assertInstanceOf(Connection::class, $product->getConnection()); + $this->assertFalse($product->exists); + $this->assertEquals('products', $product->getTable()); + $this->assertEquals('_id', $product->getKeyName()); + }); + + test('Insert', function () { + $product = new Product(); + $product['name'] = 'John Doe'; + $product['description'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus lacinia odio vitae vestibulum vestibulum.'; + $product['product_id'] = 'c1b5f730-7e5c-11e9-8f9e-2a86e4085a59'; + $product['in_stock'] = 25; + + $product->save(); + + $this->assertTrue($product->exists); + $this->assertEquals(1, Product::count()); + + $this->assertTrue(isset($product->id)); + $this->assertIsString($product->id); + $this->assertNotEquals('', (string) $product->id); + $this->assertNotEquals(0, strlen((string) $product->id)); + $this->assertInstanceOf(Carbon::class, $product->created_at); + + + $this->assertEquals('John Doe', $product->name); + $this->assertEquals(25, $product->in_stock); + }); + + test('Update', function () { + $product = new Product(); + $product['name'] = 'John Doe'; + $product['description'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus lacinia odio vitae vestibulum vestibulum.'; + $product['product_id'] = 'c1b5f730-7e5c-11e9-8f9e-2a86e4085a59'; + $product['in_stock'] = 25; + $product->save(); + + $this->assertTrue($product->exists); + $this->assertTrue(isset($product->id)); + + $check = Product::find($product->id); + $this->assertInstanceOf(Product::class, $check); + $check->in_stock = 36; + $check->save(); + + + $this->assertTrue($check->exists); + $this->assertInstanceOf(Carbon::class, $check->created_at); + $this->assertInstanceOf(Carbon::class, $check->updated_at); + $this->assertEquals(1, Product::count()); + + $this->assertEquals('John Doe', $check->name); + $this->assertEquals(36, $check->in_stock); + + $product->update(['in_stock' => 20]); + + $check = Product::find($product->id); + $this->assertEquals(20, $check->in_stock); + + $check->in_stock = 24; + $check->color = 'blue'; // new field + $check->save(); + + $check = Product::find($product->id); + $this->assertEquals(24, $check->in_stock); + $this->assertEquals('blue', $check->color); + }); + + test('Delete', function () { + $product = new Product(); + $product['name'] = 'John Doe'; + $product['description'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus lacinia odio vitae vestibulum vestibulum.'; + $product['product_id'] = 'c1b5f730-7e5c-11e9-8f9e-2a86e4085a59'; + $product['in_stock'] = 25; + $product->save(); + + $this->assertTrue($product->exists); + $this->assertEquals(1, Product::count()); + + $product->delete(); + + $this->assertEquals(0, Product::count()); + + }); + + test('All', function () { + $product = new Product(); + $product['name'] = 'John Doe'; + $product['description'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus lacinia odio vitae vestibulum vestibulum.'; + $product['in_stock'] = 24; + $product->save(); + + $product = new Product(); + $product['name'] = 'Jane Doe'; + $product['description'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus lacinia odio vitae vestibulum vestibulum.'; + $product['in_stock'] = 35; + $product->save(); + + $all = Product::all(); + + $this->assertCount(2, $all); + $this->assertContains('John Doe', $all->pluck('name')); + $this->assertContains('Jane Doe', $all->pluck('name')); + + }); + + test('Find', function () { + $product = new Product(); + $product['name'] = 'John Doe'; + $product['description'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus lacinia odio vitae vestibulum vestibulum.'; + $product['in_stock'] = 35; + $product->save(); + + $check = Product::find($product->id); + $this->assertInstanceOf(Product::class, $check); + $this->assertTrue($check->exists); + $this->assertEquals($product->id, $check->id); + + $this->assertEquals('John Doe', $check->name); + $this->assertEquals(35, $check->in_stock); + }); + + test('Get', function () { + //this also test bulk insert yay! + Product::insert([ + ['name' => 'John Doe'], + ['name' => 'Jane Doe'], + ]); + + $products = Product::get(); + $this->assertCount(2, $products); + $this->assertInstanceOf(EloquentCollection::class, $products); + $this->assertInstanceOf(Product::class, $products[0]); + }); + + test('First', function () { + //this also test bulk insert yay! + Product::insert([ + ['name' => 'John Doe'], + ['name' => 'Jane Doe'], + ]); + + $product = Product::first(); + $this->assertInstanceOf(Product::class, $product); + $this->assertEquals('John Doe', $product->name); + }); + + test('No Document', function () { + $items = Product::where('name', 'nothing')->get(); + $this->assertInstanceOf(EloquentCollection::class, $items); + $this->assertEquals(0, $items->count()); + + $item = Product::where('name', 'nothing')->first(); + $this->assertNull($item); + + $item = Product::find('51c33d8981fec6813e00000a'); + $this->assertNull($item); + + }); + + test('Find Or Fail', function () { + $this->expectException(ModelNotFoundException::class); + Product::findOrFail('51c33d8981fec6813e00000a'); + + }); + + test('Create', function () { + $product = Product::create(['name' => 'Jane Poe']); + $this->assertInstanceOf(Product::class, $product); + + $this->assertTrue($product->exists); + $this->assertEquals('Jane Poe', $product->name); + + $check = Product::where('name', 'Jane Poe')->first(); + $this->assertInstanceOf(Product::class, $check); + $this->assertEquals($product->id, $check->id); + }); + + test('Destroy', function () { + $product = new Product(); + $product['name'] = 'John Doe'; + $product['description'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus lacinia odio vitae vestibulum vestibulum.'; + $product['in_stock'] = 35; + $product->save(); + + Product::destroy((string) $product->id); + $this->assertEquals(0, Product::count()); + }); + + test('Touch', function () { + $product = new Product(); + $product['name'] = 'John Doe'; + $product['description'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus lacinia odio vitae vestibulum vestibulum.'; + $product['in_stock'] = 35; + $product->save(); + + $old = $product->updated_at; + sleep(1); + $product->touch(); + + $check = Product::find($product->id); + $this->assertInstanceOf(Product::class, $check); + + $this->assertNotEquals($old, $check->updated_at); + }); + + test('Soft Delete', function () { + Soft::create(['name' => 'John Doe']); + Soft::create(['name' => 'Jane Doe']); + + $this->assertEquals(2, Soft::count()); + + $object = Soft::where('name', 'John Doe')->first(); + $this->assertInstanceOf(Soft::class, $object); + $this->assertTrue($object->exists); + $this->assertFalse($object->trashed()); + $this->assertNull($object->deleted_at); + + $object->delete(); + $this->assertTrue($object->trashed()); + $this->assertNotNull($object->deleted_at); + + $object = Soft::where('name', 'John Doe')->first(); + $this->assertNull($object); + + $this->assertEquals(1, Soft::count()); + $this->assertEquals(2, Soft::withTrashed()->count()); + + $object = Soft::withTrashed()->where('name', 'John Doe')->first(); + $this->assertNotNull($object); + $this->assertInstanceOf(Carbon::class, $object->deleted_at); + $this->assertTrue($object->trashed()); + + $object->restore(); + $this->assertEquals(2, Soft::count()); + + })->todo(); + + test('Scope', function () { + Product::insert([ + ['name' => 'knife', 'color' => 'green'], + ['name' => 'spoon', 'color' => 'red'], + ]); + + $green = Product::green()->get(); + $this->assertEquals(1, $green->count()); + }); + + test('To Array', function () { + $product = Product::create(['name' => 'fork', 'color' => 'green']); + + $array = $product->toArray(); + $keys = array_keys($array); + sort($keys); + $this->assertEquals(['_id', 'color', 'created_at', 'name', 'updated_at'], $keys); + $this->assertIsString($array['created_at']); + $this->assertIsString($array['updated_at']); + $this->assertIsString($array['_id']); + }); + + test('Dot Notation', function () { + + $product = Product::create([ + 'name' => 'John Doe', + 'manufacturer' => [ + 'name' => 'Paris', + 'country' => 'France', + ], + ]); + + $this->assertEquals('Paris', $product->getAttribute('manufacturer.name')); + $this->assertEquals('Paris', $product['manufacturer.name']); + $this->assertEquals('Paris', $product->{'manufacturer.name'}); + + // Fill + //TODO: Fix this it's not working correctly +// $product->fill(['manufacturer.name' => 'Strasbourg']); +// +// $this->assertEquals('Strasbourg', $product['manufacturer.name']); + }); + + + test('Truncate Model', function () { + Product::create(['name' => 'John Doe']); + + Product::truncate(); + sleep(2); + + $this->assertEquals(0, Product::count()); + + }); + + test('Chunk By Id', function () { + + Product::create(['name' => 'fork', 'order_values' => [10, 20]]); + Product::create(['name' => 'spork', 'order_values' => [10, 35, 20, 30]]); + Product::create(['name' => 'spoon', 'order_values' => [20, 30]]); + + $names = []; + Product::chunkById(2, function (EloquentCollection $items) use (&$names) { + $names = array_merge($names, $items->pluck('name')->all()); + }); + + $this->assertEquals(['fork', 'spork', 'spoon'], $names); + + }); + + test('Guarded Model', function () { + $model = new Guarded(); + + // foobar is properly guarded + $model->fill(['foobar' => 'ignored', 'name' => 'John Doe']); + $this->assertFalse(isset($model->foobar)); + $this->assertSame('John Doe', $model->name); + + // foobar is guarded to any level + $model->fill(['foobar->level2' => 'v2']); + $this->assertNull($model->getAttribute('foobar->level2')); + + // multi level statement also guarded + $model->fill(['level1->level2' => 'v1']); + $this->assertNull($model->getAttribute('level1->level2')); + + // level1 is still writable + $dataValues = ['array', 'of', 'values']; + $model->fill(['level1' => $dataValues]); + $this->assertEquals($dataValues, $model->getAttribute('level1')); + + }); + + test('First Or Create', function () { + $name = 'Jane Poe'; + + $user = Product::where('name', $name)->first(); + $this->assertNull($user); + + $user = Product::firstOrCreate(['name' => $name]); + $this->assertInstanceOf(Product::class, $user); + $this->assertTrue($user->exists); + $this->assertEquals($name, $user->name); + + $check = Product::where('name', $name)->first(); + $this->assertInstanceOf(Product::class, $check); + $this->assertEquals($user->id, $check->id); + + }); + + test('Update Or Create', function () { + // Insert data to ensure we filter on the correct criteria, and not getting + // the first document randomly. + Product::insert([ + ['name' => 'fixture@example.com'], + ['name' => 'john.doe@example.com'], + ]); + + Carbon::setTestNow('2010-01-01'); + $createdAt = Carbon::now()->getTimestamp(); + $events = []; + registerModelEvents(Product::class, $events); + + // Create + $product = Product::updateOrCreate( + ['name' => 'bar'], + ['name' => 'bar', 'in_stock' => 30], + ); + + $this->assertInstanceOf(Product::class, $product); + $this->assertEquals('bar', $product->name); + $this->assertEquals(30, $product->in_stock); + $this->assertEquals($createdAt, $product->created_at->getTimestamp()); + $this->assertEquals($createdAt, $product->updated_at->getTimestamp()); + $this->assertEquals(['saving', 'creating', 'created', 'saved'], $events); + Carbon::setTestNow('2010-02-01'); + $updatedAt = Carbon::now()->getTimestamp(); + + // Update + $events = []; + $product = Product::updateOrCreate( + ['name' => 'bar'], + ['in_stock' => 25] + ); + + $this->assertInstanceOf(Product::class, $product); + $this->assertEquals('bar', $product->name); + $this->assertEquals(25, $product->in_stock); + $this->assertEquals($createdAt, $product->created_at->getTimestamp()); + $this->assertEquals($updatedAt, $product->updated_at->getTimestamp()); + $this->assertEquals(['saving', 'updating', 'updated', 'saved'], $events); + + // Stored data + $checkProduct = Product::where(['name' => 'bar'])->first(); + $this->assertInstanceOf(Product::class, $checkProduct); + $this->assertEquals('bar', $checkProduct->name); + $this->assertEquals(25, $checkProduct->in_stock); + $this->assertEquals($createdAt, $checkProduct->created_at->getTimestamp()); + $this->assertEquals($updatedAt, $checkProduct->updated_at->getTimestamp()); + }); + + + test('Create With Null Id', function (string $id) { + $product = Product::create([$id => null, 'email' => 'foo@bar']); + $this->assertNotNull($product->id); + $this->assertSame(1, Product::count()); + })->with([ + 'id', +// #TODO: this fails. +// '_id' + ]); + + function registerModelEvents(string $modelClass, array &$events): void + { + $modelClass::creating(function () use (&$events) { + $events[] = 'creating'; + }); + $modelClass::created(function () use (&$events) { + $events[] = 'created'; + }); + $modelClass::updating(function () use (&$events) { + $events[] = 'updating'; + }); + $modelClass::updated(function () use (&$events) { + $events[] = 'updated'; + }); + $modelClass::saving(function () use (&$events) { + $events[] = 'saving'; + }); + $modelClass::saved(function () use (&$events) { + $events[] = 'saved'; + }); + } diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..db0f604 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,8 @@ +in(__DIR__); diff --git a/tests/Schema/IndexBlueprintTest.php b/tests/Schema/IndexBlueprintTest.php new file mode 100644 index 0000000..330090d --- /dev/null +++ b/tests/Schema/IndexBlueprintTest.php @@ -0,0 +1,105 @@ +text('info'); + }); + $mappings = Schema::getMappings('test_index'); + expect($mappings['test_index']['mappings']['properties']['info']['type'])->toEqual('text'); +}); + +it('validates keyword field type', function () { + Schema::create('test_index', function (IndexBlueprint $index) { + $index->keyword('tag'); + }); + $mappings = Schema::getMappings('test_index'); + expect($mappings['test_index']['mappings']['properties']['tag']['type'])->toEqual('keyword'); +}); + +it('validates integer field type', function () { + Schema::create('test_index', function (IndexBlueprint $index) { + $index->integer('age'); + }); + $mappings = Schema::getMappings('test_index'); + expect($mappings['test_index']['mappings']['properties']['age']['type'])->toEqual('integer'); +}); + +it('validates float field type', function () { + Schema::create('test_index', function (IndexBlueprint $index) { + $index->float('price'); + }); + $mappings = Schema::getMappings('test_index'); + expect($mappings['test_index']['mappings']['properties']['price']['type'])->toEqual('float'); +}); + +it('validates date field type', function () { + Schema::create('test_index', function (IndexBlueprint $index) { + $index->date('birthdate'); + }); + $mappings = Schema::getMappings('test_index'); + expect($mappings['test_index']['mappings']['properties']['birthdate']['type'])->toEqual('date'); +}); + +it('validates boolean field type', function () { + Schema::create('test_index', function (IndexBlueprint $index) { + $index->boolean('is_active'); + }); + $mappings = Schema::getMappings('test_index'); + expect($mappings['test_index']['mappings']['properties']['is_active']['type'])->toEqual('boolean'); +}); + +it('validates geo_point field type', function () { + Schema::create('test_index', function (IndexBlueprint $index) { + $index->geo('location'); + }); + $mappings = Schema::getMappings('test_index'); + expect($mappings['test_index']['mappings']['properties']['location']['type'])->toEqual('geo_point'); +}); + +it('validates ip field type', function () { + Schema::create('test_index', function (IndexBlueprint $index) { + $index->ip('user_ip'); + }); + $mappings = Schema::getMappings('test_index'); + expect($mappings['test_index']['mappings']['properties']['user_ip']['type'])->toEqual('ip'); +}); + +it('validates nested field type', function () { + Schema::create('test_index', function (IndexBlueprint $index) { + $index->nested('user', [ + 'properties' => [ + 'name' => [ + 'type' => 'text', + ], + 'age' => [ + 'type' => 'integer', + ], + ], + ]); + }); + $mappings = Schema::getMappings('test_index'); + expect($mappings['test_index']['mappings']['properties']['user']['type'])->toEqual('nested') + ->and($mappings['test_index']['mappings']['properties']['user']['properties']['name']['type'])->toEqual('text') + ->and($mappings['test_index']['mappings']['properties']['user']['properties']['age']['type'])->toEqual('integer'); +}); + +it('validates object field type with dot notation', function () { + Schema::create('test_index', function (IndexBlueprint $index) { + $index->text('user.name'); + $index->integer('user.age'); + }); + $mappings = Schema::getMappings('test_index'); + expect($mappings['test_index']['mappings']['properties']['user']['properties']['name']['type'])->toEqual('text') + ->and($mappings['test_index']['mappings']['properties']['user']['properties']['age']['type'])->toEqual('integer'); +}); diff --git a/tests/Schema/MigrationsTest.php b/tests/Schema/MigrationsTest.php new file mode 100644 index 0000000..92e488c --- /dev/null +++ b/tests/Schema/MigrationsTest.php @@ -0,0 +1,95 @@ +text('name'); + $index->integer('age'); + $index->settings('number_of_shards', 1); + $index->settings('number_of_replicas', 1); + }); + $exists = Schema::hasIndex('test_index'); + expect($exists)->toBeTrue(); +}); + +it('deletes an index if it exists', function () { + Schema::createIfNotExists('test_index', function (IndexBlueprint $index) { + $index->text('description'); + }); + $deleted = Schema::deleteIfExists('test_index'); + expect($deleted)->toBeTrue(); + $exists = Schema::hasIndex('test_index'); + expect($exists)->toBeFalse(); +}); + +it('modifies an existing index by adding a new field', function () { + Schema::create('test_index', function (IndexBlueprint $index) { + $index->text('title'); + }); + Schema::modify('test_index', function (IndexBlueprint $index) { + $index->integer('year'); + }); + $hasField = Schema::hasField('test_index', 'year'); + expect($hasField)->toBeTrue(); +}); + +it('sets a custom analyzer on an index', function () { + Schema::create('test_index', function (IndexBlueprint $index) { + $index->text('content'); + }); + Schema::setAnalyser('test_index', function (AnalyzerBlueprint $settings) { + $settings->analyzer('custom_analyzer') + ->type('custom') + ->tokenizer('standard') + ->filter(['lowercase', 'asciifolding']); + }); + sleep(1); + $settings = Schema::getSettings('test_index'); + expect($settings['test_index']['settings']['index']['analysis']['analyzer']['custom_analyzer'])->toBeArray(); +}); + +it('retrieves mappings of an index', function () { + Schema::create('test_index', function (IndexBlueprint $index) { + $index->text('info'); + $index->keyword('tag'); + }); + $mappings = Schema::getMappings('test_index'); + expect($mappings['test_index']['mappings']['properties']['info']['type'])->toEqual('text') + ->and($mappings['test_index']['mappings']['properties']['tag']['type'])->toEqual('keyword'); +}); + +it('checks if an index has specific fields', function () { + Schema::create('test_index', function (IndexBlueprint $index) { + $index->text('name'); + $index->integer('age'); + }); + $hasFields = Schema::hasFields('test_index', ['name', 'age']); + expect($hasFields)->toBeTrue(); +}); + +it('fails to delete a non-existent index', function () { + $deleted = Schema::deleteIfExists('nonexistent_index'); + expect($deleted)->toBeFalse(); +}); + +it('overrides index prefix for operations', function () { + Schema::overridePrefix('test_prefix'); + Schema::create('test_index', function (IndexBlueprint $index) { + $index->text('message'); + }); + $exists = Schema::hasIndex('test_prefix_test_index'); + expect($exists)->toBeTrue(); + Schema::deleteIfExists('test_prefix_test_index'); +}); diff --git a/tests/Schema/ReindexTest.php b/tests/Schema/ReindexTest.php new file mode 100644 index 0000000..83a181b --- /dev/null +++ b/tests/Schema/ReindexTest.php @@ -0,0 +1,102 @@ + text('name'); + $index->float('price'); + $index->integer('status'); + $index->date('created_at'); + $index->date('updated_at'); + }); + + $productsHoldingSchema = Schema::create('holding_products', function (IndexBlueprint $index) { + $index->text('name'); + $index->float('price'); + $index->integer('status'); + $index->geo('manufacturer.location'); + $index->date('created_at'); + $index->date('updated_at'); + }); + + expect(! empty($productsSchema['products']['mappings']))->toBeTrue() + ->and(! empty($productsSchema['products']['settings']))->toBeTrue() + ->and(! empty($productsHoldingSchema['holding_products']['mappings']))->toBeTrue() + ->and(! empty($productsHoldingSchema['holding_products']['mappings']['properties']['manufacturer']['properties']['location']['type'] == 'geo_point'))->toBeTrue() + ->and(! empty($productsHoldingSchema['holding_products']['settings']))->toBeTrue(); + + $pf = Product::factory()->count(100)->make(); + $pf->each(function ($product) { + $product->saveWithoutRefresh(); + }); + sleep(2); + $find = Product::all(); + + expect(count($find) === 100)->toBeTrue(); + + try { + Product::filterGeoPoint('manufacturer.location', '10000km', [0, 0])->get(); + } catch (QueryException $exception) { + expect($exception->getMessage())->toContain('failed to find geo field'); + } + + $reindex = Schema::reIndex('products', 'holding_products'); + expect($reindex->data['created'] == 100)->toBeTrue(); + + sleep(2); + $findOld = DB::connection('elasticsearch')->table('products')->count(); + $findNew = DB::connection('elasticsearch')->table('holding_products')->count(); + + expect($findOld === 100)->toBeTrue() + ->and($findNew === 100)->toBeTrue(); + + Schema::deleteIfExists('products'); + expect(Schema::hasIndex('products'))->toBeFalse(); + + sleep(2); + //Now let's create the products index again but with proper mapping + $product = Schema::create('products', function (IndexBlueprint $index) { + $index->text('name'); + $index->float('price'); + $index->integer('status'); + $index->geo('manufacturer.location'); + $index->date('created_at'); + $index->date('updated_at'); + }); + + expect(! empty($product['products']['mappings']))->toBeTrue() + ->and(! empty($product['products']['settings']))->toBeTrue(); + + //now we move new to old. + $reindex = Schema::reIndex('holding_products', 'products'); + expect($reindex->data['created'] == 100)->toBeTrue(); + //Sleep to allow ES to catch up + sleep(2); + + $countOriginal = DB::connection('elasticsearch')->table('products')->count(); + $countHolding = DB::connection('elasticsearch')->table('holding_products')->count(); + + expect($countOriginal === 100)->toBeTrue() + ->and($countHolding === 100)->toBeTrue(); + + $found = Product::filterGeoPoint('manufacturer.location', '10000km', [0, 0])->get(); + expect($found->isNotEmpty())->toBeTrue(); + + //Cleanup + Schema::deleteIfExists('products'); + Schema::deleteIfExists('holding_products'); + + expect(Schema::hasIndex('products'))->toBeFalse() + ->and(Schema::hasIndex('holding_products'))->toBeFalse(); + +})->group('schema')->todo(); diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..4f547de --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,50 @@ +loadMigrationsFrom( + workbench_path('database/migrations') + ); + } + + protected function defineDatabaseSeeders(): void + { + $this->seed(DatabaseSeeder::class); + } + + protected function getEnvironmentSetUp($app): void + { + $app['config']->set('database.default', 'testing'); + $app['config']->set('database.connections.elasticsearch', [ + 'driver' => 'elasticsearch', + 'auth_type' => 'http', + 'hosts' => ['http://localhost:9200'], + ]); + } +} diff --git a/workbench/app/Models/Avatar.php b/workbench/app/Models/Avatar.php new file mode 100644 index 0000000..1c5aed5 --- /dev/null +++ b/workbench/app/Models/Avatar.php @@ -0,0 +1,48 @@ +morphTo(); + } + + public static function newFactory(): AvatarFactory + { + return AvatarFactory::new(); + } + + } diff --git a/workbench/app/Models/BlogPost.php b/workbench/app/Models/BlogPost.php new file mode 100644 index 0000000..87cc54f --- /dev/null +++ b/workbench/app/Models/BlogPost.php @@ -0,0 +1,58 @@ + encrypt($value)]; + } + } diff --git a/workbench/app/Models/Client.php b/workbench/app/Models/Client.php new file mode 100644 index 0000000..aa4f276 --- /dev/null +++ b/workbench/app/Models/Client.php @@ -0,0 +1,76 @@ + [ + 'name' => 'New', + 'level' => 1, + 'color' => 'text-neutral-500', + 'time_model' => 'created_at', + ], + + ]; + + + //Relationships ===================================== + + public function clientLogs() + { + return $this->hasMany(ClientLog::class); + } + + public function clientProfile() + { + return $this->hasOne(ClientProfile::class); + } + + public function company() + { + return $this->belongsTo(Company::class); + } + + public static function newFactory(): ClientFactory + { + return ClientFactory::new(); + } + + } diff --git a/workbench/app/Models/ClientLog.php b/workbench/app/Models/ClientLog.php new file mode 100644 index 0000000..2cb402b --- /dev/null +++ b/workbench/app/Models/ClientLog.php @@ -0,0 +1,51 @@ +belongsTo(Client::class); + } + + + public static function newFactory(): ClientLogFactory + { + return ClientLogFactory::new(); + } + } diff --git a/workbench/app/Models/ClientProfile.php b/workbench/app/Models/ClientProfile.php new file mode 100644 index 0000000..25c713a --- /dev/null +++ b/workbench/app/Models/ClientProfile.php @@ -0,0 +1,50 @@ +belongsTo(Client::class); + } + public static function newFactory(): ClientProfileFactory + { + return ClientProfileFactory::new(); + } + + } diff --git a/workbench/app/Models/Company.php b/workbench/app/Models/Company.php new file mode 100644 index 0000000..61d2d5f --- /dev/null +++ b/workbench/app/Models/Company.php @@ -0,0 +1,98 @@ + [ + 'name' => 'New', + 'level' => 1, + 'color' => 'text-neutral-500', + 'time_model' => 'created_at', + ], + + ]; + + + //Relationships ===================================== + + public function users() + { + return $this->hasMany(User::class); + } + + public function userLogs() + { + return $this->hasMany(UserLog::class); + } + + public function companyLogs() + { + return $this->hasMany(CompanyLog::class); + } + + public function companyProfile() + { + return $this->hasOne(CompanyProfile::class); + } + + public function avatar() + { + return $this->morphOne(Avatar::class, 'imageable'); + } + + public function photos() + { + return $this->morphMany(Photo::class, 'photoable'); + } + + public function clients() + { + return $this->hasMany(Client::class); + } + + public static function newFactory(): CompanyFactory + { + return CompanyFactory::new(); + } + + } diff --git a/workbench/app/Models/CompanyLog.php b/workbench/app/Models/CompanyLog.php new file mode 100644 index 0000000..ed80f82 --- /dev/null +++ b/workbench/app/Models/CompanyLog.php @@ -0,0 +1,46 @@ +belongsTo(Company::class); + } + + + public static function newFactory(): CompanyLogFactory + { + return CompanyLogFactory::new(); + } + + } diff --git a/workbench/app/Models/CompanyProfile.php b/workbench/app/Models/CompanyProfile.php new file mode 100644 index 0000000..59b2c90 --- /dev/null +++ b/workbench/app/Models/CompanyProfile.php @@ -0,0 +1,46 @@ +belongsTo(Company::class); + } + + public static function newFactory(): CompanyProfileFactory + { + return CompanyProfileFactory::new(); + } +} diff --git a/workbench/app/Models/EsPhoto.php b/workbench/app/Models/EsPhoto.php new file mode 100644 index 0000000..0cce0ee --- /dev/null +++ b/workbench/app/Models/EsPhoto.php @@ -0,0 +1,26 @@ +morphTo(); + } + + } diff --git a/workbench/app/Models/Guarded.php b/workbench/app/Models/Guarded.php new file mode 100644 index 0000000..2345dae --- /dev/null +++ b/workbench/app/Models/Guarded.php @@ -0,0 +1,16 @@ +level2']; +} diff --git a/workbench/app/Models/PageHit.php b/workbench/app/Models/PageHit.php new file mode 100644 index 0000000..7900421 --- /dev/null +++ b/workbench/app/Models/PageHit.php @@ -0,0 +1,22 @@ +morphTo(); + } + + public static function newFactory(): PhotoFactory + { + return PhotoFactory::new(); + } +} diff --git a/workbench/app/Models/Post.php b/workbench/app/Models/Post.php new file mode 100644 index 0000000..7bf7fdf --- /dev/null +++ b/workbench/app/Models/Post.php @@ -0,0 +1,37 @@ +in_stock > 0) { + return 'yes'; + } + + return 'no'; + } + + public function scopeGreen(Builder $query): Builder + { + return $query->where('color', 'green'); + } + + public function getAvgOrdersAttribute(): float|int + { + $orders = array_filter($this->order_values); + $avg = 0; + if (count($orders)) { + $avg = round(array_sum($orders) / count($orders)); + } + + return $avg; + } + + //Relationships ===================================== + + public function user(): \PDPhilip\Elasticsearch\Relations\BelongsTo + { + return $this->belongsTo(User::class); + } + + public static function newFactory(): ProductFactory + { + return ProductFactory::new(); + } +} diff --git a/workbench/app/Models/Soft.php b/workbench/app/Models/Soft.php new file mode 100644 index 0000000..56e6542 --- /dev/null +++ b/workbench/app/Models/Soft.php @@ -0,0 +1,18 @@ + 'datetime']; + + } diff --git a/workbench/app/Models/StaticPage.php b/workbench/app/Models/StaticPage.php new file mode 100644 index 0000000..61525ac --- /dev/null +++ b/workbench/app/Models/StaticPage.php @@ -0,0 +1,36 @@ + + */ + protected $fillable = [ + 'name', + 'email', + 'password', + ]; + + /** + * The attributes that should be hidden for serialization. + * + * @var array + */ + protected $hidden = [ + 'password', + 'remember_token', + ]; + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + 'email_verified_at' => 'datetime', + 'password' => 'hashed', + ]; + } + + public function userLogs() + { + return $this->hasMany(UserLog::class); + } + + public function company() + { + return $this->belongsTo(Company::class); + } + + public function userProfile() + { + return $this->hasOne(UserProfile::class); + } + + public function avatar() + { + return $this->morphOne(Avatar::class, 'imageable'); + } + + public function photos() + { + return $this->morphMany(Photo::class, 'photoable'); + } + + public function getFullNameAttribute() + { + return $this->first_name.' '.$this->last_name; + } + + public function getFirstNameAttribute($value) + { + return strtoupper($value); + } + + public static function newFactory(): UserFactory + { + return UserFactory::new(); + } + + } diff --git a/workbench/app/Models/UserLog.php b/workbench/app/Models/UserLog.php new file mode 100644 index 0000000..8bd2c6c --- /dev/null +++ b/workbench/app/Models/UserLog.php @@ -0,0 +1,78 @@ +belongsTo(User::class); + } + + public function company() + { + return $this->belongsTo(Company::class); + } + + protected $casts = [ + 'secret' => EncryptCast::class, + ]; + + public function getCodeAttribute($value) + { + return $value + 1000000; + } + + public function getTitleAttribute($value) + { + return 'MR '.ucfirst($value); + } + + public static function newFactory(): UserLogFactory + { + return UserLogFactory::new(); + } +} diff --git a/workbench/app/Models/UserProfile.php b/workbench/app/Models/UserProfile.php new file mode 100644 index 0000000..efec0cc --- /dev/null +++ b/workbench/app/Models/UserProfile.php @@ -0,0 +1,53 @@ +belongsTo(User::class); + } + public static function newFactory(): UserProfileFactory + { + return UserProfileFactory::new(); + } + + } diff --git a/workbench/database/factories/AvatarFactory.php b/workbench/database/factories/AvatarFactory.php new file mode 100644 index 0000000..5101422 --- /dev/null +++ b/workbench/database/factories/AvatarFactory.php @@ -0,0 +1,24 @@ + $this->faker->imageUrl, + 'imageable_id' => null, // To be set when creating instances + 'imageable_type' => null, // To be set when creating instances + 'created_at' => now(), + 'updated_at' => now(), + ]; + } +} diff --git a/workbench/database/factories/BlogPostFactory.php b/workbench/database/factories/BlogPostFactory.php new file mode 100644 index 0000000..452ddc1 --- /dev/null +++ b/workbench/database/factories/BlogPostFactory.php @@ -0,0 +1,55 @@ + + */ + class BlogPostFactory extends Factory + { + protected $model = BlogPost::class; + + /** + * Generates an array of random comments. + * + * @param int $count The number of comments to generate. + * @return array An array of comment data. + */ + public function generateComments(int $count): array + { + return collect(range(1, $count))->map(function () { + return [ + 'name' => fake()->name(), + 'comment' => fake()->text(), + 'country' => fake()->country(), + 'likes' => fake()->numberBetween(0, 10), + ]; + })->all(); + } + + /** + * Defines the default state for the BlogPost model. + * + * @return array + */ + public function definition(): array + { + return [ + 'title' => fake()->sentence(), + 'content' => fake()->text(), + 'comments' => $this->generateComments(fake()->numberBetween(5, 20)), + 'status' => fake()->numberBetween(1, 5), + 'active' => fake()->boolean(), + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]; + } + } diff --git a/workbench/database/factories/ClientFactory.php b/workbench/database/factories/ClientFactory.php new file mode 100644 index 0000000..bdf28f4 --- /dev/null +++ b/workbench/database/factories/ClientFactory.php @@ -0,0 +1,23 @@ + '', + 'name' => fake()->name(), + 'status' => fake()->randomNumber(), + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]; + } + } diff --git a/workbench/database/factories/ClientLogFactory.php b/workbench/database/factories/ClientLogFactory.php new file mode 100644 index 0000000..7656071 --- /dev/null +++ b/workbench/database/factories/ClientLogFactory.php @@ -0,0 +1,24 @@ + '', + 'title' => fake()->word(), + 'desc' => fake()->sentence(), + 'status' => fake()->randomNumber(), + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]; + } + } diff --git a/workbench/database/factories/ClientProfileFactory.php b/workbench/database/factories/ClientProfileFactory.php new file mode 100644 index 0000000..cf8d874 --- /dev/null +++ b/workbench/database/factories/ClientProfileFactory.php @@ -0,0 +1,28 @@ + '', + 'company_id' => function () { + return Company::factory()->create()->_id; + }, + 'contact_name' => fake()->name(), + 'contact_email' => fake()->email(), + 'website' => fake()->url(), + 'status' => fake()->randomNumber(), + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]; + } + } diff --git a/workbench/database/factories/CompanyFactory.php b/workbench/database/factories/CompanyFactory.php new file mode 100644 index 0000000..30ce309 --- /dev/null +++ b/workbench/database/factories/CompanyFactory.php @@ -0,0 +1,23 @@ + fake()->company(), + 'status' => fake()->randomNumber(), + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]; + } + + } diff --git a/workbench/database/factories/CompanyLogFactory.php b/workbench/database/factories/CompanyLogFactory.php new file mode 100644 index 0000000..3f1df6f --- /dev/null +++ b/workbench/database/factories/CompanyLogFactory.php @@ -0,0 +1,26 @@ + function () { + return Company::factory()->create()->_id; + }, + 'title' => fake()->word(), + 'desc' => fake()->sentence(), + 'status' => fake()->randomNumber(), + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]; + } + } diff --git a/workbench/database/factories/CompanyProfileFactory.php b/workbench/database/factories/CompanyProfileFactory.php new file mode 100644 index 0000000..c3bea66 --- /dev/null +++ b/workbench/database/factories/CompanyProfileFactory.php @@ -0,0 +1,25 @@ + fake()->address(), + 'website' => fake()->url(), + 'status' => fake()->randomNumber(), + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]; + } +} diff --git a/workbench/database/factories/PageHitFactory.php b/workbench/database/factories/PageHitFactory.php new file mode 100644 index 0000000..9895010 --- /dev/null +++ b/workbench/database/factories/PageHitFactory.php @@ -0,0 +1,34 @@ + fake()->ipv4(), + 'page_id' => fake()->numberBetween(1, 9), + 'date' => fake()->randomElement([ + '2021-01-01', + '2021-01-02', + '2021-01-03', + '2021-01-04', + '2021-01-05', + '2021-01-06', + '2021-01-07', + '2021-01-08', + '2021-01-09', + '2021-01-10', + ]), + ]; + } +} diff --git a/workbench/database/factories/PersonFactory.php b/workbench/database/factories/PersonFactory.php new file mode 100644 index 0000000..0e7c3b4 --- /dev/null +++ b/workbench/database/factories/PersonFactory.php @@ -0,0 +1,24 @@ + fake()->name(), + 'jobs' => fake()->words(), + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]; + } +} diff --git a/workbench/database/factories/PhotoFactory.php b/workbench/database/factories/PhotoFactory.php new file mode 100644 index 0000000..36406c7 --- /dev/null +++ b/workbench/database/factories/PhotoFactory.php @@ -0,0 +1,24 @@ + $this->faker->imageUrl, + 'photoable_id' => null, // To be set when creating instances + 'photoable_type' => null, // To be set when creating instances + 'created_at' => now(), + 'updated_at' => now(), + ]; + } +} diff --git a/workbench/database/factories/PostFactory.php b/workbench/database/factories/PostFactory.php new file mode 100644 index 0000000..c9ae6ac --- /dev/null +++ b/workbench/database/factories/PostFactory.php @@ -0,0 +1,25 @@ + fake()->name(), + 'slug' => fake()->slug(), + 'content' => fake()->realTextBetween(100), + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]; + } +} diff --git a/workbench/database/factories/ProductFactory.php b/workbench/database/factories/ProductFactory.php new file mode 100644 index 0000000..bde12a4 --- /dev/null +++ b/workbench/database/factories/ProductFactory.php @@ -0,0 +1,84 @@ + fake()->name(), + 'description' => fake()->realTextBetween(100), + 'product_id' => fake()->uuid(), + 'in_stock' => fake()->numberBetween(0, 100), + 'status' => fake()->numberBetween(1, 9), + 'color' => fake()->safeColorName(), + 'is_active' => fake()->boolean(), + 'price' => fake()->randomFloat(2, 0, 2000), + 'orders' => fake()->numberBetween(0, 250), + 'order_values' => $this->randomArrayOfInts(), + + 'manufacturer' => [ + 'location' => [ + 'lat' => fake()->latitude(), + 'lon' => fake()->longitude(), + ], + 'name' => fake()->company(), + 'country' => fake()->country(), + 'owned_by' => [ + 'name' => fake()->name(), + 'country' => fake()->country(), + ], + ], + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + 'deleted_at' => null, + ]; + } + + public function randomArrayOfInts() + { + $array = []; + $i = 0; + while ($i < rand(0, 50)) { + $array[] = rand(5, 200); + $i++; + } + + return $array; + } + + public function definitionUSA() + { + return [ + 'name' => fake()->name(), + 'product_id' => fake()->uuid(), + 'in_stock' => fake()->numberBetween(0, 100), + 'status' => fake()->numberBetween(1, 9), + 'color' => fake()->safeColorName(), + 'is_active' => fake()->boolean(), + 'price' => fake()->randomFloat(2, 0, 2000), + 'orders' => fake()->numberBetween(0, 250), + 'manufacturer' => [ + 'location' => [ + 'lat' => fake()->latitude(), + 'lon' => fake()->longitude(), + ], + 'name' => fake()->company(), + 'country' => 'United States of America', + 'owned_by' => [ + 'name' => fake()->name(), + 'country' => fake()->country(), + ], + ], + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]; + } +} diff --git a/workbench/database/factories/StaticPageFactory.php b/workbench/database/factories/StaticPageFactory.php new file mode 100644 index 0000000..4b57b74 --- /dev/null +++ b/workbench/database/factories/StaticPageFactory.php @@ -0,0 +1,31 @@ + + */ + class StaticPageFactory extends Factory + { + protected $model = StaticPage::class; + + /** + * Defines the default state for the BlogPost model. + * + * @return array + */ + public function definition(): array + { + return [ + 'title' => fake()->sentence(), + 'content' => fake()->text(), + ]; + } + } diff --git a/workbench/database/factories/UserFactory.php b/workbench/database/factories/UserFactory.php new file mode 100644 index 0000000..77d96ba --- /dev/null +++ b/workbench/database/factories/UserFactory.php @@ -0,0 +1,46 @@ + fake()->firstName(), + 'last_name' => fake()->lastName(), + 'email' => fake()->unique()->safeEmail(), + 'email_verified_at' => now(), + 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password + 'remember_token' => Str::random(10), + 'status' => fake()->numberBetween(1, 9), + ]; + } + + /** + * Indicate that the model's email address should be unverified. + * + * @return \Illuminate\Database\Eloquent\Factories\Factory + */ + public function unverified() + { + return $this->state(function (array $attributes) { + return [ + 'email_verified_at' => null, + ]; + }); + } +} diff --git a/workbench/database/factories/UserLogFactory.php b/workbench/database/factories/UserLogFactory.php new file mode 100644 index 0000000..7d738fa --- /dev/null +++ b/workbench/database/factories/UserLogFactory.php @@ -0,0 +1,41 @@ + fake()->word(), + 'score' => fake()->word(), + 'secret' => fake()->word(), + 'code' => fake()->numberBetween(1,5), + 'meta' => [], + 'agent' => [ + 'ip' => fake()->ipv4(), + 'source' => fake()->url(), + 'method' => 'GET', + 'browser' => fake()->chrome(), + 'device' => fake()->chrome(), + 'deviceType' => fake()->randomElement(['desktop', 'mobile', 'tablet']), + 'geo' => [ + 'lat' => fake()->latitude(), + 'lon' => fake()->longitude(), + ], + 'countryCode' => fake()->countryCode(), + 'city' => fake()->city(), + ], + 'status' => fake()->numberBetween(1,9), + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]; + } + } diff --git a/workbench/database/factories/UserProfileFactory.php b/workbench/database/factories/UserProfileFactory.php new file mode 100644 index 0000000..ed96568 --- /dev/null +++ b/workbench/database/factories/UserProfileFactory.php @@ -0,0 +1,25 @@ + fake()->word(), + 'facebook' => fake()->word(), + 'address' => fake()->address(), + 'timezone' => fake()->timezone(), + 'status' => fake()->randomNumber(), + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]; + } + } diff --git a/workbench/database/migrations/0000_00_00_000001_create_posts_table.php b/workbench/database/migrations/0000_00_00_000001_create_posts_table.php new file mode 100644 index 0000000..4b38be6 --- /dev/null +++ b/workbench/database/migrations/0000_00_00_000001_create_posts_table.php @@ -0,0 +1,39 @@ +text('title'); + $index->keyword('title'); + + $index->text('content'); + + $index->nested('comments'); + + $index->integer('status'); + $index->boolean('active'); + + + $index->date('created_at'); + $index->date('updated_at'); + $index->date('deleted_at'); + + }); + + } + + public function down(): void + { + Schema::deleteIfExists('posts'); + } + }; diff --git a/workbench/database/migrations/0000_00_00_000002_create_static_pages_table.php b/workbench/database/migrations/0000_00_00_000002_create_static_pages_table.php new file mode 100644 index 0000000..98d01d3 --- /dev/null +++ b/workbench/database/migrations/0000_00_00_000002_create_static_pages_table.php @@ -0,0 +1,29 @@ +text('title'); + $index->keyword('title'); + + $index->text('content'); + + }); + + } + + public function down(): void + { + Schema::deleteIfExists('static_pages'); + } +}; diff --git a/workbench/database/migrations/0000_00_00_000003_create_softs_table.php b/workbench/database/migrations/0000_00_00_000003_create_softs_table.php new file mode 100644 index 0000000..8335f53 --- /dev/null +++ b/workbench/database/migrations/0000_00_00_000003_create_softs_table.php @@ -0,0 +1,31 @@ +text('name'); + $index->keyword('name'); + + $index->date('created_at'); + $index->date('updated_at'); + $index->date('deleted_at'); + + }); + + } + + public function down(): void + { + Schema::deleteIfExists('softs'); + } +}; diff --git a/workbench/database/migrations/2024_08_01_000000_create_users_table.php b/workbench/database/migrations/2024_08_01_000000_create_users_table.php new file mode 100644 index 0000000..a29e1bb --- /dev/null +++ b/workbench/database/migrations/2024_08_01_000000_create_users_table.php @@ -0,0 +1,53 @@ +down(); + + Schema::create('users', function (Blueprint $table) { + $table->id(); + $table->string('first_name'); + $table->string('last_name'); + $table->string('email')->unique(); + $table->string('company_id')->nullable(); + $table->timestamp('email_verified_at')->nullable(); + $table->string('password'); + $table->integer('status'); + $table->rememberToken(); + $table->timestamps(); + }); + + Schema::create('photos', function (Blueprint $table) { + $table->id(); + $table->string('url'); + $table->string('photoable_id'); + $table->string('photoable_type'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('users'); + Schema::dropIfExists('photos'); + } + + public function hasTables(): bool + { + return Schema::hasTable('users') && Schema::hasTable('photos'); + } +}; diff --git a/workbench/database/migrations/2024_08_01_014350_create_products_table.php b/workbench/database/migrations/2024_08_01_014350_create_products_table.php new file mode 100644 index 0000000..86ae031 --- /dev/null +++ b/workbench/database/migrations/2024_08_01_014350_create_products_table.php @@ -0,0 +1,65 @@ +text('name'); + $index->keyword('name'); + + $index->text('description'); + $index->keyword('description'); + + $index->text('product_id'); + $index->keyword('product_id'); + + $index->integer('in_stock'); + + $index->keyword('color'); + $index->integer('status'); + + $index->boolean('is_active'); + + $index->boolean('is_approved'); + + $index->float('price'); + + $index->integer('orders'); + $index->integer('order_values'); + + $index->date('last_order_datetime'); + $index->date('last_order_ts'); + $index->date('last_order_ms'); + + $index->geo('manufacturer.location'); + + $index->keyword('manufacturer.name'); + $index->keyword('manufacturer.country'); + + $index->keyword('manufacturer.owned_by.name'); + $index->keyword('manufacturer.owned_by.country'); + + $index->keyword('type'); + + $index->date('created_at'); + $index->date('updated_at'); + $index->date('deleted_at'); + + }); + + } + + public function down(): void + { + Schema::deleteIfExists('products'); + } + }; diff --git a/workbench/database/migrations/2024_08_03_161126_create_blog_posts_table.php b/workbench/database/migrations/2024_08_03_161126_create_blog_posts_table.php new file mode 100644 index 0000000..d659492 --- /dev/null +++ b/workbench/database/migrations/2024_08_03_161126_create_blog_posts_table.php @@ -0,0 +1,39 @@ +text('title'); + $index->keyword('title'); + + $index->text('content'); + + $index->nested('comments'); + + $index->integer('status'); + $index->boolean('active'); + + + $index->date('created_at'); + $index->date('updated_at'); + $index->date('deleted_at'); + + }); + + } + + public function down(): void + { + Schema::deleteIfExists('blog_posts'); + } + }; diff --git a/workbench/database/migrations/2024_08_03_171115_create_companies_tables.php b/workbench/database/migrations/2024_08_03_171115_create_companies_tables.php new file mode 100644 index 0000000..8cae2bc --- /dev/null +++ b/workbench/database/migrations/2024_08_03_171115_create_companies_tables.php @@ -0,0 +1,56 @@ +text('name'); + $index->integer('status'); + $index->date('created_at'); + $index->date('updated_at'); + }); + + Schema::create('company_logs', function ($index) { + $index->text('company_id'); + $index->text('title'); + $index->integer('code'); + $index->date('created_at'); + $index->date('updated_at'); + }); + + Schema::create('avatars', function ($index) { + $index->text('url'); + $index->text('imageable_id'); + $index->text('imageable_type'); + $index->date('created_at'); + $index->date('updated_at'); + }); + + Schema::create('photos', function ($index) { + $index->text('url'); + $index->text('photoable_id'); + $index->text('photoable_type'); + $index->date('created_at'); + $index->date('updated_at'); + }); + } + + public function down(): void + { + Schema::deleteIfExists('companies'); + Schema::deleteIfExists('company_logs'); + Schema::deleteIfExists('avatars'); + Schema::deleteIfExists('photos'); + } +}; diff --git a/workbench/database/seeders/DatabaseSeeder.php b/workbench/database/seeders/DatabaseSeeder.php new file mode 100644 index 0000000..de507a6 --- /dev/null +++ b/workbench/database/seeders/DatabaseSeeder.php @@ -0,0 +1,15 @@ +get('/user', function (Request $request) { +// return $request->user(); +// }); diff --git a/workbench/routes/console.php b/workbench/routes/console.php new file mode 100644 index 0000000..3c0324c --- /dev/null +++ b/workbench/routes/console.php @@ -0,0 +1,19 @@ +comment(Inspiring::quote()); +// })->purpose('Display an inspiring quote'); diff --git a/workbench/routes/web.php b/workbench/routes/web.php new file mode 100644 index 0000000..bdc59b1 --- /dev/null +++ b/workbench/routes/web.php @@ -0,0 +1,20 @@ +