diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1671c9b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 + +[docker-compose.yml] +indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..311f4ec --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +docs/ export-ignore +tests/ export-ignore +.github/ export-ignore +.editorconfig export-ignore +.gitattributes export-ignore +.gitignore export-ignore +CHANGELOG.md export-ignore +infection.json5.dis export-ignore +phpstan.neon.dist export-ignore +phpunit.xml.dist export-ignore +psalm.xml.dist export-ignore +rector.php export-ignore diff --git a/.github/workflows/composer-normalize.yml b/.github/workflows/composer-normalize.yml new file mode 100644 index 0000000..d76f89f --- /dev/null +++ b/.github/workflows/composer-normalize.yml @@ -0,0 +1,30 @@ +on: + pull_request_target: + types: + - opened + - edited + - synchronize + - reopened + - ready_for_review + - converted_to_draft + - review_requested + push: + branches: + - 1.x + +jobs: + run: + name: php-cs-fixer + runs-on: ${{ matrix.operating-systems }} + + strategy: + fail-fast: false + matrix: + operating-systems: + - ubuntu-latest + php-versions: + - 8.1 + + steps: + - uses: actions/checkout@v3 + - uses: docker://ergebnis/composer-normalize-action diff --git a/.github/workflows/composer-validate.yml b/.github/workflows/composer-validate.yml new file mode 100644 index 0000000..140fea1 --- /dev/null +++ b/.github/workflows/composer-validate.yml @@ -0,0 +1,36 @@ +on: + pull_request_target: + types: + - opened + - edited + - synchronize + - reopened + - ready_for_review + - converted_to_draft + - review_requested + push: + branches: + - 1.x + +jobs: + run: + name: php-cs-fixer + runs-on: ${{ matrix.operating-systems }} + + strategy: + fail-fast: false + matrix: + operating-systems: + - ubuntu-latest + php-versions: + - 8.1 + + steps: + - uses: actions/checkout@v3 + + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: pcov + + - run: composer validate diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..0c1d51e --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,14 @@ +name: "Dependency Review" +on: [pull_request_target] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: "Checkout Repository" + uses: actions/checkout@v3 + - name: "Dependency Review" + uses: actions/dependency-review-action@v2 diff --git a/.github/workflows/pest.yml b/.github/workflows/pest.yml new file mode 100644 index 0000000..c5d0054 --- /dev/null +++ b/.github/workflows/pest.yml @@ -0,0 +1,52 @@ +on: + pull_request_target: + types: + - opened + - edited + - synchronize + - reopened + - ready_for_review + - converted_to_draft + - review_requested + push: + branches: + - 1.x + +jobs: + run: + name: php-cs-fixer + runs-on: ${{ matrix.operating-systems }} + + strategy: + fail-fast: false + matrix: + operating-systems: + - ubuntu-latest + php-versions: + - 8.1 + + steps: + - uses: actions/checkout@v3 + + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: pcov + + - id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - run: composer install --no-progress --prefer-dist --optimize-autoloader + + - run: vendor/bin/pest --order-by random --coverage --min=100 --ci + + - uses: codecov/codecov-action@v3 + with: + directory: ./coverage/ + flags: unittests diff --git a/.github/workflows/php-cs-fixer.yml b/.github/workflows/php-cs-fixer.yml new file mode 100644 index 0000000..b9e4b7f --- /dev/null +++ b/.github/workflows/php-cs-fixer.yml @@ -0,0 +1,47 @@ +on: + pull_request_target: + types: + - opened + - edited + - synchronize + - reopened + - ready_for_review + - converted_to_draft + - review_requested + push: + branches: + - 1.x + +jobs: + run: + name: php-cs-fixer + runs-on: ${{ matrix.operating-systems }} + + strategy: + fail-fast: false + matrix: + operating-systems: + - ubuntu-latest + php-versions: + - 8.1 + + steps: + - uses: actions/checkout@v3 + + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: pcov + + - id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - run: composer install --no-progress --prefer-dist --optimize-autoloader + + - run: vendor/bin/php-cs-fixer fix src --dry-run --diff diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..b25fc8a --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,47 @@ +on: + pull_request_target: + types: + - opened + - edited + - synchronize + - reopened + - ready_for_review + - converted_to_draft + - review_requested + push: + branches: + - 1.x + +jobs: + run: + name: php-cs-fixer + runs-on: ${{ matrix.operating-systems }} + + strategy: + fail-fast: false + matrix: + operating-systems: + - ubuntu-latest + php-versions: + - 8.1 + + steps: + - uses: actions/checkout@v3 + + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: pcov + + - id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - run: composer install --no-progress --prefer-dist --optimize-autoloader + + - run: vendor/bin/phpstan analyze diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml new file mode 100644 index 0000000..2b5cfd3 --- /dev/null +++ b/.github/workflows/psalm.yml @@ -0,0 +1,47 @@ +on: + pull_request_target: + types: + - opened + - edited + - synchronize + - reopened + - ready_for_review + - converted_to_draft + - review_requested + push: + branches: + - 1.x + +jobs: + run: + name: php-cs-fixer + runs-on: ${{ matrix.operating-systems }} + + strategy: + fail-fast: false + matrix: + operating-systems: + - ubuntu-latest + php-versions: + - 8.1 + + steps: + - uses: actions/checkout@v3 + + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: pcov + + - id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - run: composer install --no-progress --prefer-dist --optimize-autoloader + + - run: vendor/bin/psalm diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml new file mode 100644 index 0000000..8bac931 --- /dev/null +++ b/.github/workflows/rector.yml @@ -0,0 +1,47 @@ +on: + pull_request_target: + types: + - opened + - edited + - synchronize + - reopened + - ready_for_review + - converted_to_draft + - review_requested + push: + branches: + - 1.x + +jobs: + run: + name: php-cs-fixer + runs-on: ${{ matrix.operating-systems }} + + strategy: + fail-fast: false + matrix: + operating-systems: + - ubuntu-latest + php-versions: + - 8.1 + + steps: + - uses: actions/checkout@v3 + + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: pcov + + - id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - run: composer install --no-progress --prefer-dist --optimize-autoloader + + - run: vendor/bin/rector process src --dry-run diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4bcfbb0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +vendor/ +coverage/ +composer.lock +.php-cs-fixer.cache +.phpunit.result.cache +.DS_Store +composer.local.json +NOTES.md diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..9083e98 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,225 @@ +setRiskyAllowed(true) + ->setRules([ + 'array_indentation' => true, + 'array_push' => true, + 'array_syntax' => ['syntax' => 'short'], + 'assign_null_coalescing_to_coalesce_equal' => true, + 'backtick_to_shell_exec' => true, + 'binary_operator_spaces' => ['operators' => ['=' => 'align', 'xor' => null, '+=' => 'align_single_space', '===' => 'align_single_space_minimal', '|' => 'single_space', '=>' => 'align_single_space_minimal_by_scope']], + 'blank_line_after_namespace' => true, + 'blank_line_after_opening_tag' => true, + 'blank_line_before_statement' => true, + 'braces' => true, + 'cast_spaces' => true, + 'class_attributes_separation' => ['elements' => ['const' => 'only_if_meta', 'method' => 'one', 'property' => 'only_if_meta', 'trait_import' => 'one', 'case' => 'one']], + 'class_definition' => true, + 'class_reference_name_casing' => true, + 'clean_namespace' => true, + 'combine_consecutive_issets' => true, + 'combine_consecutive_unsets' => true, + 'combine_nested_dirname' => true, + 'comment_to_phpdoc' => ['ignored_tags' => ['codeCoverageIgnoreStart', 'codeCoverageIgnoreEnd', 'phpstan-ignore-next-line']], + 'compact_nullable_typehint' => true, + 'concat_space' => ['spacing' => 'one'], + 'constant_case' => ['case' => 'lower'], + 'date_time_create_from_format_call' => true, + 'declare_equal_normalize' => ['space' => 'none'], + 'declare_parentheses' => true, + 'declare_strict_types' => true, + 'dir_constant' => true, + 'doctrine_annotation_array_assignment' => true, + 'doctrine_annotation_braces' => true, + 'doctrine_annotation_indentation' => true, + 'doctrine_annotation_spaces' => true, + 'echo_tag_syntax' => ['format' => 'long'], + 'elseif' => true, + 'empty_loop_body' => true, + 'empty_loop_condition' => true, + 'encoding' => true, + 'ereg_to_preg' => true, + 'error_suppression' => true, + 'escape_implicit_backslashes' => true, + 'explicit_indirect_variable' => true, + 'explicit_string_variable' => true, + 'final_class' => true, + 'final_internal_class' => true, + 'fopen_flag_order' => true, + 'fopen_flags' => true, + 'full_opening_tag' => true, + 'fully_qualified_strict_types' => true, + 'function_declaration' => true, + 'function_to_constant' => true, + 'function_typehint_space' => true, + 'general_phpdoc_annotation_remove' => true, + 'general_phpdoc_tag_rename' => true, + 'get_class_to_class_keyword' => true, + 'global_namespace_import' => ['import_classes' => true, 'import_constants' => true, 'import_functions' => true], + 'group_import' => true, + 'heredoc_indentation' => true, + 'heredoc_to_nowdoc' => true, + 'implode_call' => true, + 'include' => true, + 'increment_style' => ['style' => 'pre'], + 'indentation_type' => true, + 'integer_literal_case' => true, + 'is_null' => true, + 'lambda_not_used_import' => true, + 'line_ending' => true, + 'linebreak_after_opening_tag' => true, + 'list_syntax' => ['syntax' => 'short'], + 'logical_operators' => true, + 'lowercase_cast' => true, + 'lowercase_keywords' => true, + 'lowercase_static_reference' => true, + 'magic_constant_casing' => true, + 'magic_method_casing' => true, + 'mb_str_functions' => true, + 'method_argument_space' => true, + 'method_chaining_indentation' => true, + 'modernize_strpos' => true, + 'modernize_types_casting' => true, + 'multiline_comment_opening_closing' => true, + 'multiline_whitespace_before_semicolons' => true, + 'native_function_casing' => true, + 'native_function_invocation' => true, + 'native_function_type_declaration_casing' => true, + 'new_with_braces' => 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_break_comment' => true, + 'no_closing_tag' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => true, + 'no_homoglyph_names' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => true, + 'no_multiline_whitespace_around_double_arrow' => true, + 'no_multiple_statements_per_line' => true, + 'no_php4_constructor' => 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' => true, + 'no_spaces_inside_parenthesis' => true, + 'no_superfluous_elseif' => true, + 'no_trailing_comma_in_singleline' => true, + 'no_trailing_whitespace_in_comment' => true, + 'no_trailing_whitespace_in_string' => true, + 'no_trailing_whitespace' => true, + 'no_unneeded_control_parentheses' => true, + 'no_unneeded_curly_braces' => true, + 'no_unneeded_final_method' => true, + 'no_unneeded_import_alias' => true, + 'no_unreachable_default_argument_value' => true, + 'no_unset_cast' => true, + 'no_unused_imports' => true, + 'no_useless_concat_operator' => true, + 'no_useless_else' => true, + 'no_useless_nullsafe_operator' => true, + 'no_useless_return' => true, + 'no_useless_sprintf' => true, + 'no_whitespace_before_comma_in_array' => true, + 'no_whitespace_in_blank_line' => true, + 'non_printable_character' => true, + 'normalize_index_brace' => true, + 'not_operator_with_successor_space' => true, + 'nullable_type_declaration_for_default_null_value' => true, + 'object_operator_without_whitespace' => true, + 'octal_notation' => true, + 'operator_linebreak' => true, + 'ordered_class_elements' => ['sort_algorithm' => 'alpha', 'order' => ['use_trait', 'case', 'constant', 'constant_private', 'constant_protected', 'constant_public', 'property_private', 'property_private_readonly', 'property_private_static', 'property_protected', 'property_protected_readonly', 'property_protected_static', 'property_public', 'property_public_readonly', 'property_public_static', 'property_static', 'protected', 'construct', 'destruct', 'magic', 'method', 'public', 'method_abstract', 'method_private', 'method_private_abstract', 'method_private_abstract_static', 'method_private_static', 'method_protected', 'method_protected_abstract', 'method_protected_abstract_static', 'method_protected_static', 'method_public', 'method_public_abstract', 'method_public_abstract_static', 'method_public_static', 'method_static', 'phpunit', 'private', 'property']], + 'ordered_imports' => ['sort_algorithm' => 'alpha', 'imports_order' => ['const', 'class', 'function']], + 'ordered_interfaces' => true, + 'ordered_traits' => true, + 'php_unit_fqcn_annotation' => true, + 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false], + 'phpdoc_align' => ['align' => 'vertical'], + 'phpdoc_indent' => true, + 'phpdoc_inline_tag_normalizer' => true, + 'phpdoc_line_span' => true, + 'phpdoc_no_access' => true, + 'phpdoc_no_empty_return' => true, + 'phpdoc_no_package' => true, + 'phpdoc_no_useless_inheritdoc' => true, + 'phpdoc_order_by_value' => true, + 'phpdoc_order' => true, + 'phpdoc_return_self_reference' => ['replacements' => ['this' => 'self']], + 'phpdoc_scalar' => true, + 'phpdoc_separation' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_summary' => true, + 'phpdoc_tag_type' => true, + 'phpdoc_to_comment' => ['ignored_tags' => ['var']], + 'phpdoc_trim_consecutive_blank_line_separation' => true, + 'phpdoc_trim' => true, + 'phpdoc_types_order' => true, + 'phpdoc_types' => true, + 'phpdoc_var_annotation_correct_order' => true, + 'phpdoc_var_without_name' => true, + 'pow_to_exponentiation' => true, + 'protected_to_private' => true, + 'psr_autoloading' => true, + 'random_api_migration' => true, + 'regular_callable_call' => true, + 'return_assignment' => true, + 'return_type_declaration' => ['space_before' => 'none'], + 'return_type_declaration' => true, + 'self_accessor' => true, + 'self_static_accessor' => true, + 'semicolon_after_instruction' => true, + 'set_type_to_cast' => true, + 'short_scalar_cast' => true, + 'simple_to_complex_string_variable' => true, + 'simplified_if_return' => true, + 'single_blank_line_at_eof' => true, + 'single_blank_line_before_namespace' => true, + 'single_class_element_per_statement' => true, + 'single_line_after_imports' => true, + 'single_line_comment_spacing' => true, + 'single_line_comment_style' => ['comment_types' => ['hash']], + 'single_line_throw' => true, + 'single_quote' => true, + 'single_space_after_construct' => true, + 'single_trait_insert_per_statement' => true, + 'space_after_semicolon' => true, + 'standardize_increment' => true, + 'standardize_not_equals' => true, + 'statement_indentation' => true, + 'static_lambda' => true, + 'strict_comparison' => true, + 'strict_param' => true, + 'string_length_to_empty' => true, + 'string_line_ending' => true, + 'switch_case_semicolon_to_colon' => true, + 'switch_case_space' => true, + 'switch_continue_to_break' => true, + 'ternary_operator_spaces' => true, + 'ternary_to_elvis_operator' => true, + 'ternary_to_null_coalescing' => true, + 'trailing_comma_in_multiline' => ['after_heredoc' => true, 'elements' => ['arguments', 'parameters']], + 'trim_array_spaces' => true, + 'types_spaces' => ['space' => 'single'], + 'unary_operator_spaces' => true, + 'use_arrow_functions' => true, + 'visibility_required' => true, + 'void_return' => true, + 'whitespace_after_comma_in_array' => true, + 'yoda_style' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->exclude('vendor') + ->in([__DIR__.'/src/']) + ) +; diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d204606 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Initial release. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..47239d7 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Evan Sims + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index e752964..0207c1c 100644 --- a/README.md +++ b/README.md @@ -1 +1,140 @@ -# http-factory-implementations \ No newline at end of file +**Lightweight library that discovers available [PSR-17 HTTP Factory](https://github.com/psr-discovery/http-factory-implementations) implementations by searching for a list of well-known classes that implement the relevant interface, and returns an instance of the first one that is found.** + +This package is part of the [psr-discovery/discovery](https://github.com/psr-discovery/discovery) PSR discovery collection, which also supports [PSR-18 HTTP Clients](https://github.com/psr-discovery/http-client-implementations), [PSR-14 Event Dispatchers](https://github.com/psr-discovery/event-dispatcher-implementations), [PSR-11 Containers](https://github.com/psr-discovery/container-implementations), [PSR-6 Cache](https://github.com/psr-discovery/cache-implementations) and [PSR-3 Loggers](https://github.com/psr-discovery/log-implementations). + +This is largely intended for inclusion in libraries like SDKs that wish to support PSR-17 Factories without requiring hard dependencies on specific implementations or demanding extra configuration by users. + +- [Requirements](#requirements) +- [Implementations](#implementations) +- [Installation](#installation) +- [Usage](#usage) +- [Handling Failures](#handling-failures) +- [Exceptions](#exceptions) +- [Singletons](#singletons) +- [Mocking Priority](#mocking-priority) +- [Preferring an Implementation](#preferring-an-implementation) +- [Using a Specific Implementation](#using-a-specific-implementation) + +## Requirements + +- PHP 8.0+ +- Composer 2.0+ + +Successful discovery requires the presence of a compatible implementation in the host application. This library does not install any implementations for you. + +## Implementations + +The discovery of available implementations is based on a list of well-known libraries that provide the `psr/http-factory-implementation` interface. These include: + +- ... + +If [a particular implementation](https://packagist.org/providers/psr/http-factory-implementation) is missing that you'd like to see, please open a pull request adding support. + +## Installation + +```bash +composer require --dev psr-discovery/http-factory-implementations +``` + +## Usage + +```php +use PsrDiscovery\Discovery; + +// Returns a PSR-17 RequestFactoryInterface instance +$requestFactory = Discovery::httpRequestFactory(); + +// Returns a PSR-17 ResponseFactoryInterface instance +$responseFactory = Discovery::httpResponseFactory(); + +// Returns a PSR-17 StreamFactoryInterface instance +$streamFactory = Discovery::httpStreamFactory(); + +// Returns a PSR-7 RequestInterface instance +$request = $requestFactory->createRequest('GET', 'https://example.com'); +``` + +## Handling Failures + +If the library is unable to discover a suitable PSR-17 implementation, the `Discovery::httpRequestFactory()`, `Discovery::httpResponseFactory()` or `Discovery::httpStreamFactory()` discovery methods will simply return `null`. This allows you to handle the failure gracefully, for example by falling back to a default implementation. + +Example: + +```php +use PsrDiscovery\Discovery; + +$requestFactory = Discovery::httpRequestFactory(); + +if ($requestFactory === null) { + // No suitable HTTP RequestFactory implementation was discovered. + // Fall back to a default implementation. + $requestFactory = new DefaultRequestFactory(); +} +``` + +## Singletons + +By default, the `Discovery::httpRequestFactory()`, `Discovery::httpResponseFactory()` or `Discovery::httpStreamFactory()` methods will always return a new instance of the discovered implementation. If you wish to use a singleton instance instead, simply pass `true` to the `$singleton` parameter of the discovery method. + +Example: + +```php +use PsrDiscovery\Discovery; + +// $httpResponseFactory1 !== $httpResponseFactory2 (default) +$httpResponseFactory1 = Discovery::httpResponseFactory(); +$httpResponseFactory2 = Discovery::httpResponseFactory(); + +// $httpResponseFactory1 === $httpResponseFactory2 +$httpResponseFactory1 = Discovery::httpResponseFactory(singleton: true); +$httpResponseFactory2 = Discovery::httpResponseFactory(singleton: true); +``` + +## Mocking Priority + +This library will give priority to searching for a known, available mocking library before searching for a real implementation. This is to allow for easier testing of code that uses this library. + +The expectation is that these mocking libraries will always be installed as development dependencies, and therefore if they are available, they are intended to be used. + +## Preferring an Implementation + +If you wish to prefer a specific implementation over others, you can `prefer()` it by package name: + +```php +use PsrDiscovery\Discovery; +use PsrDiscovery\Implementations\Psr17\RequestFactories; + +// Prefer the a specific implementation of PSR-17 over others. +RequestFactories::prefer('nyholm/psr7'); + +// Return an instance of Nyholm\Psr7\Factory\Psr17Factory, +// or the next available from the list of candidates, +// Returns null if none are discovered. +$factory = Discovery::httpRequestFactory(); +``` + +In this case, this will cause the `httpRequestFactory()` method to return the preferred implementation if it is available, otherwise, it will fall back to the default behavior. The same applies to `httpResponseFactory()` and `httpStreamFactory()` when their relevant classes are configured similarly. + +Note that assigning a preferred implementation will give it priority over the default preference of mocking libraries. + +## Using a Specific Implementation + +If you wish to force a specific implementation and ignore the rest of the discovery candidates, you can `use()` its package name: + +```php +use PsrDiscovery\Discovery; +use PsrDiscovery\Implementations\Psr17\ResponseFactories; + +// Only discover a specific implementation of PSR-17. +ResponseFactories::use('nyholm/psr7'); + +// Return an instance of Nyholm\Psr7\Factory\Psr17Factory, +// or null if it is not available. +$factory = Discovery::httpResponseFactory(); +``` + +In this case, this will cause the `httpResponseFactory()` method to return the preferred implementation if it is available, otherwise, it will return `null`. The same applies to `httpRequestFactory()` and `httpStreamFactory()` when their relevant classes are configured similarly. + +--- + +This library is not produced or endorsed by, or otherwise affiliated with, the PHP-FIG. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..18cfc6d --- /dev/null +++ b/composer.json @@ -0,0 +1,91 @@ +{ + "name": "psr-discovery/http-factory-implementations", + "description": "Lightweight library that discovers available PSR-17 HTTP Factory implementations by searching for a list of well-known classes that implement the relevant interface, and returns an instance of the first one that is found.", + "license": "MIT", + "type": "library", + "keywords": [ + "psr", + "discovery", + "psr-18" + ], + "authors": [ + { + "name": "Evan Sims", + "email": "hello@evansims.com", + "homepage": "https://evansims.com/" + } + ], + "homepage": "https://github.com/psr-discovery/http-factory-implementations", + "require": { + "php": "^8.0", + "psr/http-factory": "^1.0", + "psr-discovery/discovery": "@dev" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.14", + "infection/infection": "^0.26", + "mockery/mockery": "^1.5", + "pestphp/pest": "^2.0", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-strict-rules": "^1.5", + "rector/rector": "^0.15", + "vimeo/psalm": "^5.8", + "wikimedia/composer-merge-plugin": "^2.0" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "autoload": { + "psr-4": { + "PsrDiscovery\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "PsrDiscovery\\Tests\\": "tests" + } + }, + "config": { + "allow-plugins": { + "infection/extension-installer": true, + "pestphp/pest-plugin": true, + "wikimedia/composer-merge-plugin": true + }, + "optimize-autoloader": true, + "preferred-install": "dist", + "process-timeout": 0, + "sort-packages": true + }, + "extra": { + "merge-plugin": { + "ignore-duplicates": false, + "include": [ + "composer.local.json" + ], + "merge-dev": true, + "merge-extra": false, + "merge-extra-deep": false, + "merge-scripts": false, + "recurse": true, + "replace": true + } + }, + "scripts": { + "mutate": "@php ./vendor/bin/infection --test-framework=pest --show-mutations", + "pest:coverage": "@php vendor/bin/pest --order-by random --compact --coverage", + "pest": "@php vendor/bin/pest --order-by random --compact", + "phpcs:fix": "@php vendor/bin/php-cs-fixer fix src", + "phpcs": "@php vendor/bin/php-cs-fixer fix src --dry-run --diff", + "phpstan": "@php vendor/bin/phpstan analyze", + "psalm:fix": "@php vendor/bin/psalter --issues=all", + "psalm": "@php vendor/bin/psalm", + "rector:fix": "@php vendor/bin/rector process src", + "rector": "@php vendor/bin/rector process src --dry-run", + "test": [ + "@pest", + "@phpstan", + "@psalm", + "@rector", + "@phpcs" + ] + } +} diff --git a/docs/.gitkeep b/docs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/infection.json5.dist b/infection.json5.dist new file mode 100644 index 0000000..ac2a7ee --- /dev/null +++ b/infection.json5.dist @@ -0,0 +1,11 @@ +{ + source: { + directories: ["src"], + }, + logs: { + "text": "infection.log" + }, + mutators: { + "@default": true, + } +} diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..e79b78a --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,15 @@ +includes: + - ./vendor/phpstan/phpstan-strict-rules/rules.neon + +parameters: + level: max + + paths: + - src + + bootstrapFiles: + + ignoreErrors: + + reportUnmatchedIgnoredErrors: true + treatPhpDocTypesAsCertain: false diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..4d5c4b3 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,24 @@ + + + + + ./src/ + + + + ./src/Contracts + ./src/Exceptions + + + + + + + + + + + tests/Unit + + + diff --git a/psalm.xml.dist b/psalm.xml.dist new file mode 100644 index 0000000..8f1837d --- /dev/null +++ b/psalm.xml.dist @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..6ed5929 --- /dev/null +++ b/rector.php @@ -0,0 +1,576 @@ +paths([ + __DIR__ . '/config', + __DIR__ . '/src', + ]); + + $rectorConfig->ruleWithConfiguration( + RenameFunctionRector::class, + [ + 'chop' => 'rtrim', + 'doubleval' => 'floatval', + 'fputs' => 'fwrite', + 'gzputs' => 'gzwrites', + 'ini_alter' => 'ini_set', + 'is_double' => 'is_float', + 'is_integer' => 'is_int', + 'is_long' => 'is_int', + 'is_real' => 'is_float', + 'is_writeable' => 'is_writable', + 'join' => 'implode', + 'key_exists' => 'array_key_exists', + 'mbstrcut' => 'mb_strcut', + 'mbstrlen' => 'mb_strlen', + 'mbstrpos' => 'mb_strpos', + 'mbstrrpos' => 'mb_strrpos', + 'mbsubstr' => 'mb_substr', + 'pos' => 'current', + 'sizeof' => 'count', + 'split' => 'explode', + 'strchr' => 'strstr', + ], + ); + + $rectorConfig->ruleWithConfiguration( + StaticCallToFuncCallRector::class, + [ + new StaticCallToFuncCall('Nette\\Utils\\Strings', 'contains', 'str_contains'), + new StaticCallToFuncCall('Nette\\Utils\\Strings', 'endsWith', 'str_ends_with'), + new StaticCallToFuncCall('Nette\\Utils\\Strings', 'startsWith', 'str_starts_with'), + ], + ); + + $rectorConfig->ruleWithConfiguration( + ArgumentAdderRector::class, + [new ArgumentAdder('Nette\\Utils\\Strings', 'replace', 2, 'replacement', '')], + ); + + $rectorConfig->ruleWithConfiguration( + RenameFunctionRector::class, + [ + 'pg_clientencoding' => 'pg_client_encoding', + 'pg_cmdtuples' => 'pg_affected_rows', + 'pg_errormessage' => 'pg_last_error', + 'pg_fieldisnull' => 'pg_field_is_null', + 'pg_fieldname' => 'pg_field_name', + 'pg_fieldnum' => 'pg_field_num', + 'pg_fieldprtlen' => 'pg_field_prtlen', + 'pg_fieldsize' => 'pg_field_size', + 'pg_fieldtype' => 'pg_field_type', + 'pg_freeresult' => 'pg_free_result', + 'pg_getlastoid' => 'pg_last_oid', + 'pg_loclose' => 'pg_lo_close', + 'pg_locreate' => 'pg_lo_create', + 'pg_loexport' => 'pg_lo_export', + 'pg_loimport' => 'pg_lo_import', + 'pg_loopen' => 'pg_lo_open', + 'pg_loread' => 'pg_lo_read', + 'pg_loreadall' => 'pg_lo_read_all', + 'pg_lounlink' => 'pg_lo_unlink', + 'pg_lowrite' => 'pg_lo_write', + 'pg_numfields' => 'pg_num_fields', + 'pg_numrows' => 'pg_num_rows', + 'pg_result' => 'pg_fetch_result', + 'pg_setclientencoding' => 'pg_set_client_encoding' + ], + ); + + $rectorConfig->ruleWithConfiguration( + FunctionArgumentDefaultValueReplacerRector::class, + [ + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'gte', 'ge'), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'lte', 'le'), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, '', '!='), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, '!', '!='), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'g', 'gt'), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'l', 'lt'), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'gte', 'ge'), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'lte', 'le'), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'n', 'ne') + ], + ); + + $rectorConfig->ruleWithConfiguration( + FuncCallToConstFetchRector::class, + [ + 'php_sapi_name' => 'PHP_SAPI', + 'pi' => 'M_PI' + ], + ); + + $rectorConfig->rules([ + // AddParamTypeFromPropertyTypeRector::class, + // ChangeOrIfReturnToEarlyReturnRector::class, + // RemoveAlwaysTrueIfConditionRector::class, + // RenameParamToMatchTypeRector::class, + AbsolutizeRequireAndIncludePathRector::class, + ActionInjectionToConstructorInjectionRector::class, + AddArrayDefaultToArrayPropertyRector::class, + AddArrowFunctionReturnTypeRector::class, + AddClosureReturnTypeRector::class, + AddFalseDefaultToBoolPropertyRector::class, + AddMethodCallBasedStrictParamTypeRector::class, + AddParamBasedOnParentClassMethodRector::class, + AddParamTypeBasedOnPHPUnitDataProviderRector::class, + AddParamTypeSplFixedArrayRector::class, + AddPregQuoteDelimiterRector::class, + AddReturnTypeDeclarationBasedOnParentClassMethodRector::class, + AddReturnTypeDeclarationFromYieldsRector::class, + AddVoidReturnTypeWhereNoReturnRector::class, + AndAssignsToSeparateLinesRector::class, + ArrayKeyExistsTernaryThenValueToCoalescingRector::class, + ArrayKeysAndInArrayToArrayKeyExistsRector::class, + ArrayMergeOfNonArraysToSimpleArrayRector::class, + ArrayShapeFromConstantArrayReturnRector::class, + BinarySwitchToIfElseRector::class, + BooleanNotIdenticalToNotIdenticalRector::class, + BoolvalToTypeCastRector::class, + CallableThisArrayToAnonymousFunctionRector::class, + CallUserFuncArrayToVariadicRector::class, + CallUserFuncToMethodCallRector::class, + CallUserFuncToMethodCallRector::class, + CallUserFuncWithArrowFunctionToInlineRector::class, + CatchExceptionNameMatchingTypeRector::class, + // ChangeAndIfToEarlyReturnRector::class, + ChangeArrayPushToArrayAssignRector::class, + ChangeGlobalVariablesToPropertiesRector::class, + ChangeIfElseValueAssignToEarlyReturnRector::class, + ChangeNestedForeachIfsToEarlyContinueRector::class, + ChangeNestedIfsToEarlyReturnRector::class, + ChangeOrIfContinueToMultiContinueRector::class, + ChangeReadOnlyPropertyWithDefaultValueToConstantRector::class, + ChangeReadOnlyVariableWithDefaultValueToConstantRector::class, + ChangeSwitchToMatchRector::class, + ClassOnObjectRector::class, + ClassOnThisVariableObjectRector::class, + ClassPropertyAssignToConstructorPromotionRector::class, + CombinedAssignRector::class, + CombineIfRector::class, + CommonNotEqualRector::class, + CompactToVariablesRector::class, + CompleteDynamicPropertiesRector::class, + ConsecutiveNullCompareReturnsToNullCoalesceQueueRector::class, + ConsistentImplodeRector::class, + ConsistentPregDelimiterRector::class, + CountArrayToEmptyArrayComparisonRector::class, + CountArrayToEmptyArrayComparisonRector::class, + EmptyOnNullableObjectToInstanceOfRector::class, + EncapsedStringsToSprintfRector::class, + ExplicitBoolCompareRector::class, + ExplicitMethodCallOverMagicGetSetRector::class, + FinalizeClassesWithoutChildrenRector::class, + FinalPrivateToPrivateVisibilityRector::class, + FlipTypeControlToUseExclusiveTypeRector::class, + FloatvalToTypeCastRector::class, + ForeachItemsAssignToEmptyArrayToAssignRector::class, + ForeachToInArrayRector::class, + ForRepeatedCountToOwnVariableRector::class, + ForToForeachRector::class, + FuncGetArgsToVariadicParamRector::class, + FuncGetArgsToVariadicParamRector::class, + GetClassToInstanceOfRector::class, + GetDebugTypeRector::class, + InlineArrayReturnAssignRector::class, + InlineConstructorDefaultToPropertyRector::class, + InlineIfToExplicitIfRector::class, + InlineIsAInstanceOfRector::class, + IntvalToTypeCastRector::class, + IsAWithStringWithThirdArgumentRector::class, + IssetOnPropertyObjectToPropertyExistsRector::class, + JoinStringConcatRector::class, + LogicalToBooleanRector::class, + MakeInheritedMethodVisibilitySameAsParentRector::class, + // MixedTypeRector::class, + MultipleClassFileToPsr4ClassesRector::class, + NarrowUnionTypeDocRector::class, + // NewlineAfterStatementRector::class, + NewlineBeforeNewAssignSetRector::class, + NewStaticToNewSelfRector::class, + NormalizeNamespaceByPSR4ComposerAutoloadRector::class, + NullableCompareToNullRector::class, + OptionalParametersAfterRequiredRector::class, + OptionalParametersAfterRequiredRector::class, + ParamAnnotationIncorrectNullableRector::class, + ParamTypeByMethodCallTypeRector::class, + ParamTypeByParentCallTypeRector::class, + ParamTypeFromStrictTypedPropertyRector::class, + Php8ResourceReturnToObjectRector::class, + PostIncDecToPreIncDecRector::class, + PreparedValueToEarlyReturnRector::class, + PrivatizeFinalClassMethodRector::class, + PrivatizeFinalClassPropertyRector::class, + // PrivatizeLocalGetterToPropertyRector::class, + PropertyTypeFromStrictSetterGetterRector::class, + // RecastingRemovalRector::class, + RemoveAlwaysElseRector::class, + RemoveAlwaysTrueConditionSetInConstructorRector::class, + RemoveAndTrueRector::class, + // RemoveConcatAutocastRector::class, + RemoveDeadConditionAboveReturnRector::class, + RemoveDeadContinueRector::class, + RemoveDeadIfForeachForRector::class, + // RemoveDeadInstanceOfRector::class, + RemoveDeadLoopRector::class, + RemoveDeadReturnRector::class, + RemoveDeadStmtRector::class, + RemoveDeadTryCatchRector::class, + RemoveDeadZeroAndOneOperationRector::class, + RemoveDelegatingParentCallRector::class, + RemoveDoubleAssignRector::class, + RemoveDoubleUnderscoreInMethodNameRector::class, + RemoveDuplicatedArrayKeyRector::class, + RemoveDuplicatedCaseInSwitchRector::class, + RemoveDuplicatedIfReturnRector::class, + RemoveDuplicatedInstanceOfRector::class, + RemoveEmptyClassMethodRector::class, + RemoveEmptyMethodCallRector::class, + RemoveEmptyTestMethodRector::class, + RemoveExtraParametersRector::class, + RemoveFinalFromConstRector::class, + RemoveJustPropertyFetchForAssignRector::class, + RemoveJustVariableAssignRector::class, + RemoveLastReturnRector::class, + RemoveNonExistingVarAnnotationRector::class, + RemoveNullPropertyInitializationRector::class, + RemoveParentCallWithoutParentRector::class, + RemoveParentCallWithoutParentRector::class, + RemoveSoleValueSprintfRector::class, + RemoveUnreachableStatementRector::class, + RemoveUnusedConstructorParamRector::class, + RemoveUnusedForeachKeyRector::class, + RemoveUnusedNonEmptyArrayBeforeForeachRector::class, + RemoveUnusedPrivateClassConstantRector::class, + RemoveUnusedPrivateMethodParameterRector::class, + RemoveUnusedPrivateMethodRector::class, + RemoveUnusedPrivatePropertyRector::class, + RemoveUnusedPromotedPropertyRector::class, + RemoveUnusedVariableAssignRector::class, + RemoveUnusedVariableInCatchRector::class, + // RemoveUselessParamTagRector::class, + RemoveUselessReturnTagRector::class, + RemoveUselessVarTagRector::class, + RenameForeachValueVariableToMatchExprVariableRector::class, + RenameForeachValueVariableToMatchMethodCallReturnTypeRector::class, + // RenamePropertyToMatchTypeRector::class, + // RenameVariableToMatchMethodCallReturnTypeRector::class, + // RenameVariableToMatchNewTypeRector::class, + ReplaceMultipleBooleanNotRector::class, + ReturnAnnotationIncorrectNullableRector::class, + ReturnBinaryAndToEarlyReturnRector::class, + ReturnBinaryOrToEarlyReturnRector::class, + ReturnEarlyIfVariableRector::class, + ReturnNeverTypeRector::class, + ReturnTypeFromReturnDirectArrayRector::class, + ReturnTypeFromReturnNewRector::class, + ReturnTypeFromStrictBoolReturnExprRector::class, + ReturnTypeFromStrictConstantReturnRector::class, + ReturnTypeFromStrictNativeCallRector::class, + ReturnTypeFromStrictNewArrayRector::class, + ReturnTypeFromStrictScalarReturnExprRector::class, + ReturnTypeFromStrictScalarReturnExprRector::class, + ReturnTypeFromStrictTernaryRector::class, + ReturnTypeFromStrictTypedCallRector::class, + ReturnTypeFromStrictTypedPropertyRector::class, + SeparateMultiUseImportsRector::class, + SetStateToStaticRector::class, + SetTypeToCastRector::class, + ShortenElseIfRector::class, + SimplifyArraySearchRector::class, + SimplifyBoolIdenticalTrueRector::class, + SimplifyConditionsRector::class, + SimplifyDeMorganBinaryRector::class, + SimplifyEmptyArrayCheckRector::class, + SimplifyEmptyCheckOnEmptyArrayRector::class, + SimplifyForeachToArrayFilterRector::class, + SimplifyForeachToCoalescingRector::class, + SimplifyFuncGetArgsCountRector::class, + SimplifyIfElseToTernaryRector::class, + SimplifyIfElseWithSameContentRector::class, + SimplifyIfExactValueReturnValueRector::class, + SimplifyIfNotNullReturnRector::class, + SimplifyIfNullableReturnRector::class, + SimplifyIfReturnBoolRector::class, + SimplifyInArrayValuesRector::class, + SimplifyMirrorAssignRector::class, + SimplifyRegexPatternRector::class, + SimplifyStrposLowerRector::class, + SimplifyTautologyTernaryRector::class, + SimplifyUselessLastVariableAssignRector::class, + SimplifyUselessVariableRector::class, + SingleInArrayToCompareRector::class, + SingularSwitchToIfRector::class, + SplitDoubleAssignRector::class, + SplitGroupedClassConstantsRector::class, + SplitGroupedPropertiesRector::class, + SplitListAssignToSeparateLineRector::class, + StaticArrowFunctionRector::class, + StaticClosureRector::class, + StrContainsRector::class, + StrEndsWithRector::class, + StrictArraySearchRector::class, + StringableForToStringRector::class, + // StringClassNameToClassConstantRector::class, + StrlenZeroToIdenticalEmptyStringRector::class, + StrStartsWithRector::class, + StrvalToTypeCastRector::class, + SwitchNegatedTernaryRector::class, + SymplifyQuoteEscapeRector::class, + TernaryConditionVariableAssignmentRector::class, + TernaryEmptyArrayArrayDimFetchToCoalesceRector::class, + TernaryFalseExpressionToIfRector::class, + TernaryToBooleanOrFalseToBooleanAndRector::class, + ThrowWithPreviousExceptionRector::class, + TokenGetAllToObjectRector::class, + TypedPropertyFromAssignsRector::class, + TypedPropertyFromStrictConstructorRector::class, + TypedPropertyFromStrictGetterMethodReturnTypeRector::class, + TypedPropertyFromStrictSetUpRector::class, + // UnionTypesRector::class, + UnnecessaryTernaryExpressionRector::class, + UnSpreadOperatorRector::class, + UnusedForeachValueToArrayKeysRector::class, + UnwrapFutureCompatibleIfPhpVersionRector::class, + UnwrapSprintfOneArgumentRector::class, + UseClassKeywordForClassNameResolutionRector::class, + UseIdenticalOverEqualWithSameTypeRector::class, + UseIncrementAssignRector::class, + VarAnnotationIncorrectNullableRector::class, + VarConstantCommentRector::class, + VarToPublicPropertyRector::class, + VersionCompareFuncCallToConstantRector::class, + WrapEncapsedVariableInCurlyBracesRector::class, + ]); +}; diff --git a/src/Contracts/Implementations/Psr17/FactoriesContract.php b/src/Contracts/Implementations/Psr17/FactoriesContract.php new file mode 100644 index 0000000..4069eed --- /dev/null +++ b/src/Contracts/Implementations/Psr17/FactoriesContract.php @@ -0,0 +1,38 @@ +add(CandidateEntity::create( + package: 'psr-mock/http-factory-implementation', + version: '^1.0', + builder: static fn (string $class = '\PsrMock\Psr17\RequestFactory'): object => new $class(), + )); + + // nyholm/psr7 1.2+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'nyholm/psr7', + version: '^1.2', + builder: static fn (string $class = '\Nyholm\Psr7\Factory\Psr17Factory'): object => new $class(), + )); + + // guzzlehttp/psr7 1.6+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'guzzlehttp/psr7', + version: '^1.6', + builder: static fn (string $class = '\GuzzleHttp\Psr7\HttpFactory'): object => new $class(), + )); + + // zendframework/zend-diactoros 2.0+ is PSR-17 compatible. (Caution: Abandoned!) + self::$candidates->add(CandidateEntity::create( + package: 'zendframework/zend-diactoros', + version: '^2.0', + builder: static fn (string $class = '\Zend\Diactoros\RequestFactory'): object => new $class(), + )); + + // http-interop/http-factory-guzzle 1.0+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'http-interop/http-factory-guzzle', + version: '^1.0', + builder: static fn (string $class = '\Http\Factory\Guzzle\RequestFactory'): object => new $class(), + )); + + // laminas/laminas-diactoros 2.0+ is PSR-17 compatible + self::$candidates->add(CandidateEntity::create( + package: 'laminas/laminas-diactoros', + version: '^2.0', + builder: static fn (string $class = '\Laminas\Diactoros\RequestFactory'): object => new $class(), + )); + + // slim/psr7 1.0+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'slim/psr7', + version: '^1.0', + builder: static fn (string $class = '\Slim\Psr7\Factory\RequestFactory'): object => new $class(), + )); + + // typo3/core 10.0+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'typo3/core', + version: '^10.0', + builder: static fn (string $class = '\TYPO3\CMS\Core\Http\RequestFactory'): object => new $class(), + )); + + // nimbly/capsule 1.0+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'nimbly/capsule', + version: '^1.0', + builder: static fn (string $class = '\Nimbly\Capsule\Factory\RequestFactory'): object => new $class(), + )); + + // httpsoft/http-message 1.0+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'httpsoft/http-message', + version: '^1.0', + builder: static fn (string $class = '\HttpSoft\Message\RequestFactory'): object => new $class(), + )); + + return self::$candidates; + } + + /** + * @psalm-suppress MoreSpecificReturnType,LessSpecificReturnStatement + */ + public static function discover(): ?RequestFactoryInterface + { + if (null !== self::$using) { + return self::$using; + } + + return Discover::httpRequestFactory(); + } + + public static function prefer(string $package): void + { + self::$candidates ??= CandidatesCollection::create(); + parent::prefer($package); + self::use(null); + } + + public static function set(CandidatesCollection $candidates): void + { + self::$candidates ??= CandidatesCollection::create(); + parent::set($candidates); + self::use(null); + } + + public static function singleton(): ?RequestFactoryInterface + { + if (null !== self::$using) { + return self::$using; + } + + return self::$singleton ??= self::discover(); + } + + public static function use(?RequestFactoryInterface $instance): void + { + self::$singleton = $instance; + self::$using = $instance; + } +} diff --git a/src/Implementations/Psr17/ResponseFactories.php b/src/Implementations/Psr17/ResponseFactories.php new file mode 100644 index 0000000..4fd3fc2 --- /dev/null +++ b/src/Implementations/Psr17/ResponseFactories.php @@ -0,0 +1,150 @@ +add(CandidateEntity::create( + package: 'psr-mock/http-factory-implementation', + version: '^1.0', + builder: static fn (string $class = '\PsrMock\Psr17\ResponseFactory'): object => new $class(), + )); + + // nyholm/psr7 1.2+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'nyholm/psr7', + version: '^1.2', + builder: static fn (string $class = '\Nyholm\Psr7\Factory\Psr17Factory'): object => new $class(), + )); + + // guzzlehttp/psr7 1.6+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'guzzlehttp/psr7', + version: '^1.6', + builder: static fn (string $class = '\GuzzleHttp\Psr7\HttpFactory'): object => new $class(), + )); + + // zendframework/zend-diactoros 2.0+ is PSR-17 compatible. (Caution: Abandoned!) + self::$candidates->add(CandidateEntity::create( + package: 'zendframework/zend-diactoros', + version: '^2.0', + builder: static fn (string $class = '\Zend\Diactoros\ResponseFactory'): object => new $class(), + )); + + // http-interop/http-factory-guzzle 1.0+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'http-interop/http-factory-guzzle', + version: '^1.0', + builder: static fn (string $class = '\Http\Factory\Guzzle\ResponseFactory'): object => new $class(), + )); + + // laminas/laminas-diactoros 2.0+ is PSR-17 compatible + self::$candidates->add(CandidateEntity::create( + package: 'laminas/laminas-diactoros', + version: '^2.0', + builder: static fn (string $class = '\Laminas\Diactoros\ResponseFactory'): object => new $class(), + )); + + // slim/psr7 1.0+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'slim/psr7', + version: '^1.0', + builder: static fn (string $class = '\Slim\Psr7\Factory\ResponseFactory'): object => new $class(), + )); + + // typo3/core 10.0+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'typo3/core', + version: '^10.0', + builder: static fn (string $class = '\TYPO3\CMS\Core\Http\ResponseFactory'): object => new $class(), + )); + + // nimbly/capsule 1.0+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'nimbly/capsule', + version: '^1.0', + builder: static fn (string $class = '\Nimbly\Capsule\Factory\ResponseFactory'): object => new $class(), + )); + + // httpsoft/http-message 1.0+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'httpsoft/http-message', + version: '^1.0', + builder: static fn (string $class = '\HttpSoft\Message\ResponseFactory'): object => new $class(), + )); + + return self::$candidates; + } + + /** + * @psalm-suppress MoreSpecificReturnType,LessSpecificReturnStatement + */ + public static function discover(): ?ResponseFactoryInterface + { + if (null !== self::$using) { + return self::$using; + } + + return Discover::httpResponseFactory(); + } + + public static function prefer(string $package): void + { + self::$candidates ??= CandidatesCollection::create(); + parent::prefer($package); + self::use(null); + } + + public static function set(CandidatesCollection $candidates): void + { + self::$candidates ??= CandidatesCollection::create(); + parent::set($candidates); + self::use(null); + } + + public static function singleton(): ?ResponseFactoryInterface + { + if (null !== self::$using) { + return self::$using; + } + + return self::$singleton ??= self::discover(); + } + + public static function use(?ResponseFactoryInterface $instance): void + { + self::$singleton = $instance; + self::$using = $instance; + } +} diff --git a/src/Implementations/Psr17/StreamFactories.php b/src/Implementations/Psr17/StreamFactories.php new file mode 100644 index 0000000..384d7b5 --- /dev/null +++ b/src/Implementations/Psr17/StreamFactories.php @@ -0,0 +1,150 @@ +add(CandidateEntity::create( + package: 'psr-mock/http-factory-implementation', + version: '^1.0', + builder: static fn (string $class = '\PsrMock\Psr17\StreamFactory'): object => new $class(), + )); + + // nyholm/psr7 1.2+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'nyholm/psr7', + version: '^1.2', + builder: static fn (string $class = '\Nyholm\Psr7\Factory\Psr17Factory'): object => new $class(), + )); + + // guzzlehttp/psr7 1.6+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'guzzlehttp/psr7', + version: '^1.6', + builder: static fn (string $class = '\GuzzleHttp\Psr7\HttpFactory'): object => new $class(), + )); + + // zendframework/zend-diactoros 2.0+ is PSR-17 compatible. (Caution: Abandoned!) + self::$candidates->add(CandidateEntity::create( + package: 'zendframework/zend-diactoros', + version: '^2.0', + builder: static fn (string $class = '\Zend\Diactoros\StreamFactory'): object => new $class(), + )); + + // http-interop/http-factory-guzzle 1.0+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'http-interop/http-factory-guzzle', + version: '^1.0', + builder: static fn (string $class = '\Http\Factory\Guzzle\StreamFactory'): object => new $class(), + )); + + // laminas/laminas-diactoros 2.0+ is PSR-17 compatible + self::$candidates->add(CandidateEntity::create( + package: 'laminas/laminas-diactoros', + version: '^2.0', + builder: static fn (string $class = '\Laminas\Diactoros\StreamFactory'): object => new $class(), + )); + + // slim/psr7 1.0+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'slim/psr7', + version: '^1.0', + builder: static fn (string $class = '\Slim\Psr7\Factory\StreamFactory'): object => new $class(), + )); + + // typo3/core 10.0+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'typo3/core', + version: '^10.0', + builder: static fn (string $class = '\TYPO3\CMS\Core\Http\StreamFactory'): object => new $class(), + )); + + // nimbly/capsule 1.0+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'nimbly/capsule', + version: '^1.0', + builder: static fn (string $class = '\Nimbly\Capsule\Factory\StreamFactory'): object => new $class(), + )); + + // httpsoft/http-message 1.0+ is PSR-17 compatible. + self::$candidates->add(CandidateEntity::create( + package: 'httpsoft/http-message', + version: '^1.0', + builder: static fn (string $class = '\HttpSoft\Message\StreamFactory'): object => new $class(), + )); + + return self::$candidates; + } + + /** + * @psalm-suppress MoreSpecificReturnType,LessSpecificReturnStatement + */ + public static function discover(): ?StreamFactoryInterface + { + if (null !== self::$using) { + return self::$using; + } + + return Discover::httpStreamFactory(); + } + + public static function prefer(string $package): void + { + self::$candidates ??= CandidatesCollection::create(); + parent::prefer($package); + self::use(null); + } + + public static function set(CandidatesCollection $candidates): void + { + self::$candidates ??= CandidatesCollection::create(); + parent::set($candidates); + self::use(null); + } + + public static function singleton(): ?StreamFactoryInterface + { + if (null !== self::$using) { + return self::$using; + } + + return self::$singleton ??= self::discover(); + } + + public static function use(?StreamFactoryInterface $instance): void + { + self::$singleton = $instance; + self::$using = $instance; + } +}