diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fe62752 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,22 @@ +# Git +.git + +# IDEs +.idea +*.iml +.vscode +.zed + +# Composer +vendor + +# PHP code sniffer +.php_cs.cache + +# PHP Unit +build +.phpunit.result.cache + +# Dev / Testing +test.php +coverage/* diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f69d363 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,15 @@ +* text eol=lf +entrypoint.sh linguist-detectable=false +Dockerfile linguist-detectable=false + +/.github export-ignore +/coverage export-ignore +/tests export-ignore +/.dockerignore export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.php-cs-fixer.php export-ignore +/Dockerfile export-ignore +/docker-compose.yml export-ignore +/entrypoint.sh export-ignore +/phpunit.xml export-ignore diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..d48e2ea --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,28 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG] " +labels: "bug" +assignees: rancoud + +--- +## Description + + +## Steps to reproduce this issue +1. +2. +3. +4. + +## Expected behavior + + +## Actual behavior + + +## Screenshots + + +## Additional context + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..6227d75 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,23 @@ +--- +name: Feature request +about: Suggest an idea +title: "[FEATURE] " +labels: "enhancement" +assignees: rancoud + +--- + +## My problem + + +## My suggestion + + +## Alternatives + + +## Additional context + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..1bc6936 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ + +# Description diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a8ce69f..336f33b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,91 +1,57 @@ name: test +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + on: push: + branches: + - master pull_request: + paths-ignore: + - '**.md' + types: + - opened + - synchronize + - reopened jobs: test: runs-on: ${{ matrix.os }} + strategy: fail-fast: false matrix: os: - ubuntu-latest - - services: - mysql: - image: mysql:5.7 - env: - MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_USER: root - MYSQL_PASSWORD: '' - MYSQL_DATABASE: test_database - ports: - - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 - - redis: - image: redis - options: >- - --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 6379:6379 + php-version: + - '8.4' + - '8.5' steps: - - name: Verify MySQL connection from host - run: | - sudo apt-get install -y mysql-client - mysql --host 127.0.0.1 --port 3306 -uroot --password="" -e "SHOW DATABASES" - - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v6 - - uses: shivammathur/setup-php@v2 - with: - php-version: '7.4' - coverage: xdebug - - - name: Set files permission - run: | - sudo chown -R www-data:www-data /var/lib/php/sessions/ - sudo chmod -R 777 /var/lib/php/sessions/ - - - name: Validate composer.json and composer.lock - run: composer validate - - - name: Cache Composer packages - id: composer-cache - uses: actions/cache@v2 - with: - path: vendor - key: ${{ runner.os }}-node-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-node- + - name: Pull + run: docker compose pull --quiet - - name: Install dependencies - if: steps.composer-cache.outputs.cache-hit != 'true' - run: composer install --prefer-dist --no-progress --no-suggest + - name: Build + run: docker compose build --build-arg PHPVERSION=${{ matrix.php-version }} - - name: PHPCS - if: ${{ runner.os != 'Windows' }} - run: | - vendor/bin/php-cs-fixer fix --diff --dry-run + - name: Lint + if: matrix.php-version == '8.4' + run: docker compose run lib composer lint - - name: PHPUnit - run: | - vendor/bin/phpunit --colors --coverage-text --coverage-clover coverage/clover.xml - env: - MYSQL_HOST: 127.0.0.1 - MYSQL_PORT: ${{ job.services.mysql.ports[3306] }} - MYSQL_USER: root - MYSQL_PASSWORD: + - name: Test + run: docker compose run lib composer test - name: Upload coverage - uses: codecov/codecov-action@v1.0.7 + uses: codecov/codecov-action@v6 if: success() with: token: ${{ secrets.CODECOV_TOKEN }} - file: ./coverage/clover.xml \ No newline at end of file + files: ./coverage/clover.xml diff --git a/.gitignore b/.gitignore index 6bb5f43..45b8f15 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,20 @@ -# IntelliJ -.idea -*.iml - -# Composer -vendor/ - -# PHP code sniffer -.php_cs.cache - -# PHP Unit -build/ -.phpunit.result.cache - -# Dev / Testing -test.php -coverage/ \ No newline at end of file +# IDEs +.idea +*.iml +.vscode +.zed + +# Composer +vendor/ + +# PHP code sniffer +.php-cs-fixer.cache + +# PHP Unit +build/ +.phpunit.result.cache + +# Dev / Testing +test.php +coverage/* +!coverage/.gitkeep \ No newline at end of file diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php new file mode 100644 index 0000000..0b39c42 --- /dev/null +++ b/.php-cs-fixer.php @@ -0,0 +1,413 @@ + true, + 'backtick_to_shell_exec' => true, + 'ereg_to_preg' => true, + 'mb_str_functions' => true, + 'modernize_strpos' => ['modernize_stripos' => true], + 'no_alias_functions' => ['sets' => ['@all']], + 'no_alias_language_construct_call' => true, + 'no_mixed_echo_print' => ['use' => 'echo'], + 'pow_to_exponentiation' => true, + 'random_api_migration' => ['replacements' => ['getrandmax' => 'mt_getrandmax', 'srand' => 'mt_srand', 'mt_rand' => 'random_int', 'rand' => 'random_int']], + 'set_type_to_cast' => true +]; + +$arrayNotationRules = [ + 'array_syntax' => ['syntax' => 'short'], + 'no_multiline_whitespace_around_double_arrow' => true, + 'no_whitespace_before_comma_in_array' => ['after_heredoc' => true], + 'normalize_index_brace' => true, + 'return_to_yield_from' => true, + 'trim_array_spaces' => true, + 'whitespace_after_comma_in_array' => ['ensure_single_space' => true], + 'yield_from_array_to_yields' => true +]; + +$attributeNotationRules = [ + 'attribute_empty_parentheses' => ['use_parentheses' => false], + 'general_attribute_remove' => ['attributes' => []], + 'ordered_attributes' => ['order' => [], 'sort_algorithm' => 'alpha'] +]; + +$basicRules = [ + 'braces_position' => [ + 'allow_single_line_anonymous_functions' => true, + 'allow_single_line_empty_anonymous_classes' => true, + 'anonymous_classes_opening_brace' => 'same_line', + 'anonymous_functions_opening_brace' => 'same_line', + 'classes_opening_brace' => 'next_line_unless_newline_at_signature_end', + 'control_structures_opening_brace' => 'same_line', + 'functions_opening_brace' => 'next_line_unless_newline_at_signature_end' + ], + 'encoding' => true, + 'no_multiple_statements_per_line' => true, + 'no_trailing_comma_in_singleline' => ['elements' => ['arguments', 'array', 'array_destructuring', 'group_import']], + 'non_printable_character' => ['use_escape_sequences_in_strings' => true], + 'numeric_literal_separator' => false, + 'octal_notation' => true, + 'psr_autoloading' => ['dir' => null], + 'single_line_empty_body' => true +]; + +$casingRules = [ + 'class_reference_name_casing' => true, + 'constant_case' => ['case' => 'lower'], + 'integer_literal_case' => true, + 'lowercase_keywords' => true, + 'lowercase_static_reference' => true, + 'magic_constant_casing' => true, + 'magic_method_casing' => true, + 'native_function_casing' => true, + 'native_type_declaration_casing' => true +]; + +$castNotationRules = [ + 'cast_spaces' => ['space' => 'single'], + 'lowercase_cast' => true, + 'modernize_types_casting' => true, + 'no_short_bool_cast' => true, + 'no_unset_cast' => true, + 'short_scalar_cast' => true +]; + +$classNotationRules = [ + 'class_attributes_separation' => ['elements' => ['const' => 'one', 'method' => 'one', 'property' => 'one', 'trait_import' => 'none', 'case' => 'none']], + 'class_definition' => ['inline_constructor_arguments' => true, 'multi_line_extends_each_single_line' => false, 'single_item_single_line' => true, 'single_line' => true, 'space_before_parenthesis' => false], + 'final_class' => false, + 'final_internal_class' => false, + 'final_public_method_for_abstract_class' => false, + 'no_blank_lines_after_class_opening' => true, + 'no_null_property_initialization' => true, + 'no_php4_constructor' => true, + 'no_unneeded_final_method' => ['private_methods' => true], + 'ordered_class_elements' => ['case_sensitive' => false, 'order' => ['use_trait', 'case', 'constant_public', 'constant_protected', 'constant_private', 'property_public', 'property_protected', 'property_private', 'construct', 'destruct', 'magic', 'phpunit'], 'sort_algorithm' => 'none'], + 'ordered_interfaces' => ['case_sensitive' => false, 'direction' => 'ascend', 'order' => 'alpha'], + 'ordered_traits' => ['case_sensitive' => false], + 'ordered_types' => ['case_sensitive' => false, 'null_adjustment' => 'always_last', 'sort_algorithm' => 'alpha'], + 'phpdoc_readonly_class_comment_to_keyword' => true, + 'protected_to_private' => false, + 'self_accessor' => true, + 'self_static_accessor' => true, + 'single_class_element_per_statement' => ['elements' => ['const', 'property']], + 'single_trait_insert_per_statement' => true, + 'visibility_required' => ['elements' => ['const', 'method', 'property']] +]; + +$classUsageRules = [ + 'date_time_immutable' => true +]; + +$commentRules = [ + 'comment_to_phpdoc' => ['ignored_tags' => ['codeCoverageIgnoreStart', 'codeCoverageIgnoreEnd']], + 'header_comment' => false, + 'multiline_comment_opening_closing' => true, + 'no_empty_comment' => true, + 'no_trailing_whitespace_in_comment' => true, + 'single_line_comment_spacing' => true, + 'single_line_comment_style' => ['comment_types' => ['hash']] +]; + +$constantNotationRules = [ + 'native_constant_invocation' => ['exclude' => ['null', 'false', 'true'], 'fix_built_in' => true, 'include' => [], 'scope' => 'all', 'strict' => false], +]; + +$controlStructureRules = [ + 'control_structure_braces' => true, + 'control_structure_continuation_position' => ['position' => 'same_line'], + 'elseif' => true, + 'empty_loop_body' => ['style' => 'braces'], + 'empty_loop_condition' => ['style' => 'while'], + 'include' => true, + 'no_alternative_syntax' => ['fix_non_monolithic_code' => true], + 'no_break_comment' => ['comment_text' => 'no break'], + 'no_superfluous_elseif' => true, + 'no_unneeded_braces' => ['namespaces' => true], + 'no_unneeded_control_parentheses' => ['statements' => ['break', 'clone', 'continue', 'echo_print', 'others', 'return', 'switch_case', 'yield', 'yield_from']], + 'no_useless_else' => true, + 'simplified_if_return' => true, + 'switch_case_semicolon_to_colon' => true, + 'switch_case_space' => true, + 'switch_continue_to_break' => true, + 'trailing_comma_in_multiline' => false, + 'yoda_style' => false +]; + +$doctrineAnnotationRules = [ + 'doctrine_annotation_array_assignment' => false, + 'doctrine_annotation_braces' => false, + 'doctrine_annotation_indentation' => false, + 'doctrine_annotation_spaces' => false +]; + +$functionNotationRules = [ + 'combine_nested_dirname' => true, + 'date_time_create_from_format_call' => true, + 'fopen_flag_order' => true, + 'fopen_flags' => ['b_mode' => false], + 'function_declaration' => ['closure_fn_spacing' => 'one', 'closure_function_spacing' => 'one', 'trailing_comma_single_line' => false], + 'implode_call' => true, + 'lambda_not_used_import' => true, + 'method_argument_space' => ['after_heredoc' => false, 'attribute_placement' => 'ignore', 'keep_multiple_spaces_after_comma' => false, 'on_multiline' => 'ignore'], + 'native_function_invocation' => ['exclude' => [], 'include' => ['@all', '@compiler_optimized', '@internal'], 'scope' => 'all', 'strict' => false], + 'no_spaces_after_function_name' => true, + 'no_unreachable_default_argument_value' => true, + 'no_useless_sprintf' => true, + 'nullable_type_declaration_for_default_null_value' => true, + 'phpdoc_to_param_type' => false, + 'phpdoc_to_property_type' => false, + 'phpdoc_to_return_type' => false, + 'regular_callable_call' => true, + 'return_type_declaration' => ['space_before' => 'none'], + 'single_line_throw' => false, + 'static_lambda' => true, + 'use_arrow_functions' => false, + 'void_return' => true +]; + +$importRules = [ + 'fully_qualified_strict_types' => ['import_symbols' => false, 'leading_backslash_in_global_namespace' => false, 'phpdoc_tags' => ['param', 'phpstan-param', 'phpstan-property', 'phpstan-property-read', 'phpstan-property-write', 'phpstan-return', 'phpstan-var', 'property', 'property-read', 'property-write', 'psalm-param', 'psalm-property', 'psalm-property-read', 'psalm-property-write', 'psalm-return', 'psalm-var', 'return', 'see', 'throws', 'var']], + 'global_namespace_import' => ['import_classes' => false, 'import_constants' => false, 'import_functions' => false], + 'group_import' => false, + 'no_leading_import_slash' => true, + 'no_unneeded_import_alias' => true, + 'no_unused_imports' => true, + 'ordered_imports' => ['case_sensitive' => false, 'imports_order' => ['class', 'function', 'const'], 'sort_algorithm' => 'alpha'], + 'single_import_per_statement' => ['group_to_single_imports' => true], + 'single_line_after_imports' => true +]; + +$languageConstructRules = [ + 'class_keyword' => true, + 'combine_consecutive_issets' => true, + 'combine_consecutive_unsets' => true, + 'declare_equal_normalize' => ['space' => 'none'], + 'declare_parentheses' => true, + 'dir_constant' => true, + 'error_suppression' => ['mute_deprecation_error' => true, 'noise_remaining_usages' => false, 'noise_remaining_usages_exclude' => []], + 'explicit_indirect_variable' => true, + 'function_to_constant' => ['functions' => ['get_called_class', 'get_class', 'get_class_this', 'php_sapi_name', 'phpversion', 'pi']], + 'get_class_to_class_keyword' => true, + 'is_null' => true, + 'no_unset_on_property' => true, + 'nullable_type_declaration' => ['syntax' => 'question_mark'], + 'single_space_around_construct' => ['constructs_contain_a_single_space' => ['yield_from'], 'constructs_followed_by_a_single_space' => ['abstract', 'as', 'attribute', 'break', 'case', 'catch', 'class', 'clone', 'comment', 'const', 'const_import', 'continue', 'do', 'echo', 'else', 'elseif', 'enum', 'extends', 'final', 'finally', 'for', 'foreach', 'function', 'function_import', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'match', 'named_argument', 'namespace', 'new', 'open_tag_with_echo', 'php_doc', 'php_open', 'print', 'private', 'protected', 'public', 'readonly', 'require', 'require_once', 'return', 'static', 'switch', 'throw', 'trait', 'try', 'type_colon', 'use', 'use_lambda', 'use_trait', 'var', 'while', 'yield', 'yield_from'], 'constructs_preceded_by_a_single_space' => ['as', 'else', 'elseif', 'use_lambda']] +]; + +$listNotationRules = [ + 'list_syntax' => ['syntax' => 'short'] +]; + +$namespaceNotationRules = [ + 'blank_line_after_namespace' => true, + 'blank_lines_before_namespace' => ['min_line_breaks' => 2, 'max_line_breaks' => 2], + 'clean_namespace' => true, + 'no_leading_namespace_whitespace' => true +]; + +$namingRules = [ + 'no_homoglyph_names' => true +]; + +$operatorRules = [ + 'assign_null_coalescing_to_coalesce_equal' => true, + 'binary_operator_spaces' => ['default' => 'single_space', 'operators' => ['=>' => 'align']], + 'concat_space' => ['spacing' => 'one'], + 'increment_style' => ['style' => 'pre'], + 'logical_operators' => true, + 'long_to_shorthand_operator' => true, + 'new_with_parentheses' => ['anonymous_class' => true, 'named_class' => true], + 'no_space_around_double_colon' => true, + 'no_useless_concat_operator' => ['juggle_simple_strings' => false], + 'no_useless_nullsafe_operator' => true, + 'not_operator_with_space' => false, + 'not_operator_with_successor_space' => false, + 'object_operator_without_whitespace' => true, + 'operator_linebreak' => ['only_booleans' => true, 'position' => 'beginning'], + 'standardize_increment' => true, + 'standardize_not_equals' => true, + 'ternary_operator_spaces' => true, + 'ternary_to_elvis_operator' => true, + 'ternary_to_null_coalescing' => true, + 'unary_operator_spaces' => ['only_dec_inc' => false] +]; + +$phpTagRules = [ + 'blank_line_after_opening_tag' => true, + 'echo_tag_syntax' => ['format' => 'long', 'long_function' => 'echo', 'shorten_simple_statements_only' => true], + 'full_opening_tag' => true, + 'linebreak_after_opening_tag' => true, + 'no_closing_tag' => true +]; + +$phpUnitRules = [ + 'php_unit_assert_new_names' => true, + 'php_unit_attributes' => ['keep_annotations' => false], + 'php_unit_construct' => ['assertions' => ['assertEquals', 'assertNotEquals', 'assertNotSame', 'assertSame']], + 'php_unit_data_provider_method_order' => ['placement' => 'before'], + 'php_unit_data_provider_name' => ['prefix' => 'provide', 'suffix' => 'DataCases'], + 'php_unit_data_provider_return_type' => true, + 'php_unit_data_provider_static' => ['force' => true], + 'php_unit_dedicate_assert' => ['target' => 'newest'], + 'php_unit_dedicate_assert_internal_type' => ['target' => 'newest'], + 'php_unit_expectation' => ['target' => 'newest'], + 'php_unit_fqcn_annotation' => true, + 'php_unit_internal_class' => ['types' => ['abstract', 'final', 'normal']], + 'php_unit_method_casing' => ['case' => 'camel_case'], + 'php_unit_mock' => ['target' => 'newest'], + 'php_unit_mock_short_will_return' => true, + 'php_unit_namespaced' => ['target' => 'newest'], + 'php_unit_no_expectation_annotation' => ['target' => 'newest', 'use_class_const' => true], + 'php_unit_set_up_tear_down_visibility' => true, + 'php_unit_size_class' => false, + 'php_unit_strict' => ['assertions' => ['assertAttributeEquals', 'assertAttributeNotEquals', 'assertEquals', 'assertNotEquals']], + 'php_unit_test_annotation' => ['style' => 'prefix'], + 'php_unit_test_case_static_method_calls' => ['call_type' => 'static', 'methods' => []], + 'php_unit_test_class_requires_covers' => false +]; + +$phpDocRules = [ + 'align_multiline_comment' => ['comment_type' => 'phpdocs_only'], + 'general_phpdoc_annotation_remove' => ['annotations' => [], 'case_sensitive' => false], + 'general_phpdoc_tag_rename' => ['case_sensitive' => false, 'fix_annotation' => true, 'fix_inline' => true, 'replacements' => ['inheritDocs' => 'inheritDoc']], + 'no_blank_lines_after_phpdoc' => true, + 'no_empty_phpdoc' => true, + 'no_superfluous_phpdoc_tags' => ['allow_hidden_params' => false, 'allow_mixed' => false, 'allow_unused_params' => false, 'remove_inheritdoc' => true], + 'phpdoc_add_missing_param_annotation' => ['only_untyped' => true], + 'phpdoc_align' => ['align' => 'vertical', 'spacing' => ['_default' => 1], 'tags' => ['method', 'param', 'property', 'property-read', 'property-write', 'return', 'throws', 'type', 'var']], + 'phpdoc_annotation_without_dot' => true, + 'phpdoc_array_type' => false, + 'phpdoc_indent' => true, + 'phpdoc_inline_tag_normalizer' => ['tags' => ['example', 'id', 'internal', 'inheritdoc', 'inheritdocs', 'link', 'source', 'toc', 'tutorial']], + 'phpdoc_line_span' => ['const' => 'single', 'method' => 'single', 'property' => 'single'], + 'phpdoc_list_type' => false, + 'phpdoc_no_access' => true, + 'phpdoc_no_alias_tag' => ['replacements' => ['property-read' => 'property', 'property-write' => 'property', 'type' => 'var', 'link' => 'see']], + 'phpdoc_no_empty_return' => true, + 'phpdoc_no_package' => true, + 'phpdoc_no_useless_inheritdoc' => true, + 'phpdoc_order_by_value' => ['annotations' => ['author', 'covers', 'coversNothing', 'dataProvider', 'depends', 'group', 'internal', 'method', 'mixin', 'property', 'property-read', 'property-write', 'requires', 'throws', 'uses']], + 'phpdoc_order' => ['order' => ['param', 'throws', 'return']], + 'phpdoc_param_order' => true, + 'phpdoc_return_self_reference' => ['replacements' => ['this' => '$this', '@this' => '$this', '$self' => 'self', '@self' => 'self', '$static' => 'static', '@static' => 'static']], + 'phpdoc_scalar' => ['types' => ['boolean', 'callback', 'double', 'integer', 'real', 'str']], + 'phpdoc_separation' => ['groups' => [['author', 'copyright', 'license'], ['category', 'package', 'subpackage'], ['property', 'property-read', 'property-write'], ['deprecated', 'link', 'see', 'since']], 'skip_unlisted_annotations' => true], + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_summary' => true, + 'phpdoc_tag_casing' => ['tags' => ['inheritDoc']], + 'phpdoc_tag_type' => ['tags' => ['inheritDoc' => 'inline']], + 'phpdoc_to_comment' => ['allow_before_return_statement' => false, 'ignored_tags' => []], + 'phpdoc_trim_consecutive_blank_line_separation' => true, + 'phpdoc_trim' => true, + 'phpdoc_types' => ['groups' => ['alias', 'meta', 'simple']], + 'phpdoc_types_order' => ['case_sensitive' => false, 'null_adjustment' => 'always_last', 'sort_algorithm' => 'alpha'], + 'phpdoc_var_annotation_correct_order' => true, + 'phpdoc_var_without_name' => true +]; + +$returnNotationRules = [ + 'no_useless_return' => true, + 'return_assignment' => true, + 'simplified_null_return' => true +]; + +$semicolonRules = [ + 'multiline_whitespace_before_semicolons' => ['strategy' => 'no_multi_line'], + 'no_empty_statement' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'semicolon_after_instruction' => true, + 'space_after_semicolon' => ['remove_in_empty_for_expressions' => true] +]; + +$strictRules = [ + 'declare_strict_types' => true, + 'strict_comparison' => true, + 'strict_param' => true +]; + +$stringNotationRules = [ + 'explicit_string_variable' => true, + 'heredoc_closing_marker' => ['closing_marker' => 'EOD', 'explicit_heredoc_style' => false, 'reserved_closing_markers' => ['CSS', 'DIFF', 'HTML', 'JS', 'JSON', 'MD', 'PHP', 'PYTHON', 'RST', 'TS', 'SQL', 'XML', 'YAML']], + 'heredoc_to_nowdoc' => true, + 'multiline_string_to_heredoc' => true, + 'no_binary_string' => true, + 'no_trailing_whitespace_in_string' => true, + 'simple_to_complex_string_variable' => true, + 'single_quote' => ['strings_containing_single_quote_chars' => false], + 'string_implicit_backslashes' => false, + 'string_length_to_empty' => true, + 'string_line_ending' => true +]; + +$whitespaceRules = [ + 'array_indentation' => true, + 'blank_line_before_statement' => ['statements' => ['break', 'continue', 'declare', 'phpdoc', 'return', 'throw', 'try', 'yield', 'yield_from']], + 'blank_line_between_import_groups' => true, + 'compact_nullable_type_declaration' => true, + 'heredoc_indentation' => ['indentation' => 'same_as_start'], + 'indentation_type' => true, + 'line_ending' => true, + 'method_chaining_indentation' => true, + 'no_extra_blank_lines' => ['tokens' => ['attribute', 'break', 'case', 'continue', 'curly_brace_block', 'default', 'extra', 'parenthesis_brace_block', 'return', 'square_brace_block', 'switch', 'throw', 'use']], + 'no_spaces_around_offset' => ['positions' => ['inside', 'outside']], + 'no_trailing_whitespace' => true, + 'no_whitespace_in_blank_line' => true, + 'single_blank_line_at_eof' => true, + 'spaces_inside_parentheses' => ['space' => 'none'], + 'statement_indentation' => ['stick_comment_to_next_continuous_control_statement' => false], + 'type_declaration_spaces' => ['elements' => ['constant', 'function', 'property']], + 'types_spaces' => ['space' => 'none', 'space_multiple_catch' => 'none'] +]; + +$rules = \array_merge( + $aliasRules, + $arrayNotationRules, + $attributeNotationRules, + $basicRules, + $casingRules, + $castNotationRules, + $classNotationRules, + $classUsageRules, + $commentRules, + $constantNotationRules, + $controlStructureRules, + $doctrineAnnotationRules, + $functionNotationRules, + $importRules, + $languageConstructRules, + $listNotationRules, + $namespaceNotationRules, + $namingRules, + $operatorRules, + $phpTagRules, + $phpUnitRules, + $phpDocRules, + $returnNotationRules, + $semicolonRules, + $strictRules, + $stringNotationRules, + $whitespaceRules +); + +$finder = Finder::create() + ->in([ + __DIR__ . '/src', + __DIR__ . '/tests' + ]) + ->name('*.php') + ->ignoreDotFiles(true) + ->ignoreVCS(true); + +$config = new Config(); + +return $config->setFinder($finder) + ->setRules($rules) + ->setRiskyAllowed(true) + ->setUsingCache(true) + ->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect()); diff --git a/.php_cs b/.php_cs deleted file mode 100644 index f25e5bb..0000000 --- a/.php_cs +++ /dev/null @@ -1,37 +0,0 @@ -in(__DIR__ . '/src') -; -return PhpCsFixer\Config::create() - ->setRiskyAllowed(true) - ->setRules([ - '@Symfony' => true, - 'binary_operator_spaces' => array( - 'align_equals' => false, - 'align_double_arrow' => true, - ), - '@Symfony:risky' => true, - 'array_syntax' => ['syntax' => 'short'], - 'declare_strict_types' => true, - 'linebreak_after_opening_tag' => true, - 'mb_str_functions' => true, - 'native_function_invocation' => true, - 'no_php4_constructor' => true, - 'no_unreachable_default_argument_value' => true, - 'no_useless_else' => true, - 'no_useless_return' => true, - 'ordered_imports' => true, - 'php_unit_strict' => true, - 'phpdoc_order' => true, - 'semicolon_after_instruction' => true, - 'single_import_per_statement' => false, - 'strict_comparison' => true, - 'strict_param' => true, - 'concat_space' => ['spacing' => 'one'], - 'trailing_comma_in_multiline_array' => false, - 'yoda_style' => null, - 'single_line_throw' => false, - 'no_superfluous_phpdoc_tags' => false - ]) - ->setFinder($finder) - ->setCacheFile(__DIR__.'/.php_cs.cache'); \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..00b4e3f --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +contact@rancoud.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..69c87e6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,26 @@ +## Contributing + +Hi there! I'm thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. + +Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE). + +## Submitting a pull request + +1. [Fork](https://github.com/rancoud/Session/fork) and clone the repository +2. Create a new branch: `git checkout -b my-branch-name` +3. Make your change +4. Push to your fork and [submit a pull request](https://github.com/rancoud/Session/compare) +5. Pat your self on the back and wait for your pull request to be reviewed and merged. + +Here are a few things you can do that will increase the likelihood of your pull request being accepted: + +* Make sure the `README.md` and any other relevant **documentation are kept up-to-date**. +* I try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option. +* Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as **separate pull requests**. +* Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). + +## Resources + +* [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) +* [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) +* [GitHub Help](https://help.github.com) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0df38dd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +ARG PHPVERSION="8.4" +FROM php:$PHPVERSION-cli-alpine + +RUN apk --update --no-cache add \ + mysql-client \ + && rm -rf /tmp/* /var/cache/apk/* + +RUN docker-php-ext-install \ + pdo_mysql + +RUN apk add --no-cache \ + $PHPIZE_DEPS \ + linux-headers \ + && pecl install xdebug-3.5.1 \ + && docker-php-ext-enable xdebug \ + && rm -rf /tmp/* /var/cache/apk/* + +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer + +WORKDIR /app + +COPY composer.json . +COPY composer.lock . +RUN composer validate +RUN composer install --no-interaction --no-progress + +COPY . . + +ENTRYPOINT ["./entrypoint.sh"] diff --git a/LICENSE b/LICENSE index 24ac99b..09407b4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2017 Rancoud - -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 +MIT License + +Copyright (c) 2017 Rancoud + +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. \ No newline at end of file diff --git a/README.md b/README.md index c8c1b87..b12839b 100644 --- a/README.md +++ b/README.md @@ -1,201 +1,247 @@ -# Session Package - -![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/rancoud/session) -[![Packagist Version](https://img.shields.io/packagist/v/rancoud/session)](https://packagist.org/packages/rancoud/session) -[![Packagist Downloads](https://img.shields.io/packagist/dt/rancoud/session)](https://packagist.org/packages/rancoud/session) -[![Composer dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](https://github.com/rancoud/session/blob/master/composer.json) -[![Test workflow](https://img.shields.io/github/workflow/status/rancoud/session/test?label=test&logo=github)](https://github.com/rancoud/session/actions?workflow=test) -[![Codecov](https://img.shields.io/codecov/c/github/rancoud/session?logo=codecov)](https://codecov.io/gh/rancoud/session) -[![composer.lock](https://poser.pugx.org/rancoud/session/composerlock)](https://packagist.org/packages/rancoud/session) - -Session. - -## Installation -```php -composer require rancoud/session -``` - -## Informations -By default session is in read only (option read_and_close = 1). -You can specify it using `Session::setReadWrite()` or `Session::setReadOnly()` - -Session::start() is not needed, but: -* Session will automatically start in read only when using `get, has, hasKeyAndValue, getAll` -* Session will automatically start in write mode when using `set, remove, getAndRemove, keepFlash, gc, regenerate` - -## How to use it? -Set and get value from $_SESSION -```php -Session::set('key', 'value'); -$value = Session::get('key'); -``` -Use custom options -```php -// first way -Session::setOption('name', 'custom_session_name'); - -// second way -Session::start(['cookie_lifetime' => 1440]); - -Session::set('key', 'value'); -$value = Session::get('key'); -``` -With encryption on default php session -```php -Session::useDefaultEncryptionDriver('keyForEncryption'); -Session::set('key', 'value'); -$value = Session::get('key'); -``` -With File driver -```php -Session::useFileDriver(); -Session::set('key', 'value'); -$value = Session::get('key'); -``` -With Database driver (you have to install rancoud/database) -```php -$conf = new \Rancoud\Database\Configurator([ - 'engine' => 'mysql', - 'host' => '127.0.0.1', - 'user' => 'root', - 'password' => '', - 'database' => 'test_database' -]); -$db = new \Rancoud\Database\Database($conf); - -// for using a current connection -Session::useCurrentDatabaseDriver($db); - -// for creating a new connection -//Session::useNewDatabaseDriver($conf); - -Session::set('key', 'value'); -$value = Session::get('key'); -``` -With Redis driver (you have to install predis/predis) -```php -$params = [ - 'scheme' => 'tcp', - 'host' => '127.0.0.1', - 'port' => 6379, -]; -$redis = new Predis\Client($params); - -// for using a current connection -Session::useCurrentRedisDriver($redis); - -// for creating a new connection -//Session::useNewRedisDriver($params); - -Session::set('key', 'value'); -$value = Session::get('key'); -``` -With your own driver implementing SessionHandlerInterface -```php -$driver = new MyCustomDriver(); -Session::useCustomDriver($driver); -Session::set('key', 'value'); -$value = Session::get('key'); -``` - -## Session -### Static General Commands -* start([options: array = []]): void -* regenerate(): bool -* destroy(): bool -* commit(): void -* rollback(): bool -* unsaved(): bool -* hasStarted(): bool -* getId(): string -* setId(id: string): string -* gc(): void -* setReadOnly(): void -* setReadWrite(): void - -### Static Variables $_SESSION access -* set(key: string, value: mixed): void -* get(key: string): mixed -* getAll(): array -* getAndRemove(key: string): mixed -* has(key: string): bool -* hasKeyAndValue(key: string, value: mixed): bool -* remove(key: string): void - -### Static Variables flash access -Flash data are store in a separate variable. -They will dissapear at the end of the script execution. -You can use keepFlash for saving it in $_SESSION. -When flash data is restore, it will be delete in $_SESSION. - -* setFlash(key: string, value: mixed): void -* getFlash(key: string): mixed -* getAllFlash(): array -* hasFlash(key: string): bool -* hasFlashKeyAndValue(key: string, value: mixed): bool -* keepFlash([keys: array = []]): void -* restoreFlashData(): void - -### Static Options -* setOption(key: string, value): void -* setOptions(options: array): void -* getOption(key: string): mixed - -### Static Driver -* getDriver(): \SessionHandlerInterface - -#### Static PHP Session Default Driver -* useDefaultDriver(): void -* useDefaultEncryptionDriver(key: string, [method: string|null = null]): void - -#### Static File Driver -* useFileDriver(): void -* useFileEncryptionDriver(key: string, [method: string|null = null]): void -* setPrefixForFile(prefix: string): void - -#### Static Database Driver -* useNewDatabaseDriver(configuration: \Rancoud\Database\Configurator|array): void -* useCurrentDatabaseDriver(databaseInstance: \Rancoud\Database\Database): void -* useNewDatabaseEncryptionDriver(configuration: \Rancoud\Database\Configurator|array, key: string, [method: string = null]): void -* useCurrentDatabaseEncryptionDriver(databaseInstance: \Rancoud\Database\Database, key: string, [method: string = null]): void -* setUserIdForDatabase(userId: int): void - -#### Static Redis Driver -* useNewRedisDriver(configuration: array|string): void -* useCurrentRedisDriver(redisInstance: \Predis\Client): void -* useNewRedisEncryptionDriver(configuration: array|string, key: string, [method: string = null]): void -* useCurrentRedisEncryptionDriver(redisInstance: \Predis\Client, key: string, [method: string = null]): void - -#### Static Custom Driver -* useCustomDriver(customDriver: \SessionHandlerInterface): void - -## Driver Informations -### Default -Use SessionHandler -### File -Extends SessionHandler -### Database -You need to install -```php -composer require rancoud/database -``` -```sql -CREATE TABLE IF NOT EXISTS `sessions` ( - `id` varchar(128) NOT NULL, - `id_user` int(10) unsigned DEFAULT NULL, - `last_access` datetime NOT NULL, - `content` text NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - -``` -### Redis -You need to install -```php -composer require predis/predis -``` - -## How to Dev -`composer ci` for php-cs-fixer and phpunit and coverage -`composer lint` for php-cs-fixer -`composer test` for phpunit and coverage \ No newline at end of file +# Session Package + +![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/rancoud/session) +[![Packagist Version](https://img.shields.io/packagist/v/rancoud/session)](https://packagist.org/packages/rancoud/session) +[![Packagist Downloads](https://img.shields.io/packagist/dt/rancoud/session)](https://packagist.org/packages/rancoud/session) +[![Composer dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](https://github.com/rancoud/session/blob/master/composer.json) +[![Test workflow](https://img.shields.io/github/actions/workflow/status/rancoud/session/test.yml?branch=master)](https://github.com/rancoud/session/actions/workflows/test.yml) +[![Codecov](https://img.shields.io/codecov/c/github/rancoud/session?logo=codecov)](https://codecov.io/gh/rancoud/session) + +Session. + +## Installation +```php +composer require rancoud/session +``` + +## Informations +By default session is in read only (option read_and_close = 1). +You can specify it using `Session::setReadWrite()` or `Session::setReadOnly()` + +Session::start() is not needed, but: +* Session will automatically start in read only when using `get, has, hasKeyAndValue, getAll` +* Session will automatically start in write mode when using `set, remove, getAndRemove, keepFlash, gc, regenerate` + +## How to use it? +Set and get value from $_SESSION +```php +Session::set('key', 'value'); +$value = Session::get('key'); +``` +Use custom options +```php +// first way +Session::setOption('name', 'custom_session_name'); + +// second way +Session::start(['cookie_lifetime' => 1440]); + +Session::set('key', 'value'); +$value = Session::get('key'); +``` +With encryption on default php session +```php +Session::useDefaultEncryptionDriver('keyForEncryption'); +Session::set('key', 'value'); +$value = Session::get('key'); +``` +With File driver +```php +Session::useFileDriver(); +Session::set('key', 'value'); +$value = Session::get('key'); +``` +With Database driver (you have to install rancoud/database) +```php +$conf = new \Rancoud\Database\Configurator([ + 'engine' => 'mysql', + 'host' => '127.0.0.1', + 'user' => 'root', + 'password' => '', + 'database' => 'test_database' +]); +$db = new \Rancoud\Database\Database($conf); + +// for using a current connection +Session::useCurrentDatabaseDriver($db); + +// for creating a new connection +//Session::useNewDatabaseDriver($conf); + +Session::set('key', 'value'); +$value = Session::get('key'); +``` +With Redis driver (you have to install predis/predis) +```php +$params = [ + 'scheme' => 'tcp', + 'host' => '127.0.0.1', + 'port' => 6379, +]; +$redis = new Predis\Client($params); + +// for using a current connection +Session::useCurrentRedisDriver($redis); + +// for creating a new connection +//Session::useNewRedisDriver($params); + +Session::set('key', 'value'); +$value = Session::get('key'); +``` +With your own driver implementing `SessionHandlerInterface` and/or `SessionUpdateTimestampHandlerInterface` +```php +$driver = new MyCustomDriver(); +Session::useCustomDriver($driver); +Session::set('key', 'value'); +$value = Session::get('key'); +``` + +## Session +### Static General Commands +* start([options: array = []]): void +* regenerate(): bool +* destroy(): bool +* commit(): void +* rollback(): bool +* unsaved(): bool +* hasStarted(): bool +* getId(): string +* setId(id: string): string +* gc(): void +* setReadOnly(): void +* setReadWrite(): void +* isReadOnly(): bool + +### Static Variables $_SESSION access +* set(key: string, value: mixed): void +* get(key: string): mixed +* getAll(): array +* getAndRemove(key: string): mixed +* has(key: string): bool +* hasKeyAndValue(key: string, value: mixed): bool +* remove(key: string): void + +### Static Variables flash access +Flash data are store in a separate variable. +They will dissapear at the end of the script execution or after `commit()` `unsaved()`. +You can use keepFlash for saving it in $_SESSION. +When flash data is restore, it will be delete in $_SESSION. + +* setFlash(key: string, value: mixed): void +* getFlash(key: string): mixed +* getAllFlash(): array +* hasFlash(key: string): bool +* hasFlashKeyAndValue(key: string, value: mixed): bool +* keepFlash([keys: array = []]): void + +### Static Options +* setOption(key: string, value): void +* setOptions(options: array): void +* getOption(key: string): mixed + +### Static Driver +* getDriver(): \SessionHandlerInterface + +#### Static PHP Session Default Driver +* useDefaultDriver(): void +* useDefaultEncryptionDriver(key: string, [method: string|null = null]): void +* setLengthSessionID(length: int): void +* getLengthSessionID(): int + +#### Static File Driver +* useFileDriver(): void +* useFileEncryptionDriver(key: string, [method: string|null = null]): void +* setPrefixForFile(prefix: string): void +* setLengthSessionID(length: int): void +* getLengthSessionID(): int + +#### Static Database Driver +* useNewDatabaseDriver(configuration: \Rancoud\Database\Configurator|array): void +* useCurrentDatabaseDriver(databaseInstance: \Rancoud\Database\Database): void +* useNewDatabaseEncryptionDriver(configuration: \Rancoud\Database\Configurator|array, key: string, [method: string = null]): void +* useCurrentDatabaseEncryptionDriver(databaseInstance: \Rancoud\Database\Database, key: string, [method: string = null]): void +* setUserIdForDatabase(userId: int): void +* setLengthSessionID(length: int): void +* getLengthSessionID(): int +* deleteUserSessionsInDatabase(int $userID): void +* deleteAnonymousSessionsInDatabase(): int + +#### Static Redis Driver +* useNewRedisDriver(configuration: array|string): void +* useCurrentRedisDriver(redisInstance: \Predis\Client): void +* useNewRedisEncryptionDriver(configuration: array|string, key: string, [method: string = null]): void +* useCurrentRedisEncryptionDriver(redisInstance: \Predis\Client, key: string, [method: string = null]): void +* setLengthSessionID(length: int): void +* getLengthSessionID(): int + +#### Static Custom Driver +* useCustomDriver(customDriver: \SessionHandlerInterface): void + +## Session options +List of session options you can change: +* save_path +* name +* save_handler +* auto_start +* gc_probability +* gc_divisor +* gc_maxlifetime +* serialize_handler +* cookie_lifetime +* cookie_path +* cookie_domain +* cookie_secure +* cookie_httponly +* cookie_samesite +* use_strict_mode +* use_cookies +* use_only_cookies +* referer_check +* cache_limiter +* cache_expire +* use_trans_sid +* trans_sid_tags +* trans_sid_hosts +* sid_length +* sid_bits_per_character +* upload_progress.enabled +* upload_progress.cleanup +* upload_progress.prefix +* upload_progress.name +* upload_progress.freq +* upload_progress.min_freq +* lazy_write +* read_and_close + +## Driver Informations +### Default +Use SessionHandler + +### File +Extends SessionHandler + +### Database +You need to install +```php +composer require rancoud/database +``` + +```sql +CREATE TABLE IF NOT EXISTS `sessions` ( + `id` varchar(128) NOT NULL, + `id_user` int(10) unsigned DEFAULT NULL, + `last_access` datetime NOT NULL, + `content` text NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +### Redis +You need to install +```php +composer require predis/predis +``` + +## How to Dev +`docker compose build && docker compose run lib composer ci` for launching tests diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..9b044b8 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Reporting a Vulnerability + +The project maintainers take security seriously. If you discover a security +issue, please bring it to their attention right away! + +**Please _DO NOT_ file a public issue**, instead send your report privately to +contact@rancoud.com . + +Security reports are greatly appreciated. + +We currently do not offer a paid security bounty program. diff --git a/composer.json b/composer.json index c6de6c1..89dd7c3 100644 --- a/composer.json +++ b/composer.json @@ -15,16 +15,22 @@ "Rancoud\\Session\\": "src" } }, + "autoload-dev": { + "psr-4": { + "tests\\": "tests" + } + }, "require": { - "php": ">=7.4.0", - "ext-openssl": ">=7.4.0" + "php": ">=8.4.0", + "ext-openssl": "*", + "ext-session": "*", + "ext-mbstring": "*" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.16", - "phpunit/phpunit": "^9.1", - "predis/predis": "^1.1", - "rancoud/database": "^4.0", - "squizlabs/php_codesniffer": "^3.5" + "friendsofphp/php-cs-fixer": "3.95.1", + "phpunit/phpunit": "12.5.22", + "predis/predis": "3.4.2", + "rancoud/database": "7.0.3" }, "scripts": { "ci": [ @@ -32,11 +38,10 @@ "@test" ], "lint": [ - "phpcbf", - "php-cs-fixer fix" + "php-cs-fixer fix -vvv" ], "test": [ - "phpunit --colors --coverage-html ./coverage" + "phpunit --colors --display-all-issues --coverage-html ./coverage --coverage-clover coverage/clover.xml" ] } } diff --git a/composer.lock b/composer.lock index e7353ec..ce63cef 100644 --- a/composer.lock +++ b/composer.lock @@ -4,33 +4,177 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7204fb14ec0ef8f966236c6d2b3aa6e0", + "content-hash": "b39a2acc6b6ece9034e7f23c8bb51819", "packages": [], "packages-dev": [ + { + "name": "clue/ndjson-react", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-ndjson.git", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\NDJson\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "homepage": "https://github.com/clue/reactphp-ndjson", + "keywords": [ + "NDJSON", + "json", + "jsonlines", + "newline", + "reactphp", + "streaming" + ], + "support": { + "issues": "https://github.com/clue/reactphp-ndjson/issues", + "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2022-12-23T10:58:28+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, { "name": "composer/semver", - "version": "1.5.1", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de" + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/c6bea70230ef4dd483e6bbcab6005f682ed3a8de", - "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.5 || ^5.0.5" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { @@ -66,28 +210,46 @@ "validation", "versioning" ], - "time": "2020-01-13T12:06:48+00:00" + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" }, { "name": "composer/xdebug-handler", - "version": "1.4.2", + "version": "3.0.5", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51" + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51", - "reference": "fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0 || ^8.0", - "psr/log": "^1.0" + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8" + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" }, "type": "library", "autoload": { @@ -110,6 +272,11 @@ "Xdebug", "performance" ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, "funding": [ { "url": "https://packagist.com", @@ -124,40 +291,55 @@ "type": "tidelift" } ], - "time": "2020-06-04T11:16:35+00:00" + "time": "2024-05-06T16:37:16+00:00" }, { - "name": "doctrine/annotations", - "version": "1.10.3", + "name": "ergebnis/agent-detector", + "version": "1.2.0", "source": { "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "5db60a4969eba0e0c197a19c077780aadbc43c5d" + "url": "https://github.com/ergebnis/agent-detector.git", + "reference": "e211f17928c8b95a51e06040792d57f5462fb271" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/5db60a4969eba0e0c197a19c077780aadbc43c5d", - "reference": "5db60a4969eba0e0c197a19c077780aadbc43c5d", + "url": "https://api.github.com/repos/ergebnis/agent-detector/zipball/e211f17928c8b95a51e06040792d57f5462fb271", + "reference": "e211f17928c8b95a51e06040792d57f5462fb271", "shasum": "" }, "require": { - "doctrine/lexer": "1.*", - "ext-tokenizer": "*", - "php": "^7.1 || ^8.0" + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0 || ~8.6.0" }, "require-dev": { - "doctrine/cache": "1.*", - "phpunit/phpunit": "^7.5" + "ergebnis/composer-normalize": "^2.51.0", + "ergebnis/license": "^2.7.0", + "ergebnis/php-cs-fixer-config": "^6.60.2", + "ergebnis/phpstan-rules": "^2.13.1", + "ergebnis/phpunit-slow-test-detector": "^2.24.0", + "ergebnis/rector-rules": "^1.18.1", + "fakerphp/faker": "^1.24.1", + "infection/infection": "^0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.54", + "phpstan/phpstan-deprecation-rules": "^2.0.4", + "phpstan/phpstan-phpunit": "^2.0.16", + "phpstan/phpstan-strict-rules": "^2.0.10", + "phpunit/phpunit": "^9.6.34", + "rector/rector": "^2.4.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9.x-dev" + "dev-main": "1.2-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" } }, "autoload": { "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + "Ergebnis\\AgentDetector\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -166,70 +348,44 @@ ], "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" } ], - "description": "Docblock Annotations Parser", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "time": "2020-05-25T17:24:27+00:00" + "description": "Provides a detector for detecting the presence of an agent.", + "homepage": "https://github.com/ergebnis/agent-detector", + "support": { + "issues": "https://github.com/ergebnis/agent-detector/issues", + "security": "https://github.com/ergebnis/agent-detector/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/agent-detector" + }, + "time": "2026-05-07T08:19:07+00:00" }, { - "name": "doctrine/instantiator", - "version": "1.3.1", + "name": "evenement/evenement", + "version": "v3.0.2", "source": { "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", - "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": ">=7.0" }, "require-dev": { - "doctrine/coding-standard": "^6.0", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.13", - "phpstan/phpstan-phpunit": "^0.11", - "phpstan/phpstan-shim": "^0.11", - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^9 || ^6" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, "autoload": { "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + "Evenement\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -238,64 +394,53 @@ ], "authors": [ { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" } ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "description": "Événement is a very simple event dispatching library for PHP", "keywords": [ - "constructor", - "instantiate" - ], - "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%2Finstantiator", - "type": "tidelift" - } + "event-dispatcher", + "event-emitter" ], - "time": "2020-05-29T17:27:14+00:00" + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" }, { - "name": "doctrine/lexer", - "version": "1.2.1", + "name": "fidry/cpu-core-counter", + "version": "1.3.0", "source": { "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", - "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpstan/phpstan": "^0.11.8", - "phpunit/phpunit": "^8.2" + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, "autoload": { "psr-4": { - "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + "Fidry\\CpuCoreCounter\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -304,95 +449,86 @@ ], "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" } ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "description": "Tiny utility to get the number of CPU cores.", "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", - "php" + "CPU", + "core" ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" + }, "funding": [ { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" + "url": "https://github.com/theofidry", + "type": "github" } ], - "time": "2020-05-25T17:44:05+00:00" + "time": "2025-08-14T07:29:31+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.16.4", + "version": "v3.95.1", "source": { "type": "git", - "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "1023c3458137ab052f6ff1e09621a721bfdeca13" + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "a9727678fbd12997f1d9de8f4a37824ed9df1065" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/1023c3458137ab052f6ff1e09621a721bfdeca13", - "reference": "1023c3458137ab052f6ff1e09621a721bfdeca13", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a9727678fbd12997f1d9de8f4a37824ed9df1065", + "reference": "a9727678fbd12997f1d9de8f4a37824ed9df1065", "shasum": "" }, "require": { - "composer/semver": "^1.4", - "composer/xdebug-handler": "^1.2", - "doctrine/annotations": "^1.2", + "clue/ndjson-react": "^1.3", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.5", + "ergebnis/agent-detector": "^1.1.1", + "ext-filter": "*", + "ext-hash": "*", "ext-json": "*", "ext-tokenizer": "*", - "php": "^5.6 || ^7.0", - "php-cs-fixer/diff": "^1.3", - "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0", - "symfony/event-dispatcher": "^3.0 || ^4.0 || ^5.0", - "symfony/filesystem": "^3.0 || ^4.0 || ^5.0", - "symfony/finder": "^3.0 || ^4.0 || ^5.0", - "symfony/options-resolver": "^3.0 || ^4.0 || ^5.0", - "symfony/polyfill-php70": "^1.0", - "symfony/polyfill-php72": "^1.4", - "symfony/process": "^3.0 || ^4.0 || ^5.0", - "symfony/stopwatch": "^3.0 || ^4.0 || ^5.0" + "fidry/cpu-core-counter": "^1.3", + "php": "^7.4 || ^8.0", + "react/child-process": "^0.6.6", + "react/event-loop": "^1.5", + "react/socket": "^1.16", + "react/stream": "^1.4", + "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0", + "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/options-resolver": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.33", + "symfony/polyfill-php80": "^1.33", + "symfony/polyfill-php81": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2 || ^8.0", + "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0" }, "require-dev": { - "johnkary/phpunit-speedtrap": "^1.1 || ^2.0 || ^3.0", - "justinrainbow/json-schema": "^5.0", - "keradus/cli-executor": "^1.2", - "mikey179/vfsstream": "^1.6", - "php-coveralls/php-coveralls": "^2.1", - "php-cs-fixer/accessible-object": "^1.0", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.1", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.1", - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.1", - "phpunitgoodpractices/traits": "^1.8", - "symfony/phpunit-bridge": "^5.1", - "symfony/yaml": "^3.0 || ^4.0 || ^5.0" + "facile-it/paraunit": "^1.3.1 || ^2.8.0", + "infection/infection": "^0.32.6", + "justinrainbow/json-schema": "^6.8.0", + "keradus/cli-executor": "^2.3", + "mikey179/vfsstream": "^1.6.12", + "php-coveralls/php-coveralls": "^2.9.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.8", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.8", + "phpunit/phpunit": "^9.6.34 || ^10.5.63 || ^11.5.55", + "symfony/polyfill-php85": "^1.33", + "symfony/var-dumper": "^5.4.48 || ^6.4.32 || ^7.4.4 || ^8.0.8", + "symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0.8" }, "suggest": { "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters.", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "For IsIdenticalString constraint.", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "For XmlMatchesXsd constraint.", - "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." + "ext-mbstring": "For handling non-UTF8 characters." }, "bin": [ "php-cs-fixer" @@ -402,17 +538,8 @@ "psr-4": { "PhpCsFixer\\": "src/" }, - "classmap": [ - "tests/Test/AbstractFixerTestCase.php", - "tests/Test/AbstractIntegrationCaseFactory.php", - "tests/Test/AbstractIntegrationTestCase.php", - "tests/Test/Assert/AssertTokensTrait.php", - "tests/Test/IntegrationCase.php", - "tests/Test/IntegrationCaseFactory.php", - "tests/Test/IntegrationCaseFactoryInterface.php", - "tests/Test/InternalIntegrationCaseFactory.php", - "tests/Test/IsIdenticalConstraint.php", - "tests/TestCase.php" + "exclude-from-classmap": [ + "src/**/Internal/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -430,47 +557,59 @@ } ], "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.1" + }, "funding": [ { "url": "https://github.com/keradus", "type": "github" } ], - "time": "2020-06-27T23:57:46+00:00" + "time": "2026-04-12T17:00:09+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.10.1", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", - "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, - "replace": { - "myclabs/deep-copy": "self.version" + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^7.1" + "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": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, "files": [ "src/DeepCopy/deep_copy.php" - ] + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -484,83 +623,102 @@ "object", "object graph" ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, "funding": [ { "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", "type": "tidelift" } ], - "time": "2020-06-29T13:22:24+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { - "name": "paragonie/random_compat", - "version": "v9.99.99", + "name": "nikic/php-parser", + "version": "v5.7.0", "source": { "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { - "php": "^7" + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" }, "require-dev": { - "phpunit/phpunit": "4.*|5.*", - "vimeo/psalm": "^1" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" }, + "bin": [ + "bin/php-parse" + ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" + "name": "Nikita Popov" } ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "description": "A PHP parser written in PHP", "keywords": [ - "csprng", - "polyfill", - "pseudorandom", - "random" + "parser", + "php" ], - "time": "2018-07-02T15:55:56+00:00" + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" }, { "name": "phar-io/manifest", - "version": "1.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", - "phar-io/version": "^2.0", - "php": "^5.6 || ^7.0" + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -590,24 +748,34 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2018-07-08T19:23:20+00:00" + "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": "2.0.1", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { @@ -637,30 +805,52 @@ } ], "description": "Library for handling version information and constraints", - "time": "2018-07-08T19:19:57+00:00" + "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-cs-fixer/diff", - "version": "v1.3.0", + "name": "phpunit/php-code-coverage", + "version": "12.5.7", "source": { "type": "git", - "url": "https://github.com/PHP-CS-Fixer/diff.git", - "reference": "78bb099e9c16361126c86ce82ec4405ebab8e756" + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "186dab580576598076de6818596d12b61801880e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/78bb099e9c16361126c86ce82ec4405ebab8e756", - "reference": "78bb099e9c16361126c86ce82ec4405ebab8e756", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/186dab580576598076de6818596d12b61801880e", + "reference": "186dab580576598076de6818596d12b61801880e", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.7.0", + "php": ">=8.3", + "phpunit/php-text-template": "^5.0", + "sebastian/complexity": "^5.0", + "sebastian/environment": "^8.1.2", + "sebastian/lines-of-code": "^4.0.1", + "sebastian/version": "^6.0", + "theseer/tokenizer": "^2.0.1" }, "require-dev": { - "phpunit/phpunit": "^5.7.23 || ^6.4.3", - "symfony/process": "^3.3" + "phpunit/phpunit": "^12.5.28" + }, + "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": "12.5.x-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -672,274 +862,355 @@ ], "authors": [ { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" + "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/12.5.7" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" }, { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" }, { - "name": "SpacePossum" + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" } ], - "description": "sebastian/diff v2 backport support for PHP5.6", - "homepage": "https://github.com/PHP-CS-Fixer", - "keywords": [ - "diff" - ], - "time": "2018-02-15T16:58:55+00:00" + "time": "2026-06-01T13:24:19+00:00" }, { - "name": "phpdocumentor/reflection-common", - "version": "2.2.0", + "name": "phpunit/php-file-iterator", + "version": "6.0.1", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", + "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-2.x": "2.x-dev" + "dev-main": "6.0-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" + "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/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" + } ], - "time": "2020-06-27T09:03:43+00:00" + "time": "2026-02-02T14:04:18+00:00" }, { - "name": "phpdocumentor/reflection-docblock", - "version": "5.1.0", + "name": "phpunit/php-invoker", + "version": "6.0.0", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e" + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", - "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406", "shasum": "" }, "require": { - "ext-filter": "^7.1", - "php": "^7.2", - "phpdocumentor/reflection-common": "^2.0", - "phpdocumentor/type-resolver": "^1.0", - "webmozart/assert": "^1" + "php": ">=8.3" }, "require-dev": { - "doctrine/instantiator": "^1", - "mockery/mockery": "^1" + "ext-pcntl": "*", + "phpunit/phpunit": "^12.0" + }, + "suggest": { + "ext-pcntl": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.x-dev" + "dev-main": "6.0-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0" + }, + "funding": [ { - "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2020-02-22T12:28:44+00:00" + "time": "2025-02-07T04:58:58+00:00" }, { - "name": "phpdocumentor/type-resolver", - "version": "1.3.0", + "name": "phpunit/php-text-template", + "version": "5.0.0", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "e878a14a65245fbe78f8080eba03b47c3b705651" + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e878a14a65245fbe78f8080eba03b47c3b705651", - "reference": "e878a14a65245fbe78f8080eba03b47c3b705651", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" + "php": ">=8.3" }, "require-dev": { - "ext-tokenizer": "*" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-1.x": "1.x-dev" + "dev-main": "5.0-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" + "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/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "time": "2020-06-27T10:12:23+00:00" + "time": "2025-02-07T04:59:16+00:00" }, { - "name": "phpspec/prophecy", - "version": "v1.10.3", + "name": "phpunit/php-timer", + "version": "8.0.0", "source": { "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "451c3cd1418cf640de218914901e51b064abb093" + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", - "reference": "451c3cd1418cf640de218914901e51b064abb093", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", - "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + "php": ">=8.3" }, "require-dev": { - "phpspec/phpspec": "^2.5 || ^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.10.x-dev" + "dev-main": "8.0-dev" } }, "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" + "timer" ], - "time": "2020-03-05T15:02:03+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:59:38+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "8.0.2", + "name": "phpunit/phpunit", + "version": "12.5.22", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "ca6647ffddd2add025ab3f21644a441d7c146cdc" + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "e07667405f0f43317a1799d3907c43a123bc5587" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca6647ffddd2add025ab3f21644a441d7c146cdc", - "reference": "ca6647ffddd2add025ab3f21644a441d7c146cdc", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e07667405f0f43317a1799d3907c43a123bc5587", + "reference": "e07667405f0f43317a1799d3907c43a123bc5587", "shasum": "" }, "require": { "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", "ext-xmlwriter": "*", - "php": "^7.3", - "phpunit/php-file-iterator": "^3.0", - "phpunit/php-text-template": "^2.0", - "phpunit/php-token-stream": "^4.0", - "sebastian/code-unit-reverse-lookup": "^2.0", - "sebastian/environment": "^5.0", - "sebastian/version": "^3.0", - "theseer/tokenizer": "^1.1.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" - }, - "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.3", + "phpunit/php-code-coverage": "^12.5.6", + "phpunit/php-file-iterator": "^6.0.1", + "phpunit/php-invoker": "^6.0.0", + "phpunit/php-text-template": "^5.0.0", + "phpunit/php-timer": "^8.0.0", + "sebastian/cli-parser": "^4.2.0", + "sebastian/comparator": "^7.1.6", + "sebastian/diff": "^7.0.0", + "sebastian/environment": "^8.1.0", + "sebastian/exporter": "^7.0.2", + "sebastian/global-state": "^8.0.2", + "sebastian/object-enumerator": "^7.0.0", + "sebastian/recursion-context": "^7.0.1", + "sebastian/type": "^6.0.3", + "sebastian/version": "^6.0.0", + "staabm/side-effects-detector": "^1.0.5" }, + "bin": [ + "phpunit" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "8.0-dev" + "dev-main": "12.5-dev" } }, "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], "classmap": [ "src/" ] @@ -955,427 +1226,521 @@ "role": "lead" } ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", "keywords": [ - "coverage", + "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/12.5.22" + }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://phpunit.de/sponsoring.html", + "type": "other" } ], - "time": "2020-05-23T08:02:54+00:00" + "time": "2026-04-17T12:51:04+00:00" }, { - "name": "phpunit/php-file-iterator", - "version": "3.0.3", + "name": "predis/predis", + "version": "v3.4.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "8e282e5f5e2db5fb2271b3962ad69875c34a6f41" + "url": "https://github.com/predis/predis.git", + "reference": "2033429520d8997a7815a2485f56abe6d2d0e075" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/8e282e5f5e2db5fb2271b3962ad69875c34a6f41", - "reference": "8e282e5f5e2db5fb2271b3962ad69875c34a6f41", + "url": "https://api.github.com/repos/predis/predis/zipball/2033429520d8997a7815a2485f56abe6d2d0e075", + "reference": "2033429520d8997a7815a2485f56abe6d2d0e075", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0" + "php": "^7.2 || ^8.0", + "psr/http-message": "^1.0|^2.0" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "friendsofphp/php-cs-fixer": "^3.3", + "phpstan/phpstan": "^1.9", + "phpunit/phpcov": "^6.0 || ^8.0", + "phpunit/phpunit": "^8.0 || ~9.4.4" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } + "suggest": { + "ext-relay": "Faster connection with in-memory caching (>=0.6.2)" }, + "type": "library", "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Predis\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Till Krüss", + "homepage": "https://till.im", + "role": "Maintainer" } ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "description": "A flexible and feature-complete Redis/Valkey client for PHP.", + "homepage": "http://github.com/predis/predis", "keywords": [ - "filesystem", - "iterator" + "nosql", + "predis", + "redis" ], + "support": { + "issues": "https://github.com/predis/predis/issues", + "source": "https://github.com/predis/predis/tree/v3.4.2" + }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/sponsors/tillkruss", "type": "github" } ], - "time": "2020-06-26T11:50:37+00:00" + "time": "2026-03-09T20:33:04+00:00" }, { - "name": "phpunit/php-invoker", - "version": "3.0.2", + "name": "psr/container", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "f6eedfed1085dd1f4c599629459a0277d25f9a66" + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f6eedfed1085dd1f4c599629459a0277d25f9a66", - "reference": "f6eedfed1085dd1f4c599629459a0277d25f9a66", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0" - }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^9.0" - }, - "suggest": { - "ext-pcntl": "*" + "php": ">=7.4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\Container\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", "keywords": [ - "process" - ], - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" ], - "time": "2020-06-26T11:53:53+00:00" + "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": "phpunit/php-text-template", - "version": "2.0.2", + "name": "psr/event-dispatcher", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "6ff9c8ea4d3212b88fcf74e25e516e2c51c99324" + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/6ff9c8ea4d3212b88fcf74e25e516e2c51c99324", - "reference": "6ff9c8ea4d3212b88fcf74e25e516e2c51c99324", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" + "php": ">=7.2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" } ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "description": "Standard interfaces for event handling.", "keywords": [ - "template" + "events", + "psr", + "psr-14" ], - "funding": [ + "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-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": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "time": "2020-06-26T11:55:37+00:00" + "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": "phpunit/php-timer", - "version": "5.0.1", + "name": "psr/log", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "cc49734779cbb302bf51a44297dab8c4bbf941e7" + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/cc49734779cbb302bf51a44297dab8c4bbf941e7", - "reference": "cc49734779cbb302bf51a44297dab8c4bbf941e7", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.2" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "3.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\Log\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ - "timer" + "log", + "psr", + "psr-3" ], - "funding": [ + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "rancoud/database", + "version": "7.0.3", + "source": { + "type": "git", + "url": "https://github.com/rancoud/Database.git", + "reference": "b16bf7b65400d0afc4dc06041bc667a9a1b72fd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rancoud/Database/zipball/b16bf7b65400d0afc4dc06041bc667a9a1b72fd5", + "reference": "b16bf7b65400d0afc4dc06041bc667a9a1b72fd5", + "shasum": "" + }, + "require": { + "ext-pdo": "*", + "php": ">=8.4.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.95.1", + "phpunit/phpunit": "12.5.22" + }, + "suggest": { + "ext-pdo_mysql": "Needed to connect MySQL", + "ext-pdo_pgsql": "Needed to connect PostgreSQL", + "ext-pdo_sqlite": "Needed to connect SQLite" + }, + "type": "library", + "autoload": { + "psr-4": { + "Rancoud\\Database\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "name": "Sebastien Rancoud", + "email": "contact@rancoud.com" } ], - "time": "2020-06-26T11:58:13+00:00" + "description": "Database package", + "support": { + "issues": "https://github.com/rancoud/Database/issues", + "source": "https://github.com/rancoud/Database/tree/7.0.3" + }, + "time": "2026-06-01T21:56:55+00:00" }, { - "name": "phpunit/php-token-stream", - "version": "4.0.3", + "name": "react/cache", + "version": "v1.2.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "5672711b6b07b14d5ab694e700c62eeb82fcf374" + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/5672711b6b07b14d5ab694e700c62eeb82fcf374", - "reference": "5672711b6b07b14d5ab694e700c62eeb82fcf374", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": "^7.3 || ^8.0" + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "React\\Cache\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "description": "Async, Promise-based cache interface for ReactPHP", "keywords": [ - "tokenizer" + "cache", + "caching", + "promise", + "reactphp" ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2020-06-27T06:36:25+00:00" + "time": "2022-11-30T15:59:55+00:00" }, { - "name": "phpunit/phpunit", - "version": "9.2.5", + "name": "react/child-process", + "version": "v0.6.7", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "ad7cc5ec3ab2597b329880e30442d9054526023b" + "url": "https://github.com/reactphp/child-process.git", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ad7cc5ec3ab2597b329880e30442d9054526023b", - "reference": "ad7cc5ec3ab2597b329880e30442d9054526023b", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/970f0e71945556422ee4570ccbabaedc3cf04ad3", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.2.0", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.9.1", - "phar-io/manifest": "^1.0.3", - "phar-io/version": "^2.0.1", - "php": "^7.3", - "phpspec/prophecy": "^1.8.1", - "phpunit/php-code-coverage": "^8.0.1", - "phpunit/php-file-iterator": "^3.0", - "phpunit/php-invoker": "^3.0", - "phpunit/php-text-template": "^2.0", - "phpunit/php-timer": "^5.0", - "sebastian/code-unit": "^1.0.2", - "sebastian/comparator": "^4.0", - "sebastian/diff": "^4.0", - "sebastian/environment": "^5.0.1", - "sebastian/exporter": "^4.0", - "sebastian/global-state": "^4.0", - "sebastian/object-enumerator": "^4.0", - "sebastian/resource-operations": "^3.0", - "sebastian/type": "^2.1", - "sebastian/version": "^3.0" + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/event-loop": "^1.2", + "react/stream": "^1.4" }, "require-dev": { - "ext-pdo": "*", - "phpspec/prophecy-phpunit": "^2.0" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/socket": "^1.16", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" }, - "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" - }, - "bin": [ - "phpunit" - ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.2-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ], - "files": [ - "src/Framework/Assert/Functions.php" - ] + "psr-4": { + "React\\ChildProcess\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", + "description": "Event-driven library for executing child processes with ReactPHP.", "keywords": [ - "phpunit", - "testing", - "xunit" + "event-driven", + "process", + "reactphp" ], + "support": { + "issues": "https://github.com/reactphp/child-process/issues", + "source": "https://github.com/reactphp/child-process/tree/v0.6.7" + }, "funding": [ { - "url": "https://phpunit.de/donate.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2020-06-22T07:10:55+00:00" + "time": "2025-12-23T15:25:20+00:00" }, { - "name": "predis/predis", - "version": "v1.1.1", + "name": "react/dns", + "version": "v1.14.0", "source": { "type": "git", - "url": "https://github.com/nrk/predis.git", - "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1" + "url": "https://github.com/reactphp/dns.git", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nrk/predis/zipball/f0210e38881631afeafb56ab43405a92cafd9fd1", - "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1", + "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" }, "require-dev": { - "phpunit/phpunit": "~4.8" - }, - "suggest": { - "ext-curl": "Allows access to Webdis when paired with phpiredis", - "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" }, "type": "library", "autoload": { "psr-4": { - "Predis\\": "src/" + "React\\Dns\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1384,46 +1749,72 @@ ], "authors": [ { - "name": "Daniele Alessandri", - "email": "suppakilla@gmail.com", - "homepage": "http://clorophilla.net" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Flexible and feature-complete Redis client for PHP and HHVM", - "homepage": "http://github.com/nrk/predis", + "description": "Async DNS resolver for ReactPHP", "keywords": [ - "nosql", - "predis", - "redis" + "async", + "dns", + "dns-resolver", + "reactphp" ], - "time": "2016-06-16T16:22:20+00:00" + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.14.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-18T19:34:28+00:00" }, { - "name": "psr/container", - "version": "1.0.0", + "name": "react/event-loop", + "version": "v1.6.0", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + "url": "https://github.com/reactphp/event-loop.git", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a", "shasum": "" }, "require": { "php": ">=5.3.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", "autoload": { "psr-4": { - "Psr\\Container\\": "src/" + "React\\EventLoop\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1432,47 +1823,71 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.6.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } ], - "time": "2017-02-14T16:28:37+00:00" + "time": "2025-11-17T20:46:25+00:00" }, { - "name": "psr/event-dispatcher", - "version": "1.0.0", + "name": "react/promise", + "version": "v3.3.0", "source": { "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + "url": "https://github.com/reactphp/promise.git", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", "shasum": "" }, "require": { - "php": ">=7.2.0" + "php": ">=7.1.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "require-dev": { + "phpstan/phpstan": "1.12.28 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" }, + "type": "library", "autoload": { + "files": [ + "src/functions_include.php" + ], "psr-4": { - "Psr\\EventDispatcher\\": "src/" + "React\\Promise\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1481,44 +1896,75 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Standard interfaces for event handling.", + "description": "A lightweight implementation of CommonJS Promises/A for PHP", "keywords": [ - "events", - "psr", - "psr-14" + "promise", + "promises" ], - "time": "2019-01-08T18:20:26+00:00" + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-08-19T18:57:03+00:00" }, { - "name": "psr/log", - "version": "1.1.3", + "name": "react/socket", + "version": "v1.17.0", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + "url": "https://github.com/reactphp/socket.git", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08", "shasum": "" }, "require": { - "php": ">=5.3.0" + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" }, + "type": "library", "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "React\\Socket\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1527,46 +1973,73 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", "keywords": [ - "log", - "psr", - "psr-3" + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.17.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } ], - "time": "2020-03-23T09:12:05+00:00" + "time": "2025-11-19T20:47:34+00:00" }, { - "name": "rancoud/database", - "version": "4.0.1", + "name": "react/stream", + "version": "v1.4.0", "source": { "type": "git", - "url": "https://github.com/rancoud/Database.git", - "reference": "1c2aa2e8b7554950f4eed765d23ec240b61d2248" + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rancoud/Database/zipball/1c2aa2e8b7554950f4eed765d23ec240b61d2248", - "reference": "1c2aa2e8b7554950f4eed765d23ec240b61d2248", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", "shasum": "" }, "require": { - "ext-pdo": ">=7.4.0", - "php": ">=7.4.0" + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.16", - "phpunit/phpunit": "^9.1", - "squizlabs/php_codesniffer": "^3.5" + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, "type": "library", "autoload": { "psr-4": { - "Rancoud\\Database\\": "src" + "React\\Stream\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1575,37 +2048,73 @@ ], "authors": [ { - "name": "Sebastien Rancoud", - "email": "contact@rancoud.com" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Database package", - "time": "2020-07-01T20:03:41+00:00" + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" }, { - "name": "sebastian/code-unit", - "version": "1.0.5", + "name": "sebastian/cli-parser", + "version": "4.2.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "c1e2df332c905079980b119c4db103117e5e5c90" + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "7d05781b13f7dec9043a629a21d086ed74582a15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/c1e2df332c905079980b119c4db103117e5e5c90", - "reference": "c1e2df332c905079980b119c4db103117e5e5c90", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/7d05781b13f7dec9043a629a21d086ed74582a15", + "reference": "7d05781b13f7dec9043a629a21d086ed74582a15", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^12.5.25" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "4.2-dev" } }, "autoload": { @@ -1624,40 +2133,64 @@ "role": "lead" } ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", + "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/4.2.1" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser", + "type": "tidelift" } ], - "time": "2020-06-26T12:50:45+00:00" + "time": "2026-05-17T05:29:34+00:00" }, { - "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.2", + "name": "sebastian/comparator", + "version": "7.1.8", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ee51f9bb0c6d8a43337055db3120829fa14da819" + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "7c65c1e79836812819705b473a90c12399542485" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ee51f9bb0c6d8a43337055db3120829fa14da819", - "reference": "ee51f9bb0c6d8a43337055db3120829fa14da819", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/7c65c1e79836812819705b473a90c12399542485", + "reference": "7c65c1e79836812819705b473a90c12399542485", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/diff": "^7.0", + "sebastian/exporter": "^7.0.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^12.5.25" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "7.1-dev" } }, "autoload": { @@ -1673,44 +2206,77 @@ { "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": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "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/7.1.8" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2020-06-26T12:04:00+00:00" + "time": "2026-05-21T04:45:25+00:00" }, { - "name": "sebastian/comparator", - "version": "4.0.3", + "name": "sebastian/complexity", + "version": "5.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "dcc580eadfaa4e7f9d2cf9ae1922134ea962e14f" + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/dcc580eadfaa4e7f9d2cf9ae1922134ea962e14f", - "reference": "dcc580eadfaa4e7f9d2cf9ae1922134ea962e14f", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" + "nikic/php-parser": "^5.0", + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -1725,61 +2291,50 @@ "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" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], + "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/5.0.0" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-06-26T12:05:46+00:00" + "time": "2025-02-07T04:55:25+00:00" }, { "name": "sebastian/diff", - "version": "4.0.2", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "1e90b4cf905a7d06c420b1d2e9d11a4dc8a13113" + "reference": "7ab1ea946c012266ca32390913653d844ecd085f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/1e90b4cf905a7d06c420b1d2e9d11a4dc8a13113", - "reference": "1e90b4cf905a7d06c420b1d2e9d11a4dc8a13113", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.0", - "symfony/process": "^4.2 || ^5" + "phpunit/phpunit": "^12.0", + "symfony/process": "^7.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -1809,33 +2364,38 @@ "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/7.0.0" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-06-30T04:46:02+00:00" + "time": "2025-02-07T04:55:46+00:00" }, { "name": "sebastian/environment", - "version": "5.1.2", + "version": "8.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "0a757cab9d5b7ef49a619f1143e6c9c1bc0fe9d2" + "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/0a757cab9d5b7ef49a619f1143e6c9c1bc0fe9d2", - "reference": "0a757cab9d5b7ef49a619f1143e6c9c1bc0fe9d2", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/9d32c685773823b1983e256ae4ecd48a10d6e439", + "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^12.5.26" }, "suggest": { "ext-posix": "*" @@ -1843,7 +2403,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "8.1-dev" } }, "autoload": { @@ -1862,46 +2422,63 @@ } ], "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", + "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/8.1.2" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" } ], - "time": "2020-06-26T12:07:24+00:00" + "time": "2026-05-25T13:40:20+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.2", + "version": "7.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "571d721db4aec847a0e59690b954af33ebf9f023" + "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/571d721db4aec847a0e59690b954af33ebf9f023", - "reference": "571d721db4aec847a0e59690b954af33ebf9f023", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23", + "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0", - "sebastian/recursion-context": "^4.0" + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/recursion-context": "^7.0.1" }, "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.2" + "phpunit/phpunit": "^12.5.25" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -1936,49 +2513,63 @@ } ], "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", + "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/7.0.3" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2020-06-26T12:08:55+00:00" + "time": "2026-05-20T04:37:17+00:00" }, { "name": "sebastian/global-state", - "version": "4.0.0", + "version": "8.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bdb1e7c79e592b8c82cb1699be3c8743119b8a72" + "reference": "b164d3274d6537ab462591c5755f76a8f5b1aae9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bdb1e7c79e592b8c82cb1699be3c8743119b8a72", - "reference": "bdb1e7c79e592b8c82cb1699be3c8743119b8a72", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b164d3274d6537ab462591c5755f76a8f5b1aae9", + "reference": "b164d3274d6537ab462591c5755f76a8f5b1aae9", "shasum": "" }, "require": { - "php": "^7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0.1" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^9.0" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^12.5.28" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -1997,38 +2588,60 @@ } ], "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", "keywords": [ "global state" ], - "time": "2020-02-07T06:11:37+00:00" + "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/8.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" + } + ], + "time": "2026-06-01T15:10:33+00:00" }, { - "name": "sebastian/object-enumerator", - "version": "4.0.2", + "name": "sebastian/lines-of-code", + "version": "4.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "074fed2d0a6d08e1677dd8ce9d32aecb384917b8" + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/074fed2d0a6d08e1677dd8ce9d32aecb384917b8", - "reference": "074fed2d0a6d08e1677dd8ce9d32aecb384917b8", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d543b8ef219dcd8da262cbb958639a96bedba10e", + "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "nikic/php-parser": "^5.7.0", + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^12.5.25" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -2043,43 +2656,63 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "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/4.0.1" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/lines-of-code", + "type": "tidelift" } ], - "time": "2020-06-26T12:11:32+00:00" + "time": "2026-05-19T16:22:07+00:00" }, { - "name": "sebastian/object-reflector", - "version": "2.0.2", + "name": "sebastian/object-enumerator", + "version": "7.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "127a46f6b057441b201253526f81d5406d6c7840" + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/127a46f6b057441b201253526f81d5406d6c7840", - "reference": "127a46f6b057441b201253526f81d5406d6c7840", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0" + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -2097,40 +2730,45 @@ "email": "sebastian@phpunit.de" } ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-06-26T12:12:55+00:00" + "time": "2025-02-07T04:57:48+00:00" }, { - "name": "sebastian/recursion-context", - "version": "4.0.2", + "name": "sebastian/object-reflector", + "version": "5.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "062231bf61d2b9448c4fa5a7643b5e1829c11d63" + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/062231bf61d2b9448c4fa5a7643b5e1829c11d63", - "reference": "062231bf61d2b9448c4fa5a7643b5e1829c11d63", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -2146,50 +2784,47 @@ { "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": "http://www.github.com/sebastianbergmann/recursion-context", + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-06-26T12:14:17+00:00" + "time": "2025-02-07T04:58:17+00:00" }, { - "name": "sebastian/resource-operations", - "version": "3.0.2", + "name": "sebastian/recursion-context", + "version": "7.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0653718a5a629b065e91f774595267f8dc32e213" + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0653718a5a629b065e91f774595267f8dc32e213", - "reference": "0653718a5a629b065e91f774595267f8dc32e213", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -2205,42 +2840,67 @@ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" } ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2020-06-26T12:16:22+00:00" + "time": "2025-08-13T04:44:59+00:00" }, { "name": "sebastian/type", - "version": "2.1.1", + "version": "6.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "56b3ba194e0cbaaf3de7ccd353c289d7a84ed022" + "reference": "82ff822c2edc46724be9f7411d3163021f602773" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/56b3ba194e0cbaaf3de7ccd353c289d7a84ed022", - "reference": "56b3ba194e0cbaaf3de7ccd353c289d7a84ed022", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/82ff822c2edc46724be9f7411d3163021f602773", + "reference": "82ff822c2edc46724be9f7411d3163021f602773", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.2" + "phpunit/phpunit": "^12.5.25" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -2261,35 +2921,52 @@ ], "description": "Collection of value objects that represent the types of the PHP type system", "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/6.0.4" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" } ], - "time": "2020-06-26T12:17:54+00:00" + "time": "2026-05-20T06:45:45+00:00" }, { "name": "sebastian/version", - "version": "3.0.1", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "626586115d0ed31cb71483be55beb759b5af5a3c" + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/626586115d0ed31cb71483be55beb759b5af5a3c", - "reference": "626586115d0ed31cb71483be55beb759b5af5a3c", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0" + "php": ">=8.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -2310,118 +2987,118 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/6.0.0" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-06-26T12:18:43+00:00" + "time": "2025-02-07T05:00:38+00:00" }, { - "name": "squizlabs/php_codesniffer", - "version": "3.5.5", + "name": "staabm/side-effects-detector", + "version": "1.0.5", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "73e2e7f57d958e7228fce50dc0c61f58f017f9f6" + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/73e2e7f57d958e7228fce50dc0c61f58f017f9f6", - "reference": "73e2e7f57d958e7228fce50dc0c61f58f017f9f6", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", "shasum": "" }, "require": { - "ext-simplexml": "*", "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" + "php": "^7.4 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } + "autoload": { + "classmap": [ + "lib/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "authors": [ + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ { - "name": "Greg Sherwood", - "role": "lead" + "url": "https://github.com/staabm", + "type": "github" } ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "time": "2020-04-17T01:09:41+00:00" + "time": "2024-10-20T05:08:20+00:00" }, { "name": "symfony/console", - "version": "v5.1.2", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "34ac555a3627e324b660e318daa07572e1140123" + "reference": "f5a856c6ecb56b3c21ed94a5b7bf940d857d110a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/34ac555a3627e324b660e318daa07572e1140123", - "reference": "34ac555a3627e324b660e318daa07572e1140123", + "url": "https://api.github.com/repos/symfony/console/zipball/f5a856c6ecb56b3c21ed94a5b7bf940d857d110a", + "reference": "f5a856c6ecb56b3c21ed94a5b7bf940d857d110a", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.8", - "symfony/polyfill-php80": "^1.15", - "symfony/service-contracts": "^1.1|^2", - "symfony/string": "^5.1" + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php85": "^1.32", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.4.6|^8.0.6" }, "conflict": { - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" + "symfony/dependency-injection": "<8.1", + "symfony/event-dispatcher": "<8.1" }, "provide": { - "psr/log-implementation": "1.0" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/lock": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", - "symfony/var-dumper": "^4.4|^5.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } + "psr/log-implementation": "1.0|2.0|3.0" }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^8.1", + "symfony/event-dispatcher": "^8.1", + "symfony/filesystem": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Console\\": "" @@ -2444,8 +3121,17 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Console Component", + "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/v8.1.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2455,34 +3141,42 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-06-15T12:59:21+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v2.1.2", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337" + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337", - "reference": "dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.1" }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-master": "2.1-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -2506,6 +3200,9 @@ ], "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2515,59 +3212,56 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-05-27T08:34:37+00:00" + "time": "2026-04-13T15:52:40+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.1.2", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "cc0d059e2e997e79ca34125a52f3e33de4424ac7" + "reference": "f249ae3f680958b6f1f9dd76e5747cf0695b4102" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/cc0d059e2e997e79ca34125a52f3e33de4424ac7", - "reference": "cc0d059e2e997e79ca34125a52f3e33de4424ac7", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f249ae3f680958b6f1f9dd76e5747cf0695b4102", + "reference": "f249ae3f680958b6f1f9dd76e5747cf0695b4102", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/event-dispatcher-contracts": "^2", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<4.4" + "symfony/security-http": "<7.4", + "symfony/service-contracts": "<2.5" }, "provide": { "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0" + "symfony/event-dispatcher-implementation": "2.0|3.0" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/stopwatch": "^4.4|^5.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^7.4|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" @@ -2590,8 +3284,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony EventDispatcher Component", + "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/v8.1.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2601,38 +3298,43 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.1.2", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "405952c4e90941a17e52ef7489a2bd94870bb290" + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/405952c4e90941a17e52ef7489a2bd94870bb290", - "reference": "405952c4e90941a17e52ef7489a2bd94870bb290", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/ccba7060602b7fed0b03c85bf025257f76d9ef32", + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "psr/event-dispatcher": "^1" }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-master": "2.1-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -2664,6 +3366,9 @@ "interoperability", "standards" ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2673,37 +3378,41 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2026-01-05T13:30:16+00:00" }, { "name": "symfony/filesystem", - "version": "v5.1.2", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "6e4320f06d5f2cce0d96530162491f4465179157" + "reference": "99aec13b82b4967ec5088222c4a3ecca955949c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/6e4320f06d5f2cce0d96530162491f4465179157", - "reference": "6e4320f06d5f2cce0d96530162491f4465179157", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/99aec13b82b4967ec5088222c4a3ecca955949c2", + "reference": "99aec13b82b4967ec5088222c4a3ecca955949c2", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-ctype": "~1.8" + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } + "require-dev": { + "symfony/process": "^7.4|^8.0" }, + "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Filesystem\\": "" @@ -2726,8 +3435,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Filesystem Component", + "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v8.1.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2737,36 +3449,38 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-05-30T20:35:19+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/finder", - "version": "v5.1.2", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "4298870062bfc667cb78d2b379be4bf5dec5f187" + "reference": "58d2e767a66052c1487356f953445634a8194c64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/4298870062bfc667cb78d2b379be4bf5dec5f187", - "reference": "4298870062bfc667cb78d2b379be4bf5dec5f187", + "url": "https://api.github.com/repos/symfony/finder/zipball/58d2e767a66052c1487356f953445634a8194c64", + "reference": "58d2e767a66052c1487356f953445634a8194c64", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=8.4.1" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } + "require-dev": { + "symfony/filesystem": "^7.4|^8.0" }, + "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Finder\\": "" @@ -2789,8 +3503,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Finder Component", + "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v8.1.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2800,38 +3517,36 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/options-resolver", - "version": "v5.1.2", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "663f5dd5e14057d1954fe721f9709d35837f2447" + "reference": "88f9c561f678a02d54b897014049fa839e33ff82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/663f5dd5e14057d1954fe721f9709d35837f2447", - "reference": "663f5dd5e14057d1954fe721f9709d35837f2447", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/88f9c561f678a02d54b897014049fa839e33ff82", + "reference": "88f9c561f678a02d54b897014049fa839e33ff82", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\OptionsResolver\\": "" @@ -2854,13 +3569,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony OptionsResolver Component", + "description": "Provides an improved replacement for the array_replace PHP function", "homepage": "https://symfony.com", "keywords": [ "config", "configuration", "options" ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v8.1.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2870,50 +3588,54 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-05-23T13:08:13+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.17.1", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d" + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d", - "reference": "2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" }, "suggest": { "ext-ctype": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.17-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2937,6 +3659,9 @@ "polyfill", "portable" ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2946,50 +3671,51 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-06-06T08:46:27+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.17.1", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "6e4dbcf5e81eba86e36731f94fe56b1726835846" + "reference": "e9247d281d694a5120554d9afaf54e070e88a603" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/6e4dbcf5e81eba86e36731f94fe56b1726835846", - "reference": "6e4dbcf5e81eba86e36731f94fe56b1726835846", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603", + "reference": "e9247d281d694a5120554d9afaf54e070e88a603", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.17-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3015,6 +3741,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.38.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3024,50 +3753,51 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-06-06T08:46:27+00:00" + "time": "2026-05-26T05:58:03+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.17.1", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "40309d1700e8f72447bb9e7b54af756eeea35620" + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/40309d1700e8f72447bb9e7b54af756eeea35620", - "reference": "40309d1700e8f72447bb9e7b54af756eeea35620", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.17-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -3096,6 +3826,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3105,50 +3838,55 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-06-14T14:40:37+00:00" + "time": "2026-05-25T13:48:31+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.17.1", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "7110338d81ce1cbc3e273136e4574663627037a7" + "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7110338d81ce1cbc3e273136e4574663627037a7", - "reference": "7110338d81ce1cbc3e273136e4574663627037a7", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/14c5439eec4ccff081ac14eca2dc57feb2a66d92", + "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92", "shasum": "" }, "require": { - "php": ">=5.3.3" + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" }, "suggest": { "ext-mbstring": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.17-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3173,6 +3911,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3182,48 +3923,48 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-06-06T08:46:27+00:00" + "time": "2026-05-26T12:51:13+00:00" }, { - "name": "symfony/polyfill-php70", - "version": "v1.17.1", + "name": "symfony/polyfill-php80", + "version": "v1.37.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "471b096aede7025bace8eb356b9ac801aaba7e2d" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/471b096aede7025bace8eb356b9ac801aaba7e2d", - "reference": "471b096aede7025bace8eb356b9ac801aaba7e2d", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", "shasum": "" }, "require": { - "paragonie/random_compat": "~1.0|~2.0|~9.99", - "php": ">=5.3.3" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.17-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php70\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -3233,6 +3974,10 @@ "MIT" ], "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -3242,7 +3987,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -3250,6 +3995,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3259,42 +4007,50 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-06-06T08:46:27+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { - "name": "symfony/polyfill-php72", - "version": "v1.17.0", + "name": "symfony/polyfill-php81", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "f048e612a3905f34931127360bdd2def19a5e582" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/f048e612a3905f34931127360bdd2def19a5e582", - "reference": "f048e612a3905f34931127360bdd2def19a5e582", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.17-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - }, "files": [ "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3311,7 +4067,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -3319,6 +4075,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.38.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3328,47 +4087,48 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-05-12T16:47:27+00:00" + "time": "2026-05-26T12:45:58+00:00" }, { - "name": "symfony/polyfill-php73", - "version": "v1.17.1", + "name": "symfony/polyfill-php84", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "fa0837fe02d617d31fbb25f990655861bb27bd1a" + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fa0837fe02d617d31fbb25f990655861bb27bd1a", - "reference": "fa0837fe02d617d31fbb25f990655861bb27bd1a", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.17-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -3387,7 +4147,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -3395,6 +4155,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3404,47 +4167,48 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-06-06T08:46:27+00:00" + "time": "2026-05-26T12:51:13+00:00" }, { - "name": "symfony/polyfill-php80", - "version": "v1.17.1", + "name": "symfony/polyfill-php85", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "4a5b6bba3259902e386eb80dd1956181ee90b5b2" + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4a5b6bba3259902e386eb80dd1956181ee90b5b2", - "reference": "4a5b6bba3259902e386eb80dd1956181ee90b5b2", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", + "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", "shasum": "" }, "require": { - "php": ">=7.0.8" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.17-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -3454,10 +4218,6 @@ "MIT" ], "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -3467,7 +4227,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -3475,6 +4235,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.38.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3484,37 +4247,35 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-06-06T08:46:27+00:00" + "time": "2026-05-26T02:25:22+00:00" }, { "name": "symfony/process", - "version": "v5.1.2", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1" + "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1", - "reference": "7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1", + "url": "https://api.github.com/repos/symfony/process/zipball/c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", + "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.4.1" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Process\\": "" @@ -3537,8 +4298,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Process Component", + "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v8.1.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3548,44 +4312,56 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-05-30T20:35:19+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.1.2", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "66a8f0957a3ca54e4f724e49028ab19d75a8918b" + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/66a8f0957a3ca54e4f724e49028ab19d75a8918b", - "reference": "66a8f0957a3ca54e4f724e49028ab19d75a8918b", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.0" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, - "suggest": { - "symfony/service-implementation": "" + "conflict": { + "ext-psr": "<1.1|>=2" }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-master": "2.1-dev" + "dev-main": "3.7-dev" } }, "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3611,6 +4387,9 @@ "interoperability", "standards" ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.7.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3620,37 +4399,36 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2026-03-28T09:44:51+00:00" }, { "name": "symfony/stopwatch", - "version": "v5.1.2", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "0f7c58cf81dbb5dd67d423a89d577524a2ec0323" + "reference": "21c07b026905d596e8379caeb115d87aa479499d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/0f7c58cf81dbb5dd67d423a89d577524a2ec0323", - "reference": "0f7c58cf81dbb5dd67d423a89d577524a2ec0323", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/21c07b026905d596e8379caeb115d87aa479499d", + "reference": "21c07b026905d596e8379caeb115d87aa479499d", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/service-contracts": "^1.0|^2" + "php": ">=8.4.1", + "symfony/service-contracts": "^2.5|^3" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Stopwatch\\": "" @@ -3673,8 +4451,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Stopwatch Component", + "description": "Provides a way to profile code", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v8.1.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3684,54 +4465,56 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/string", - "version": "v5.1.2", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "ac70459db781108db7c6d8981dd31ce0e29e3298" + "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/ac70459db781108db7c6d8981dd31ce0e29e3298", - "reference": "ac70459db781108db7c6d8981dd31ce0e29e3298", + "url": "https://api.github.com/repos/symfony/string/zipball/afd5944f4005862d961efb85c8bbd5c523c4e3c9", + "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" + "php": ">=8.4.1", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^4.4|^5.0", - "symfony/http-client": "^4.4|^5.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0" + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^7.4|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { - "psr-4": { - "Symfony\\Component\\String\\": "" - }, "files": [ "Resources/functions.php" ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, "exclude-from-classmap": [ "/Tests/" ] @@ -3750,7 +4533,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony String component", + "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", @@ -3760,6 +4543,9 @@ "utf-8", "utf8" ], + "support": { + "source": "https://github.com/symfony/string/tree/v8.1.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3769,32 +4555,36 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-06-11T12:16:36+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "theseer/tokenizer", - "version": "1.1.3", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", - "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/7989e43bf381af0eac72e4f0ca5bcbfa81658be4", + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4", "shasum": "" }, "require": { "ext-dom": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", - "php": "^7.0" + "php": "^8.1" }, "type": "library", "autoload": { @@ -3814,67 +4604,30 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2019-06-13T22:48:21+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.9.0", - "source": { - "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "9dc4f203e36f2b486149058bade43c851dd97451" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/9dc4f203e36f2b486149058bade43c851dd97451", - "reference": "9dc4f203e36f2b486149058bade43c851dd97451", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<3.9.1" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.36 || ^7.5.13" + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/2.0.1" }, - "type": "library", - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ + "funding": [ { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "url": "https://github.com/theseer", + "type": "github" } ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "time": "2020-06-16T10:16:42+00:00" + "time": "2025-12-08T11:19:18+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.4.0", - "ext-openssl": ">=7.4.0" + "php": ">=8.4.0", + "ext-openssl": "*", + "ext-session": "*", + "ext-mbstring": "*" }, - "platform-dev": [], - "plugin-api-version": "1.1.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } diff --git a/coverage/.gitkeep b/coverage/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..812037d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +services: + mariadb: + image: mariadb:10.4 + environment: + - "MYSQL_ALLOW_EMPTY_PASSWORD=yes" + - "MYSQL_DATABASE=test_database" + restart: "on-failure" + + redis: + image: redis:6 + restart: "on-failure" + + lib: + build: ./ + depends_on: + - mariadb + - redis + environment: + - "MYSQL_HOST=mariadb" + - "REDIS_HOST=redis" + - "XDEBUG_MODE=coverage" + volumes: + - "./coverage:/app/coverage" + restart: "no" diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..e8b4842 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +DB_TIMEOUT=${DB_TIMEOUT:-45} + +# Check MySQL up +MYSQL_CMD="mysql -h ${MYSQL_HOST} -P ${MYSQL_PORT:-3306} -u ${MYSQL_USER:-root}" +echo "Waiting ${DB_TIMEOUT}s for MySQL database to be ready..." +counter=1 +while ! ${MYSQL_CMD} -e "show databases;" > /dev/null 2>&1; do + sleep 1 + counter=$((counter + 1)) + if [ ${counter} -gt "${DB_TIMEOUT}" ]; then + >&2 echo "ERROR: Failed to connect to MySQL database on $MYSQL_HOST" + exit 1 + fi; +done +echo "MySQL database ready!" + +exec "$@" diff --git a/phpcs.xml b/phpcs.xml deleted file mode 100644 index 317373d..0000000 --- a/phpcs.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - Session - - - - - src - - - - \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml index aa208dd..add1e99 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,13 +1,13 @@ - - - - tests - - - - - - src/ - - - \ No newline at end of file + + + + + tests + + + + + src/ + + + diff --git a/src/ArrayManager.php b/src/ArrayManager.php index cee61d3..e93a8c1 100644 --- a/src/ArrayManager.php +++ b/src/ArrayManager.php @@ -4,24 +4,17 @@ namespace Rancoud\Session; -/** - * Trait ArrayManager. - */ trait ArrayManager { - /** @var array */ protected static array $flashData = []; + /** @throws SessionException */ abstract protected static function startSessionIfNotHasStarted(); + /** @throws SessionException */ abstract protected static function startSessionIfNotHasStartedForceWrite(); - /** - * @param string $key - * @param mixed $value - * - * @throws \Exception - */ + /** @throws SessionException */ public static function set(string $key, $value): void { static::startSessionIfNotHasStartedForceWrite(); @@ -29,13 +22,7 @@ public static function set(string $key, $value): void $_SESSION[$key] = $value; } - /** - * @param string $key - * - * @throws \Exception - * - * @return bool - */ + /** @throws SessionException */ public static function has(string $key): bool { static::startSessionIfNotHasStarted(); @@ -43,14 +30,7 @@ public static function has(string $key): bool return \array_key_exists($key, $_SESSION); } - /** - * @param string $key - * @param mixed $value - * - * @throws \Exception - * - * @return bool - */ + /** @throws SessionException */ public static function hasKeyAndValue(string $key, $value): bool { static::startSessionIfNotHasStarted(); @@ -58,13 +38,7 @@ public static function hasKeyAndValue(string $key, $value): bool return \array_key_exists($key, $_SESSION) && $_SESSION[$key] === $value; } - /** - * @param string $key - * - * @throws \Exception - * - * @return mixed - */ + /** @throws SessionException */ public static function get(string $key) { static::startSessionIfNotHasStarted(); @@ -72,11 +46,7 @@ public static function get(string $key) return (static::has($key)) ? $_SESSION[$key] : null; } - /** - * @param string $key - * - * @throws \Exception - */ + /** @throws SessionException */ public static function remove(string $key): void { static::startSessionIfNotHasStartedForceWrite(); @@ -86,11 +56,7 @@ public static function remove(string $key): void } } - /** - * @throws \Exception - * - * @return array - */ + /** @throws SessionException */ public static function getAll(): array { static::startSessionIfNotHasStarted(); @@ -98,13 +64,7 @@ public static function getAll(): array return $_SESSION; } - /** - * @param string $key - * - * @throws \Exception - * - * @return mixed - */ + /** @throws SessionException */ public static function getAndRemove(string $key) { static::startSessionIfNotHasStartedForceWrite(); @@ -115,53 +75,34 @@ public static function getAndRemove(string $key) return $value; } - /** - * @param string $key - * @param mixed $value - * - * @throws \Exception - */ public static function setFlash(string $key, $value): void { static::$flashData[$key] = $value; } - /** - * @param string $key - * - * @return bool - */ public static function hasFlash(string $key): bool { return \array_key_exists($key, static::$flashData); } - /** - * @param string $key - * @param mixed $value - * - * @return bool - */ public static function hasFlashKeyAndValue(string $key, $value): bool { return \array_key_exists($key, static::$flashData) && static::$flashData[$key] === $value; } - /** - * @param string $key - * - * @throws \Exception - * - * @return mixed - */ public static function getFlash(string $key) { return (static::hasFlash($key)) ? static::$flashData[$key] : null; } - /** - * @param array $keys - */ + public static function removeFlash(string $key): void + { + if (static::hasFlash($key)) { + unset(static::$flashData[$key]); + } + } + + /** @throws SessionException */ public static function keepFlash(array $keys = []): void { static::startSessionIfNotHasStartedForceWrite(); @@ -179,20 +120,6 @@ public static function keepFlash(array $keys = []): void } } - /** - * @throws \Exception - */ - public static function restoreFlashData(): void - { - $data = static::getAndRemove('flash_data'); - if (null !== $data) { - static::$flashData = $data; - } - } - - /** - * @return array - */ public static function getAllFlash(): array { return static::$flashData; diff --git a/src/Database.php b/src/Database.php index da44307..59a3e54 100644 --- a/src/Database.php +++ b/src/Database.php @@ -1,202 +1,213 @@ db = new Db($configuration); - } else { - $this->db = new Db(new Configurator($configuration)); + try { + if ($configuration instanceof Configurator) { + $this->db = new DB($configuration); + } else { + $this->db = new DB(new Configurator($configuration)); + } + } catch (DatabaseException $e) { + throw new SessionException('could not set database: ' . $e->getMessage(), $e->getCode(), $e->getPrevious()); } } - /** - * @param Db $database - */ - public function setCurrentDatabase($database): void + public function setCurrentDatabase(DB $database): void { $this->db = $database; } - /** - * @param int $userId - */ public function setUserId(?int $userId): void { $this->userId = $userId; } - /** - * @param string $savePath - * @param string $sessionName - * - * @return bool - */ - public function open($savePath, $sessionName): bool + /** @throws SessionException */ + public function setLengthSessionID(int $length): void + { + if ($length < 32) { + throw new SessionException('could not set length session ID below 32'); + } + + $this->lengthSessionID = $length; + } + + public function getLengthSessionID(): int + { + return $this->lengthSessionID; + } + + /** @throws SessionException */ + public function deleteUserSessions(int $userID): void + { + try { + $sql = 'DELETE FROM sessions WHERE id_user = :id_user'; + $params = ['id_user' => $userID]; + $this->db->delete($sql, $params); + } catch (DatabaseException $e) { + throw new SessionException('could not delete sessions: ' . $e->getMessage(), $e->getCode(), $e->getPrevious()); + } + } + + /** @throws SessionException */ + public function deleteAnonymousSessions(): void + { + try { + $sql = 'DELETE FROM sessions WHERE id_user IS NULL'; + $this->db->delete($sql); + } catch (DatabaseException $e) { + throw new SessionException('could not delete sessions: ' . $e->getMessage(), $e->getCode(), $e->getPrevious()); + } + } + + public function open(string $path, string $name): bool { return true; } - /** - * @return bool - */ public function close(): bool { return true; } - /** - * @param string $sessionId - * - * @throws DatabaseException - * - * @return string - */ - public function read($sessionId): string + /** @throws SessionException */ + public function read(string $id): string { - $sql = 'SELECT content FROM sessions WHERE id = :id'; - $params = ['id' => $sessionId]; + try { + $sql = 'SELECT content FROM sessions WHERE id = :id'; + $params = ['id' => $id]; - return (string) $this->db->selectVar($sql, $params); + return (string) $this->db->selectVar($sql, $params); + } catch (DatabaseException $e) { + throw new SessionException('could not read session: ' . $e->getMessage(), $e->getCode(), $e->getPrevious()); + } } - /** - * @param string $sessionId - * @param string $data - * - * @throws DatabaseException - * - * @return bool - */ - public function write($sessionId, $data): bool + /** @throws SessionException */ + public function write(string $id, string $data): bool { - $sql = 'REPLACE INTO sessions VALUES(:id, :id_user, NOW(), :content)'; - $params = ['id' => $sessionId, 'id_user' => $this->userId, 'content' => $data]; + try { + $sql = 'REPLACE INTO sessions VALUES(:id, :id_user, UTC_TIMESTAMP(), :content)'; + $params = ['id' => $id, 'id_user' => $this->userId, 'content' => $data]; - $this->db->exec($sql, $params); + $this->db->exec($sql, $params); - return true; + return true; + } catch (DatabaseException $e) { + throw new SessionException('could not update session: ' . $e->getMessage(), $e->getCode(), $e->getPrevious()); + } } - /** - * @param string $sessionId - * - * @throws DatabaseException - * - * @return bool - */ - public function destroy($sessionId): bool + /** @throws SessionException */ + public function destroy(string $id): bool { - $sql = 'DELETE FROM sessions WHERE id = :id'; - $params = ['id' => $sessionId]; - $this->db->delete($sql, $params); - - return true; + try { + $sql = 'DELETE FROM sessions WHERE id = :id'; + $params = ['id' => $id]; + $this->db->delete($sql, $params); + + return true; + } catch (DatabaseException $e) { + throw new SessionException('could not delete session: ' . $e->getMessage(), $e->getCode(), $e->getPrevious()); + } } - /** - * @param int $lifetime - * - * @throws DatabaseException - * - * @return bool - */ - public function gc($lifetime): bool + /** @throws SessionException */ + #[\ReturnTypeWillChange] + public function gc(int $max_lifetime): bool { - $sql = 'DELETE FROM sessions WHERE DATE_ADD(last_access, INTERVAL :seconds second) < NOW()'; - $params = ['seconds' => $lifetime]; - $this->db->delete($sql, $params); - - return true; + try { + $sql = 'DELETE FROM sessions WHERE DATE_ADD(last_access, INTERVAL :seconds second) < UTC_TIMESTAMP()'; + $params = ['seconds' => $max_lifetime]; + $this->db->delete($sql, $params); + + return true; + } catch (DatabaseException $e) { + throw new SessionException('could not clean old sessions: ' . $e->getMessage(), $e->getCode(), $e->getPrevious()); + } } /** * Checks format and id exists, if not session_id will be regenerate. * - * @param string $key - * - * @throws DatabaseException - * - * @return bool + * @throws SessionException */ - public function validateId($key): bool + public function validateId(string $id): bool { - if (\preg_match('/^[a-zA-Z0-9-]{127}+$/', $key) !== 1) { - return false; + try { + if (\preg_match('/^[a-zA-Z0-9-]{' . $this->lengthSessionID . '}+$/', $id) !== 1) { + return false; + } + + $sql = 'SELECT COUNT(id) FROM sessions WHERE id=:id'; + $params = ['id' => $id]; + $count = $this->db->count($sql, $params); + + return $count === 1; + } catch (DatabaseException $e) { + throw new SessionException('could not validate id: ' . $e->getMessage(), $e->getCode(), $e->getPrevious()); } - - $sql = 'SELECT COUNT(id) FROM sessions WHERE id=:id'; - $params = ['id' => $key]; - $count = $this->db->count($sql, $params); - - return $count === 1; } /** * Updates the timestamp of a session when its data didn't change. * - * @param string $sessionId - * @param string $sessionData - * - * @throws DatabaseException - * - * @return bool + * @throws SessionException */ - public function updateTimestamp($sessionId, $sessionData): bool + public function updateTimestamp(string $id, string $data): bool { - return $this->write($sessionId, $sessionData); + return $this->write($id, $data); } /** - * @throws \Exception - * @throws DatabaseException + * @throws SessionException * - * @return string + * @noinspection PhpMethodNamingConventionInspection */ public function create_sid(): string { - $string = ''; - $caracters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-'; - - $countCaracters = 62; - for ($i = 0; $i < 127; ++$i) { - $string .= $caracters[\random_int(0, $countCaracters)]; - } - - $sql = 'SELECT COUNT(id) FROM sessions WHERE id=:id'; - $params = ['id' => $string]; - $count = $this->db->count($sql, $params); - if ($count !== 0) { - return $this->create_sid(); + try { + $string = ''; + $characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-'; + + $countCharacters = 62; + for ($i = 0; $i < $this->lengthSessionID; ++$i) { + $string .= $characters[\random_int(0, $countCharacters)]; + } + + $sql = 'SELECT COUNT(id) FROM sessions WHERE id=:id'; + $params = ['id' => $string]; + $count = $this->db->count($sql, $params); + if ($count !== 0) { + // @codeCoverageIgnoreStart + // Could not reach this statement without mocking the function + return $this->create_sid(); + // @codeCoverageIgnoreEnd + } + + return $string; + } catch (\Exception $e) { + throw new SessionException('could not create sid: ' . $e->getMessage(), $e->getCode(), $e->getPrevious()); } - - return $string; } } diff --git a/src/DatabaseEncryption.php b/src/DatabaseEncryption.php index a7c2655..ebd8b79 100644 --- a/src/DatabaseEncryption.php +++ b/src/DatabaseEncryption.php @@ -4,43 +4,23 @@ namespace Rancoud\Session; -use Rancoud\Database\DatabaseException; - -/** - * Class DatabaseEncryption. - */ class DatabaseEncryption extends Database { use Encryption; - /** - * @param string $sessionId - * - * @throws SessionException - * @throws DatabaseException - * - * @return string - */ - public function read($sessionId): string + /** @throws SessionException */ + public function read(string $id): string { - $encryptedData = parent::read($sessionId); + $encryptedData = parent::read($id); return $this->decrypt($encryptedData); } - /** - * @param string $sessionId - * @param string $data - * - * @throws DatabaseException - * @throws SessionException - * - * @return bool - */ - public function write($sessionId, $data): bool + /** @throws SessionException */ + public function write(string $id, string $data): bool { $cryptedData = $this->encrypt($data); - return parent::write($sessionId, $cryptedData); + return parent::write($id, $cryptedData); } } diff --git a/src/DefaultEncryption.php b/src/DefaultEncryption.php index 98cd168..9cd462f 100644 --- a/src/DefaultEncryption.php +++ b/src/DefaultEncryption.php @@ -4,41 +4,23 @@ namespace Rancoud\Session; -use SessionHandler; - -/** - * Class DefaultEncryption. - */ -class DefaultEncryption extends SessionHandler +class DefaultEncryption extends \SessionHandler { use Encryption; - /** - * @param string $sessionId - * - * @throws SessionException - * - * @return string - */ - public function read($sessionId): string + /** @throws SessionException */ + public function read(string $id): string { - $encryptedData = parent::read($sessionId); + $encryptedData = parent::read($id); return $this->decrypt($encryptedData); } - /** - * @param string $sessionId - * @param string $data - * - * @throws SessionException - * - * @return bool - */ - public function write($sessionId, $data): bool + /** @throws SessionException */ + public function write(string $id, string $data): bool { $cryptedData = $this->encrypt($data); - return parent::write($sessionId, $cryptedData); + return parent::write($id, $cryptedData); } } diff --git a/src/DriverManager.php b/src/DriverManager.php index c90ca7a..7ac0308 100644 --- a/src/DriverManager.php +++ b/src/DriverManager.php @@ -4,24 +4,20 @@ namespace Rancoud\Session; -use SessionHandler; -use SessionHandlerInterface; +use Predis\Client as PredisClient; +use Rancoud\Database\Configurator; +use Rancoud\Database\Database as DB; -/** - * Class DriverManager. - */ abstract class DriverManager { - /** @var SessionHandlerInterface */ - protected static ?SessionHandlerInterface $driver = null; + protected static ?\SessionHandlerInterface $driver = null; + /** @throws SessionException */ abstract protected static function throwExceptionIfHasStarted(); abstract protected static function getLifetimeForRedis(); - /** - * @throws \Exception - */ + /** @throws SessionException */ protected static function configureDriver(): void { if (empty(static::$driver)) { @@ -29,23 +25,16 @@ protected static function configureDriver(): void } } - /** - * @throws \Exception - */ + /** @throws SessionException */ public static function useDefaultDriver(): void { static::throwExceptionIfHasStarted(); - static::$driver = new SessionHandler(); + static::$driver = new \SessionHandler(); } - /** - * @param string $key - * @param string|null $method - * - * @throws \Exception - */ - public static function useDefaultEncryptionDriver(string $key, string $method = null): void + /** @throws SessionException */ + public static function useDefaultEncryptionDriver(string $key, ?string $method = null): void { static::throwExceptionIfHasStarted(); @@ -55,9 +44,7 @@ public static function useDefaultEncryptionDriver(string $key, string $method = static::$driver = $driver; } - /** - * @throws \Exception - */ + /** @throws SessionException */ public static function useFileDriver(): void { static::throwExceptionIfHasStarted(); @@ -65,13 +52,8 @@ public static function useFileDriver(): void static::$driver = new File(); } - /** - * @param string $key - * @param string|null $method - * - * @throws \Exception - */ - public static function useFileEncryptionDriver(string $key, string $method = null): void + /** @throws SessionException */ + public static function useFileEncryptionDriver(string $key, ?string $method = null): void { static::throwExceptionIfHasStarted(); @@ -81,12 +63,8 @@ public static function useFileEncryptionDriver(string $key, string $method = nul static::$driver = $driver; } - /** - * @param \Rancoud\Database\Configurator|array $configuration - * - * @throws \Exception - */ - public static function useNewDatabaseDriver($configuration): void + /** @throws SessionException */ + public static function useNewDatabaseDriver(array|Configurator $configuration): void { static::throwExceptionIfHasStarted(); @@ -96,12 +74,8 @@ public static function useNewDatabaseDriver($configuration): void static::$driver = $driver; } - /** - * @param \Rancoud\Database\Database $databaseInstance - * - * @throws \Exception - */ - public static function useCurrentDatabaseDriver($databaseInstance): void + /** @throws SessionException */ + public static function useCurrentDatabaseDriver(DB $databaseInstance): void { static::throwExceptionIfHasStarted(); @@ -111,14 +85,8 @@ public static function useCurrentDatabaseDriver($databaseInstance): void static::$driver = $driver; } - /** - * @param \Rancoud\Database\Configurator|array $configuration - * @param string $key - * @param string $method - * - * @throws \Exception - */ - public static function useNewDatabaseEncryptionDriver($configuration, string $key, string $method = null): void + /** @throws SessionException */ + public static function useNewDatabaseEncryptionDriver(array|Configurator $configuration, string $key, ?string $method = null): void { static::throwExceptionIfHasStarted(); @@ -129,14 +97,8 @@ public static function useNewDatabaseEncryptionDriver($configuration, string $ke static::$driver = $driver; } - /** - * @param \Rancoud\Database\Database $databaseInstance - * @param string $key - * @param string $method - * - * @throws \Exception - */ - public static function useCurrentDatabaseEncryptionDriver($databaseInstance, string $key, string $method = null): void + /** @throws SessionException */ + public static function useCurrentDatabaseEncryptionDriver(DB $databaseInstance, string $key, ?string $method = null): void { static::throwExceptionIfHasStarted(); @@ -147,12 +109,8 @@ public static function useCurrentDatabaseEncryptionDriver($databaseInstance, str static::$driver = $driver; } - /** - * @param array|string $configuration - * - * @throws \Exception - */ - public static function useNewRedisDriver($configuration): void + /** @throws SessionException */ + public static function useNewRedisDriver(array|string $configuration): void { static::throwExceptionIfHasStarted(); @@ -163,12 +121,8 @@ public static function useNewRedisDriver($configuration): void static::$driver = $driver; } - /** - * @param \Predis\Client $redisInstance - * - * @throws \Exception - */ - public static function useCurrentRedisDriver($redisInstance): void + /** @throws SessionException */ + public static function useCurrentRedisDriver(PredisClient $redisInstance): void { static::throwExceptionIfHasStarted(); @@ -179,14 +133,8 @@ public static function useCurrentRedisDriver($redisInstance): void static::$driver = $driver; } - /** - * @param array|string $configuration - * @param string $key - * @param string $method - * - * @throws \Exception - */ - public static function useNewRedisEncryptionDriver($configuration, string $key, string $method = null): void + /** @throws SessionException */ + public static function useNewRedisEncryptionDriver(array|string $configuration, string $key, ?string $method = null): void { static::throwExceptionIfHasStarted(); @@ -198,14 +146,8 @@ public static function useNewRedisEncryptionDriver($configuration, string $key, static::$driver = $driver; } - /** - * @param \Predis\Client $redisInstance - * @param string $key - * @param string $method - * - * @throws \Exception - */ - public static function useCurrentRedisEncryptionDriver($redisInstance, string $key, string $method = null): void + /** @throws SessionException */ + public static function useCurrentRedisEncryptionDriver(PredisClient $redisInstance, string $key, ?string $method = null): void { static::throwExceptionIfHasStarted(); @@ -218,13 +160,11 @@ public static function useCurrentRedisEncryptionDriver($redisInstance, string $k } /** - * @param Encryption $driver - * @param $key - * @param $method + * @param Encryption $driver (use Encryption trait) * - * @throws \Exception + * @throws SessionException */ - private static function setKeyAndMethod($driver, $key, $method): void + private static function setKeyAndMethod(mixed $driver, string $key, ?string $method): void { $driver->setKey($key); if ($method !== null) { @@ -232,29 +172,19 @@ private static function setKeyAndMethod($driver, $key, $method): void } } - /** - * @param SessionHandlerInterface $customDriver - * - * @throws \Exception - */ - public static function useCustomDriver(SessionHandlerInterface $customDriver): void + /** @throws SessionException */ + public static function useCustomDriver(\SessionHandlerInterface $customDriver): void { static::throwExceptionIfHasStarted(); static::$driver = $customDriver; } - /** - * @return SessionHandlerInterface - */ - public static function getDriver(): SessionHandlerInterface + public static function getDriver(): \SessionHandlerInterface { return static::$driver; } - /** - * @param int $userId - */ public static function setUserIdForDatabase(int $userId): void { if (\method_exists(static::$driver, 'setUserId')) { @@ -262,13 +192,24 @@ public static function setUserIdForDatabase(int $userId): void } } - /** - * @param string $prefix - */ public static function setPrefixForFile(string $prefix): void { if (\method_exists(static::$driver, 'setPrefix')) { static::$driver->setPrefix($prefix); } } + + public static function deleteUserSessionsInDatabase(int $userID): void + { + if (\method_exists(static::$driver, 'deleteUserSessions')) { + static::$driver->deleteUserSessions($userID); + } + } + + public static function deleteAnonymousSessionsInDatabase(): void + { + if (\method_exists(static::$driver, 'deleteAnonymousSessions')) { + static::$driver->deleteAnonymousSessions(); + } + } } diff --git a/src/Encryption.php b/src/Encryption.php index 86b9ca8..863f602 100644 --- a/src/Encryption.php +++ b/src/Encryption.php @@ -4,106 +4,119 @@ namespace Rancoud\Session; -/** - * Trait Encryption. - */ trait Encryption { - /** @var string */ - protected $key; + protected ?string $key = null; - /** @var string */ - protected $method = 'aes-256-cbc'; + protected string $method = 'aes-256-cbc'; - /** - * @param string $key - */ public function setKey(string $key): void { $this->key = $key; } - /** - * @param string $method - * - * @throws SessionException - */ + /** @throws SessionException */ public function setMethod(string $method): void { if (!\in_array($method, $this->getAvailableMethods(), true)) { - throw new SessionException(\sprintf('Method unknowed: %s', $method)); + throw new SessionException(\sprintf('Unknown method: %s', $method)); } $this->method = $method; } - /** - * @return array - */ public function getAvailableMethods(): array { $ciphers = \openssl_get_cipher_methods(); $ciphersAndAliases = \openssl_get_cipher_methods(true); $cipherAliases = \array_diff($ciphersAndAliases, $ciphers); - $ciphers = \array_filter($ciphers, function ($n) { - return \mb_stripos($n, 'ecb') === false; - }); - $ciphers = \array_filter($ciphers, function ($c) { - return \mb_stripos($c, 'des') === false; - }); - $ciphers = \array_filter($ciphers, function ($c) { - return \mb_stripos($c, 'rc2') === false; - }); - $ciphers = \array_filter($ciphers, function ($c) { - return \mb_stripos($c, 'rc4') === false; - }); - $ciphers = \array_filter($ciphers, function ($c) { - return \mb_stripos($c, 'md5') === false; - }); - $ciphers = \array_filter($ciphers, function ($c) { - return \mb_stripos($c, '-ocb') === false; - }); - $ciphers = \array_filter($ciphers, function ($c) { - return \mb_stripos($c, '-ccm') === false; - }); - $ciphers = \array_filter($ciphers, function ($c) { - return \mb_stripos($c, '-gcm') === false; - }); - $ciphers = \array_filter($ciphers, function ($c) { - return \mb_stripos($c, '-wrap') === false; + $ciphers = \array_filter($ciphers, static function ($n) { + $excludeMethods = [ + 'ecb', 'des', 'rc2', 'rc4', 'md5', + '-ocb', '-ccm', '-gcm', '-wrap' + ]; + foreach ($excludeMethods as $excludeMethod) { + if (\mb_stripos($n, $excludeMethod) !== false) { + return false; + } + } + + return true; }); - $cipherAliases = \array_filter($cipherAliases, function ($c) { - return \mb_stripos($c, 'des') === false; - }); - $cipherAliases = \array_filter($cipherAliases, function ($c) { - return \mb_stripos($c, 'rc2') === false; - }); - $cipherAliases = \array_filter($cipherAliases, function ($c) { - return \mb_stripos($c, '-wrap') === false; + $cipherAliases = \array_filter($cipherAliases, static function ($c) { + $excludeMethods = [ + 'des', 'rc2', '-wrap', 'id-' + ]; + + foreach ($excludeMethods as $excludeMethod) { + if (\mb_stripos($c, $excludeMethod) !== false) { + return false; + } + } + + return true; }); $methods = \array_merge($ciphers, $cipherAliases); - $methods = \array_filter($methods, function ($c) { - $forbiddenMethods = ['AES-128-CBC-HMAC-SHA1', 'AES-256-CBC-HMAC-SHA1', - 'aes-128-cbc-hmac-sha1', 'aes-256-cbc-hmac-sha1']; - - return !\in_array($c, $forbiddenMethods, true); + return \array_filter($methods, static function ($c) { + $forbiddenMethods = [ + 'aes-128-cbc-hmac-sha1', + 'aes-256-cbc-hmac-sha1', + 'aes-128-cbc-cts', + 'aes-128-cbc-hmac-sha256', + 'aes-128-siv', + 'aes-192-cbc-cts', + 'aes-192-siv', + 'aes-256-cbc-cts', + 'aes-256-cbc-hmac-sha256', + 'aes-256-siv', + 'camellia-128-cbc-cts', + 'camellia-192-cbc-cts', + 'camellia-256-cbc-cts', + 'null', + 'sm4-xts', + '0.3.4401.5.3.1.9.1', + '0.3.4401.5.3.1.9.21', + '0.3.4401.5.3.1.9.41', + '1.2.410.200046.1.1.1', + '1.2.410.200046.1.1.11', + '1.2.410.200046.1.1.34', + '1.2.410.200046.1.1.35', + '1.2.410.200046.1.1.36', + '1.2.410.200046.1.1.37', + '1.2.410.200046.1.1.38', + '1.2.410.200046.1.1.39', + '1.2.410.200046.1.1.6', + '1.2.156.10197.1.104.10', + '1.2.840.113549.1.9.16.3.6', + '1.3.14.3.2.17', + '2.16.840.1.101.3.4.1.1', + '2.16.840.1.101.3.4.1.21', + '2.16.840.1.101.3.4.1.25', + '2.16.840.1.101.3.4.1.26', + '2.16.840.1.101.3.4.1.27', + '2.16.840.1.101.3.4.1.41', + '2.16.840.1.101.3.4.1.45', + '2.16.840.1.101.3.4.1.46', + '2.16.840.1.101.3.4.1.47', + '2.16.840.1.101.3.4.1.5', + '2.16.840.1.101.3.4.1.6', + '2.16.840.1.101.3.4.1.7', + '1.2.156.10197.1.104.1', + '1.2.156.10197.1.104.8', + '1.2.156.10197.1.104.9', + 'chacha20-poly1305' + ]; + + return !\in_array(\mb_strtolower($c), $forbiddenMethods, true); }); - - return $methods; } - /** - * @param string $data - * - * @throws SessionException - * - * @return string|bool - */ - public function decrypt(string $data) + /** @throws SessionException */ + public function decrypt(string $data): string { $this->throwExceptionIfKeyEmpty(); @@ -111,32 +124,49 @@ public function decrypt(string $data) return ''; } - list($encrypted_data, $iv) = \explode('::', \base64_decode($data, true), 2); + [$encryptedData, $iv] = \explode('::', \base64_decode($data, true), 2); + + $dataDecrypted = \openssl_decrypt($encryptedData, $this->method, $this->key, 0, $iv); + if ($dataDecrypted === false) { + // @codeCoverageIgnoreStart + // Could not reach this statement without mocking the function + throw new SessionException('Could not decrypt with openssl_decrypt'); + // @codeCoverageIgnoreEnd + } - return \openssl_decrypt($encrypted_data, $this->method, $this->key, 0, $iv); + return $dataDecrypted; } - /** - * @param string $data - * - * @throws SessionException - * - * @return string - */ + /** @throws SessionException */ public function encrypt(string $data): string { $this->throwExceptionIfKeyEmpty(); - $iv = \openssl_random_pseudo_bytes(\openssl_cipher_iv_length($this->method)); + try { + $length = \openssl_cipher_iv_length($this->method); + if ($length === false || $length < 1) { + throw new SessionException('IV generation failed'); + } + } catch (\Exception) { + throw new SessionException('IV generation failed'); + } + + /** @noinspection CryptographicallySecureRandomnessInspection */ + $iv = \openssl_random_pseudo_bytes($length, $cstrong); + /** @noinspection PhpStrictComparisonWithOperandsOfDifferentTypesInspection */ + if ($iv === false || $cstrong === false) { + // @codeCoverageIgnoreStart + // Could not reach this statement without mocking the function + throw new SessionException('IV generation failed'); + // @codeCoverageIgnoreEnd + } + $encrypted = \openssl_encrypt($data, $this->method, $this->key, 0, $iv); - $data = \base64_encode($encrypted . '::' . $iv); - return $data; + return \base64_encode($encrypted . '::' . $iv); } - /** - * @throws SessionException - */ + /** @throws SessionException */ protected function throwExceptionIfKeyEmpty(): void { if ($this->key === null || $this->key === '') { diff --git a/src/File.php b/src/File.php index 0b1dd80..b778896 100644 --- a/src/File.php +++ b/src/File.php @@ -4,106 +4,87 @@ namespace Rancoud\Session; -use SessionHandlerInterface; -use SessionUpdateTimestampHandlerInterface; - -/** - * Class File. - */ -class File implements SessionHandlerInterface, SessionUpdateTimestampHandlerInterface +class File implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface { - /** @var string|null */ protected ?string $savePath = null; - /** @var string */ protected string $prefix = 'sess_'; - /** - * @param string $prefix - */ + protected int $lengthSessionID = 127; + public function setPrefix(string $prefix): void { $this->prefix = $prefix; } - /** - * @param string $savePath - * @param string $sessionName - * - * @return bool - */ - public function open($savePath, $sessionName): bool + /** @throws SessionException */ + public function setLengthSessionID(int $length): void + { + if ($length < 32) { + throw new SessionException('could not set length session ID below 32'); + } + + $this->lengthSessionID = $length; + } + + public function getLengthSessionID(): int + { + return $this->lengthSessionID; + } + + /** @throws SessionException */ + public function open(string $path, string $name): bool { - $this->savePath = $savePath; + $this->savePath = $path; - if (!\is_dir($this->savePath) && !\mkdir($this->savePath, 0750) && !\is_dir($this->savePath)) { - throw new \RuntimeException(\sprintf('Directory "%s" was not created', $this->savePath)); + if (!\is_dir($this->savePath) && !\mkdir($this->savePath, 0o750) && !\is_dir($this->savePath)) { + // @codeCoverageIgnoreStart + // Could not reach this statement without mocking the filesystem + throw new SessionException(\sprintf('Directory "%s" was not created', $this->savePath)); + // @codeCoverageIgnoreEnd } return true; } - /** - * @return bool - */ public function close(): bool { return true; } - /** - * @param string $sessionId - * - * @return string - */ - public function read($sessionId): string + public function read(string $id): string { - $filename = $this->savePath . \DIRECTORY_SEPARATOR . $this->prefix . $sessionId; - if (\file_exists($filename)) { + $filename = $this->savePath . \DIRECTORY_SEPARATOR . $this->prefix . $id; + if (\file_exists($filename) && \is_file($filename)) { return (string) \file_get_contents($filename); } return ''; } - /** - * @param string $sessionId - * @param string $data - * - * @return bool - */ - public function write($sessionId, $data): bool + public function write(string $id, string $data): bool { - $filename = $this->savePath . \DIRECTORY_SEPARATOR . $this->prefix . $sessionId; + $filename = $this->savePath . \DIRECTORY_SEPARATOR . $this->prefix . $id; - return \file_put_contents($filename, $data) === false ? false : true; + return !(\file_put_contents($filename, $data) === false); } - /** - * @param string $sessionId - * - * @return bool - */ - public function destroy($sessionId): bool + public function destroy(string $id): bool { - $filename = $this->savePath . \DIRECTORY_SEPARATOR . $this->prefix . $sessionId; - if (\file_exists($filename)) { + $filename = $this->savePath . \DIRECTORY_SEPARATOR . $this->prefix . $id; + if (\file_exists($filename) && \is_file($filename)) { \unlink($filename); } return true; } - /** - * @param int $lifetime - * - * @return bool - */ - public function gc($lifetime): bool + #[\ReturnTypeWillChange] + public function gc(int $max_lifetime): bool { $pattern = $this->savePath . \DIRECTORY_SEPARATOR . $this->prefix . '*'; foreach (\glob($pattern) as $file) { - if (\filemtime($file) + $lifetime < \time() && \file_exists($file)) { + if (\file_exists($file) && \is_file($file) && \filemtime($file) + $max_lifetime < \time()) { \unlink($file); } } @@ -111,55 +92,54 @@ public function gc($lifetime): bool return true; } - /** - * Checks format and id exists, if not session_id will be regenerate. - * - * @param string $key - * - * @return bool - */ - public function validateId($key): bool + /** Checks format and id exists, if not session_id will be regenerate. */ + public function validateId(string $id): bool { - if (\preg_match('/^[a-zA-Z0-9-]{127}+$/', $key) !== 1) { + if (\preg_match('/^[a-zA-Z0-9-]{' . $this->lengthSessionID . '}+$/', $id) !== 1) { return false; } - $filename = $this->savePath . \DIRECTORY_SEPARATOR . $this->prefix . $key; + $filename = $this->savePath . \DIRECTORY_SEPARATOR . $this->prefix . $id; return \file_exists($filename); } - /** - * Updates the timestamp of a session when its data didn't change. - * - * @param string $sessionId - * @param string $sessionData - * - * @return bool - */ - public function updateTimestamp($sessionId, $sessionData): bool + /** Updates the timestamp of a session when its data didn't change. */ + public function updateTimestamp(string $id, string $data): bool { - return $this->write($sessionId, $sessionData); + return $this->write($id, $data); } /** - * @throws \Exception + * @throws SessionException * - * @return string + * @noinspection PhpMethodNamingConventionInspection */ public function create_sid(): string { $string = ''; - $caracters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-'; + $characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-'; - $countCaracters = 62; - for ($i = 0; $i < 127; ++$i) { - $string .= $caracters[\random_int(0, $countCaracters)]; + try { + $countCharacters = 62; + for ($i = 0; $i < $this->lengthSessionID; ++$i) { + $string .= $characters[\random_int(0, $countCharacters)]; + } + // @codeCoverageIgnoreStart + } catch (\Exception $e) { + /* If an appropriate source of randomness cannot be found, an Exception will be thrown. + * The list of randomness: https://www.php.net/manual/en/function.random-int.php + */ + throw new SessionException('could not create sid: ' . $e->getMessage(), $e->getCode(), $e->getPrevious()); + // @codeCoverageIgnoreEnd } $filename = $this->savePath . \DIRECTORY_SEPARATOR . $this->prefix . $string; if (\file_exists($filename)) { + // @codeCoverageIgnoreStart + // Could not reach this statement without mocking the filesystem return $this->create_sid(); + // @codeCoverageIgnoreEnd } return $string; diff --git a/src/FileEncryption.php b/src/FileEncryption.php index cabbd93..e1ff480 100644 --- a/src/FileEncryption.php +++ b/src/FileEncryption.php @@ -4,39 +4,23 @@ namespace Rancoud\Session; -/** - * Class FileEncryption. - */ class FileEncryption extends File { use Encryption; - /** - * @param $sessionId - * - * @throws SessionException - * - * @return string - */ - public function read($sessionId): string + /** @throws SessionException */ + public function read(string $id): string { - $encryptedData = parent::read($sessionId); + $encryptedData = parent::read($id); return $this->decrypt($encryptedData); } - /** - * @param $sessionId - * @param $data - * - * @throws SessionException - * - * @return bool - */ - public function write($sessionId, $data): bool + /** @throws SessionException */ + public function write(string $id, string $data): bool { $cryptedData = $this->encrypt($data); - return parent::write($sessionId, $cryptedData); + return parent::write($id, $cryptedData); } } diff --git a/src/Redis.php b/src/Redis.php index 13f3d6f..03a5221 100644 --- a/src/Redis.php +++ b/src/Redis.php @@ -5,158 +5,129 @@ namespace Rancoud\Session; use Predis\Client as Predis; -use SessionHandlerInterface; -use SessionUpdateTimestampHandlerInterface; -/** - * Class Redis. - */ -class Redis implements SessionHandlerInterface, SessionUpdateTimestampHandlerInterface +class Redis implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface { - /** @var Predis */ protected Predis $redis; - /** @var int */ protected int $lifetime = 1440; - /** - * @param string|array $configuration - */ - public function setNewRedis($configuration): void + protected int $lengthSessionID = 127; + + public function setNewRedis(array|string $configuration): void { $this->redis = new Predis($configuration); } - /** - * @param Predis $redis - */ - public function setCurrentRedis($redis): void + public function setCurrentRedis(Predis $redis): void { $this->redis = $redis; } - /** - * @param int $lifetime - */ public function setLifetime(int $lifetime): void { $this->lifetime = $lifetime; } - /** - * @param string $savePath - * @param string $sessionName - * - * @return bool - */ - public function open($savePath, $sessionName): bool + /** @throws SessionException */ + public function setLengthSessionID(int $length): void + { + if ($length < 32) { + throw new SessionException('could not set length session ID below 32'); + } + + $this->lengthSessionID = $length; + } + + public function getLengthSessionID(): int + { + return $this->lengthSessionID; + } + + public function open(string $path, string $name): bool { return true; } - /** - * @return bool - */ public function close(): bool { return true; } - /** - * @param string $sessionId - * - * @return string - */ - public function read($sessionId): string + public function read(string $id): string { - return (string) $this->redis->get($sessionId); + return (string) $this->redis->get($id); } - /** - * @param string $sessionId - * @param string $data - * - * @return bool - */ - public function write($sessionId, $data): bool + public function write(string $id, string $data): bool { - $this->redis->set($sessionId, $data); - $this->redis->expireat($sessionId, \time() + $this->lifetime); + $this->redis->set($id, $data); + $this->redis->expireat($id, \time() + $this->lifetime); return true; } - /** - * @param string $sessionId - * - * @return bool - */ - public function destroy($sessionId): bool + public function destroy(string $id): bool { - $this->redis->del([$sessionId]); + $this->redis->del([$id]); return true; } - /** - * @param int $lifetime - * - * @return bool - */ - public function gc($lifetime): bool + #[\ReturnTypeWillChange] + public function gc(int $max_lifetime): bool { return true; } - /** - * Checks format and id exists, if not session_id will be regenerate. - * - * @param string $key - * - * @return bool - */ - public function validateId($key): bool + /** Checks format and id exists, if not session_id will be regenerate. */ + public function validateId(string $id): bool { - if (\preg_match('/^[a-zA-Z0-9-]{127}+$/', $key) !== 1) { + if (\preg_match('/^[a-zA-Z0-9-]{' . $this->lengthSessionID . '}+$/', $id) !== 1) { return false; } - $exist = $this->redis->exists($key); + $exist = $this->redis->exists($id); return $exist === 1; } - /** - * Updates the timestamp of a session when its data didn't change. - * - * @param string $sessionId - * @param string $sessionData - * - * @return bool - */ - public function updateTimestamp($sessionId, $sessionData): bool + /** Updates the timestamp of a session when its data didn't change. */ + public function updateTimestamp(string $id, string $data): bool { - return $this->write($sessionId, $sessionData); + return $this->write($id, $data); } /** - * @throws \Exception + * @throws SessionException * - * @return string + * @noinspection PhpMethodNamingConventionInspection */ public function create_sid(): string { $string = ''; - $caracters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-'; - - $countCaracters = 62; - for ($i = 0; $i < 127; ++$i) { - $string .= $caracters[\random_int(0, $countCaracters)]; + $characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-'; + + try { + $countCharacters = 62; + for ($i = 0; $i < $this->lengthSessionID; ++$i) { + $string .= $characters[\random_int(0, $countCharacters)]; + } + // @codeCoverageIgnoreStart + } catch (\Exception $e) { + /* If an appropriate source of randomness cannot be found, an Exception will be thrown. + * The list of randomness: https://www.php.net/manual/en/function.random-int.php + */ + throw new SessionException('could not create sid: ' . $e->getMessage(), $e->getCode(), $e->getPrevious()); + // @codeCoverageIgnoreEnd } $exist = $this->redis->exists($string); if ($exist !== 0) { + // @codeCoverageIgnoreStart + // Could not reach this statement without mocking the function return $this->create_sid(); + // @codeCoverageIgnoreEnd } return $string; diff --git a/src/RedisEncryption.php b/src/RedisEncryption.php index cd146ec..d87cae2 100644 --- a/src/RedisEncryption.php +++ b/src/RedisEncryption.php @@ -4,39 +4,23 @@ namespace Rancoud\Session; -/** - * Class RedisEncryption. - */ class RedisEncryption extends Redis { use Encryption; - /** - * @param $sessionId - * - * @throws SessionException - * - * @return string - */ - public function read($sessionId): string + /** @throws SessionException */ + public function read(string $id): string { - $encryptedData = parent::read($sessionId); + $encryptedData = parent::read($id); return $this->decrypt($encryptedData); } - /** - * @param $sessionId - * @param $data - * - * @throws SessionException - * - * @return bool - */ - public function write($sessionId, $data): bool + /** @throws SessionException */ + public function write(string $id, string $data): bool { $cryptedData = $this->encrypt($data); - return parent::write($sessionId, $cryptedData); + return parent::write($id, $cryptedData); } } diff --git a/src/Session.php b/src/Session.php index d3445f4..927b1fd 100644 --- a/src/Session.php +++ b/src/Session.php @@ -4,19 +4,14 @@ namespace Rancoud\Session; -use Exception; - -/** - * Class Session. - */ class Session extends DriverManager { use ArrayManager; - /** @var bool */ protected static bool $hasStarted = false; - /** @var array */ + protected static bool $hasChanged = true; + protected static array $options = [ 'read_and_close' => true, 'cookie_httponly' => '1', @@ -25,11 +20,7 @@ class Session extends DriverManager 'use_strict_mode' => '1' ]; - /** - * @param array $options - * - * @throws Exception - */ + /** @throws SessionException */ public static function start(array $options = []): void { static::throwExceptionIfHasStarted(); @@ -39,38 +30,42 @@ public static function start(array $options = []): void static::setupAndStart(); } - /** - * @param array $options - * - * @throws Exception - */ + /** @throws SessionException */ protected static function populateOptions(array $options = []): void { - self::validateOptions($options); + static::validateOptions($options); static::setOptions($options); } - /** - * @throws Exception - */ + /** @throws SessionException */ protected static function setupAndStart(): void { + if (static::$hasChanged === false) { + return; + } + + static::$hasChanged = false; + static::configureDriver(); static::setupSessionParameters(); static::startSession(); - static::$hasStarted = true; - if (static::$options['read_and_close']) { - static::$hasStarted = false; - } else { - static::restoreFlashData(); + $sessionFlashData = $_SESSION['flash_data'] ?? []; + foreach ($sessionFlashData as $key => $value) { + static::$flashData[$key] = $value; } + + if (static::$hasStarted === false && !empty(static::$flashData) && static::isReadOnly()) { + static::setReadWrite(); + static::startSession(); + } + unset($_SESSION['flash_data']); + + static::$hasStarted = (static::$options['read_and_close'] === false); } - /** - * @throws SessionException - */ + /** @throws SessionException */ protected static function throwExceptionIfHasStarted(): void { if (static::hasStarted()) { @@ -78,27 +73,19 @@ protected static function throwExceptionIfHasStarted(): void } } - /** - * @throws SessionException - */ + /** @throws SessionException */ protected static function setupSessionParameters(): void { \session_name(static::getOption('name')); - \session_set_save_handler(static::$driver); + \session_set_save_handler(static::$driver, true); \session_save_path(static::getOption('save_path')); static::setupCookieParams(); - - \register_shutdown_function('session_write_close'); } - /** - * @throws SessionException - * - * @return bool - */ + /** @throws SessionException */ protected static function startSession(): bool { static::validateOptions(static::$options); @@ -108,11 +95,7 @@ protected static function startSession(): bool return \session_start(static::$options); } - /** - * @param array $options - * - * @throws SessionException - */ + /** @throws SessionException */ protected static function validateOptions(array $options = []): void { if (empty($options)) { @@ -133,6 +116,7 @@ protected static function validateOptions(array $options = []): void 'cookie_domain', 'cookie_secure', 'cookie_httponly', + 'cookie_samesite', 'use_strict_mode', 'use_cookies', 'use_only_cookies', @@ -162,24 +146,22 @@ protected static function validateOptions(array $options = []): void } } - /** - * @throws SessionException - */ + /** @throws SessionException */ protected static function setupCookieParams(): void { - // https://www.php.net/manual/fr/function.session-set-cookie-params.php new signature \session_set_cookie_params( - static::getOption('cookie_lifetime'), - static::getOption('cookie_path'), - static::getOption('cookie_domain'), - isset($_SERVER['HTTPS']), - true + [ + 'lifetime' => static::getOption('cookie_lifetime'), + 'path' => static::getOption('cookie_path'), + 'domain' => static::getOption('cookie_domain'), + 'secure' => isset($_SERVER['HTTPS']), + 'httponly' => true, + 'samesite' => static::getOption('cookie_samesite') + ] ); } - /** - * @throws Exception - */ + /** @throws SessionException */ public static function regenerate(): bool { static::startSessionIfNotHasStartedForceWrite(); @@ -187,11 +169,9 @@ public static function regenerate(): bool return \session_regenerate_id(true); } - /** - * @return bool - */ public static function destroy(): bool { + static::$hasChanged = true; \session_unset(); return \session_destroy(); @@ -200,57 +180,44 @@ public static function destroy(): bool public static function commit(): void { static::$hasStarted = false; + static::$flashData = []; + static::$hasChanged = true; \session_write_close(); } - /** - * @return bool - */ public static function rollback(): bool { return \session_reset(); } - /** - * @return bool - */ public static function unsaved(): bool { static::$hasStarted = false; + static::$flashData = []; + static::$hasChanged = true; return \session_abort(); } - /** - * @return bool - */ public static function hasStarted(): bool { return static::$hasStarted; } - /** - * @return string - */ public static function getId(): string { return \session_id(); } - /** - * @param string $id - * - * @return string - */ public static function setId(string $id): string { + static::$hasChanged = true; + return \session_id($id); } - /** - * @throws Exception - */ + /** @throws SessionException */ public static function gc(): void { static::startSessionIfNotHasStartedForceWrite(); @@ -258,9 +225,7 @@ public static function gc(): void \session_gc(); } - /** - * @throws Exception - */ + /** @throws SessionException */ protected static function startSessionIfNotHasStarted(): void { if (!static::hasStarted()) { @@ -268,67 +233,55 @@ protected static function startSessionIfNotHasStarted(): void } } - /** - * @throws Exception - */ + /** @throws SessionException */ protected static function startSessionIfNotHasStartedForceWrite(): void { if (!static::hasStarted()) { - static::$options['read_and_close'] = false; + static::setReadWrite(); static::setupAndStart(); } } public static function setReadOnly(): void { + static::$hasChanged = true; static::$options['read_and_close'] = true; } public static function setReadWrite(): void { + static::$hasChanged = true; static::$options['read_and_close'] = false; } - /** - * @throws SessionException - * - * @return int - */ + public static function isReadOnly(): bool + { + return static::$options['read_and_close'] === true; + } + + /** @throws SessionException */ protected static function getLifetimeForRedis(): int { return (int) static::getOption('cookie_lifetime'); } - /** - * @param string $key - * @param $value - * - * @throws SessionException - */ + /** @throws SessionException */ public static function setOption(string $key, $value): void { + static::$hasChanged = true; static::validateOptions([$key => $value]); static::$options[$key] = $value; } - /** - * @param array $options - * - * @throws SessionException - */ + /** @throws SessionException */ public static function setOptions(array $options): void { + static::$hasChanged = true; static::validateOptions($options); static::$options = $options + static::$options; } - /** - * @param string $key - * - * @throws SessionException - * - * @return mixed - */ + /** @throws SessionException */ public static function getOption(string $key) { if (\array_key_exists($key, static::$options)) { diff --git a/src/SessionException.php b/src/SessionException.php index de4c753..fe4068a 100644 --- a/src/SessionException.php +++ b/src/SessionException.php @@ -4,11 +4,4 @@ namespace Rancoud\Session; -use Exception; - -/** - * Class SessionException. - */ -class SessionException extends Exception -{ -} +class SessionException extends \Exception {} diff --git a/tests/DatabaseEncryptionTest.php b/tests/DatabaseEncryptionTest.php index 2eace80..5dcc959 100644 --- a/tests/DatabaseEncryptionTest.php +++ b/tests/DatabaseEncryptionTest.php @@ -1,488 +1,368 @@ - 'mysql', - 'host' => '127.0.0.1', - 'user' => 'root', - 'password' => '', - 'database' => 'test_database' - ]); - static::$db = new \Rancoud\Database\Database($conf); - - $sql = ' - CREATE TABLE IF NOT EXISTS `sessions` ( - `id` varchar(128) NOT NULL, - `id_user` int(10) unsigned DEFAULT NULL, - `last_access` datetime NOT NULL, - `content` text NOT NULL, - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8; - '; - try { - static::$db->exec($sql); - static::$db->truncateTables('sessions'); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - } - - protected function setUp(): void - { - try { - static::$db->truncateTables('sessions'); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - } - - public function testOpen(): void - { - $database = new DatabaseEncryption(); - $database->setKey('randomKey'); - - $database->setCurrentDatabase(static::$db); - - $savePath = ''; - $sessionName = ''; - $success = $database->open($savePath, $sessionName); - static::assertTrue($success); - } - - public function testClose(): void - { - $database = new DatabaseEncryption(); - $database->setKey('randomKey'); - - $database->setCurrentDatabase(static::$db); - - $success = $database->close(); - static::assertTrue($success); - } - - /** - * @throws DatabaseException - * @throws \Rancoud\Session\SessionException - */ - public function testWrite(): void - { - $database = new DatabaseEncryption(); - $database->setKey('randomKey'); - - $database->setCurrentDatabase(static::$db); - - $sessionId = 'sessionId'; - $data = 'azerty'; - try { - $success = $database->write($sessionId, $data); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - - $sql = 'SELECT * FROM sessions WHERE id = :id'; - $params = ['id' => $sessionId]; - $row = static::$db->selectRow($sql, $params); - static::assertNotEmpty($row); - static::assertNotEquals($data, $row['content']); - - $encryptionTrait = $this->getObjectForTrait('Rancoud\Session\Encryption'); - $encryptionTrait->setKey('randomKey'); - $dataInDatabaseDecrypted = $encryptionTrait->decrypt($row['content']); - static::assertEquals($data, $dataInDatabaseDecrypted); - } - - /** - * @throws \Rancoud\Session\SessionException - */ - public function testRead(): void - { - $database = new DatabaseEncryption(); - $database->setKey('randomKey'); - - $database->setCurrentDatabase(static::$db); - - $sessionId = 'sessionId'; - $data = 'azerty'; - try { - $database->write($sessionId, $data); - $dataOutput = $database->read($sessionId); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertNotEmpty($dataOutput); - static::assertIsString($dataOutput); - static::assertEquals($data, $dataOutput); - - $sessionId = ''; - try { - $dataOutput = $database->read($sessionId); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertEmpty($dataOutput); - static::assertIsString($dataOutput); - } - - /** - * @throws DatabaseException - * @throws \Rancoud\Session\SessionException - */ - public function testDestroy(): void - { - $database = new DatabaseEncryption(); - $database->setKey('randomKey'); - - $database->setCurrentDatabase(static::$db); - - $sessionId = 'todelete'; - try { - $success = $database->destroy($sessionId); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - - $sessionId = 'sessionId'; - $data = 'azerty'; - try { - $database->write($sessionId, $data); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - - $sql = 'SELECT COUNT(id) FROM sessions WHERE id = :id'; - $params = ['id' => $sessionId]; - $isRowExist = (static::$db->count($sql, $params) === 1); - - static::assertTrue($isRowExist); - try { - $success = $database->destroy($sessionId); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - $isRowNotExist = (static::$db->count($sql, $params) === 0); - static::assertTrue($isRowNotExist); - } - - /** - * @throws DatabaseException - * @throws \Rancoud\Session\SessionException - */ - public function testGc(): void - { - $database = new DatabaseEncryption(); - $database->setKey('randomKey'); - - $database->setCurrentDatabase(static::$db); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $sql = 'SELECT COUNT(id) FROM sessions WHERE id = :id'; - $params = ['id' => $sessionId]; - - try { - $success = $database->write($sessionId, $data); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - - $isRowExist = (static::$db->count($sql, $params) === 1); - static::assertTrue($isRowExist); - - $lifetime = -1000; - try { - $success = $database->gc($lifetime); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - - $isRowNotExist = (static::$db->count($sql, $params) === 0); - static::assertTrue($isRowNotExist); - } - - /** - * @throws \Rancoud\Session\SessionException - */ - public function testSetUserId(): void - { - $database = new DatabaseEncryption(); - $database->setKey('randomKey'); - - $database->setCurrentDatabase(static::$db); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $userId = 5; - $database->setUserId($userId); - - try { - $success = $database->write($sessionId, $data); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - - $sql = 'SELECT id_user FROM sessions WHERE id = :id'; - $params = ['id' => $sessionId]; - try { - $userIdInDatabase = static::$db->selectVar($sql, $params); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertNotNull($userIdInDatabase); - static::assertEquals($userId, $userIdInDatabase); - - $userId = null; - $database->setUserId($userId); - - try { - $success = $database->write($sessionId, $data); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - - $sql = 'SELECT id_user FROM sessions WHERE id = :id'; - $params = ['id' => $sessionId]; - try { - $userIdInDatabase = static::$db->selectVar($sql, $params); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertNull($userIdInDatabase); - static::assertEquals($userId, $userIdInDatabase); - } - - /** - * @throws DatabaseException - * @throws \Rancoud\Session\SessionException - */ - public function testSetNewDatabaseWithArray(): void - { - $database = new DatabaseEncryption(); - $database->setKey('randomKey'); - - $params = [ - 'engine' => 'mysql', - 'host' => '127.0.0.1', - 'user' => 'root', - 'password' => '', - 'database' => 'test_database' - ]; - $database->setNewDatabase($params); - - $sessionId = 'sessionId'; - $data = 'azerty'; - - try { - $success = $database->write($sessionId, $data); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - } - - /** - * @throws DatabaseException - * @throws \Rancoud\Session\SessionException - */ - public function testSetNewDatabaseWithConfigurator(): void - { - $database = new DatabaseEncryption(); - $database->setKey('randomKey'); - - $params = [ - 'engine' => 'mysql', - 'host' => '127.0.0.1', - 'user' => 'root', - 'password' => '', - 'database' => 'test_database' - ]; - $conf = new Configurator($params); - $database->setNewDatabase($conf); - - $sessionId = 'sessionId'; - $data = 'azerty'; - - try { - $success = $database->write($sessionId, $data); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - } - - /** - * @throws DatabaseException - * @throws \Rancoud\Session\SessionException - */ - public function testValidateId(): void - { - $database = new DatabaseEncryption(); - $database->setKey('randomKey'); - $database->setCurrentDatabase(static::$db); - - $baseId = 'DiqKrZDUGp5ubt3klF0oorIlFiADXC9jxig9e8leUcCYuZ9w0mXh0b1foEGIBs7SSsdOuLor58vU5liBRVPsTobnvt'; - $endId1 = 'Dj8hh65DlR3tTFI1SGX3mFciDA9rMOa4LlnMr'; - $endId2 = 'Dklezfoipvfk0lferijkoefzjklgrvefLlnMr'; - - try { - $database->write($baseId . $endId1, 'a'); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - - static::assertTrue($database->validateId($baseId . $endId1)); - static::assertFalse($database->validateId($baseId . $endId2)); - static::assertFalse($database->validateId('kjlfez/fez')); - } - - /** - * @throws \Rancoud\Session\SessionException - */ - public function testUpdateTimestamp(): void - { - $database = new DatabaseEncryption(); - $database->setKey('randomKey'); - $database->setCurrentDatabase(static::$db); - - $sessionId = 'sessionId'; - $data = 'azerty'; - - $success = false; - try { - $success = $database->write($sessionId, $data); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - - $sql = 'SELECT * FROM sessions WHERE id = :id'; - $params = ['id' => $sessionId]; - - try { - $row1 = static::$db->selectRow($sql, $params); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - - static::assertNotEmpty($row1); - static::assertNotEquals($data, $row1['content']); - - $encryptionTrait = $this->getObjectForTrait('Rancoud\Session\Encryption'); - $encryptionTrait->setKey('randomKey'); - $dataInDatabaseDecrypted = $encryptionTrait->decrypt($row1['content']); - static::assertEquals($data, $dataInDatabaseDecrypted); - - sleep(1); - - try { - $success = $database->updateTimestamp($sessionId, $data); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - - static::assertTrue($success); - - $sql = 'SELECT * FROM sessions WHERE id = :id'; - $params = ['id' => $sessionId]; - - try { - $row2 = static::$db->selectRow($sql, $params); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - - static::assertNotEmpty($row2); - static::assertNotEquals($data, $row2['content']); - $encryptionTrait = $this->getObjectForTrait('Rancoud\Session\Encryption'); - $encryptionTrait->setKey('randomKey'); - $dataInDatabaseDecrypted = $encryptionTrait->decrypt($row2['content']); - static::assertEquals($data, $dataInDatabaseDecrypted); - - static::assertTrue($row1['last_access'] < $row2['last_access']); - } - - /** - * @throws DatabaseException - */ - public function testCreateId(): void - { - $database = new DatabaseEncryption(); - $database->setKey('randomKey'); - $database->setCurrentDatabase(static::$db); - - $string = $database->create_sid(); - - static::assertSame(preg_match('/^[a-zA-Z0-9-]{127}+$/', $string), 1); - } -} + 'mysql', + 'host' => 'mariadb', + 'user' => 'root', + 'password' => '', + 'database' => 'test_database' + ]); + + $mysqlHost = \getenv('MYSQL_HOST', true); + $conf->setHost(($mysqlHost !== false) ? $mysqlHost : '127.0.0.1'); + + static::$db = new DB($conf); + + $sql = <<<'SQL' + CREATE TABLE IF NOT EXISTS `sessions` ( + `id` varchar(128) NOT NULL, + `id_user` int(10) unsigned DEFAULT NULL, + `last_access` datetime NOT NULL, + `content` text NOT NULL, + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + SQL; + + static::$db->exec($sql); + static::$db->truncateTables('sessions'); + } + + /** @throws DatabaseException */ + protected function setUp(): void + { + static::$db->truncateTables('sessions'); + } + + public function testOpen(): void + { + $database = new DatabaseEncryption(); + $database->setKey('randomKey'); + + $database->setCurrentDatabase(static::$db); + + $savePath = ''; + $sessionName = ''; + + static::assertTrue($database->open($savePath, $sessionName)); + } + + public function testClose(): void + { + $database = new DatabaseEncryption(); + $database->setKey('randomKey'); + + $database->setCurrentDatabase(static::$db); + + static::assertTrue($database->close()); + } + + /** + * @throws DatabaseException + * @throws SessionException + */ + public function testWrite(): void + { + $database = new DatabaseEncryption(); + $database->setKey('randomKey'); + + $database->setCurrentDatabase(static::$db); + + $sessionId = 'sessionId'; + $data = 'azerty'; + + static::assertTrue($database->write($sessionId, $data)); + + $sql = 'SELECT * FROM sessions WHERE id = :id'; + $params = ['id' => $sessionId]; + $row = static::$db->selectRow($sql, $params); + static::assertNotEmpty($row); + static::assertNotSame($data, $row['content']); + + $encryptionTrait = new class() { + use \Rancoud\Session\Encryption; + }; + $encryptionTrait->setKey('randomKey'); + $dataInDatabaseDecrypted = $encryptionTrait->decrypt($row['content']); + static::assertSame($data, $dataInDatabaseDecrypted); + } + + /** @throws SessionException */ + public function testRead(): void + { + $database = new DatabaseEncryption(); + $database->setKey('randomKey'); + + $database->setCurrentDatabase(static::$db); + + $sessionId = 'sessionId'; + $data = 'azerty'; + + $database->write($sessionId, $data); + $dataOutput = $database->read($sessionId); + + static::assertNotEmpty($dataOutput); + static::assertIsString($dataOutput); + static::assertSame($data, $dataOutput); + + $sessionId = ''; + $dataOutput = $database->read($sessionId); + + static::assertEmpty($dataOutput); + static::assertIsString($dataOutput); + } + + /** + * @throws DatabaseException + * @throws SessionException + */ + public function testDestroy(): void + { + $database = new DatabaseEncryption(); + $database->setKey('randomKey'); + + $database->setCurrentDatabase(static::$db); + + $sessionId = 'todelete'; + static::assertTrue($database->destroy($sessionId)); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $database->write($sessionId, $data); + + $sql = 'SELECT COUNT(id) FROM sessions WHERE id = :id'; + $params = ['id' => $sessionId]; + $isRowExist = (static::$db->count($sql, $params) === 1); + + static::assertTrue($isRowExist); + static::assertTrue($database->destroy($sessionId)); + $isRowNotExist = (static::$db->count($sql, $params) === 0); + static::assertTrue($isRowNotExist); + } + + /** + * @throws DatabaseException + * @throws SessionException + */ + public function testGc(): void + { + $database = new DatabaseEncryption(); + $database->setKey('randomKey'); + + $database->setCurrentDatabase(static::$db); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $sql = 'SELECT COUNT(id) FROM sessions WHERE id = :id'; + $params = ['id' => $sessionId]; + + static::assertTrue($database->write($sessionId, $data)); + + $isRowExist = (static::$db->count($sql, $params) === 1); + static::assertTrue($isRowExist); + + $lifetime = -1000; + static::assertTrue($database->gc($lifetime)); + + $isRowNotExist = (static::$db->count($sql, $params) === 0); + static::assertTrue($isRowNotExist); + } + + /** + * @throws DatabaseException + * @throws SessionException + */ + public function testSetUserId(): void + { + $database = new DatabaseEncryption(); + $database->setKey('randomKey'); + + $database->setCurrentDatabase(static::$db); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $userId = 5; + $database->setUserId($userId); + + static::assertTrue($database->write($sessionId, $data)); + + $sql = 'SELECT id_user FROM sessions WHERE id = :id'; + $params = ['id' => $sessionId]; + $userIdInDatabase = (int) static::$db->selectVar($sql, $params); + static::assertNotNull($userIdInDatabase); + static::assertSame($userId, $userIdInDatabase); + + $userId = null; + $database->setUserId($userId); + + static::assertTrue($database->write($sessionId, $data)); + + $userIdInDatabase = static::$db->selectVar($sql, $params); + static::assertNull($userIdInDatabase); + static::assertSame($userId, $userIdInDatabase); + } + + /** @throws SessionException */ + public function testSetNewDatabaseWithArray(): void + { + $database = new DatabaseEncryption(); + $database->setKey('randomKey'); + + $params = [ + 'driver' => 'mysql', + 'user' => 'root', + 'password' => '', + 'database' => 'test_database' + ]; + + $mysqlHost = \getenv('MYSQL_HOST', true); + $params['host'] = ($mysqlHost !== false) ? $mysqlHost : '127.0.0.1'; + + $database->setNewDatabase($params); + + $sessionId = 'sessionId'; + $data = 'azerty'; + + static::assertTrue($database->write($sessionId, $data)); + } + + /** + * @throws DatabaseException + * @throws SessionException + */ + public function testSetNewDatabaseWithConfigurator(): void + { + $database = new DatabaseEncryption(); + $database->setKey('randomKey'); + + $conf = new Configurator([ + 'driver' => 'mysql', + 'host' => 'mariadb', + 'user' => 'root', + 'password' => '', + 'database' => 'test_database' + ]); + + $mysqlHost = \getenv('MYSQL_HOST', true); + $conf->setHost(($mysqlHost !== false) ? $mysqlHost : '127.0.0.1'); + + $database->setNewDatabase($conf); + + $sessionId = 'sessionId'; + $data = 'azerty'; + + static::assertTrue($database->write($sessionId, $data)); + } + + /** @throws SessionException */ + public function testSetNewDatabaseSessionException(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('could not set database: "invalid" settings is not recognized'); + + $database = new DatabaseEncryption(); + $database->setKey('randomKey'); + $database->setNewDatabase([ + 'invalid' => 'invalid' + ]); + } + + /** @throws SessionException */ + public function testValidateId(): void + { + $database = new DatabaseEncryption(); + $database->setKey('randomKey'); + $database->setCurrentDatabase(static::$db); + + $baseId = 'DiqKrZDUGp5ubt3klF0oorIlFiADXC9jxig9e8leUcCYuZ9w0mXh0b1foEGIBs7SSsdOuLor58vU5liBRVPsTobnvt'; + $endId1 = 'Dj8hh65DlR3tTFI1SGX3mFciDA9rMOa4LlnMr'; + $endId2 = 'Dklezfoipvfk0lferijkoefzjklgrvefLlnMr'; + + $database->write($baseId . $endId1, 'a'); + + static::assertTrue($database->validateId($baseId . $endId1)); + static::assertFalse($database->validateId($baseId . $endId2)); + static::assertFalse($database->validateId('kjlfez/fez')); + } + + /** + * @throws DatabaseException + * @throws SessionException + */ + public function testUpdateTimestamp(): void + { + $database = new DatabaseEncryption(); + $database->setKey('randomKey'); + $database->setCurrentDatabase(static::$db); + + $sessionId = 'sessionId'; + $data = 'azerty'; + + static::assertTrue($database->write($sessionId, $data)); + + $sql = 'SELECT * FROM sessions WHERE id = :id'; + $params = ['id' => $sessionId]; + + $row1 = static::$db->selectRow($sql, $params); + static::assertNotEmpty($row1); + static::assertNotSame($data, $row1['content']); + + $encryptionTrait = new class() { + use \Rancoud\Session\Encryption; + }; + $encryptionTrait->setKey('randomKey'); + $dataInDatabaseDecrypted = $encryptionTrait->decrypt($row1['content']); + static::assertSame($data, $dataInDatabaseDecrypted); + + \sleep(1); + + static::assertTrue($database->updateTimestamp($sessionId, $data)); + + $row2 = static::$db->selectRow($sql, $params); + + static::assertNotEmpty($row2); + static::assertNotSame($data, $row2['content']); + $encryptionTrait = new class() { + use \Rancoud\Session\Encryption; + }; + $encryptionTrait->setKey('randomKey'); + $dataInDatabaseDecrypted = $encryptionTrait->decrypt($row2['content']); + static::assertSame($data, $dataInDatabaseDecrypted); + + static::assertTrue($row1['last_access'] < $row2['last_access']); + } + + /** @throws SessionException */ + public function testCreateId(): void + { + $database = new DatabaseEncryption(); + $database->setKey('randomKey'); + $database->setCurrentDatabase(static::$db); + + $string = $database->create_sid(); + + static::assertMatchesRegularExpression('/^[a-zA-Z0-9-]{127}+$/', $string); + } +} diff --git a/tests/DatabaseTest.php b/tests/DatabaseTest.php index 90ad1d5..223d3d0 100644 --- a/tests/DatabaseTest.php +++ b/tests/DatabaseTest.php @@ -1,438 +1,574 @@ - 'mysql', - 'host' => '127.0.0.1', - 'user' => 'root', - 'password' => '', - 'database' => 'test_database' - ]); - static::$db = new \Rancoud\Database\Database($conf); - - $sql = ' - CREATE TABLE IF NOT EXISTS `sessions` ( - `id` varchar(128) NOT NULL, - `id_user` int(10) unsigned DEFAULT NULL, - `last_access` datetime NOT NULL, - `content` text NOT NULL, - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8; - '; - try { - static::$db->exec($sql); - static::$db->truncateTables('sessions'); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - } - - protected function setUp(): void - { - try { - static::$db->truncateTables('sessions'); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - } - - public function testOpen(): void - { - $database = new Database(); - $database->setCurrentDatabase(static::$db); - - $savePath = ''; - $sessionName = ''; - $success = $database->open($savePath, $sessionName); - static::assertTrue($success); - } - - public function testClose(): void - { - $database = new Database(); - $database->setCurrentDatabase(static::$db); - - $success = $database->close(); - static::assertTrue($success); - } - - /** - * @throws DatabaseException - */ - public function testWrite(): void - { - $database = new Database(); - $database->setCurrentDatabase(static::$db); - - $sessionId = 'sessionId'; - $data = 'azerty'; - try { - $success = $database->write($sessionId, $data); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - - $sql = 'SELECT * FROM sessions WHERE id = :id'; - $params = ['id' => $sessionId]; - $row = static::$db->selectRow($sql, $params); - static::assertNotEmpty($row); - static::assertEquals($data, $row['content']); - } - - public function testRead(): void - { - $database = new Database(); - $database->setCurrentDatabase(static::$db); - - $sessionId = 'sessionId'; - $data = 'azerty'; - try { - $database->write($sessionId, $data); - $dataOutput = $database->read($sessionId); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertNotEmpty($dataOutput); - static::assertIsString($dataOutput); - static::assertEquals($data, $dataOutput); - - $sessionId = ''; - try { - $dataOutput = $database->read($sessionId); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertEmpty($dataOutput); - static::assertIsString($dataOutput); - } - - /** - * @throws DatabaseException - */ - public function testDestroy(): void - { - $database = new Database(); - $database->setCurrentDatabase(static::$db); - - $sessionId = 'todelete'; - try { - $success = $database->destroy($sessionId); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - - $sessionId = 'sessionId'; - $data = 'azerty'; - try { - $database->write($sessionId, $data); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - - $sql = 'SELECT COUNT(id) FROM sessions WHERE id = :id'; - $params = ['id' => $sessionId]; - $isRowExist = (static::$db->count($sql, $params) === 1); - - static::assertTrue($isRowExist); - try { - $success = $database->destroy($sessionId); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - $isRowNotExist = (static::$db->count($sql, $params) === 0); - static::assertTrue($isRowNotExist); - } - - /** - * @throws DatabaseException - */ - public function testGc(): void - { - $database = new Database(); - $database->setCurrentDatabase(static::$db); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $sql = 'SELECT COUNT(id) FROM sessions WHERE id = :id'; - $params = ['id' => $sessionId]; - - try { - $success = $database->write($sessionId, $data); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - - $isRowExist = (static::$db->count($sql, $params) === 1); - static::assertTrue($isRowExist); - - $lifetime = -1000; - try { - $success = $database->gc($lifetime); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - - $isRowNotExist = (static::$db->count($sql, $params) === 0); - static::assertTrue($isRowNotExist); - } - - public function testSetUserId(): void - { - $database = new Database(); - $database->setCurrentDatabase(static::$db); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $userId = 5; - $database->setUserId($userId); - - try { - $success = $database->write($sessionId, $data); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - - $sql = 'SELECT id_user FROM sessions WHERE id = :id'; - $params = ['id' => $sessionId]; - try { - $userIdInDatabase = static::$db->selectVar($sql, $params); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertNotNull($userIdInDatabase); - static::assertEquals($userId, $userIdInDatabase); - - $userId = null; - $database->setUserId($userId); - - try { - $success = $database->write($sessionId, $data); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - - $sql = 'SELECT id_user FROM sessions WHERE id = :id'; - $params = ['id' => $sessionId]; - try { - $userIdInDatabase = static::$db->selectVar($sql, $params); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertNull($userIdInDatabase); - static::assertEquals($userId, $userIdInDatabase); - } - - /** - * @throws DatabaseException - */ - public function testSetNewDatabaseWithArray(): void - { - $database = new Database(); - $params = [ - 'engine' => 'mysql', - 'host' => '127.0.0.1', - 'user' => 'root', - 'password' => '', - 'database' => 'test_database' - ]; - $database->setNewDatabase($params); - - $sessionId = 'sessionId'; - $data = 'azerty'; - - try { - $success = $database->write($sessionId, $data); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - } - - /** - * @throws DatabaseException - */ - public function testSetNewDatabaseWithConfigurator(): void - { - $database = new Database(); - $params = [ - 'engine' => 'mysql', - 'host' => '127.0.0.1', - 'user' => 'root', - 'password' => '', - 'database' => 'test_database' - ]; - $conf = new Configurator($params); - $database->setNewDatabase($conf); - - $sessionId = 'sessionId'; - $data = 'azerty'; - - try { - $success = $database->write($sessionId, $data); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - } - - /** - * @throws DatabaseException - */ - public function testValidateId(): void - { - $database = new Database(); - $database->setCurrentDatabase(static::$db); - - $baseId = 'DiqKrZDUGp5ubt3klF0oorIlFiADXC9jxig9e8leUcCYuZ9w0mXh0b1foEGIBs7SSsdOuLor58vU5liBRVPsTobnvt'; - $endId1 = 'Dj8hh65DlR3tTFI1SGX3mFciDA9rMOa4LlnMr'; - $endId2 = 'Dklezfoipvfk0lferijkoefzjklgrvefLlnMr'; - - try { - $database->write($baseId . $endId1, 'a'); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - - static::assertTrue($database->validateId($baseId . $endId1)); - static::assertFalse($database->validateId($baseId . $endId2)); - static::assertFalse($database->validateId('kjlfez/fez')); - } - - public function testUpdateTimestamp(): void - { - $database = new Database(); - $database->setCurrentDatabase(static::$db); - - $sessionId = 'sessionId'; - $data = 'azerty'; - - $success = false; - try { - $success = $database->write($sessionId, $data); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - static::assertTrue($success); - - $sql = 'SELECT * FROM sessions WHERE id = :id'; - $params = ['id' => $sessionId]; - - try { - $row1 = static::$db->selectRow($sql, $params); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - - static::assertNotEmpty($row1); - static::assertEquals($data, $row1['content']); - - sleep(1); - - try { - $success = $database->updateTimestamp($sessionId, $data); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - - static::assertTrue($success); - - $sql = 'SELECT * FROM sessions WHERE id = :id'; - $params = ['id' => $sessionId]; - - try { - $row2 = static::$db->selectRow($sql, $params); - } catch (DatabaseException $e) { - var_dump(static::$db->getErrors()); - - return; - } - - static::assertNotEmpty($row2); - static::assertEquals($data, $row2['content']); - - static::assertTrue($row1['last_access'] < $row2['last_access']); - } - - /** - * @throws DatabaseException - */ - public function testCreateId(): void - { - $database = new Database(); - $database->setCurrentDatabase(static::$db); - - $string = $database->create_sid(); - - static::assertSame(preg_match('/^[a-zA-Z0-9-]{127}+$/', $string), 1); - } -} + 'mysql', + 'host' => 'mariadb', + 'user' => 'root', + 'password' => '', + 'database' => 'test_database' + ]); + + $mysqlHost = \getenv('MYSQL_HOST', true); + $conf->setHost(($mysqlHost !== false) ? $mysqlHost : '127.0.0.1'); + + static::$db = new DB($conf); + + $sql = <<<'SQL' + CREATE TABLE IF NOT EXISTS `sessions` ( + `id` varchar(128) NOT NULL, + `id_user` int(10) unsigned DEFAULT NULL, + `last_access` datetime NOT NULL, + `content` text NOT NULL, + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + SQL; + + static::$db->exec($sql); + static::$db->truncateTables('sessions'); + } + + /** @throws DatabaseException */ + protected function setUp(): void + { + static::$db->truncateTables('sessions'); + } + + public function testOpen(): void + { + $database = new Database(); + $database->setCurrentDatabase(static::$db); + + $savePath = ''; + $sessionName = ''; + + static::assertTrue($database->open($savePath, $sessionName)); + } + + public function testClose(): void + { + $database = new Database(); + $database->setCurrentDatabase(static::$db); + + static::assertTrue($database->close()); + } + + /** + * @throws DatabaseException + * @throws SessionException + */ + public function testWrite(): void + { + $database = new Database(); + $database->setCurrentDatabase(static::$db); + + $sessionId = 'sessionId'; + $data = 'azerty'; + + static::assertTrue($database->write($sessionId, $data)); + + $sql = 'SELECT * FROM sessions WHERE id = :id'; + $params = ['id' => $sessionId]; + $row = static::$db->selectRow($sql, $params); + static::assertNotEmpty($row); + static::assertSame($data, $row['content']); + } + + /** @throws SessionException */ + public function testWriteSessionException(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('could not update session: Error Connecting Database'); + + $database = new Database(); + $database->setNewDatabase([ + 'driver' => 'mysql', + 'host' => 'mariadb', + 'user' => 'invalid', + 'password' => '', + 'database' => 'test_database' + ]); + + $database->write('invalid_session_id', ''); + } + + /** @throws SessionException */ + public function testRead(): void + { + $database = new Database(); + $database->setCurrentDatabase(static::$db); + + $sessionId = 'sessionId'; + $data = 'azerty'; + + $database->write($sessionId, $data); + $dataOutput = $database->read($sessionId); + + static::assertNotEmpty($dataOutput); + static::assertIsString($dataOutput); + static::assertSame($data, $dataOutput); + + $sessionId = ''; + $dataOutput = $database->read($sessionId); + + static::assertEmpty($dataOutput); + static::assertIsString($dataOutput); + } + + /** @throws SessionException */ + public function testReadSessionException(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('could not read session: Error Connecting Database'); + + $database = new Database(); + $database->setNewDatabase([ + 'driver' => 'mysql', + 'host' => 'mariadb', + 'user' => 'invalid', + 'password' => '', + 'database' => 'test_database' + ]); + + $database->read('invalid_session_id'); + } + + /** + * @throws DatabaseException + * @throws SessionException + */ + public function testDestroy(): void + { + $database = new Database(); + $database->setCurrentDatabase(static::$db); + + $sessionId = 'todelete'; + static::assertTrue($database->destroy($sessionId)); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $database->write($sessionId, $data); + + $sql = 'SELECT COUNT(id) FROM sessions WHERE id = :id'; + $params = ['id' => $sessionId]; + $isRowExist = (static::$db->count($sql, $params) === 1); + + static::assertTrue($isRowExist); + static::assertTrue($database->destroy($sessionId)); + $isRowNotExist = static::$db->count($sql, $params) === 0; + static::assertTrue($isRowNotExist); + } + + /** @throws SessionException */ + public function testDestroySessionException(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('could not delete session: Error Connecting Database'); + + $database = new Database(); + $database->setNewDatabase([ + 'driver' => 'mysql', + 'host' => 'mariadb', + 'user' => 'invalid', + 'password' => '', + 'database' => 'test_database' + ]); + + $database->destroy('invalid_session_id'); + } + + /** + * @throws DatabaseException + * @throws SessionException + */ + public function testGc(): void + { + $database = new Database(); + $database->setCurrentDatabase(static::$db); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $sql = 'SELECT COUNT(id) FROM sessions WHERE id = :id'; + $params = ['id' => $sessionId]; + + static::assertTrue($database->write($sessionId, $data)); + + $isRowExist = (static::$db->count($sql, $params) === 1); + static::assertTrue($isRowExist); + + $lifetime = -1000; + static::assertTrue($database->gc($lifetime)); + + $isRowNotExist = (static::$db->count($sql, $params) === 0); + static::assertTrue($isRowNotExist); + } + + /** @throws SessionException */ + public function testGcSessionException(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('could not clean old sessions: Error Connecting Database'); + + $database = new Database(); + $database->setNewDatabase([ + 'driver' => 'mysql', + 'host' => 'mariadb', + 'user' => 'invalid', + 'password' => '', + 'database' => 'test_database' + ]); + + $database->gc(-1000); + } + + /** + * @throws DatabaseException + * @throws SessionException + */ + public function testSetUserId(): void + { + $database = new Database(); + $database->setCurrentDatabase(static::$db); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $userId = 5; + $database->setUserId($userId); + + static::assertTrue($database->write($sessionId, $data)); + + $sql = 'SELECT id_user FROM sessions WHERE id = :id'; + $params = ['id' => $sessionId]; + $userIdInDatabase = (int) static::$db->selectVar($sql, $params); + static::assertNotNull($userIdInDatabase); + static::assertSame($userId, $userIdInDatabase); + + $userId = null; + $database->setUserId($userId); + + static::assertTrue($database->write($sessionId, $data)); + + $userIdInDatabase = static::$db->selectVar($sql, $params); + static::assertNull($userIdInDatabase); + static::assertSame($userId, $userIdInDatabase); + } + + /** @throws SessionException */ + public function testSetNewDatabaseWithArray(): void + { + $database = new Database(); + $params = [ + 'driver' => 'mysql', + 'user' => 'root', + 'password' => '', + 'database' => 'test_database' + ]; + + $mysqlHost = \getenv('MYSQL_HOST', true); + $params['host'] = ($mysqlHost !== false) ? $mysqlHost : '127.0.0.1'; + + $database->setNewDatabase($params); + + $sessionId = 'sessionId'; + $data = 'azerty'; + + static::assertTrue($database->write($sessionId, $data)); + } + + /** + * @throws DatabaseException + * @throws SessionException + */ + public function testSetNewDatabaseWithConfigurator(): void + { + $database = new Database(); + $conf = new Configurator([ + 'driver' => 'mysql', + 'host' => 'mariadb', + 'user' => 'root', + 'password' => '', + 'database' => 'test_database' + ]); + + $mysqlHost = \getenv('MYSQL_HOST', true); + $conf->setHost(($mysqlHost !== false) ? $mysqlHost : '127.0.0.1'); + + $database->setNewDatabase($conf); + + $sessionId = 'sessionId'; + $data = 'azerty'; + + static::assertTrue($database->write($sessionId, $data)); + } + + /** @throws SessionException */ + public function testSetNewDatabaseSessionException(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('could not set database: "invalid" settings is not recognized'); + + $database = new Database(); + + $database->setNewDatabase([ + 'invalid' => 'invalid' + ]); + } + + /** @throws SessionException */ + public function testValidateId(): void + { + $database = new Database(); + $database->setCurrentDatabase(static::$db); + + $baseId = 'DiqKrZDUGp5ubt3klF0oorIlFiADXC9jxig9e8leUcCYuZ9w0mXh0b1foEGIBs7SSsdOuLor58vU5liBRVPsTobnvt'; + $endId1 = 'Dj8hh65DlR3tTFI1SGX3mFciDA9rMOa4LlnMr'; + $endId2 = 'Dklezfoipvfk0lferijkoefzjklgrvefLlnMr'; + + $database->write($baseId . $endId1, 'a'); + + static::assertTrue($database->validateId($baseId . $endId1)); + static::assertFalse($database->validateId($baseId . $endId2)); + static::assertFalse($database->validateId('kjlfez/fez')); + } + + /** @throws SessionException */ + public function testValidateIdSessionException(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('could not validate id: Error Connecting Database'); + + $database = new Database(); + $database->setNewDatabase([ + 'driver' => 'mysql', + 'host' => 'mariadb', + 'user' => 'invalid', + 'password' => '', + 'database' => 'test_database' + ]); + + $database->setLengthSessionID(32); + $database->validateId('-------invalid-session-id-------'); + } + + /** + * @throws DatabaseException + * @throws SessionException + */ + public function testUpdateTimestamp(): void + { + $database = new Database(); + $database->setCurrentDatabase(static::$db); + + $sessionId = 'sessionId'; + $data = 'azerty'; + + static::assertTrue($database->write($sessionId, $data)); + + $sql = 'SELECT * FROM sessions WHERE id = :id'; + $params = ['id' => $sessionId]; + + $row1 = static::$db->selectRow($sql, $params); + static::assertNotEmpty($row1); + static::assertSame($data, $row1['content']); + + \sleep(1); + + static::assertTrue($database->updateTimestamp($sessionId, $data)); + + $row2 = static::$db->selectRow($sql, $params); + + static::assertNotEmpty($row2); + static::assertSame($data, $row2['content']); + + static::assertTrue($row1['last_access'] < $row2['last_access']); + } + + /** @throws SessionException */ + public function testCreateId(): void + { + $database = new Database(); + $database->setCurrentDatabase(static::$db); + + $string = $database->create_sid(); + + static::assertMatchesRegularExpression('/^[a-zA-Z0-9-]{127}+$/', $string); + } + + /** @throws SessionException */ + public function testCreateIdSessionException(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('could not create sid: Error Connecting Database'); + + $database = new Database(); + $database->setNewDatabase([ + 'driver' => 'mysql', + 'host' => 'mariadb', + 'user' => 'invalid', + 'password' => '', + 'database' => 'test_database' + ]); + + $database->create_sid(); + } + + /** @throws SessionException */ + public function testLengthSessionID(): void + { + $database = new Database(); + $database->setLengthSessionID(50); + static::assertSame(50, $database->getLengthSessionID()); + } + + public function testLengthSessionIDSessionException(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('could not set length session ID below 32'); + + $database = new Database(); + $database->setLengthSessionID(1); + } + + /** + * @throws DatabaseException + * @throws SessionException + */ + public function testDeleteUserSessions(): void + { + $database = new Database(); + $database->setCurrentDatabase(static::$db); + + $data = 'azerty'; + + $database->setUserId(10); + + static::assertTrue($database->write('session_id_10', $data)); + static::assertTrue($database->write('session_id_20', $data)); + static::assertTrue($database->write('session_id_30', $data)); + + $database->setUserId(20); + + static::assertTrue($database->write('session_id_40', $data)); + static::assertTrue($database->write('session_id_50', $data)); + static::assertTrue($database->write('session_id_60', $data)); + + $sql = 'SELECT id FROM sessions WHERE id_user = :id_user'; + $params = ['id_user' => 10]; + $sessionsFromDatabase = static::$db->selectCol($sql, $params); + static::assertSame(['session_id_10', 'session_id_20', 'session_id_30'], $sessionsFromDatabase); + + $sql = 'SELECT id FROM sessions WHERE id_user = :id_user'; + $params = ['id_user' => 20]; + $sessionsFromDatabase = static::$db->selectCol($sql, $params); + static::assertSame(['session_id_40', 'session_id_50', 'session_id_60'], $sessionsFromDatabase); + + $database->deleteUserSessions(10); + + $sql = 'SELECT id FROM sessions'; + $sessionsFromDatabase = static::$db->selectCol($sql); + static::assertSame(['session_id_40', 'session_id_50', 'session_id_60'], $sessionsFromDatabase); + } + + /** + * @throws DatabaseException + * @throws SessionException + */ + public function testDeleteUserSessionsSessionException(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('could not delete sessions: Error Connecting Database'); + + $database = new Database(); + $database->setNewDatabase([ + 'driver' => 'mysql', + 'host' => 'mariadb', + 'user' => 'invalid', + 'password' => '', + 'database' => 'test_database' + ]); + + $database->deleteUserSessions(1); + } + + /** + * @throws DatabaseException + * @throws SessionException + */ + public function testDeleteAnonymousSessions(): void + { + $database = new Database(); + $database->setCurrentDatabase(static::$db); + + $data = 'azerty'; + + $database->setUserId(10); + + static::assertTrue($database->write('session_id_10', $data)); + static::assertTrue($database->write('session_id_20', $data)); + static::assertTrue($database->write('session_id_30', $data)); + + $database->setUserId(null); + + static::assertTrue($database->write('session_id_40', $data)); + static::assertTrue($database->write('session_id_50', $data)); + static::assertTrue($database->write('session_id_60', $data)); + + $sql = 'SELECT id FROM sessions WHERE id_user = :id_user'; + $params = ['id_user' => 10]; + $sessionsFromDatabase = static::$db->selectCol($sql, $params); + static::assertSame(['session_id_10', 'session_id_20', 'session_id_30'], $sessionsFromDatabase); + + $sql = 'SELECT id FROM sessions WHERE id_user IS NULL'; + $sessionsFromDatabase = static::$db->selectCol($sql); + static::assertSame(['session_id_40', 'session_id_50', 'session_id_60'], $sessionsFromDatabase); + + $database->deleteAnonymousSessions(10); + + $sql = 'SELECT id FROM sessions'; + $sessionsFromDatabase = static::$db->selectCol($sql); + static::assertSame(['session_id_10', 'session_id_20', 'session_id_30'], $sessionsFromDatabase); + } + + /** + * @throws DatabaseException + * @throws SessionException + */ + public function testDeleteAnonymousSessionsSessionException(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('could not delete sessions: Error Connecting Database'); + + $database = new Database(); + $database->setNewDatabase([ + 'driver' => 'mysql', + 'host' => 'mariadb', + 'user' => 'invalid', + 'password' => '', + 'database' => 'test_database' + ]); + + $database->deleteAnonymousSessions(); + } +} diff --git a/tests/DefaultEncryptionTest.php b/tests/DefaultEncryptionTest.php index d0308f6..3c1477d 100644 --- a/tests/DefaultEncryptionTest.php +++ b/tests/DefaultEncryptionTest.php @@ -1,69 +1,66 @@ foundSessionFile(); static::assertNotFalse($data); - $encryptionTrait = $this->getObjectForTrait('Rancoud\Session\Encryption'); + $encryptionTrait = new class() { + use \Rancoud\Session\Encryption; + }; $encryptionTrait->setKey('randomKey'); $dataDecrypted = $encryptionTrait->decrypt($data); - static::assertEquals('a|s:1:"b";', $dataDecrypted); + static::assertSame('a|s:1:"b";', $dataDecrypted); } } diff --git a/tests/EncryptionTest.php b/tests/EncryptionTest.php index 6094095..755dfc8 100644 --- a/tests/EncryptionTest.php +++ b/tests/EncryptionTest.php @@ -1,64 +1,161 @@ -getObjectForTrait('Rancoud\Session\Encryption'); - - $dataToEncrypt = 'this is something to encrypt'; - - $encryptionTrait->setKey('my key'); - $encryptedData = $encryptionTrait->encrypt($dataToEncrypt); - - $finalData = $encryptionTrait->decrypt($encryptedData); - static::assertEquals($dataToEncrypt, $finalData); - } - - public function testAllEncryptionMethods(): void - { - $encryptionTrait = $this->getObjectForTrait('Rancoud\Session\Encryption'); - - $dataToEncrypt = 'this is something to encrypt'; - - $encryptionTrait->setKey('my key'); - $methods = $encryptionTrait->getAvailableMethods(); - foreach ($methods as $method) { - $encryptionTrait->setMethod($method); - $encryptedData = $encryptionTrait->encrypt($dataToEncrypt); - - $finalData = $encryptionTrait->decrypt($encryptedData); - static::assertEquals($dataToEncrypt, $finalData, $method . ' fail!'); - } - } - - public function testExceptionMethod(): void - { - $this->expectException(SessionException::class); - $this->expectExceptionMessage('Method unknowed: method'); - - $encryptionTrait = $this->getObjectForTrait('Rancoud\Session\Encryption'); - $encryptionTrait->setMethod('method'); - } - - public function testExceptionEmptyKey(): void - { - $this->expectException(SessionException::class); - $this->expectExceptionMessage('Key has to be a non-empty string'); - - $encryptionTrait = $this->getObjectForTrait('Rancoud\Session\Encryption'); - $dataToEncrypt = 'this is something to encrypt'; - $encryptionTrait->encrypt($dataToEncrypt); - } -} +setKey('my key'); + $encryptedData = $encryptionTrait->encrypt($dataToEncrypt); + + $finalData = $encryptionTrait->decrypt($encryptedData); + static::assertSame($dataToEncrypt, $finalData); + } + + public function testAllEncryptionMethods(): void + { + $failedMethods = []; + + $encryptionTrait = new class() { + use \Rancoud\Session\Encryption; + }; + + $dataToEncrypt = 'this is something to encrypt'; + + $encryptionTrait->setKey('my key'); + $methods = $encryptionTrait->getAvailableMethods(); + foreach ($methods as $method) { + try { + $encryptionTrait->setMethod($method); + $encryptedData = $encryptionTrait->encrypt($dataToEncrypt); + + $finalData = $encryptionTrait->decrypt($encryptedData); + static::assertSame($dataToEncrypt, $finalData, $method . ' fail!'); + } catch (\Exception) { + $failedMethods[] = $method; + + continue; + } + } + + if (\count($failedMethods) > 0) { + static::fail('Methods ' . \implode(', ', $failedMethods) . ' fail!'); + } + } + + public function testExceptionMethod(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('Unknown method: method'); + + $encryptionTrait = new class() { + use \Rancoud\Session\Encryption; + }; + $encryptionTrait->setMethod('method'); + } + + public function testExceptionMethodForced(): void + { + $forbiddenMethods = [ + 'aes-128-cbc-hmac-sha1', + 'aes-256-cbc-hmac-sha1', + 'aes-128-cbc-cts', + 'aes-128-cbc-hmac-sha256', + 'aes-128-siv', + 'aes-192-cbc-cts', + 'aes-192-siv', + 'aes-256-cbc-cts', + 'aes-256-cbc-hmac-sha256', + 'aes-256-siv', + 'camellia-128-cbc-cts', + 'camellia-192-cbc-cts', + 'camellia-256-cbc-cts', + 'null', + '0.3.4401.5.3.1.9.1', + '0.3.4401.5.3.1.9.21', + '0.3.4401.5.3.1.9.41', + '1.2.410.200046.1.1.1', + '1.2.410.200046.1.1.11', + '1.2.410.200046.1.1.34', + '1.2.410.200046.1.1.35', + '1.2.410.200046.1.1.36', + '1.2.410.200046.1.1.37', + '1.2.410.200046.1.1.38', + '1.2.410.200046.1.1.39', + '1.2.410.200046.1.1.6', + '1.2.840.113549.1.9.16.3.6', + '1.3.14.3.2.17', + '2.16.840.1.101.3.4.1.1', + '2.16.840.1.101.3.4.1.21', + '2.16.840.1.101.3.4.1.25', + '2.16.840.1.101.3.4.1.26', + '2.16.840.1.101.3.4.1.27', + '2.16.840.1.101.3.4.1.41', + '2.16.840.1.101.3.4.1.45', + '2.16.840.1.101.3.4.1.46', + '2.16.840.1.101.3.4.1.47', + '2.16.840.1.101.3.4.1.5', + '2.16.840.1.101.3.4.1.6', + '2.16.840.1.101.3.4.1.7', + 'chacha20-poly1305' + ]; + + $encryptionTrait = new class() { + use \Rancoud\Session\Encryption; + + public function setMethod(string $method): void + { + $this->method = $method; + } + }; + + $dataToEncrypt = 'this is something to encrypt'; + + $encryptionTrait->setKey('my key'); + + $countInvalidMethods = \count($forbiddenMethods); + foreach ($forbiddenMethods as $method) { + try { + $encryptionTrait->setMethod($method); + $finalData = $encryptionTrait->encrypt($dataToEncrypt); + static::assertNotSame($dataToEncrypt, $finalData); + --$countInvalidMethods; + } catch (\Exception $e) { + static::assertThat($e->getMessage(), static::logicalOr( + static::equalTo('IV generation failed'), + static::equalTo('openssl_encrypt(): A tag should be provided when using AEAD mode') + )); + --$countInvalidMethods; + } + } + + static::assertSame(0, $countInvalidMethods); + } + + public function testExceptionEmptyKey(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('Key has to be a non-empty string'); + + $encryptionTrait = new class() { + use \Rancoud\Session\Encryption; + }; + $dataToEncrypt = 'this is something to encrypt'; + $encryptionTrait->encrypt($dataToEncrypt); + } +} diff --git a/tests/FileEncryptionTest.php b/tests/FileEncryptionTest.php index 4acc2df..20645fc 100644 --- a/tests/FileEncryptionTest.php +++ b/tests/FileEncryptionTest.php @@ -1,256 +1,250 @@ -open($this->getPath(), ''); - static::assertTrue($success); - } - - public function testOpen(): void - { - $fileEncryption = new FileEncryption(); - $fileEncryption->setKey('randomKey'); - - $savePath = $this->getPath(); - $sessionName = ''; - $success = $fileEncryption->open($savePath, $sessionName); - static::assertTrue($success); - - $savePathNotCreated = $savePath . DIRECTORY_SEPARATOR . 'tests'; - $success = $fileEncryption->open($savePathNotCreated, $sessionName); - static::assertTrue($success); - $success = file_exists($savePathNotCreated); - static::assertTrue($success); - } - - public function testClose(): void - { - $fileEncryption = new FileEncryption(); - $fileEncryption->setKey('randomKey'); - $success = $fileEncryption->close(); - static::assertTrue($success); - } - - /** - * @throws \Rancoud\Session\SessionException - */ - public function testWrite(): void - { - $fileEncryption = new FileEncryption(); - $fileEncryption->setKey('randomKey'); - - $this->openSessionForSavingSavePath($fileEncryption); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $fileEncryption->write($sessionId, $data); - static::assertTrue($success); - - $dataInFile = file_get_contents($this->getPath() . DIRECTORY_SEPARATOR . 'sess_' . $sessionId); - static::assertNotEquals($data, $dataInFile); - - $encryptionTrait = $this->getObjectForTrait('Rancoud\Session\Encryption'); - $encryptionTrait->setKey('randomKey'); - $dataInFileDecrypted = $encryptionTrait->decrypt($dataInFile); - static::assertEquals($data, $dataInFileDecrypted); - } - - /** - * @throws \Rancoud\Session\SessionException - */ - public function testRead(): void - { - $fileEncryption = new FileEncryption(); - $fileEncryption->setKey('randomKey'); - - $this->openSessionForSavingSavePath($fileEncryption); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $fileEncryption->write($sessionId, $data); - static::assertTrue($success); - - $dataOutput = $fileEncryption->read($sessionId); - static::assertNotEmpty($dataOutput); - static::assertIsString($dataOutput); - static::assertEquals($data, $dataOutput); - - $sessionId = ''; - $dataOutput = $fileEncryption->read($sessionId); - static::assertEmpty($dataOutput); - static::assertIsString($dataOutput); - } - - /** - * @throws \Rancoud\Session\SessionException - */ - public function testDestroy(): void - { - $fileEncryption = new FileEncryption(); - $fileEncryption->setKey('randomKey'); - - $this->openSessionForSavingSavePath($fileEncryption); - - $sessionId = 'todelete'; - $success = $fileEncryption->destroy($sessionId); - static::assertTrue($success); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $fileEncryption->write($sessionId, $data); - static::assertTrue($success); - - $isFileExist = file_exists($this->getPath() . DIRECTORY_SEPARATOR . 'sess_' . $sessionId); - static::assertTrue($isFileExist); - $success = $fileEncryption->destroy($sessionId); - static::assertTrue($success); - $isFileNotExist = !file_exists($this->getPath() . DIRECTORY_SEPARATOR . 'sess_' . $sessionId); - static::assertTrue($isFileNotExist); - } - - /** - * @throws \Rancoud\Session\SessionException - */ - public function testGc(): void - { - $fileEncryption = new FileEncryption(); - $fileEncryption->setKey('randomKey'); - - $this->openSessionForSavingSavePath($fileEncryption); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $fileEncryption->write($sessionId, $data); - static::assertTrue($success); - - $isFileExist = file_exists($this->getPath() . DIRECTORY_SEPARATOR . 'sess_' . $sessionId); - static::assertTrue($isFileExist); - - $lifetime = -1000; - $success = $fileEncryption->gc($lifetime); - static::assertTrue($success); - - $isFileNotExist = !file_exists($this->getPath() . DIRECTORY_SEPARATOR . 'sess_' . $sessionId); - static::assertTrue($isFileNotExist); - } - - /** - * @throws \Rancoud\Session\SessionException - */ - public function testValidateId(): void - { - $fileEncryption = new FileEncryption(); - $fileEncryption->setKey('randomKey'); - - $this->openSessionForSavingSavePath($fileEncryption); - - $baseId = 'TiqKrZDUGp5ubt3klF0oorIlFiADXC9jxig9e8leUcCYuZ9w0mXh0b1foEGIBs7SSsdOuLor58vU5liBRVPsTobnvt'; - $endId1 = 'Tj8hh65DlR3tTFI1SGX3mFciDA9rMOa4LlnMr'; - $endId2 = 'Tklezfoipvfk0lferijkoefzjklgrvefLlnMr'; - - $fileEncryption->write($baseId . $endId1, 'a'); - - static::assertTrue($fileEncryption->validateId($baseId . $endId1)); - static::assertFalse($fileEncryption->validateId($baseId . $endId2)); - static::assertFalse($fileEncryption->validateId('kjlfez/fez')); - } - - /** - * @throws \Rancoud\Session\SessionException - */ - public function testUpdateTimestamp(): void - { - $fileEncryption = new FileEncryption(); - $fileEncryption->setKey('randomKey'); - - $this->openSessionForSavingSavePath($fileEncryption); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $fileEncryption->write($sessionId, $data); - static::assertTrue($success); - - $dataInFile = file_get_contents($this->getPath() . DIRECTORY_SEPARATOR . 'sess_' . $sessionId); - $oldFileModifiedTime = filemtime($this->getPath() . DIRECTORY_SEPARATOR . 'sess_' . $sessionId); - static::assertNotEquals($data, $dataInFile); - - $encryptionTrait = $this->getObjectForTrait('Rancoud\Session\Encryption'); - $encryptionTrait->setKey('randomKey'); - $dataInFileDecrypted = $encryptionTrait->decrypt($dataInFile); - static::assertEquals($data, $dataInFileDecrypted); - - sleep(1); - $success = $fileEncryption->updateTimestamp($sessionId, $data); - static::assertTrue($success); - - clearstatcache(); - - $dataInFile2 = file_get_contents($this->getPath() . DIRECTORY_SEPARATOR . 'sess_' . $sessionId); - static::assertNotEquals($data, $dataInFile2); - - $encryptionTrait = $this->getObjectForTrait('Rancoud\Session\Encryption'); - $encryptionTrait->setKey('randomKey'); - $dataInFileDecrypted = $encryptionTrait->decrypt($dataInFile2); - static::assertEquals($data, $dataInFileDecrypted); - static::assertNotEquals($dataInFile, $dataInFile2); - - $newFileModifiedTime = filemtime($this->getPath() . DIRECTORY_SEPARATOR . 'sess_' . $sessionId); - - static::assertTrue($oldFileModifiedTime < $newFileModifiedTime); - } - - /** - * @throws \Exception - */ - public function testCreateId(): void - { - $fileEncryption = new FileEncryption(); - $fileEncryption->setKey('randomKey'); - - $string = $fileEncryption->create_sid(); - - static::assertSame(preg_match('/^[a-zA-Z0-9-]{127}+$/', $string), 1); - } -} +open($this->getPath(), ''); + static::assertTrue($success); + } + + /** @throws SessionException */ + public function testOpen(): void + { + $fileEncryption = new FileEncryption(); + $fileEncryption->setKey('randomKey'); + + $savePath = $this->getPath(); + $sessionName = ''; + $success = $fileEncryption->open($savePath, $sessionName); + static::assertTrue($success); + + $savePathNotCreated = $savePath . \DIRECTORY_SEPARATOR . 'tests'; + $success = $fileEncryption->open($savePathNotCreated, $sessionName); + static::assertTrue($success); + $success = \file_exists($savePathNotCreated); + static::assertTrue($success); + } + + public function testClose(): void + { + $fileEncryption = new FileEncryption(); + $fileEncryption->setKey('randomKey'); + $success = $fileEncryption->close(); + static::assertTrue($success); + } + + /** @throws SessionException */ + public function testWrite(): void + { + $fileEncryption = new FileEncryption(); + $fileEncryption->setKey('randomKey'); + + $this->openSessionForSavingSavePath($fileEncryption); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $fileEncryption->write($sessionId, $data); + static::assertTrue($success); + + $dataInFile = \file_get_contents($this->getPath() . \DIRECTORY_SEPARATOR . 'sess_' . $sessionId); + static::assertNotSame($data, $dataInFile); + + $encryptionTrait = new class() { + use \Rancoud\Session\Encryption; + }; + $encryptionTrait->setKey('randomKey'); + $dataInFileDecrypted = $encryptionTrait->decrypt($dataInFile); + static::assertSame($data, $dataInFileDecrypted); + } + + /** @throws SessionException */ + public function testRead(): void + { + $fileEncryption = new FileEncryption(); + $fileEncryption->setKey('randomKey'); + + $this->openSessionForSavingSavePath($fileEncryption); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $fileEncryption->write($sessionId, $data); + static::assertTrue($success); + + $dataOutput = $fileEncryption->read($sessionId); + static::assertNotEmpty($dataOutput); + static::assertIsString($dataOutput); + static::assertSame($data, $dataOutput); + + $sessionId = ''; + $dataOutput = $fileEncryption->read($sessionId); + static::assertEmpty($dataOutput); + static::assertIsString($dataOutput); + } + + /** @throws SessionException */ + public function testDestroy(): void + { + $fileEncryption = new FileEncryption(); + $fileEncryption->setKey('randomKey'); + + $this->openSessionForSavingSavePath($fileEncryption); + + $sessionId = 'todelete'; + $success = $fileEncryption->destroy($sessionId); + static::assertTrue($success); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $fileEncryption->write($sessionId, $data); + static::assertTrue($success); + + $isFileExist = \file_exists($this->getPath() . \DIRECTORY_SEPARATOR . 'sess_' . $sessionId); + static::assertTrue($isFileExist); + $success = $fileEncryption->destroy($sessionId); + static::assertTrue($success); + $isFileNotExist = !\file_exists($this->getPath() . \DIRECTORY_SEPARATOR . 'sess_' . $sessionId); + static::assertTrue($isFileNotExist); + } + + /** @throws SessionException */ + public function testGc(): void + { + $fileEncryption = new FileEncryption(); + $fileEncryption->setKey('randomKey'); + + $this->openSessionForSavingSavePath($fileEncryption); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $fileEncryption->write($sessionId, $data); + static::assertTrue($success); + + $isFileExist = \file_exists($this->getPath() . \DIRECTORY_SEPARATOR . 'sess_' . $sessionId); + static::assertTrue($isFileExist); + + $lifetime = -1000; + $success = $fileEncryption->gc($lifetime); + static::assertTrue($success); + + $isFileNotExist = !\file_exists($this->getPath() . \DIRECTORY_SEPARATOR . 'sess_' . $sessionId); + static::assertTrue($isFileNotExist); + } + + /** @throws SessionException */ + public function testValidateId(): void + { + $fileEncryption = new FileEncryption(); + $fileEncryption->setKey('randomKey'); + + $this->openSessionForSavingSavePath($fileEncryption); + + $baseId = 'TiqKrZDUGp5ubt3klF0oorIlFiADXC9jxig9e8leUcCYuZ9w0mXh0b1foEGIBs7SSsdOuLor58vU5liBRVPsTobnvt'; + $endId1 = 'Tj8hh65DlR3tTFI1SGX3mFciDA9rMOa4LlnMr'; + $endId2 = 'Tklezfoipvfk0lferijkoefzjklgrvefLlnMr'; + + $fileEncryption->write($baseId . $endId1, 'a'); + + static::assertTrue($fileEncryption->validateId($baseId . $endId1)); + static::assertFalse($fileEncryption->validateId($baseId . $endId2)); + static::assertFalse($fileEncryption->validateId('kjlfez/fez')); + } + + /** @throws SessionException */ + public function testUpdateTimestamp(): void + { + $fileEncryption = new FileEncryption(); + $fileEncryption->setKey('randomKey'); + + $this->openSessionForSavingSavePath($fileEncryption); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $fileEncryption->write($sessionId, $data); + static::assertTrue($success); + + $dataInFile = \file_get_contents($this->getPath() . \DIRECTORY_SEPARATOR . 'sess_' . $sessionId); + $oldFileModifiedTime = \filemtime($this->getPath() . \DIRECTORY_SEPARATOR . 'sess_' . $sessionId); + static::assertNotSame($data, $dataInFile); + + $encryptionTrait = new class() { + use \Rancoud\Session\Encryption; + }; + $encryptionTrait->setKey('randomKey'); + $dataInFileDecrypted = $encryptionTrait->decrypt($dataInFile); + static::assertSame($data, $dataInFileDecrypted); + + \sleep(1); + $success = $fileEncryption->updateTimestamp($sessionId, $data); + static::assertTrue($success); + + \clearstatcache(); + + $dataInFile2 = \file_get_contents($this->getPath() . \DIRECTORY_SEPARATOR . 'sess_' . $sessionId); + static::assertNotSame($data, $dataInFile2); + + $encryptionTrait = new class() { + use \Rancoud\Session\Encryption; + }; + $encryptionTrait->setKey('randomKey'); + $dataInFileDecrypted = $encryptionTrait->decrypt($dataInFile2); + static::assertSame($data, $dataInFileDecrypted); + static::assertNotSame($dataInFile, $dataInFile2); + + $newFileModifiedTime = \filemtime($this->getPath() . \DIRECTORY_SEPARATOR . 'sess_' . $sessionId); + + static::assertTrue($oldFileModifiedTime < $newFileModifiedTime); + } + + /** @throws \Exception */ + public function testCreateId(): void + { + $fileEncryption = new FileEncryption(); + $fileEncryption->setKey('randomKey'); + + $string = $fileEncryption->create_sid(); + + static::assertMatchesRegularExpression('/^[a-zA-Z0-9-]{127}+$/', $string); + } +} diff --git a/tests/FileTest.php b/tests/FileTest.php index f0e08a7..1856afd 100644 --- a/tests/FileTest.php +++ b/tests/FileTest.php @@ -1,220 +1,235 @@ -open($this->getPath(), ''); - static::assertTrue($success); - } - - public function testOpen(): void - { - $file = new File(); - $savePath = $this->getPath(); - $sessionName = ''; - $success = $file->open($savePath, $sessionName); - static::assertTrue($success); - - $savePathNotCreated = $savePath . DIRECTORY_SEPARATOR . 'tests'; - $success = $file->open($savePathNotCreated, $sessionName); - static::assertTrue($success); - $success = file_exists($savePathNotCreated); - static::assertTrue($success); - } - - public function testClose(): void - { - $file = new File(); - $success = $file->close(); - static::assertTrue($success); - } - - public function testWrite(): void - { - $file = new File(); - - $this->openSessionForSavingSavePath($file); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $file->write($sessionId, $data); - static::assertTrue($success); - - $dataInFile = file_get_contents($this->getPath() . DIRECTORY_SEPARATOR . 'sess_' . $sessionId); - static::assertEquals($data, $dataInFile); - } - - public function testRead(): void - { - $file = new File(); - - $this->openSessionForSavingSavePath($file); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $file->write($sessionId, $data); - static::assertTrue($success); - - $dataOutput = $file->read($sessionId); - static::assertNotEmpty($dataOutput); - static::assertIsString($dataOutput); - static::assertEquals($data, $dataOutput); - - $sessionId = ''; - $dataOutput = $file->read($sessionId); - static::assertEmpty($dataOutput); - static::assertIsString($dataOutput); - } - - public function testDestroy(): void - { - $file = new File(); - - $this->openSessionForSavingSavePath($file); - - $sessionId = 'todelete'; - $success = $file->destroy($sessionId); - static::assertTrue($success); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $file->write($sessionId, $data); - static::assertTrue($success); - - $isFileExist = file_exists($this->getPath() . DIRECTORY_SEPARATOR . 'sess_' . $sessionId); - static::assertTrue($isFileExist); - $success = $file->destroy($sessionId); - static::assertTrue($success); - $isFileNotExist = !file_exists($this->getPath() . DIRECTORY_SEPARATOR . 'sess_' . $sessionId); - static::assertTrue($isFileNotExist); - } - - public function testGc(): void - { - $file = new File(); - - $this->openSessionForSavingSavePath($file); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $file->write($sessionId, $data); - static::assertTrue($success); - - $isFileExist = file_exists($this->getPath() . DIRECTORY_SEPARATOR . 'sess_' . $sessionId); - static::assertTrue($isFileExist); - - $lifetime = -1000; - $success = $file->gc($lifetime); - static::assertTrue($success); - - $isFileNotExist = !file_exists($this->getPath() . DIRECTORY_SEPARATOR . 'sess_' . $sessionId); - static::assertTrue($isFileNotExist); - } - - public function testValidateId(): void - { - $file = new File(); - - $this->openSessionForSavingSavePath($file); - - $baseId = 'XiqKrZDUGp5ubt3klF0oorIlFiADXC9jxig9e8leUcCYuZ9w0mXh0b1foEGIBs7SSsdOuLor58vU5liBRVPsTobnvt'; - $endId1 = 'Xj8hh65DlR3tTFI1SGX3mFciDA9rMOa4LlnMr'; - $endId2 = 'Xklezfoipvfk0lferijkoefzjklgrvefLlnMr'; - - $file->write($baseId . $endId1, 'a'); - - static::assertTrue($file->validateId($baseId . $endId1)); - static::assertFalse($file->validateId($baseId . $endId2)); - static::assertFalse($file->validateId('kjlfez/fez')); - } - - public function testUpdateTimestamp(): void - { - $file = new File(); - - $this->openSessionForSavingSavePath($file); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $file->write($sessionId, $data); - static::assertTrue($success); - - $dataInFile = file_get_contents($this->getPath() . DIRECTORY_SEPARATOR . 'sess_' . $sessionId); - $oldFileModifiedTime = filemtime($this->getPath() . DIRECTORY_SEPARATOR . 'sess_' . $sessionId); - static::assertEquals($data, $dataInFile); - - sleep(1); - $success = $file->updateTimestamp($sessionId, $data); - static::assertTrue($success); - - clearstatcache(); - - $dataInFile2 = file_get_contents($this->getPath() . DIRECTORY_SEPARATOR . 'sess_' . $sessionId); - static::assertEquals($data, $dataInFile2); - static::assertEquals($dataInFile, $dataInFile2); - $newFileModifiedTime = filemtime($this->getPath() . DIRECTORY_SEPARATOR . 'sess_' . $sessionId); - - static::assertTrue($oldFileModifiedTime < $newFileModifiedTime); - } - - /** - * @throws \Exception - */ - public function testCreateId(): void - { - $file = new File(); - - $string = $file->create_sid(); - - static::assertSame(preg_match('/^[a-zA-Z0-9-]{127}+$/', $string), 1); - } -} +open($this->getPath(), ''); + static::assertTrue($success); + } + + /** @throws SessionException */ + public function testOpen(): void + { + $file = new File(); + $savePath = $this->getPath(); + $sessionName = ''; + $success = $file->open($savePath, $sessionName); + static::assertTrue($success); + + $savePathNotCreated = $savePath . \DIRECTORY_SEPARATOR . 'tests'; + $success = $file->open($savePathNotCreated, $sessionName); + static::assertTrue($success); + $success = \file_exists($savePathNotCreated); + static::assertTrue($success); + } + + public function testClose(): void + { + $file = new File(); + $success = $file->close(); + static::assertTrue($success); + } + + /** @throws SessionException */ + public function testWrite(): void + { + $file = new File(); + + $this->openSessionForSavingSavePath($file); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $file->write($sessionId, $data); + static::assertTrue($success); + + $dataInFile = \file_get_contents($this->getPath() . \DIRECTORY_SEPARATOR . 'sess_' . $sessionId); + static::assertSame($data, $dataInFile); + } + + /** @throws SessionException */ + public function testRead(): void + { + $file = new File(); + + $this->openSessionForSavingSavePath($file); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $file->write($sessionId, $data); + static::assertTrue($success); + + $dataOutput = $file->read($sessionId); + static::assertNotEmpty($dataOutput); + static::assertIsString($dataOutput); + static::assertSame($data, $dataOutput); + + $sessionId = ''; + $dataOutput = $file->read($sessionId); + static::assertEmpty($dataOutput); + static::assertIsString($dataOutput); + } + + /** @throws SessionException */ + public function testDestroy(): void + { + $file = new File(); + + $this->openSessionForSavingSavePath($file); + + $sessionId = 'todelete'; + $success = $file->destroy($sessionId); + static::assertTrue($success); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $file->write($sessionId, $data); + static::assertTrue($success); + + $isFileExist = \file_exists($this->getPath() . \DIRECTORY_SEPARATOR . 'sess_' . $sessionId); + static::assertTrue($isFileExist); + $success = $file->destroy($sessionId); + static::assertTrue($success); + $isFileNotExist = !\file_exists($this->getPath() . \DIRECTORY_SEPARATOR . 'sess_' . $sessionId); + static::assertTrue($isFileNotExist); + } + + /** @throws SessionException */ + public function testGc(): void + { + $file = new File(); + + $this->openSessionForSavingSavePath($file); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $file->write($sessionId, $data); + static::assertTrue($success); + + $isFileExist = \file_exists($this->getPath() . \DIRECTORY_SEPARATOR . 'sess_' . $sessionId); + static::assertTrue($isFileExist); + + $lifetime = -1000; + $success = $file->gc($lifetime); + static::assertTrue($success); + + $isFileNotExist = !\file_exists($this->getPath() . \DIRECTORY_SEPARATOR . 'sess_' . $sessionId); + static::assertTrue($isFileNotExist); + } + + /** @throws SessionException */ + public function testValidateId(): void + { + $file = new File(); + + $this->openSessionForSavingSavePath($file); + + $baseId = 'XiqKrZDUGp5ubt3klF0oorIlFiADXC9jxig9e8leUcCYuZ9w0mXh0b1foEGIBs7SSsdOuLor58vU5liBRVPsTobnvt'; + $endId1 = 'Xj8hh65DlR3tTFI1SGX3mFciDA9rMOa4LlnMr'; + $endId2 = 'Xklezfoipvfk0lferijkoefzjklgrvefLlnMr'; + + $file->write($baseId . $endId1, 'a'); + + static::assertTrue($file->validateId($baseId . $endId1)); + static::assertFalse($file->validateId($baseId . $endId2)); + static::assertFalse($file->validateId('kjlfez/fez')); + } + + /** @throws SessionException */ + public function testUpdateTimestamp(): void + { + $file = new File(); + + $this->openSessionForSavingSavePath($file); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $file->write($sessionId, $data); + static::assertTrue($success); + + $dataInFile = \file_get_contents($this->getPath() . \DIRECTORY_SEPARATOR . 'sess_' . $sessionId); + $oldFileModifiedTime = \filemtime($this->getPath() . \DIRECTORY_SEPARATOR . 'sess_' . $sessionId); + static::assertSame($data, $dataInFile); + + \sleep(1); + $success = $file->updateTimestamp($sessionId, $data); + static::assertTrue($success); + + \clearstatcache(); + + $dataInFile2 = \file_get_contents($this->getPath() . \DIRECTORY_SEPARATOR . 'sess_' . $sessionId); + static::assertSame($data, $dataInFile2); + static::assertSame($dataInFile, $dataInFile2); + $newFileModifiedTime = \filemtime($this->getPath() . \DIRECTORY_SEPARATOR . 'sess_' . $sessionId); + + static::assertTrue($oldFileModifiedTime < $newFileModifiedTime); + } + + /** @throws \Exception */ + public function testCreateId(): void + { + $file = new File(); + + $string = $file->create_sid(); + + static::assertMatchesRegularExpression('/^[a-zA-Z0-9-]{127}+$/', $string); + } + + /** @throws SessionException */ + public function testLengthSessionID(): void + { + $file = new File(); + $file->setLengthSessionID(50); + static::assertSame(50, $file->getLengthSessionID()); + } + + public function testLengthSessionIDSessionException(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('could not set length session ID below 32'); + + $file = new File(); + $file->setLengthSessionID(1); + } +} diff --git a/tests/FileWithNewPrefixTest.php b/tests/FileWithNewPrefixTest.php index 762ab30..e0738e4 100644 --- a/tests/FileWithNewPrefixTest.php +++ b/tests/FileWithNewPrefixTest.php @@ -1,230 +1,226 @@ -open($this->getPath(), ''); - static::assertTrue($success); - } - - public function testOpen(): void - { - $file = new File(); - $file->setPrefix('myprefix_'); - $savePath = $this->getPath(); - $sessionName = ''; - $success = $file->open($savePath, $sessionName); - static::assertTrue($success); - - $savePathNotCreated = $savePath . DIRECTORY_SEPARATOR . 'tests'; - $success = $file->open($savePathNotCreated, $sessionName); - static::assertTrue($success); - $success = file_exists($savePathNotCreated); - static::assertTrue($success); - } - - public function testClose(): void - { - $file = new File(); - $file->setPrefix('myprefix_'); - $success = $file->close(); - static::assertTrue($success); - } - - public function testWrite(): void - { - $file = new File(); - $file->setPrefix('myprefix_'); - - $this->openSessionForSavingSavePath($file); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $file->write($sessionId, $data); - static::assertTrue($success); - - $dataInFile = file_get_contents($this->getPath() . DIRECTORY_SEPARATOR . 'myprefix_' . $sessionId); - static::assertEquals($data, $dataInFile); - } - - public function testRead(): void - { - $file = new File(); - $file->setPrefix('myprefix_'); - - $this->openSessionForSavingSavePath($file); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $file->write($sessionId, $data); - static::assertTrue($success); - - $dataOutput = $file->read($sessionId); - static::assertNotEmpty($dataOutput); - static::assertIsString($dataOutput); - static::assertEquals($data, $dataOutput); - - $sessionId = ''; - $dataOutput = $file->read($sessionId); - static::assertEmpty($dataOutput); - static::assertIsString($dataOutput); - } - - public function testDestroy(): void - { - $file = new File(); - $file->setPrefix('myprefix_'); - - $this->openSessionForSavingSavePath($file); - - $sessionId = 'todelete'; - $success = $file->destroy($sessionId); - static::assertTrue($success); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $file->write($sessionId, $data); - static::assertTrue($success); - - $isFileExist = file_exists($this->getPath() . DIRECTORY_SEPARATOR . 'myprefix_' . $sessionId); - static::assertTrue($isFileExist); - $success = $file->destroy($sessionId); - static::assertTrue($success); - $isFileNotExist = !file_exists($this->getPath() . DIRECTORY_SEPARATOR . 'myprefix_' . $sessionId); - static::assertTrue($isFileNotExist); - } - - public function testGc(): void - { - $file = new File(); - $file->setPrefix('myprefix_'); - - $this->openSessionForSavingSavePath($file); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $file->write($sessionId, $data); - static::assertTrue($success); - - $isFileExist = file_exists($this->getPath() . DIRECTORY_SEPARATOR . 'myprefix_' . $sessionId); - static::assertTrue($isFileExist); - - $lifetime = -1000; - $success = $file->gc($lifetime); - static::assertTrue($success); - - $isFileNotExist = !file_exists($this->getPath() . DIRECTORY_SEPARATOR . 'myprefix_' . $sessionId); - static::assertTrue($isFileNotExist); - } - - public function testValidateId(): void - { - $file = new File(); - $file->setPrefix('myprefix_'); - - $this->openSessionForSavingSavePath($file); - - $baseId = 'YiqKrZDUGp5ubt3klF0oorIlFiADXC9jxig9e8leUcCYuZ9w0mXh0b1foEGIBs7SSsdOuLor58vU5liBRVPsTobnvt'; - $endId1 = 'Yj8hh65DlR3tTFI1SGX3mFciDA9rMOa4LlnMr'; - $endId2 = 'Yklezfoipvfk0lferijkoefzjklgrvefLlnMr'; - - $file->write($baseId . $endId1, 'a'); - - static::assertTrue($file->validateId($baseId . $endId1)); - static::assertFalse($file->validateId($baseId . $endId2)); - static::assertFalse($file->validateId('kjlfez/fez')); - } - - public function testUpdateTimestamp(): void - { - $file = new File(); - $file->setPrefix('myprefix_'); - - $this->openSessionForSavingSavePath($file); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $file->write($sessionId, $data); - static::assertTrue($success); - - $dataInFile = file_get_contents($this->getPath() . DIRECTORY_SEPARATOR . 'myprefix_' . $sessionId); - $oldFileModifiedTime = filemtime($this->getPath() . DIRECTORY_SEPARATOR . 'myprefix_' . $sessionId); - static::assertEquals($data, $dataInFile); - - sleep(1); - $success = $file->updateTimestamp($sessionId, $data); - static::assertTrue($success); - - clearstatcache(); - - $dataInFile2 = file_get_contents($this->getPath() . DIRECTORY_SEPARATOR . 'myprefix_' . $sessionId); - static::assertEquals($data, $dataInFile2); - static::assertEquals($dataInFile, $dataInFile2); - $newFileModifiedTime = filemtime($this->getPath() . DIRECTORY_SEPARATOR . 'myprefix_' . $sessionId); - - static::assertTrue($oldFileModifiedTime < $newFileModifiedTime); - } - - /** - * @throws \Exception - */ - public function testCreateId(): void - { - $file = new File(); - $file->setPrefix('myprefix_'); - - $string = $file->create_sid(); - - static::assertSame(preg_match('/^[a-zA-Z0-9-]{127}+$/', $string), 1); - } -} +open($this->getPath(), ''); + static::assertTrue($success); + } + + /** @throws \Rancoud\Session\SessionException */ + public function testOpen(): void + { + $file = new File(); + $file->setPrefix('myprefix_'); + $savePath = $this->getPath(); + $sessionName = ''; + $success = $file->open($savePath, $sessionName); + static::assertTrue($success); + + $savePathNotCreated = $savePath . \DIRECTORY_SEPARATOR . 'tests'; + $success = $file->open($savePathNotCreated, $sessionName); + static::assertTrue($success); + $success = \file_exists($savePathNotCreated); + static::assertTrue($success); + } + + public function testClose(): void + { + $file = new File(); + $file->setPrefix('myprefix_'); + $success = $file->close(); + static::assertTrue($success); + } + + /** @throws \Rancoud\Session\SessionException */ + public function testWrite(): void + { + $file = new File(); + $file->setPrefix('myprefix_'); + + $this->openSessionForSavingSavePath($file); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $file->write($sessionId, $data); + static::assertTrue($success); + + $dataInFile = \file_get_contents($this->getPath() . \DIRECTORY_SEPARATOR . 'myprefix_' . $sessionId); + static::assertSame($data, $dataInFile); + } + + /** @throws \Rancoud\Session\SessionException */ + public function testRead(): void + { + $file = new File(); + $file->setPrefix('myprefix_'); + + $this->openSessionForSavingSavePath($file); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $file->write($sessionId, $data); + static::assertTrue($success); + + $dataOutput = $file->read($sessionId); + static::assertNotEmpty($dataOutput); + static::assertIsString($dataOutput); + static::assertSame($data, $dataOutput); + + $sessionId = ''; + $dataOutput = $file->read($sessionId); + static::assertEmpty($dataOutput); + static::assertIsString($dataOutput); + } + + /** @throws \Rancoud\Session\SessionException */ + public function testDestroy(): void + { + $file = new File(); + $file->setPrefix('myprefix_'); + + $this->openSessionForSavingSavePath($file); + + $sessionId = 'todelete'; + $success = $file->destroy($sessionId); + static::assertTrue($success); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $file->write($sessionId, $data); + static::assertTrue($success); + + $isFileExist = \file_exists($this->getPath() . \DIRECTORY_SEPARATOR . 'myprefix_' . $sessionId); + static::assertTrue($isFileExist); + $success = $file->destroy($sessionId); + static::assertTrue($success); + $isFileNotExist = !\file_exists($this->getPath() . \DIRECTORY_SEPARATOR . 'myprefix_' . $sessionId); + static::assertTrue($isFileNotExist); + } + + /** @throws \Rancoud\Session\SessionException */ + public function testGc(): void + { + $file = new File(); + $file->setPrefix('myprefix_'); + + $this->openSessionForSavingSavePath($file); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $file->write($sessionId, $data); + static::assertTrue($success); + + $isFileExist = \file_exists($this->getPath() . \DIRECTORY_SEPARATOR . 'myprefix_' . $sessionId); + static::assertTrue($isFileExist); + + $lifetime = -1000; + $success = $file->gc($lifetime); + static::assertTrue($success); + + $isFileNotExist = !\file_exists($this->getPath() . \DIRECTORY_SEPARATOR . 'myprefix_' . $sessionId); + static::assertTrue($isFileNotExist); + } + + /** @throws \Rancoud\Session\SessionException */ + public function testValidateId(): void + { + $file = new File(); + $file->setPrefix('myprefix_'); + + $this->openSessionForSavingSavePath($file); + + $baseId = 'YiqKrZDUGp5ubt3klF0oorIlFiADXC9jxig9e8leUcCYuZ9w0mXh0b1foEGIBs7SSsdOuLor58vU5liBRVPsTobnvt'; + $endId1 = 'Yj8hh65DlR3tTFI1SGX3mFciDA9rMOa4LlnMr'; + $endId2 = 'Yklezfoipvfk0lferijkoefzjklgrvefLlnMr'; + + $file->write($baseId . $endId1, 'a'); + + static::assertTrue($file->validateId($baseId . $endId1)); + static::assertFalse($file->validateId($baseId . $endId2)); + static::assertFalse($file->validateId('kjlfez/fez')); + } + + /** @throws \Rancoud\Session\SessionException */ + public function testUpdateTimestamp(): void + { + $file = new File(); + $file->setPrefix('myprefix_'); + + $this->openSessionForSavingSavePath($file); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $file->write($sessionId, $data); + static::assertTrue($success); + + $dataInFile = \file_get_contents($this->getPath() . \DIRECTORY_SEPARATOR . 'myprefix_' . $sessionId); + $oldFileModifiedTime = \filemtime($this->getPath() . \DIRECTORY_SEPARATOR . 'myprefix_' . $sessionId); + static::assertSame($data, $dataInFile); + + \sleep(1); + $success = $file->updateTimestamp($sessionId, $data); + static::assertTrue($success); + + \clearstatcache(); + + $dataInFile2 = \file_get_contents($this->getPath() . \DIRECTORY_SEPARATOR . 'myprefix_' . $sessionId); + static::assertSame($data, $dataInFile2); + static::assertSame($dataInFile, $dataInFile2); + $newFileModifiedTime = \filemtime($this->getPath() . \DIRECTORY_SEPARATOR . 'myprefix_' . $sessionId); + + static::assertTrue($oldFileModifiedTime < $newFileModifiedTime); + } + + /** @throws \Exception */ + public function testCreateId(): void + { + $file = new File(); + $file->setPrefix('myprefix_'); + + $string = $file->create_sid(); + + static::assertMatchesRegularExpression('/^[a-zA-Z0-9-]{127}+$/', $string); + } +} diff --git a/tests/RedisEncryptionTest.php b/tests/RedisEncryptionTest.php index 3be1dc1..99b1547 100644 --- a/tests/RedisEncryptionTest.php +++ b/tests/RedisEncryptionTest.php @@ -1,268 +1,261 @@ - 'tcp', - 'host' => '127.0.0.1', - 'port' => 6379, - ]; - static::$redis = new Predis($params); - static::$redis->flushdb(); - } - - protected function setUp(): void - { - static::$redis->flushdb(); - } - - public function testOpen(): void - { - $redis = new RedisEncryption(); - $redis->setKey('randomKey'); - - $redis->setCurrentRedis(static::$redis); - - $savePath = ''; - $sessionName = ''; - $success = $redis->open($savePath, $sessionName); - static::assertTrue($success); - } - - public function testClose(): void - { - $redis = new RedisEncryption(); - $redis->setKey('randomKey'); - - $redis->setCurrentRedis(static::$redis); - $success = $redis->close(); - static::assertTrue($success); - } - - /** - * @throws \Rancoud\Session\SessionException - */ - public function testWrite(): void - { - $redis = new RedisEncryption(); - $redis->setKey('randomKey'); - - $redis->setCurrentRedis(static::$redis); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $redis->write($sessionId, $data); - static::assertTrue($success); - - $dataInRedis = static::$redis->get($sessionId); - static::assertNotEquals($data, $dataInRedis); - - $encryptionTrait = $this->getObjectForTrait('Rancoud\Session\Encryption'); - $encryptionTrait->setKey('randomKey'); - $dataInRedisDecrypted = $encryptionTrait->decrypt($dataInRedis); - static::assertEquals($data, $dataInRedisDecrypted); - } - - /** - * @throws \Rancoud\Session\SessionException - */ - public function testRead(): void - { - $redis = new RedisEncryption(); - $redis->setKey('randomKey'); - - $redis->setCurrentRedis(static::$redis); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $redis->write($sessionId, $data); - static::assertTrue($success); - - $dataOutput = $redis->read($sessionId); - static::assertNotEmpty($dataOutput); - static::assertIsString($dataOutput); - static::assertEquals($data, $dataOutput); - - $sessionId = ''; - $dataOutput = $redis->read($sessionId); - static::assertEmpty($dataOutput); - static::assertIsString($dataOutput); - } - - /** - * @throws \Rancoud\Session\SessionException - */ - public function testDestroy(): void - { - $redis = new RedisEncryption(); - $redis->setKey('randomKey'); - - $redis->setCurrentRedis(static::$redis); - - $sessionId = 'todelete'; - $success = $redis->destroy($sessionId); - static::assertTrue($success); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $redis->write($sessionId, $data); - static::assertTrue($success); - - $isKeyExist = static::$redis->exists($sessionId) === 1; - static::assertTrue($isKeyExist); - $success = $redis->destroy($sessionId); - static::assertTrue($success); - $isKeyNotExist = static::$redis->exists($sessionId) === 0; - static::assertTrue($isKeyNotExist); - } - - /** - * @throws \Rancoud\Session\SessionException - */ - public function testGc(): void - { - $redis = new RedisEncryption(); - $redis->setKey('randomKey'); - - $redis->setCurrentRedis(static::$redis); - $redis->setLifetime(1); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $redis->write($sessionId, $data); - static::assertTrue($success); - - $isKeyExist = static::$redis->exists($sessionId) === 1; - static::assertTrue($isKeyExist); - - sleep(2); - - $lifetime = 0; - $success = $redis->gc($lifetime); - static::assertTrue($success); - - $isKeyNotExist = static::$redis->exists($sessionId) === 0; - static::assertTrue($isKeyNotExist); - } - - /** - * @throws \Rancoud\Session\SessionException - */ - public function testSetNewRedis(): void - { - $redis = new RedisEncryption(); - $redis->setKey('randomKey'); - - $params = [ - 'scheme' => 'tcp', - 'host' => '127.0.0.1', - 'port' => 6379, - ]; - $redis->setNewRedis($params); - - $sessionId = 'sessionId'; - $data = 'azerty'; - - $success = $redis->write($sessionId, $data); - static::assertTrue($success); - } - - /** - * @throws \Rancoud\Session\SessionException - */ - public function testValidateId(): void - { - $redis = new RedisEncryption(); - $redis->setKey('randomKey'); - - $redis->setCurrentRedis(static::$redis); - - $baseId = 'DiqKrZDUGp5ubt3klF0oorIlFiADXC9jxig9e8leUcCYuZ9w0mXh0b1foEGIBs7SSsdOuLor58vU5liBRVPsTobnvt'; - $endId1 = 'Dj8hh65DlR3tTFI1SGX3mFciDA9rMOa4LlnMr'; - $endId2 = 'Dklezfoipvfk0lferijkoefzjklgrvefLlnMr'; - - $redis->write($baseId . $endId1, 'a'); - - static::assertTrue($redis->validateId($baseId . $endId1)); - static::assertFalse($redis->validateId($baseId . $endId2)); - static::assertFalse($redis->validateId('kjlfez/fez')); - } - - /** - * @throws \Rancoud\Session\SessionException - */ - public function testUpdateTimestamp(): void - { - $redis = new RedisEncryption(); - $redis->setKey('randomKey'); - - $redis->setCurrentRedis(static::$redis); - - $sessionId = 'sessionId'; - $data = 'azerty'; - - $success = $redis->write($sessionId, $data); - static::assertTrue($success); - - $dataInRedis = static::$redis->get($sessionId); - static::assertNotEquals($data, $dataInRedis); - $ttl1 = static::$redis->ttl($sessionId); - - $encryptionTrait = $this->getObjectForTrait('Rancoud\Session\Encryption'); - $encryptionTrait->setKey('randomKey'); - $dataInRedisDecrypted = $encryptionTrait->decrypt($dataInRedis); - static::assertEquals($data, $dataInRedisDecrypted); - - sleep(2); - - $ttl2 = static::$redis->ttl($sessionId); - - $success = $redis->updateTimestamp($sessionId, $data); - static::assertTrue($success); - - $dataInRedis2 = static::$redis->get($sessionId); - static::assertNotEquals($data, $dataInRedis2); - $ttl3 = static::$redis->ttl($sessionId); - - $encryptionTrait = $this->getObjectForTrait('Rancoud\Session\Encryption'); - $encryptionTrait->setKey('randomKey'); - $dataInRedisDecrypted = $encryptionTrait->decrypt($dataInRedis2); - static::assertEquals($data, $dataInRedisDecrypted); - - static::assertTrue($ttl2 < $ttl1); - static::assertTrue($ttl3 > $ttl2); - } - - /** - * @throws \Exception - */ - public function testCreateId(): void - { - $redis = new RedisEncryption(); - $redis->setKey('randomKey'); - - $redis->setCurrentRedis(static::$redis); - - $string = $redis->create_sid(); - - static::assertSame(preg_match('/^[a-zA-Z0-9-]{127}+$/', $string), 1); - } -} + 'tcp', + 'port' => 6379, + ]; + + $redisHost = \getenv('REDIS_HOST', true); + $params['host'] = ($redisHost !== false) ? $redisHost : '127.0.0.1'; + + static::$redis = new Predis($params); + static::$redis->flushdb(); + } + + protected function setUp(): void + { + static::$redis->flushdb(); + } + + public function testOpen(): void + { + $redis = new RedisEncryption(); + $redis->setKey('randomKey'); + + $redis->setCurrentRedis(static::$redis); + + $savePath = ''; + $sessionName = ''; + $success = $redis->open($savePath, $sessionName); + static::assertTrue($success); + } + + public function testClose(): void + { + $redis = new RedisEncryption(); + $redis->setKey('randomKey'); + + $redis->setCurrentRedis(static::$redis); + $success = $redis->close(); + static::assertTrue($success); + } + + /** @throws \Rancoud\Session\SessionException */ + public function testWrite(): void + { + $redis = new RedisEncryption(); + $redis->setKey('randomKey'); + + $redis->setCurrentRedis(static::$redis); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $redis->write($sessionId, $data); + static::assertTrue($success); + + $dataInRedis = static::$redis->get($sessionId); + static::assertNotSame($data, $dataInRedis); + + $encryptionTrait = new class() { + use \Rancoud\Session\Encryption; + }; + $encryptionTrait->setKey('randomKey'); + $dataInRedisDecrypted = $encryptionTrait->decrypt($dataInRedis); + static::assertSame($data, $dataInRedisDecrypted); + } + + /** @throws \Rancoud\Session\SessionException */ + public function testRead(): void + { + $redis = new RedisEncryption(); + $redis->setKey('randomKey'); + + $redis->setCurrentRedis(static::$redis); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $redis->write($sessionId, $data); + static::assertTrue($success); + + $dataOutput = $redis->read($sessionId); + static::assertNotEmpty($dataOutput); + static::assertIsString($dataOutput); + static::assertSame($data, $dataOutput); + + $sessionId = ''; + $dataOutput = $redis->read($sessionId); + static::assertEmpty($dataOutput); + static::assertIsString($dataOutput); + } + + /** @throws \Rancoud\Session\SessionException */ + public function testDestroy(): void + { + $redis = new RedisEncryption(); + $redis->setKey('randomKey'); + + $redis->setCurrentRedis(static::$redis); + + $sessionId = 'todelete'; + $success = $redis->destroy($sessionId); + static::assertTrue($success); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $redis->write($sessionId, $data); + static::assertTrue($success); + + $isKeyExist = static::$redis->exists($sessionId) === 1; + static::assertTrue($isKeyExist); + $success = $redis->destroy($sessionId); + static::assertTrue($success); + $isKeyNotExist = static::$redis->exists($sessionId) === 0; + static::assertTrue($isKeyNotExist); + } + + /** @throws \Rancoud\Session\SessionException */ + public function testGc(): void + { + $redis = new RedisEncryption(); + $redis->setKey('randomKey'); + + $redis->setCurrentRedis(static::$redis); + $redis->setLifetime(1); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $redis->write($sessionId, $data); + static::assertTrue($success); + + $isKeyExist = static::$redis->exists($sessionId) === 1; + static::assertTrue($isKeyExist); + + \sleep(2); + + $lifetime = 0; + $success = $redis->gc($lifetime); + static::assertTrue($success); + + $isKeyNotExist = static::$redis->exists($sessionId) === 0; + static::assertTrue($isKeyNotExist); + } + + /** @throws \Rancoud\Session\SessionException */ + public function testSetNewRedis(): void + { + $redis = new RedisEncryption(); + $redis->setKey('randomKey'); + + $params = [ + 'scheme' => 'tcp', + 'port' => 6379, + ]; + + $redisHost = \getenv('REDIS_HOST', true); + $params['host'] = ($redisHost !== false) ? $redisHost : '127.0.0.1'; + + $redis->setNewRedis($params); + + $sessionId = 'sessionId'; + $data = 'azerty'; + + $success = $redis->write($sessionId, $data); + static::assertTrue($success); + } + + /** @throws \Rancoud\Session\SessionException */ + public function testValidateId(): void + { + $redis = new RedisEncryption(); + $redis->setKey('randomKey'); + + $redis->setCurrentRedis(static::$redis); + + $baseId = 'DiqKrZDUGp5ubt3klF0oorIlFiADXC9jxig9e8leUcCYuZ9w0mXh0b1foEGIBs7SSsdOuLor58vU5liBRVPsTobnvt'; + $endId1 = 'Dj8hh65DlR3tTFI1SGX3mFciDA9rMOa4LlnMr'; + $endId2 = 'Dklezfoipvfk0lferijkoefzjklgrvefLlnMr'; + + $redis->write($baseId . $endId1, 'a'); + + static::assertTrue($redis->validateId($baseId . $endId1)); + static::assertFalse($redis->validateId($baseId . $endId2)); + static::assertFalse($redis->validateId('kjlfez/fez')); + } + + /** @throws \Rancoud\Session\SessionException */ + public function testUpdateTimestamp(): void + { + $redis = new RedisEncryption(); + $redis->setKey('randomKey'); + + $redis->setCurrentRedis(static::$redis); + + $sessionId = 'sessionId'; + $data = 'azerty'; + + $success = $redis->write($sessionId, $data); + static::assertTrue($success); + + $dataInRedis = static::$redis->get($sessionId); + static::assertNotSame($data, $dataInRedis); + $ttl1 = static::$redis->ttl($sessionId); + + $encryptionTrait = new class() { + use \Rancoud\Session\Encryption; + }; + $encryptionTrait->setKey('randomKey'); + $dataInRedisDecrypted = $encryptionTrait->decrypt($dataInRedis); + static::assertSame($data, $dataInRedisDecrypted); + + \sleep(2); + + $ttl2 = static::$redis->ttl($sessionId); + + $success = $redis->updateTimestamp($sessionId, $data); + static::assertTrue($success); + + $dataInRedis2 = static::$redis->get($sessionId); + static::assertNotSame($data, $dataInRedis2); + $ttl3 = static::$redis->ttl($sessionId); + + $encryptionTrait = new class() { + use \Rancoud\Session\Encryption; + }; + $encryptionTrait->setKey('randomKey'); + $dataInRedisDecrypted = $encryptionTrait->decrypt($dataInRedis2); + static::assertSame($data, $dataInRedisDecrypted); + + static::assertTrue($ttl2 < $ttl1); + static::assertTrue($ttl3 > $ttl2); + } + + /** @throws \Exception */ + public function testCreateId(): void + { + $redis = new RedisEncryption(); + $redis->setKey('randomKey'); + + $redis->setCurrentRedis(static::$redis); + + $string = $redis->create_sid(); + + static::assertMatchesRegularExpression('/^[a-zA-Z0-9-]{127}+$/', $string); + } +} diff --git a/tests/RedisTest.php b/tests/RedisTest.php index b4155f2..bf9108a 100644 --- a/tests/RedisTest.php +++ b/tests/RedisTest.php @@ -1,212 +1,231 @@ - 'tcp', - 'host' => '127.0.0.1', - 'port' => 6379, - ]; - static::$redis = new Predis($params); - static::$redis->flushdb(); - } - - protected function setUp(): void - { - static::$redis->flushdb(); - } - - public function testOpen(): void - { - $redis = new Redis(); - $redis->setCurrentRedis(static::$redis); - - $savePath = ''; - $sessionName = ''; - $success = $redis->open($savePath, $sessionName); - static::assertTrue($success); - } - - public function testClose(): void - { - $redis = new Redis(); - $redis->setCurrentRedis(static::$redis); - $success = $redis->close(); - static::assertTrue($success); - } - - public function testWrite(): void - { - $redis = new Redis(); - $redis->setCurrentRedis(static::$redis); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $redis->write($sessionId, $data); - static::assertTrue($success); - - $dataInRedis = static::$redis->get($sessionId); - static::assertEquals($data, $dataInRedis); - } - - public function testRead(): void - { - $redis = new Redis(); - $redis->setCurrentRedis(static::$redis); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $redis->write($sessionId, $data); - static::assertTrue($success); - - $dataOutput = $redis->read($sessionId); - static::assertNotEmpty($dataOutput); - static::assertIsString($dataOutput); - static::assertEquals($data, $dataOutput); - - $sessionId = ''; - $dataOutput = $redis->read($sessionId); - static::assertEmpty($dataOutput); - static::assertIsString($dataOutput); - } - - public function testDestroy(): void - { - $redis = new Redis(); - $redis->setCurrentRedis(static::$redis); - - $sessionId = 'todelete'; - $success = $redis->destroy($sessionId); - static::assertTrue($success); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $redis->write($sessionId, $data); - static::assertTrue($success); - - $isKeyExist = static::$redis->exists($sessionId) === 1; - static::assertTrue($isKeyExist); - $success = $redis->destroy($sessionId); - static::assertTrue($success); - $isKeyNotExist = static::$redis->exists($sessionId) === 0; - static::assertTrue($isKeyNotExist); - } - - public function testGc(): void - { - $redis = new Redis(); - $redis->setCurrentRedis(static::$redis); - $redis->setLifetime(1); - - $sessionId = 'sessionId'; - $data = 'azerty'; - $success = $redis->write($sessionId, $data); - static::assertTrue($success); - - $isKeyExist = static::$redis->exists($sessionId) === 1; - static::assertTrue($isKeyExist); - - sleep(2); - - $lifetime = 0; - $success = $redis->gc($lifetime); - static::assertTrue($success); - - $isKeyNotExist = static::$redis->exists($sessionId) === 0; - static::assertTrue($isKeyNotExist); - } - - public function testSetNewRedis(): void - { - $redis = new Redis(); - $params = [ - 'scheme' => 'tcp', - 'host' => '127.0.0.1', - 'port' => 6379, - ]; - $redis->setNewRedis($params); - - $sessionId = 'sessionId'; - $data = 'azerty'; - - $success = $redis->write($sessionId, $data); - static::assertTrue($success); - } - - public function testValidateId(): void - { - $redis = new Redis(); - $redis->setCurrentRedis(static::$redis); - - $baseId = 'DiqKrZDUGp5ubt3klF0oorIlFiADXC9jxig9e8leUcCYuZ9w0mXh0b1foEGIBs7SSsdOuLor58vU5liBRVPsTobnvt'; - $endId1 = 'Dj8hh65DlR3tTFI1SGX3mFciDA9rMOa4LlnMr'; - $endId2 = 'Dklezfoipvfk0lferijkoefzjklgrvefLlnMr'; - - $redis->write($baseId . $endId1, 'a'); - - static::assertTrue($redis->validateId($baseId . $endId1)); - static::assertFalse($redis->validateId($baseId . $endId2)); - static::assertFalse($redis->validateId('kjlfez/fez')); - } - - public function testUpdateTimestamp(): void - { - $redis = new Redis(); - $redis->setCurrentRedis(static::$redis); - - $sessionId = 'sessionId'; - $data = 'azerty'; - - $success = $redis->write($sessionId, $data); - static::assertTrue($success); - - $dataInRedis = static::$redis->get($sessionId); - static::assertEquals($data, $dataInRedis); - $ttl1 = static::$redis->ttl($sessionId); - - sleep(2); - - $ttl2 = static::$redis->ttl($sessionId); - - $success = $redis->updateTimestamp($sessionId, $data); - static::assertTrue($success); - - $dataInRedis2 = static::$redis->get($sessionId); - static::assertEquals($data, $dataInRedis2); - $ttl3 = static::$redis->ttl($sessionId); - - static::assertTrue($ttl2 < $ttl1); - static::assertTrue($ttl3 > $ttl2); - } - - /** - * @throws \Exception - */ - public function testCreateId(): void - { - $redis = new Redis(); - $redis->setCurrentRedis(static::$redis); - - $string = $redis->create_sid(); - - static::assertSame(preg_match('/^[a-zA-Z0-9-]{127}+$/', $string), 1); - } -} + 'tcp', + 'port' => 6379, + ]; + + $redisHost = \getenv('REDIS_HOST', true); + $params['host'] = ($redisHost !== false) ? $redisHost : '127.0.0.1'; + + static::$redis = new Predis($params); + static::$redis->flushdb(); + } + + protected function setUp(): void + { + static::$redis->flushdb(); + } + + public function testOpen(): void + { + $redis = new Redis(); + $redis->setCurrentRedis(static::$redis); + + $savePath = ''; + $sessionName = ''; + $success = $redis->open($savePath, $sessionName); + static::assertTrue($success); + } + + public function testClose(): void + { + $redis = new Redis(); + $redis->setCurrentRedis(static::$redis); + $success = $redis->close(); + static::assertTrue($success); + } + + public function testWrite(): void + { + $redis = new Redis(); + $redis->setCurrentRedis(static::$redis); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $redis->write($sessionId, $data); + static::assertTrue($success); + + $dataInRedis = static::$redis->get($sessionId); + static::assertSame($data, $dataInRedis); + } + + public function testRead(): void + { + $redis = new Redis(); + $redis->setCurrentRedis(static::$redis); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $redis->write($sessionId, $data); + static::assertTrue($success); + + $dataOutput = $redis->read($sessionId); + static::assertNotEmpty($dataOutput); + static::assertIsString($dataOutput); + static::assertSame($data, $dataOutput); + + $sessionId = ''; + $dataOutput = $redis->read($sessionId); + static::assertEmpty($dataOutput); + static::assertIsString($dataOutput); + } + + public function testDestroy(): void + { + $redis = new Redis(); + $redis->setCurrentRedis(static::$redis); + + $sessionId = 'todelete'; + $success = $redis->destroy($sessionId); + static::assertTrue($success); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $redis->write($sessionId, $data); + static::assertTrue($success); + + $isKeyExist = static::$redis->exists($sessionId) === 1; + static::assertTrue($isKeyExist); + $success = $redis->destroy($sessionId); + static::assertTrue($success); + $isKeyNotExist = static::$redis->exists($sessionId) === 0; + static::assertTrue($isKeyNotExist); + } + + public function testGc(): void + { + $redis = new Redis(); + $redis->setCurrentRedis(static::$redis); + $redis->setLifetime(1); + + $sessionId = 'sessionId'; + $data = 'azerty'; + $success = $redis->write($sessionId, $data); + static::assertTrue($success); + + $isKeyExist = static::$redis->exists($sessionId) === 1; + static::assertTrue($isKeyExist); + + \sleep(2); + + $lifetime = 0; + $success = $redis->gc($lifetime); + static::assertTrue($success); + + $isKeyNotExist = static::$redis->exists($sessionId) === 0; + static::assertTrue($isKeyNotExist); + } + + public function testSetNewRedis(): void + { + $redis = new Redis(); + $params = [ + 'scheme' => 'tcp', + 'port' => 6379, + ]; + + $redisHost = \getenv('REDIS_HOST', true); + $params['host'] = ($redisHost !== false) ? $redisHost : '127.0.0.1'; + + $redis->setNewRedis($params); + + $sessionId = 'sessionId'; + $data = 'azerty'; + + $success = $redis->write($sessionId, $data); + static::assertTrue($success); + } + + public function testValidateId(): void + { + $redis = new Redis(); + $redis->setCurrentRedis(static::$redis); + + $baseId = 'DiqKrZDUGp5ubt3klF0oorIlFiADXC9jxig9e8leUcCYuZ9w0mXh0b1foEGIBs7SSsdOuLor58vU5liBRVPsTobnvt'; + $endId1 = 'Dj8hh65DlR3tTFI1SGX3mFciDA9rMOa4LlnMr'; + $endId2 = 'Dklezfoipvfk0lferijkoefzjklgrvefLlnMr'; + + $redis->write($baseId . $endId1, 'a'); + + static::assertTrue($redis->validateId($baseId . $endId1)); + static::assertFalse($redis->validateId($baseId . $endId2)); + static::assertFalse($redis->validateId('kjlfez/fez')); + } + + public function testUpdateTimestamp(): void + { + $redis = new Redis(); + $redis->setCurrentRedis(static::$redis); + + $sessionId = 'sessionId'; + $data = 'azerty'; + + $success = $redis->write($sessionId, $data); + static::assertTrue($success); + + $dataInRedis = static::$redis->get($sessionId); + static::assertSame($data, $dataInRedis); + $ttl1 = static::$redis->ttl($sessionId); + + \sleep(2); + + $ttl2 = static::$redis->ttl($sessionId); + + $success = $redis->updateTimestamp($sessionId, $data); + static::assertTrue($success); + + $dataInRedis2 = static::$redis->get($sessionId); + static::assertSame($data, $dataInRedis2); + $ttl3 = static::$redis->ttl($sessionId); + + static::assertTrue($ttl2 < $ttl1); + static::assertTrue($ttl3 > $ttl2); + } + + /** @throws \Exception */ + public function testCreateId(): void + { + $redis = new Redis(); + $redis->setCurrentRedis(static::$redis); + + $string = $redis->create_sid(); + + static::assertMatchesRegularExpression('/^[a-zA-Z0-9-]{127}+$/', $string); + } + + /** @throws SessionException */ + public function testLengthSessionID(): void + { + $redis = new Redis(); + $redis->setLengthSessionID(50); + static::assertSame(50, $redis->getLengthSessionID()); + } + + public function testLengthSessionIDSessionException(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('could not set length session ID below 32'); + + $redis = new Redis(); + $redis->setLengthSessionID(1); + } +} diff --git a/tests/SessionTest.php b/tests/SessionTest.php index 22e419c..e252bca 100644 --- a/tests/SessionTest.php +++ b/tests/SessionTest.php @@ -1,634 +1,626 @@ -expectException(SessionException::class); - $this->expectExceptionMessage('Session already started'); - - Session::setReadWrite(); - Session::start(); - Session::start(); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testUseDefaultDriverWhenAlreadyStartedException(): void - { - $this->expectException(SessionException::class); - $this->expectExceptionMessage('Session already started'); - - Session::setReadWrite(); - Session::start(); - Session::useDefaultDriver(); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testUseFileDriverWhenAlreadyStartedException(): void - { - $this->expectException(SessionException::class); - $this->expectExceptionMessage('Session already started'); - - Session::setReadWrite(); - Session::start(); - Session::useFileDriver(); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testUseCustomDriverWhenAlreadyStartedException(): void - { - $this->expectException(SessionException::class); - $this->expectExceptionMessage('Session already started'); - - Session::setReadWrite(); - Session::start(); - Session::useCustomDriver(new File()); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testUseDefaultDriver(): void - { - Session::useDefaultDriver(); - Session::start(); - - static::assertInstanceOf(\SessionHandler::class, Session::getDriver()); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testUseDefaultEncryptionDriver(): void - { - Session::useDefaultEncryptionDriver('randomKey'); - Session::start(); - - static::assertInstanceOf(\Rancoud\Session\DefaultEncryption::class, Session::getDriver()); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testUseFileDriver(): void - { - Session::useFileDriver(); - Session::start(); - - static::assertInstanceOf(File::class, Session::getDriver()); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testUseFileDriverWithPrefix(): void - { - $prefix = 'youhou_'; - Session::useFileDriver(); - Session::setPrefixForFile($prefix); - Session::setReadWrite(); - Session::start(['lazy_write' => '0']); - $sessionId = Session::getId(); - $path = Session::getOption('save_path'); - Session::commit(); - - static::assertInstanceOf(File::class, Session::getDriver()); - static::assertFileExists($path . DIRECTORY_SEPARATOR . $prefix . $sessionId); - - $pattern = $path . DIRECTORY_SEPARATOR . 'youhou_*'; - foreach (glob($pattern) as $file) { - if (file_exists($file)) { - unlink($file); - } - } - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testUseFileEncryptionDriver(): void - { - Session::useFileEncryptionDriver('randomKey'); - Session::start(); - - static::assertInstanceOf(\Rancoud\Session\FileEncryption::class, Session::getDriver()); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testUseNewDatabaseDriver(): void - { - $params = [ - 'engine' => 'mysql', - 'host' => '127.0.0.1', - 'user' => 'root', - 'password' => '', - 'database' => 'test_database' - ]; - Session::useNewDatabaseDriver($params); - Session::start(); - - static::assertInstanceOf(\Rancoud\Session\Database::class, Session::getDriver()); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testUseNewDatabaseEncryptionDriver(): void - { - $params = [ - 'engine' => 'mysql', - 'host' => '127.0.0.1', - 'user' => 'root', - 'password' => '', - 'database' => 'test_database' - ]; - Session::useNewDatabaseEncryptionDriver($params, 'randomKey'); - Session::start(); - - static::assertInstanceOf(\Rancoud\Session\DatabaseEncryption::class, Session::getDriver()); - } - - /** - * @runInSeparateProcess - * @throws \Rancoud\Database\DatabaseException - * @throws \Exception - */ - public function testUseCurrentDatabaseDriver(): void - { - $conf = new \Rancoud\Database\Configurator([ - 'engine' => 'mysql', - 'host' => '127.0.0.1', - 'user' => 'root', - 'password' => '', - 'database' => 'test_database' - ]); - $db = new \Rancoud\Database\Database($conf); - Session::useCurrentDatabaseDriver($db); - Session::start(); - - static::assertInstanceOf(\Rancoud\Session\Database::class, Session::getDriver()); - } - - /** - * @runInSeparateProcess - * @throws \Rancoud\Database\DatabaseException - * @throws \Exception - */ - public function testUseCurrentDatabaseEncryptionDriver(): void - { - $userId = 50; - $conf = new \Rancoud\Database\Configurator([ - 'engine' => 'mysql', - 'host' => '127.0.0.1', - 'user' => 'root', - 'password' => '', - 'database' => 'test_database' - ]); - $db = new \Rancoud\Database\Database($conf); - Session::useCurrentDatabaseEncryptionDriver($db, 'randomKey'); - static::assertInstanceOf(\Rancoud\Session\DatabaseEncryption::class, Session::getDriver()); - - Session::setUserIdForDatabase($userId); - Session::setOption('lazy_write', '0'); - Session::set('a', 'b'); - $sessionId = Session::getId(); - Session::commit(); - - $userIdInTable = $db->selectVar('SELECT id_user FROM sessions WHERE id = :id', ['id' => $sessionId]); - static::assertEquals($userId, $userIdInTable); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testUseNewRedisDriver(): void - { - $params = [ - 'scheme' => 'tcp', - 'host' => '127.0.0.1', - 'port' => 6379, - ]; - Session::useNewRedisDriver($params); - Session::start(); - - static::assertInstanceOf(\Rancoud\Session\Redis::class, Session::getDriver()); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testUseNewRedisEncryptionDriver(): void - { - $params = [ - 'scheme' => 'tcp', - 'host' => '127.0.0.1', - 'port' => 6379, - ]; - Session::useNewRedisEncryptionDriver($params, 'randomKey'); - Session::start(); - - static::assertInstanceOf(\Rancoud\Session\RedisEncryption::class, Session::getDriver()); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testUseCurrentRedisDriver(): void - { - $params = [ - 'scheme' => 'tcp', - 'host' => '127.0.0.1', - 'port' => 6379, - ]; - $redis = new \Predis\Client($params); - Session::useCurrentRedisDriver($redis); - Session::start(); - - static::assertInstanceOf(\Rancoud\Session\Redis::class, Session::getDriver()); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testUseCurrentRedisEncryptionDriver(): void - { - $params = [ - 'scheme' => 'tcp', - 'host' => '127.0.0.1', - 'port' => 6379, - ]; - $redis = new \Predis\Client($params); - Session::useCurrentRedisEncryptionDriver($redis, 'randomKey'); - Session::start(); - - static::assertInstanceOf(\Rancoud\Session\RedisEncryption::class, Session::getDriver()); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testUseCustomDriver(): void - { - Session::useCustomDriver(new File()); - Session::start(); - - static::assertInstanceOf(File::class, Session::getDriver()); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testUseEncryptionDriverThrowExceptionWhenMethodIncrorrect(): void - { - $this->expectException(SessionException::class); - $this->expectExceptionMessage('Method unknowed: incorrect'); - - Session::useFileEncryptionDriver('randomKey', 'incorrect'); - } - - //exception quand methode pas bonne dans les encryption - - /** - * @runInSeparateProcess - * @throws SessionException - * @throws \Exception - */ - public function testSetOption(): void - { - $defaultOption = Session::getOption('name'); - static::assertEquals($defaultOption, ini_get('session.name')); - - Session::setOption('name', 'my_custom_name'); - $customOption = Session::getOption('name'); - - static::assertEquals('my_custom_name', $customOption); - - Session::start(['name' => 'my_other_name']); - - $customOption = Session::getOption('name'); - static::assertEquals('my_other_name', $customOption); - } - - /** - * @runInSeparateProcess - */ - public function testSetOptionThrowException(): void - { - $this->expectException(SessionException::class); - $this->expectExceptionMessage('Incorrect option: azerty'); - - Session::getOption('azerty'); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testGetAll(): void - { - $sessionValues = Session::getAll(); - static::assertEmpty($sessionValues); - - Session::set('a', 'b'); - - $sessionValues = Session::getAll(); - static::assertEquals(['a' => 'b'], $sessionValues); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testFlash(): void - { - $flaKey1 = 'a'; - $flaValue1 = 'b'; - $flaKey2 = 'y'; - $flaValue2 = 'u'; - $flaKey3 = 'my_key'; - $flaValue3 = null; - $flaKey4 = '10'; - $flaValue4 = 55; - - Session::setFlash($flaKey1, $flaValue1); - Session::setFlash($flaKey2, $flaValue2); - - static::assertTrue(Session::hasFlash($flaKey1)); - static::assertEquals($flaValue1, Session::getFlash($flaKey1)); - - Session::start(['lazy_write' => '0']); - Session::keepFlash([$flaKey2]); - - static::assertEquals(['flash_data' => [$flaKey2 => $flaValue2]], $_SESSION); - $sessionId = Session::getId(); - Session::commit(); - - Session::setId($sessionId); - Session::setReadWrite(); - Session::start(); - - static::assertEmpty($_SESSION); - static::assertTrue(Session::hasFlash($flaKey2)); - static::assertEquals($flaValue2, Session::getFlash($flaKey2)); - static::assertTrue(Session::hasFlashKeyAndValue($flaKey2, $flaValue2)); - - Session::setFlash($flaKey3, $flaValue3); - Session::setFlash($flaKey4, $flaValue4); - - Session::keepFlash(); - - Session::commit(); - Session::start(); - - $expectedFlashValues = [$flaKey2 => $flaValue2, $flaKey3 => $flaValue3, $flaKey4 => $flaValue4]; - static::assertEquals($expectedFlashValues, Session::getAllFlash()); - static::assertEmpty($_SESSION); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testRollback(): void - { - Session::set('a', 'b'); - Session::commit(); - - static::assertTrue(Session::has('a')); - Session::set('azerty', 'b'); - static::assertTrue(Session::has('azerty')); - Session::rollback(); - static::assertFalse(Session::has('azerty')); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testUnsaved(): void - { - Session::set('a', 'b'); - Session::commit(); - - static::assertTrue(Session::has('a')); - Session::set('azerty', 'b'); - static::assertTrue(Session::has('azerty')); - Session::unsaved(); - Session::start(); - static::assertEquals(['a' => 'b'], $_SESSION); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testRegenerate(): void - { - Session::set('a', 'v'); - $sessionId = Session::getId(); - $success = Session::regenerate(); - static::assertTrue($success); - static::assertNotEquals($sessionId, Session::getId()); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testDestroy(): void - { - Session::set('a', 'v'); - $sessionId = Session::getId(); - $success = Session::destroy(); - static::assertTrue($success); - static::assertNotEquals($sessionId, Session::getId()); - static::assertEmpty($_SESSION); - } - - /** - * @runInSeparateProcess - * @throws \Exception - */ - public function testSetReadOnly(): void - { - Session::setReadOnly(); - Session::start(); - static::assertFalse(Session::hasStarted()); - } - - /** - * @runInSeparateProcess - * @throws \Rancoud\Database\DatabaseException - * @throws \Exception - */ - public function testGc(): void - { - $conf = new \Rancoud\Database\Configurator([ - 'engine' => 'mysql', - 'host' => '127.0.0.1', - 'user' => 'root', - 'password' => '', - 'database' => 'test_database' - ]); - $db = new \Rancoud\Database\Database($conf); - $db->truncateTables('sessions'); - - Session::useCurrentDatabaseDriver($db); - Session::setReadWrite(); - Session::setOption('lazy_write', '0'); - $baseId = 'siqKrZDUGp5ubt3klF0oorIlFiADXC9jxig9e8leUcCYuZ9w0mXh0b1foEGIBs7SSsdOuLor58vU5liBRVPsTobnvt'; - $endId1 = 'Qj8hh65DlR3tTFI1SGX3mFciDA9rMOa4LlnMr'; - $endId2 = 'jklezfoipvfk0lferijkoefzjklgrvefLlnMr'; - Session::setId($baseId . $endId1); - Session::set('a', 'b'); - Session::commit(); - - Session::setId($baseId . $endId2); - Session::set('b', 'a'); - $sessionId = Session::getId(); - Session::commit(); - - sleep(1); - - $sql = 'update sessions set last_access = DATE_ADD(NOW(), INTERVAL 50000 SECOND) WHERE id = :id'; - $params = ['id' => $sessionId]; - $db->update($sql, $params); - - Session::setOption('gc_maxlifetime', '1'); - - sleep(1); - - Session::gc(); - - $count = $db->count('SELECT COUNT(*) FROM sessions'); - static::assertEquals(1, $count); - } -} +setStaticPropertyValue('hasStarted', false); + + $class->setStaticPropertyValue('driver', null); + + $class->setStaticPropertyValue('hasChanged', true); + + $class->setStaticPropertyValue('options', [ + 'read_and_close' => true, + 'cookie_httponly' => '1', + 'use_only_cookies' => '1', + 'use_trans_sid' => '0', + 'use_strict_mode' => '1' + ]); + + $path = \ini_get('session.save_path'); + if (empty($path)) { + $path = \DIRECTORY_SEPARATOR . 'tmp'; + } + + $pattern = $path . \DIRECTORY_SEPARATOR . 'sess_*'; + foreach (\glob($pattern) as $file) { + if (\file_exists($file)) { + \unlink($file); + } + } + } + + /** @throws \Exception */ + public function testGetNull(): void + { + $value = Session::get('emptykey'); + static::assertNull($value); + } + + /** @throws \Exception */ + public function testSet(): void + { + Session::set('a', 'b'); + $value = Session::get('a'); + static::assertSame('b', $value); + } + + /** @throws \Exception */ + public function testHas(): void + { + Session::set('c', 'd'); + + $value = Session::has('c'); + static::assertTrue($value); + + $value = Session::has('empty'); + static::assertFalse($value); + } + + /** @throws \Exception */ + public function testHasKeyAndValue(): void + { + Session::set('e', 'f'); + + $value = Session::hasKeyAndValue('e', 'f'); + static::assertTrue($value); + + $value = Session::has('empty'); + static::assertFalse($value); + + $value = Session::hasKeyAndValue('e', 'empty'); + static::assertFalse($value); + } + + /** @throws \Exception */ + public function testRemove(): void + { + Session::set('g', 'h'); + + Session::remove('g'); + $value = Session::get('g'); + static::assertNull($value); + + Session::remove('empty'); + $value = Session::get('empty'); + static::assertNull($value); + } + + /** @throws \Exception */ + public function testGetAndRemove(): void + { + Session::set('i', 'j'); + + $value = Session::getAndRemove('i'); + static::assertSame('j', $value); + + $value = Session::getAndRemove('empty'); + static::assertNull($value); + } + + /** @throws \Exception */ + public function testStartException(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('Session already started'); + + Session::setReadWrite(); + Session::start(); + Session::start(); + } + + /** @throws \Exception */ + public function testUseDefaultDriverWhenAlreadyStartedException(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('Session already started'); + + Session::setReadWrite(); + Session::start(); + Session::useDefaultDriver(); + } + + /** @throws \Exception */ + public function testUseFileDriverWhenAlreadyStartedException(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('Session already started'); + + Session::setReadWrite(); + Session::start(); + Session::useFileDriver(); + } + + /** @throws \Exception */ + public function testUseCustomDriverWhenAlreadyStartedException(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('Session already started'); + + Session::setReadWrite(); + Session::start(); + Session::useCustomDriver(new File()); + } + + /** @throws \Exception */ + public function testUseDefaultDriver(): void + { + Session::useDefaultDriver(); + Session::start(); + + static::assertInstanceOf(\SessionHandler::class, Session::getDriver()); + } + + /** @throws \Exception */ + public function testUseDefaultEncryptionDriver(): void + { + Session::useDefaultEncryptionDriver('randomKey'); + Session::start(); + + static::assertInstanceOf(\Rancoud\Session\DefaultEncryption::class, Session::getDriver()); + } + + /** @throws \Exception */ + public function testUseFileDriver(): void + { + Session::useFileDriver(); + Session::start(); + + static::assertInstanceOf(File::class, Session::getDriver()); + } + + /** @throws \Exception */ + public function testUseFileDriverWithPrefix(): void + { + $prefix = 'youhou_'; + Session::useFileDriver(); + Session::setPrefixForFile($prefix); + Session::setReadWrite(); + Session::start(['lazy_write' => '0']); + $sessionId = Session::getId(); + $path = Session::getOption('save_path'); + Session::commit(); + + static::assertInstanceOf(File::class, Session::getDriver()); + static::assertFileExists($path . \DIRECTORY_SEPARATOR . $prefix . $sessionId); + + $pattern = $path . \DIRECTORY_SEPARATOR . 'youhou_*'; + foreach (\glob($pattern) as $file) { + if (\file_exists($file)) { + \unlink($file); + } + } + } + + /** @throws \Exception */ + public function testUseFileEncryptionDriver(): void + { + Session::useFileEncryptionDriver('randomKey'); + Session::start(); + + static::assertInstanceOf(\Rancoud\Session\FileEncryption::class, Session::getDriver()); + } + + /** @throws \Exception */ + public function testUseNewDatabaseDriver(): void + { + $params = [ + 'driver' => 'mysql', + 'user' => 'root', + 'password' => '', + 'database' => 'test_database' + ]; + + $mysqlHost = \getenv('MYSQL_HOST', true); + $params['host'] = ($mysqlHost !== false) ? $mysqlHost : '127.0.0.1'; + + Session::useNewDatabaseDriver($params); + Session::start(); + + static::assertInstanceOf(\Rancoud\Session\Database::class, Session::getDriver()); + } + + /** @throws \Exception */ + public function testUseNewDatabaseEncryptionDriver(): void + { + $params = [ + 'driver' => 'mysql', + 'user' => 'root', + 'password' => '', + 'database' => 'test_database' + ]; + + $mysqlHost = \getenv('MYSQL_HOST', true); + $params['host'] = ($mysqlHost !== false) ? $mysqlHost : '127.0.0.1'; + + Session::useNewDatabaseEncryptionDriver($params, 'randomKey'); + Session::start(); + + static::assertInstanceOf(\Rancoud\Session\DatabaseEncryption::class, Session::getDriver()); + } + + /** + * @throws \Exception + * @throws \Rancoud\Database\DatabaseException + */ + public function testUseCurrentDatabaseDriver(): void + { + $conf = new \Rancoud\Database\Configurator([ + 'driver' => 'mysql', + 'host' => 'mysql', + 'user' => 'root', + 'password' => '', + 'database' => 'test_database' + ]); + + $mysqlHost = \getenv('MYSQL_HOST', true); + $conf->setHost(($mysqlHost !== false) ? $mysqlHost : '127.0.0.1'); + + $db = new \Rancoud\Database\Database($conf); + Session::useCurrentDatabaseDriver($db); + Session::start(); + + static::assertInstanceOf(\Rancoud\Session\Database::class, Session::getDriver()); + } + + /** + * @throws \Exception + * @throws \Rancoud\Database\DatabaseException + */ + public function testUseCurrentDatabaseEncryptionDriver(): void + { + $userId = 50; + $conf = new \Rancoud\Database\Configurator([ + 'driver' => 'mysql', + 'host' => 'mysql', + 'user' => 'root', + 'password' => '', + 'database' => 'test_database' + ]); + + $mysqlHost = \getenv('MYSQL_HOST', true); + $conf->setHost(($mysqlHost !== false) ? $mysqlHost : '127.0.0.1'); + + $db = new \Rancoud\Database\Database($conf); + Session::useCurrentDatabaseEncryptionDriver($db, 'randomKey'); + static::assertInstanceOf(\Rancoud\Session\DatabaseEncryption::class, Session::getDriver()); + + Session::setUserIdForDatabase($userId); + Session::setOption('lazy_write', '0'); + Session::set('a', 'b'); + $sessionId = Session::getId(); + Session::commit(); + + $sql = 'REPLACE INTO sessions VALUES(:id, null, UTC_TIMESTAMP(), :content)'; + $params = ['id' => 'session_123', 'content' => 'content']; + $db->insert($sql, $params); + + static::assertSame(1, $db->count('SELECT COUNT(id) FROM sessions WHERE id_user IS NULL')); + static::assertSame(1, $db->count('SELECT COUNT(id) FROM sessions WHERE id_user IS NOT NULL')); + + $userIdInTable = (int) $db->selectVar('SELECT id_user FROM sessions WHERE id = :id', ['id' => $sessionId]); + static::assertSame($userId, $userIdInTable); + + Session::deleteAnonymousSessionsInDatabase(); + + static::assertEmpty($db->count('SELECT COUNT(id) FROM sessions WHERE id_user IS NULL')); + static::assertSame(1, $db->count('SELECT COUNT(id) FROM sessions WHERE id_user IS NOT NULL')); + + Session::deleteUserSessionsInDatabase($userId); + + static::assertEmpty($db->count('SELECT COUNT(id) FROM sessions WHERE id = :id', ['id' => $sessionId])); + } + + /** @throws \Exception */ + public function testUseNewRedisDriver(): void + { + $params = [ + 'scheme' => 'tcp', + 'port' => 6379, + ]; + + $redisHost = \getenv('REDIS_HOST', true); + $params['host'] = ($redisHost !== false) ? $redisHost : '127.0.0.1'; + + Session::useNewRedisDriver($params); + Session::start(); + + static::assertInstanceOf(\Rancoud\Session\Redis::class, Session::getDriver()); + } + + /** @throws \Exception */ + public function testUseNewRedisEncryptionDriver(): void + { + $params = [ + 'scheme' => 'tcp', + 'port' => 6379, + ]; + + $redisHost = \getenv('REDIS_HOST', true); + $params['host'] = ($redisHost !== false) ? $redisHost : '127.0.0.1'; + + Session::useNewRedisEncryptionDriver($params, 'randomKey'); + Session::start(); + + static::assertInstanceOf(\Rancoud\Session\RedisEncryption::class, Session::getDriver()); + } + + /** @throws \Exception */ + public function testUseCurrentRedisDriver(): void + { + $params = [ + 'scheme' => 'tcp', + 'port' => 6379, + ]; + + $redisHost = \getenv('REDIS_HOST', true); + $params['host'] = ($redisHost !== false) ? $redisHost : '127.0.0.1'; + + $redis = new \Predis\Client($params); + Session::useCurrentRedisDriver($redis); + Session::start(); + + static::assertInstanceOf(\Rancoud\Session\Redis::class, Session::getDriver()); + } + + /** @throws \Exception */ + public function testUseCurrentRedisEncryptionDriver(): void + { + $params = [ + 'scheme' => 'tcp', + 'port' => 6379, + ]; + + $redisHost = \getenv('REDIS_HOST', true); + $params['host'] = ($redisHost !== false) ? $redisHost : '127.0.0.1'; + + $redis = new \Predis\Client($params); + Session::useCurrentRedisEncryptionDriver($redis, 'randomKey'); + Session::start(); + + static::assertInstanceOf(\Rancoud\Session\RedisEncryption::class, Session::getDriver()); + } + + /** @throws \Exception */ + public function testUseCustomDriver(): void + { + Session::useCustomDriver(new File()); + Session::start(); + + static::assertInstanceOf(File::class, Session::getDriver()); + } + + /** @throws \Exception */ + public function testUseEncryptionDriverThrowExceptionWhenMethodIncrorrect(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('Unknown method: incorrect'); + + Session::useFileEncryptionDriver('randomKey', 'incorrect'); + } + + /** + * @throws \Exception + * @throws SessionException + */ + public function testSetOption(): void + { + $defaultOption = Session::getOption('name'); + static::assertSame($defaultOption, \ini_get('session.name')); + + Session::setOption('name', 'my_custom_name'); + $customOption = Session::getOption('name'); + + static::assertSame('my_custom_name', $customOption); + + Session::start(['name' => 'my_other_name']); + + $customOption = Session::getOption('name'); + static::assertSame('my_other_name', $customOption); + } + + public function testSetOptionThrowException(): void + { + $this->expectException(SessionException::class); + $this->expectExceptionMessage('Incorrect option: azerty'); + + Session::getOption('azerty'); + } + + /** @throws \Exception */ + public function testGetAll(): void + { + $sessionValues = Session::getAll(); + static::assertEmpty($sessionValues); + + Session::set('a', 'b'); + + $sessionValues = Session::getAll(); + static::assertSame(['a' => 'b'], $sessionValues); + } + + /** @throws \Exception */ + public function testFlash(): void + { + $flaKey1 = 'a'; + $flaValue1 = 'b'; + + $flaKey2 = 'y'; + $flaValue2 = 'u'; + + $flaKey3 = 'my_key'; + $flaValue3 = null; + + $flaKey4 = '10'; + $flaValue4 = 55; + + Session::setFlash($flaKey1, $flaValue1); + Session::setFlash($flaKey2, $flaValue2); + + static::assertTrue(Session::hasFlash($flaKey1)); + static::assertSame($flaValue1, Session::getFlash($flaKey1)); + static::assertSame([$flaKey1 => $flaValue1, $flaKey2 => $flaValue2], Session::getAllFlash()); + + Session::start(['lazy_write' => '0']); + + Session::keepFlash([$flaKey2]); + static::assertSame(['flash_data' => [$flaKey2 => $flaValue2]], $_SESSION); + + $sessionId = Session::getId(); + Session::commit(); + + static::assertSame([], Session::getAllFlash()); + + Session::setId($sessionId); + Session::setReadWrite(); + Session::start(); + + static::assertEmpty($_SESSION); + static::assertTrue(Session::hasFlash($flaKey2)); + static::assertSame($flaValue2, Session::getFlash($flaKey2)); + static::assertTrue(Session::hasFlashKeyAndValue($flaKey2, $flaValue2)); + + Session::setFlash($flaKey3, $flaValue3); + Session::setFlash($flaKey4, $flaValue4); + + Session::keepFlash(); + + Session::commit(); + + static::assertSame([], Session::getAllFlash()); + + Session::start(); + + $expectedFlashValues = [$flaKey2 => $flaValue2, $flaKey3 => $flaValue3, $flaKey4 => $flaValue4]; + static::assertSame($expectedFlashValues, Session::getAllFlash()); + static::assertEmpty($_SESSION); + + Session::removeFlash($flaKey2); + Session::removeFlash($flaKey3); + static::assertSame([$flaKey4 => $flaValue4], Session::getAllFlash()); + } + + /** @throws \Exception */ + public function testRollback(): void + { + Session::set('a', 'b'); + Session::commit(); + + static::assertTrue(Session::has('a')); + Session::set('azerty', 'b'); + static::assertTrue(Session::has('azerty')); + Session::rollback(); + static::assertFalse(Session::has('azerty')); + } + + /** @throws \Exception */ + public function testUnsaved(): void + { + Session::set('a', 'b'); + Session::commit(); + + static::assertTrue(Session::has('a')); + Session::set('azerty', 'b'); + static::assertTrue(Session::has('azerty')); + Session::unsaved(); + Session::start(); + static::assertSame(['a' => 'b'], $_SESSION); + } + + /** @throws \Exception */ + public function testRegenerate(): void + { + Session::set('a', 'v'); + $sessionId = Session::getId(); + $success = Session::regenerate(); + static::assertTrue($success); + static::assertNotSame($sessionId, Session::getId()); + } + + /** @throws \Exception */ + public function testDestroy(): void + { + Session::set('a', 'v'); + $sessionId = Session::getId(); + $success = Session::destroy(); + static::assertTrue($success); + static::assertNotSame($sessionId, Session::getId()); + static::assertEmpty($_SESSION); + } + + /** @throws \Exception */ + public function testSetReadOnly(): void + { + Session::setReadOnly(); + Session::start(); + static::assertFalse(Session::hasStarted()); + } + + /** + * @throws \Exception + * @throws \Rancoud\Database\DatabaseException + */ + public function testGc(): void + { + $conf = new \Rancoud\Database\Configurator([ + 'driver' => 'mysql', + 'host' => 'mysql', + 'user' => 'root', + 'password' => '', + 'database' => 'test_database' + ]); + + $mysqlHost = \getenv('MYSQL_HOST', true); + $conf->setHost(($mysqlHost !== false) ? $mysqlHost : '127.0.0.1'); + + $db = new \Rancoud\Database\Database($conf); + $db->truncateTables('sessions'); + + Session::useCurrentDatabaseDriver($db); + Session::setReadWrite(); + Session::setOption('lazy_write', '0'); + $baseId = 'siqKrZDUGp5ubt3klF0oorIlFiADXC9jxig9e8leUcCYuZ9w0mXh0b1foEGIBs7SSsdOuLor58vU5liBRVPsTobnvt'; + $endId1 = 'Qj8hh65DlR3tTFI1SGX3mFciDA9rMOa4LlnMr'; + $endId2 = 'jklezfoipvfk0lferijkoefzjklgrvefLlnMr'; + Session::setId($baseId . $endId1); + Session::set('a', 'b'); + Session::commit(); + + Session::setId($baseId . $endId2); + Session::set('b', 'a'); + $sessionId = Session::getId(); + Session::commit(); + + \sleep(1); + + $sql = 'UPDATE sessions SET last_access = DATE_ADD(NOW(), INTERVAL 50000 SECOND) WHERE id = :id'; + $params = ['id' => $sessionId]; + $db->update($sql, $params); + + Session::setOption('gc_maxlifetime', '1'); + + \sleep(1); + + Session::gc(); + + $count = $db->count('SELECT COUNT(*) FROM sessions'); + static::assertSame(1, $count); + } +}