From 83599365a5d221dca498dbf6da37367a7441be79 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Mon, 26 Jun 2023 00:53:31 -0500 Subject: [PATCH 1/2] Update Test Suite --- .circleci/config.yml | 147 ----- .github/workflows/php_composer.yml | 36 ++ .github/workflows/php_pest.yml | 35 ++ .github/workflows/php_phpcsf.yml | 29 + .github/workflows/php_phpstan.yml | 29 + .github/workflows/php_psalm.yml | 29 + .github/workflows/php_rector.yml | 29 + .github/workflows/repo_cron_snyk.yml | 36 ++ .github/workflows/repo_pr_await_approval.yml | 20 + .github/workflows/repo_pr_await_changes.yml | 17 + .github/workflows/repo_pr_label_approval.yml | 29 + .../repo_pr_label_approval_revoke.yml | 31 ++ .github/workflows/sec_snyk.yml | 44 ++ app/.dockerignore | 3 - app/.php-cs-fixer.dist.php | 231 ++++++++ app/.phpcs.xml.dist | 13 + app/composer.json | 62 ++- app/docker-compose.yml | 33 -- app/phpinsights.php | 67 --- app/phpstan.neon.dist | 26 +- app/phpunit.xml | 17 + app/phpunit.xml.dist | 26 + app/psalm.xml.dist | 28 + app/rector.php | 517 ++++++++++++++++++ 24 files changed, 1241 insertions(+), 293 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .github/workflows/php_composer.yml create mode 100644 .github/workflows/php_pest.yml create mode 100644 .github/workflows/php_phpcsf.yml create mode 100644 .github/workflows/php_phpstan.yml create mode 100644 .github/workflows/php_psalm.yml create mode 100644 .github/workflows/php_rector.yml create mode 100644 .github/workflows/repo_cron_snyk.yml create mode 100644 .github/workflows/repo_pr_await_approval.yml create mode 100644 .github/workflows/repo_pr_await_changes.yml create mode 100644 .github/workflows/repo_pr_label_approval.yml create mode 100644 .github/workflows/repo_pr_label_approval_revoke.yml create mode 100644 .github/workflows/sec_snyk.yml delete mode 100644 app/.dockerignore create mode 100644 app/.php-cs-fixer.dist.php create mode 100644 app/.phpcs.xml.dist delete mode 100644 app/docker-compose.yml delete mode 100644 app/phpinsights.php create mode 100644 app/phpunit.xml create mode 100644 app/phpunit.xml.dist create mode 100644 app/psalm.xml.dist create mode 100644 app/rector.php diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 1ff54ef..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,147 +0,0 @@ -version: 2.1 - -commands: - setup: - steps: - - run: - name: Configure environment - command: | - apk update - - - checkout - - - run: - working_directory: app - name: Install dependencies - command: | - EXPECTED_CHECKSUM="$(php -r 'copy("https://composer.github.io/installer.sig", "php://stdout");')" - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" - ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" - - if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]; then - >&2 echo 'ERROR: Invalid installer checksum' - rm composer-setup.php - exit 1 - fi - - php composer-setup.php --quiet - RESULT=$? - rm composer-setup.php - - php composer.phar install --no-interaction --prefer-dist - - setup-pcov: - steps: - - run: - name: Configure pcov - command: | - apk add autoconf gcc make musl-dev - pecl install pcov - docker-php-ext-enable pcov - - run-phpinsights: - steps: - - run: - working_directory: app - name: Run code quality analysis (PHP Insights) - command: php -d memory-limit=-1 ./vendor/bin/phpinsights -v --no-interaction --min-quality=100 --min-complexity=50 --min-architecture=100 --min-style=100 - - run-phpstan: - steps: - - run: - working_directory: app - name: Run static code analysis (PHPStan) - command: php -d memory_limit=-1 ./vendor/bin/phpstan analyse --ansi - - run-pest: - steps: - - run: - working_directory: app - name: Run unit tests (Pest) - command: php -d memory_limit=-1 ./vendor/bin/pest --stop-on-failure - - run-snyk: - steps: - - run: - working_directory: app - name: Run vulnerabilities tests (Snyk) - command: | - apk add npm - npm install -g snyk - snyk test --dev - -jobs: - phpinsights: - parameters: - php: - type: string - docker: - - image: php:<< parameters.php >>-cli-alpine - steps: - - setup - - run-phpinsights - - phpstan: - parameters: - php: - type: string - docker: - - image: php:<< parameters.php >>-cli-alpine - steps: - - setup - - run-phpstan - - pest: - parameters: - php: - type: string - docker: - - image: php:<< parameters.php >>-cli-alpine - steps: - - setup - - setup-pcov - - run-pest - - snyk: - parameters: - php: - type: string - docker: - - image: php:<< parameters.php >>-cli-alpine - steps: - - setup - - run-snyk - - semgrep: - docker: - - image: returntocorp/semgrep-agent:v1 - environment: - SEMGREP_REPO_NAME: "auth0-samples/auth0-php-api-samples" - SEMGREP_REPO_URL: "https://github.com/auth0-samples/auth0-php-api-samples" - steps: - - checkout - - run: - name: Run vulnerabilities tests (Semgrep) - command: | - semgrep-agent --baseline-ref main --publish-token $SEMGREP_TOKEN - -workflows: - test: - jobs: - - phpinsights: - matrix: - parameters: - php: ["7.4", "8.0", "8.1"] - - phpstan: - matrix: - parameters: - php: ["7.4", "8.0", "8.1"] - - semgrep: - context: - - semgrep-env - - snyk: - matrix: - parameters: - php: ["8.0", "8.1"] - context: - - snyk-env diff --git a/.github/workflows/php_composer.yml b/.github/workflows/php_composer.yml new file mode 100644 index 0000000..34457b0 --- /dev/null +++ b/.github/workflows/php_composer.yml @@ -0,0 +1,36 @@ +name: "Composer" + +on: + pull_request: + merge_group: + push: + branches: ["master", "main"] + +permissions: {} + +defaults: + run: + working-directory: app + +jobs: + validate: + name: "Validate" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - run: composer validate + + normalize: + name: "Normalize" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - run: composer require --dev ergebnis/composer-normalize + + - run: composer config allow-plugins.ergebnis/composer-normalize true + + - run: composer normalize diff --git a/.github/workflows/php_pest.yml b/.github/workflows/php_pest.yml new file mode 100644 index 0000000..f649061 --- /dev/null +++ b/.github/workflows/php_pest.yml @@ -0,0 +1,35 @@ +name: "PEST" + +on: + pull_request: + merge_group: + push: + branches: ["master", "main"] + +permissions: {} + +defaults: + run: + working-directory: app + +jobs: + pest: + name: "Scan" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + coverage: pcov + + - run: composer install --no-progress + + - run: vendor/bin/pest --order-by random --fail-on-risky --stop-on-defect --coverage --parallel + + - uses: codecov/codecov-action@v3 + with: + directory: ./coverage/ + flags: unittests diff --git a/.github/workflows/php_phpcsf.yml b/.github/workflows/php_phpcsf.yml new file mode 100644 index 0000000..7915405 --- /dev/null +++ b/.github/workflows/php_phpcsf.yml @@ -0,0 +1,29 @@ +name: "PHP CS Fixer" + +on: + pull_request: + merge_group: + push: + branches: ["master", "main"] + +permissions: {} + +defaults: + run: + working-directory: app + +jobs: + phpcsf: + name: "Scan" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - run: composer install --no-progress + + - run: vendor/bin/php-cs-fixer fix src --dry-run --diff diff --git a/.github/workflows/php_phpstan.yml b/.github/workflows/php_phpstan.yml new file mode 100644 index 0000000..f8bb2db --- /dev/null +++ b/.github/workflows/php_phpstan.yml @@ -0,0 +1,29 @@ +name: "PHPStan" + +on: + pull_request: + merge_group: + push: + branches: ["master", "main"] + +permissions: {} + +defaults: + run: + working-directory: app + +jobs: + phpstan: + name: "Scan" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - run: composer install --no-progress + + # - run: vendor/bin/phpstan analyze --no-ansi --no-progress --debug diff --git a/.github/workflows/php_psalm.yml b/.github/workflows/php_psalm.yml new file mode 100644 index 0000000..3b2a1ba --- /dev/null +++ b/.github/workflows/php_psalm.yml @@ -0,0 +1,29 @@ +name: "Psalm" + +on: + pull_request: + merge_group: + push: + branches: ["master", "main"] + +permissions: {} + +defaults: + run: + working-directory: app + +jobs: + psalm: + name: "Scan" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - run: composer install --no-progress + + # - run: vendor/bin/psalm diff --git a/.github/workflows/php_rector.yml b/.github/workflows/php_rector.yml new file mode 100644 index 0000000..5cf3821 --- /dev/null +++ b/.github/workflows/php_rector.yml @@ -0,0 +1,29 @@ +name: "Rector" + +on: + pull_request: + merge_group: + push: + branches: ["master", "main"] + +permissions: {} + +defaults: + run: + working-directory: app + +jobs: + rector: + name: "Scan" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - run: composer install --no-progress + + - run: vendor/bin/rector process --dry-run diff --git a/.github/workflows/repo_cron_snyk.yml b/.github/workflows/repo_cron_snyk.yml new file mode 100644 index 0000000..8396351 --- /dev/null +++ b/.github/workflows/repo_cron_snyk.yml @@ -0,0 +1,36 @@ +name: "Snyk (Scheduled)" + +# This workflow will run after a push to the main branch and as a scheduled job. + +on: + push: + branches: ["master", "main"] + +permissions: {} + +defaults: + run: + working-directory: app + +jobs: + snyk: + name: "Scan" + runs-on: ubuntu-latest + + steps: + - uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + coverage: none + extensions: mbstring + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/checkout@v3 + + - run: composer install --no-progress + + - uses: snyk/actions/php@master + continue-on-error: true + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} diff --git a/.github/workflows/repo_pr_await_approval.yml b/.github/workflows/repo_pr_await_approval.yml new file mode 100644 index 0000000..fed0fd1 --- /dev/null +++ b/.github/workflows/repo_pr_await_approval.yml @@ -0,0 +1,20 @@ +name: "Pull Request Approval" + +# Monitor for pull requests being approved. + +on: + pull_request: + types: [synchronize] + pull_request_review: + types: [submitted] + +permissions: {} + +jobs: + wait: + name: "Waiting" + runs-on: ubuntu-latest + + if: github.actor == 'dependabot[bot]' || github.event.review.state == 'approved' + steps: + - run: echo "PR approved." diff --git a/.github/workflows/repo_pr_await_changes.yml b/.github/workflows/repo_pr_await_changes.yml new file mode 100644 index 0000000..37408b9 --- /dev/null +++ b/.github/workflows/repo_pr_await_changes.yml @@ -0,0 +1,17 @@ +name: "Pull Request Changes" + +# Monitor for changes to pull requests. + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: {} + +jobs: + wait: + name: "Watching" + runs-on: ubuntu-latest + + steps: + - run: echo "Changes detected." diff --git a/.github/workflows/repo_pr_label_approval.yml b/.github/workflows/repo_pr_label_approval.yml new file mode 100644 index 0000000..b8672ea --- /dev/null +++ b/.github/workflows/repo_pr_label_approval.yml @@ -0,0 +1,29 @@ +name: "Pull Request Labels (Apply Approval)" + +# This workflow will run after a pull request has been approved, and is +# triggered as a result of the "Pull Request Changes" workflow succeeding. +# By being executed in this manner, the workflow will have access to the +# environment variables of the origin repository, allowing builds to +# successfully run for forked pull requests without exposing secrets. + +on: + workflow_run: + workflows: ["Pull Request Approval"] + types: + - completed + +permissions: + # We are modifying labels, and need write access to do so. + pull-requests: write + +jobs: + wait: + name: "Applying" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - uses: actions-ecosystem/action-add-labels@v1 + with: + labels: Approved diff --git a/.github/workflows/repo_pr_label_approval_revoke.yml b/.github/workflows/repo_pr_label_approval_revoke.yml new file mode 100644 index 0000000..0caca73 --- /dev/null +++ b/.github/workflows/repo_pr_label_approval_revoke.yml @@ -0,0 +1,31 @@ +name: "Pull Request Labels (Revoke Approval)" + +# This workflow will run after a pull request has been approved, and is +# triggered as a result of the "Pull Request Changes" workflow succeeding. +# By being executed in this manner, the workflow will have access to the +# environment variables of the origin repository, allowing builds to +# successfully run for forked pull requests without exposing secrets. + +on: + workflow_run: + workflows: ["Pull Request Changes"] + types: + - completed + +permissions: + # We are modifying labels, and need write access to do so. + pull-requests: write + +jobs: + wait: + name: "Revoking" + runs-on: ubuntu-latest + + if: contains(github.event.issue.labels.*.name, 'Approved') + + steps: + - uses: actions/checkout@v2 + + - uses: actions-ecosystem/action-remove-labels@v1 + with: + labels: Approved diff --git a/.github/workflows/sec_snyk.yml b/.github/workflows/sec_snyk.yml new file mode 100644 index 0000000..033e2bd --- /dev/null +++ b/.github/workflows/sec_snyk.yml @@ -0,0 +1,44 @@ +name: "Snyk" + +# This workflow will run after a pull request has been approved, and is +# triggered as a result of the "Pull Request Approval" workflow succeeding. +# By being executed in this manner, the workflow will have access to the +# environment variables of the origin repository, allowing builds to +# successfully run for forked pull requests without exposing secrets. + +on: + workflow_run: + workflows: ["Pull Request Approval"] + types: + - completed + +permissions: {} + +defaults: + run: + working-directory: app + +jobs: + snyk: + name: "Scan" + runs-on: ubuntu-latest + + if: github.event.workflow_run.conclusion == 'success' && contains(github.event.issue.labels.*.name, 'Approved') + + steps: + - uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + coverage: none + extensions: mbstring + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/checkout@v3 + + - run: composer install --no-progress + + - uses: snyk/actions/php@master + continue-on-error: true + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} diff --git a/app/.dockerignore b/app/.dockerignore deleted file mode 100644 index 137a061..0000000 --- a/app/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -.composer.lock -.env.example diff --git a/app/.php-cs-fixer.dist.php b/app/.php-cs-fixer.dist.php new file mode 100644 index 0000000..54f9af5 --- /dev/null +++ b/app/.php-cs-fixer.dist.php @@ -0,0 +1,231 @@ +setRiskyAllowed(true) + ->setRules([ + 'array_indentation' => true, + 'array_push' => true, + 'array_syntax' => ['syntax' => 'short'], + 'assign_null_coalescing_to_coalesce_equal' => true, + 'backtick_to_shell_exec' => true, + 'binary_operator_spaces' => true, + 'blank_line_after_namespace' => true, + 'blank_line_after_opening_tag' => true, + 'blank_line_before_statement' => true, + 'blank_line_between_import_groups' => true, + 'braces' => true, + 'cast_spaces' => true, + 'class_attributes_separation' => ['elements' => ['const' => 'one', 'method' => 'one', 'property' => 'one', 'trait_import' => 'one', 'case' => 'one']], + 'class_definition' => ['multi_line_extends_each_single_line' => true, 'single_line' => true, 'single_item_single_line' => true, 'space_before_parenthesis' => false, 'inline_constructor_arguments' => false], + 'class_reference_name_casing' => true, + 'clean_namespace' => true, + 'combine_consecutive_issets' => true, + 'combine_consecutive_unsets' => true, + 'combine_nested_dirname' => true, + 'comment_to_phpdoc' => ['ignored_tags' => ['codeCoverageIgnoreStart', 'codeCoverageIgnoreEnd', 'phpstan-ignore-next-line']], + 'compact_nullable_typehint' => true, + 'concat_space' => ['spacing' => 'one'], + 'constant_case' => ['case' => 'lower'], + 'curly_braces_position' => ['control_structures_opening_brace' => 'same_line', 'functions_opening_brace' => 'next_line_unless_newline_at_signature_end', 'anonymous_functions_opening_brace' => 'same_line', 'classes_opening_brace' => 'next_line_unless_newline_at_signature_end', 'anonymous_classes_opening_brace' => 'same_line', 'allow_single_line_empty_anonymous_classes' => true, 'allow_single_line_anonymous_functions' => true], + 'date_time_create_from_format_call' => true, + 'date_time_immutable' => true, + 'declare_equal_normalize' => ['space' => 'none'], + 'declare_parentheses' => true, + 'declare_strict_types' => true, + 'dir_constant' => true, + 'doctrine_annotation_array_assignment' => true, + 'doctrine_annotation_braces' => true, + 'doctrine_annotation_indentation' => true, + 'doctrine_annotation_spaces' => true, + 'echo_tag_syntax' => ['format' => 'long'], + 'elseif' => true, + 'empty_loop_body' => true, + 'empty_loop_condition' => true, + 'encoding' => true, + 'ereg_to_preg' => true, + 'error_suppression' => true, + 'escape_implicit_backslashes' => true, + 'explicit_indirect_variable' => true, + 'explicit_string_variable' => true, + 'final_class' => true, + 'final_internal_class' => true, + 'final_public_method_for_abstract_class' => true, + 'fopen_flag_order' => true, + 'fopen_flags' => true, + 'full_opening_tag' => true, + 'fully_qualified_strict_types' => true, + 'function_declaration' => true, + 'function_to_constant' => true, + 'function_typehint_space' => true, + 'general_phpdoc_annotation_remove' => true, + 'general_phpdoc_tag_rename' => true, + 'get_class_to_class_keyword' => true, + 'global_namespace_import' => ['import_classes' => true, 'import_constants' => true, 'import_functions' => true], + 'group_import' => true, + 'heredoc_indentation' => true, + 'heredoc_to_nowdoc' => true, + 'implode_call' => true, + 'include' => true, + 'increment_style' => ['style' => 'pre'], + 'indentation_type' => true, + 'integer_literal_case' => true, + 'is_null' => true, + 'lambda_not_used_import' => true, + 'line_ending' => true, + 'linebreak_after_opening_tag' => true, + 'list_syntax' => ['syntax' => 'short'], + 'logical_operators' => true, + 'lowercase_cast' => true, + 'lowercase_keywords' => true, + 'lowercase_static_reference' => true, + 'magic_constant_casing' => true, + 'magic_method_casing' => true, + 'mb_str_functions' => false, + 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline', 'after_heredoc' => true], + 'method_chaining_indentation' => true, + 'modernize_strpos' => true, + 'modernize_types_casting' => true, + 'multiline_comment_opening_closing' => true, + 'multiline_whitespace_before_semicolons' => true, + 'native_function_casing' => true, + 'native_function_invocation' => true, + 'native_function_type_declaration_casing' => true, + 'new_with_braces' => true, + 'no_alias_functions' => true, + 'no_alias_language_construct_call' => true, + 'no_alternative_syntax' => true, + 'no_binary_string' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_break_comment' => true, + 'no_closing_tag' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => true, + 'no_homoglyph_names' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => true, + 'no_multiline_whitespace_around_double_arrow' => true, + 'no_multiple_statements_per_line' => true, + 'no_php4_constructor' => true, + 'no_short_bool_cast' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_space_around_double_colon' => true, + 'no_spaces_after_function_name' => true, + 'no_spaces_around_offset' => true, + 'no_spaces_inside_parenthesis' => true, + 'no_superfluous_elseif' => true, + 'no_trailing_comma_in_singleline' => true, + 'no_trailing_whitespace_in_comment' => true, + 'no_trailing_whitespace_in_string' => true, + 'no_trailing_whitespace' => true, + 'no_unneeded_control_parentheses' => true, + 'no_unneeded_curly_braces' => true, + 'no_unneeded_final_method' => true, + 'no_unneeded_import_alias' => true, + 'no_unreachable_default_argument_value' => true, + 'no_unset_cast' => true, + 'no_unused_imports' => true, + 'no_useless_concat_operator' => true, + 'no_useless_else' => true, + 'no_useless_nullsafe_operator' => true, + 'no_useless_return' => true, + 'no_useless_sprintf' => true, + 'no_whitespace_before_comma_in_array' => true, + 'no_whitespace_in_blank_line' => true, + 'non_printable_character' => true, + 'normalize_index_brace' => true, + 'not_operator_with_successor_space' => true, + 'nullable_type_declaration_for_default_null_value' => true, + 'object_operator_without_whitespace' => true, + 'octal_notation' => true, + 'operator_linebreak' => true, + 'ordered_class_elements' => ['sort_algorithm' => 'alpha', 'order' => ['use_trait', 'case', 'constant', 'constant_private', 'constant_protected', 'constant_public', 'property_private', 'property_private_readonly', 'property_private_static', 'property_protected', 'property_protected_readonly', 'property_protected_static', 'property_public', 'property_public_readonly', 'property_public_static', 'property_static', 'protected', 'construct', 'destruct', 'magic', 'method', 'public', 'method_public', 'method_abstract', 'method_public_abstract', 'method_public_abstract_static', 'method_public_static', 'method_static', 'method_private', 'method_private_abstract', 'method_private_abstract_static', 'method_private_static', 'method_protected', 'method_protected_abstract', 'method_protected_abstract_static', 'method_protected_static', 'phpunit', 'private', 'property']], + 'ordered_imports' => ['sort_algorithm' => 'alpha', 'imports_order' => ['const', 'class', 'function']], + 'ordered_interfaces' => true, + 'ordered_traits' => true, + 'php_unit_fqcn_annotation' => true, + 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false], + 'phpdoc_align' => ['align' => 'vertical'], + 'phpdoc_indent' => true, + 'phpdoc_inline_tag_normalizer' => true, + 'phpdoc_line_span' => true, + 'phpdoc_no_access' => true, + 'phpdoc_no_empty_return' => true, + 'phpdoc_no_package' => true, + 'phpdoc_no_useless_inheritdoc' => true, + 'phpdoc_order_by_value' => true, + 'phpdoc_order' => true, + 'phpdoc_return_self_reference' => ['replacements' => ['this' => 'self']], + 'phpdoc_scalar' => true, + 'phpdoc_separation' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_summary' => true, + 'phpdoc_tag_type' => true, + 'phpdoc_to_comment' => ['ignored_tags' => ['var']], + 'phpdoc_trim_consecutive_blank_line_separation' => true, + 'phpdoc_trim' => true, + 'phpdoc_types_order' => true, + 'phpdoc_types' => true, + 'phpdoc_var_annotation_correct_order' => true, + 'phpdoc_var_without_name' => true, + 'pow_to_exponentiation' => true, + 'protected_to_private' => true, + 'psr_autoloading' => true, + 'random_api_migration' => true, + 'regular_callable_call' => true, + 'return_assignment' => true, + 'return_type_declaration' => ['space_before' => 'none'], + 'return_type_declaration' => true, + 'self_accessor' => true, + 'self_static_accessor' => true, + 'semicolon_after_instruction' => true, + 'set_type_to_cast' => true, + 'short_scalar_cast' => true, + 'simple_to_complex_string_variable' => true, + 'simplified_if_return' => true, + 'single_blank_line_at_eof' => true, + 'single_blank_line_before_namespace' => true, + 'single_class_element_per_statement' => true, + 'single_line_after_imports' => true, + 'single_line_comment_spacing' => true, + 'single_line_comment_style' => ['comment_types' => ['hash']], + 'single_line_throw' => true, + 'single_quote' => true, + 'single_space_after_construct' => true, + 'single_space_around_construct' => true, + 'single_trait_insert_per_statement' => true, + 'space_after_semicolon' => true, + 'standardize_increment' => true, + 'standardize_not_equals' => true, + 'statement_indentation' => true, + 'static_lambda' => true, + 'strict_comparison' => true, + 'strict_param' => true, + 'string_length_to_empty' => true, + 'string_line_ending' => true, + 'switch_case_semicolon_to_colon' => true, + 'switch_case_space' => true, + 'switch_continue_to_break' => true, + 'ternary_operator_spaces' => true, + 'ternary_to_elvis_operator' => true, + 'ternary_to_null_coalescing' => true, + 'trailing_comma_in_multiline' => ['after_heredoc' => true, 'elements' => ['arguments', 'arrays', 'match', 'parameters']], + 'trim_array_spaces' => true, + 'types_spaces' => ['space' => 'single', 'space_multiple_catch' => 'single'], + 'unary_operator_spaces' => true, + 'use_arrow_functions' => true, + 'visibility_required' => true, + 'void_return' => true, + 'whitespace_after_comma_in_array' => true, + 'yoda_style' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->exclude('vendor') + ->in([__DIR__ . '/src/']), + ); diff --git a/app/.phpcs.xml.dist b/app/.phpcs.xml.dist new file mode 100644 index 0000000..993f321 --- /dev/null +++ b/app/.phpcs.xml.dist @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/app/composer.json b/app/composer.json index 64923c0..d2cbeb8 100644 --- a/app/composer.json +++ b/app/composer.json @@ -2,46 +2,62 @@ "name": "auth0-samples/auth0-php-web-app", "description": "Auth0 Integration Samples for PHP Web Applications.", "require": { - "php": "^7.4 | ^8.0", + "php": "^8.0", "auth0/auth0-php": "^8.0", "guzzlehttp/guzzle": "^7.3", "guzzlehttp/psr7": "^2.2", "http-interop/http-factory-guzzle": "^1.0", + "hyperf/event": "^2.1", "php-http/httplug": "^2.2", "vlucas/phpdotenv": "^5.3" }, "require-dev": { - "nunomaduro/phpinsights": "^2.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12" + "friendsofphp/php-cs-fixer": "^3", + "mockery/mockery": "^1", + "nunomaduro/larastan": "^2", + "orchestra/testbench": "^7 || ^8", + "pestphp/pest": "^2", + "pestphp/pest-plugin-laravel": "^2", + "phpstan/phpstan": "^1", + "phpstan/phpstan-strict-rules": "^1", + "psalm/plugin-laravel": "^2", + "rector/rector": "0.17.0", + "spatie/laravel-ray": "^1", + "squizlabs/php_codesniffer": "^3", + "symfony/cache": "^6", + "vimeo/psalm": "^5", + "wikimedia/composer-merge-plugin": "^2" }, "config": { "optimize-autoloader": true, "sort-packages": true, "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true + "dealerdirect/phpcodesniffer-composer-installer": true, + "friendsofphp/well-known-implementations": false, + "wikimedia/composer-merge-plugin": true, + "php-http/discovery": false, + "pestphp/pest-plugin": true } }, "scripts": { - "app": [ + "pest:coverage": "@php vendor/bin/pest --coverage --parallel --no-progress", + "pest:debug": "@php vendor/bin/pest --fail-on-risky --stop-on-defect", + "pest:profile": "@php vendor/bin/pest --profile", + "pest": "@php vendor/bin/pest --order-by random --fail-on-risky --stop-on-defect --coverage --parallel", + "phpcs:fix": "@php vendor/bin/php-cs-fixer fix", + "phpcs": "@php vendor/bin/php-cs-fixer fix --dry-run --diff", + "phpstan": "@php vendor/bin/phpstan analyze", + "psalm:fix": "@php vendor/bin/psalter --issues=all", + "psalm": "@php vendor/bin/psalm", + "rector:fix": "@php vendor/bin/rector process src", + "rector": "@php vendor/bin/rector process src --dry-run", + "test": [ "Composer\\Config::disableProcessTimeout", - "@pre-run-app", - "composer install --no-dev", - "php -S 127.0.0.1:3000 public/index.php" - ], - "docker": [ - "Composer\\Config::disableProcessTimeout", - "@pre-run-app", - "docker-compose run install", - "docker-compose run --rm --service-ports quickstart" - ], - "tests": [ - "Composer\\Config::disableProcessTimeout", - "@pre-run-app", - "docker-compose run --rm tests" - ], - "pre-run-app": [ - "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" + "@pest", + "@phpstan", + "@psalm", + "@rector", + "@phpcs" ] }, "license": "MIT" diff --git a/app/docker-compose.yml b/app/docker-compose.yml deleted file mode 100644 index 48fc7ce..0000000 --- a/app/docker-compose.yml +++ /dev/null @@ -1,33 +0,0 @@ -version: "3.9" - -services: - quickstart: - image: php:7.4-cli-alpine - volumes: - - ./:/app - working_dir: /app - entrypoint: ["php", "-S", "0.0.0.0:3000", "public/index.php"] - ports: - - "3000:3000" - env_file: .env - - tests: - image: composer:2.1 - volumes: - - ./:/app - working_dir: /app - command: > - sh -c "rm -f ./composer.lock && - composer validate && - composer install && - php ./vendor/bin/phpinsights -v --no-interaction && - php ./vendor/bin/phpstan analyse --ansi --memory-limit 512M - - install: - image: composer:2.1 - volumes: - - ./:/app - working_dir: /app - command: > - sh -c "rm -f ./composer.lock && - composer install --no-dev" diff --git a/app/phpinsights.php b/app/phpinsights.php deleted file mode 100644 index d18426b..0000000 --- a/app/phpinsights.php +++ /dev/null @@ -1,67 +0,0 @@ - 'default', - 'ide' => null, - - 'exclude' => [ - 'public/bootstrap.php', - 'templates', - ], - - 'add' => [ - \NunoMaduro\PhpInsights\Domain\Metrics\Code\Comments::class => [ - \SlevomatCodingStandard\Sniffs\Classes\RequireMultiLineMethodSignatureSniff::class, - ], - ], - - 'remove' => [ - \NunoMaduro\PhpInsights\Domain\Insights\CyclomaticComplexityIsHigh::class, - \NunoMaduro\PhpInsights\Domain\Insights\ForbiddenGlobals::class, - \NunoMaduro\PhpInsights\Domain\Insights\ForbiddenNormalClasses::class, - \NunoMaduro\PhpInsights\Domain\Insights\ForbiddenTraits::class, - \NunoMaduro\PhpInsights\Domain\Sniffs\ForbiddenSetterSniff::class, - \ObjectCalisthenics\Sniffs\Files\ClassTraitAndInterfaceLengthSniff::class, - \ObjectCalisthenics\Sniffs\Files\FunctionLengthSniff::class, - \ObjectCalisthenics\Sniffs\Metrics\MethodPerClassLimitSniff::class, - \ObjectCalisthenics\Sniffs\Metrics\MaxNestingLevelSniff::class, - \PHP_CodeSniffer\Standards\Generic\Sniffs\Files\LineLengthSniff::class, - \PHP_CodeSniffer\Standards\Generic\Sniffs\Commenting\TodoSniff::class, - \SlevomatCodingStandard\Sniffs\Classes\SuperfluousExceptionNamingSniff::class, - \SlevomatCodingStandard\Sniffs\Classes\SuperfluousInterfaceNamingSniff::class, - \SlevomatCodingStandard\Sniffs\Classes\UnusedPrivateElementsSniff::class, - \SlevomatCodingStandard\Sniffs\Functions\FunctionLengthSniff::class, - \SlevomatCodingStandard\Sniffs\TypeHints\DisallowMixedTypeHintSniff::class, - \SlevomatCodingStandard\Sniffs\TypeHints\ParameterTypeHintSniff::class, - \SlevomatCodingStandard\Sniffs\TypeHints\PropertyTypeHintSniff::class, - \SlevomatCodingStandard\Sniffs\TypeHints\ReturnTypeHintSniff::class, - ], - - 'config' => [ - \PHP_CodeSniffer\Standards\Generic\Sniffs\PHP\DeprecatedFunctionsSniff::class => [ - 'exclude' => [ - ], - ], - \SlevomatCodingStandard\Sniffs\Functions\UnusedParameterSniff::class => [ - 'exclude' => [ - ], - ], - \SlevomatCodingStandard\Sniffs\Classes\ModernClassNameReferenceSniff::class => [ - 'exclude' => [ - ], - ], - \SlevomatCodingStandard\Sniffs\Classes\RequireMultiLineMethodSignatureSniff::class => [ - 'minLineLength' => '0', - ], - ], - - 'requirements' => [ - 'min-quality' => 90, - 'min-complexity' => 50, - 'min-architecture' => 90, - 'min-style' => 90, - 'disable-security-check' => false, - ], -]; diff --git a/app/phpstan.neon.dist b/app/phpstan.neon.dist index 690472e..d62703b 100644 --- a/app/phpstan.neon.dist +++ b/app/phpstan.neon.dist @@ -1,5 +1,6 @@ includes: - - vendor/phpstan/phpstan-strict-rules/rules.neon + - ./vendor/phpstan/phpstan-strict-rules/rules.neon + - ./vendor/nunomaduro/larastan/extension.neon parameters: level: max @@ -7,25 +8,10 @@ parameters: paths: - src - bootstrapFiles: - - public/bootstrap.php - ignoreErrors: - - "#Function (app|auth|event|redirect) not found.#" - - "#Constructor in (.*) has parameter (.*) with default value.#" - - "#Constructor in (.*) has parameter (.*) with null as default value.#" - - "#Method (.*) has parameter (.*) with a nullable type declaration.#" - - "#Method (.*) has parameter (.*) with null as default value.#" - - "#Method (.*) has a nullable return type declaration.#" - - '#Language construct isset\(\) should not be used.#' - - "#Method (.*) has parameter (.*) with no value type specified in iterable type array.#" - - "#not allowed to extend#" - - "#Cannot call method (.*) on mixed#" - - "#no value type specified in iterable type array.#" - - "#is not final, but since the containing class is abstract, it should be.#" - - '#Class "Auth0\\Login\\Exception\\(.*)" is not allowed to extend "Exception".#' - - '#Class "Auth0\\Laravel\\Exception\\(.*)" is not allowed to extend "Exception".#' - - '#Call to an undefined method Illuminate\\(.*).#' - - '#\$hash is never read, only written.#' + - '#Method (.*) has parameter (.*) with no value type specified in iterable type array.#' + - '#no value type specified in iterable type array.#' reportUnmatchedIgnoredErrors: false + treatPhpDocTypesAsCertain: false + checkGenericClassInNonGenericObjectType: false diff --git a/app/phpunit.xml b/app/phpunit.xml new file mode 100644 index 0000000..e57bdd1 --- /dev/null +++ b/app/phpunit.xml @@ -0,0 +1,17 @@ + + + + + ./tests + + + + + ./src + + + diff --git a/app/phpunit.xml.dist b/app/phpunit.xml.dist new file mode 100644 index 0000000..a52f9a8 --- /dev/null +++ b/app/phpunit.xml.dist @@ -0,0 +1,26 @@ + + + + + + + + + + + tests/Unit + + + + + + + + ./src/ + + + ./src/Events/ + ./src/Exceptions/ + + + diff --git a/app/psalm.xml.dist b/app/psalm.xml.dist new file mode 100644 index 0000000..e8a12b6 --- /dev/null +++ b/app/psalm.xml.dist @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/rector.php b/app/rector.php new file mode 100644 index 0000000..0581861 --- /dev/null +++ b/app/rector.php @@ -0,0 +1,517 @@ +paths([ + __DIR__ . '/config', + __DIR__ . '/src', + ]); + + $rectorConfig->ruleWithConfiguration( + RenameFunctionRector::class, + [ + 'chop' => 'rtrim', + 'doubleval' => 'floatval', + 'fputs' => 'fwrite', + 'gzputs' => 'gzwrites', + 'ini_alter' => 'ini_set', + 'is_double' => 'is_float', + 'is_integer' => 'is_int', + 'is_long' => 'is_int', + 'is_real' => 'is_float', + 'is_writeable' => 'is_writable', + 'join' => 'implode', + 'key_exists' => 'array_key_exists', + 'mbstrcut' => 'mb_strcut', + 'mbstrlen' => 'mb_strlen', + 'mbstrpos' => 'mb_strpos', + 'mbstrrpos' => 'mb_strrpos', + 'mbsubstr' => 'mb_substr', + 'pos' => 'current', + 'sizeof' => 'count', + 'split' => 'explode', + 'strchr' => 'strstr', + ], + ); + + $rectorConfig->ruleWithConfiguration( + StaticCallToFuncCallRector::class, + [ + new StaticCallToFuncCall('Nette\\Utils\\Strings', 'contains', 'str_contains'), + new StaticCallToFuncCall('Nette\\Utils\\Strings', 'endsWith', 'str_ends_with'), + new StaticCallToFuncCall('Nette\\Utils\\Strings', 'startsWith', 'str_starts_with'), + ], + ); + + $rectorConfig->ruleWithConfiguration( + ArgumentAdderRector::class, + [new ArgumentAdder('Nette\\Utils\\Strings', 'replace', 2, 'replacement', '')], + ); + + $rectorConfig->ruleWithConfiguration( + RenameFunctionRector::class, + [ + 'pg_clientencoding' => 'pg_client_encoding', + 'pg_cmdtuples' => 'pg_affected_rows', + 'pg_errormessage' => 'pg_last_error', + 'pg_fieldisnull' => 'pg_field_is_null', + 'pg_fieldname' => 'pg_field_name', + 'pg_fieldnum' => 'pg_field_num', + 'pg_fieldprtlen' => 'pg_field_prtlen', + 'pg_fieldsize' => 'pg_field_size', + 'pg_fieldtype' => 'pg_field_type', + 'pg_freeresult' => 'pg_free_result', + 'pg_getlastoid' => 'pg_last_oid', + 'pg_loclose' => 'pg_lo_close', + 'pg_locreate' => 'pg_lo_create', + 'pg_loexport' => 'pg_lo_export', + 'pg_loimport' => 'pg_lo_import', + 'pg_loopen' => 'pg_lo_open', + 'pg_loread' => 'pg_lo_read', + 'pg_loreadall' => 'pg_lo_read_all', + 'pg_lounlink' => 'pg_lo_unlink', + 'pg_lowrite' => 'pg_lo_write', + 'pg_numfields' => 'pg_num_fields', + 'pg_numrows' => 'pg_num_rows', + 'pg_result' => 'pg_fetch_result', + 'pg_setclientencoding' => 'pg_set_client_encoding', + ], + ); + + $rectorConfig->ruleWithConfiguration( + FunctionArgumentDefaultValueReplacerRector::class, + [ + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'gte', 'ge'), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'lte', 'le'), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, '', '!='), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, '!', '!='), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'g', 'gt'), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'l', 'lt'), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'gte', 'ge'), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'lte', 'le'), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'n', 'ne'), + ], + ); + + $rectorConfig->ruleWithConfiguration( + FuncCallToConstFetchRector::class, + [ + 'php_sapi_name' => 'PHP_SAPI', + 'pi' => 'M_PI', + ], + ); + + $rectorConfig->rules([ + AbsolutizeRequireAndIncludePathRector::class, + ActionInjectionToConstructorInjectionRector::class, + AddArrayDefaultToArrayPropertyRector::class, + AddArrowFunctionReturnTypeRector::class, + AddClosureReturnTypeRector::class, + AddMethodCallBasedStrictParamTypeRector::class, + AddParamBasedOnParentClassMethodRector::class, + AddParamTypeBasedOnPHPUnitDataProviderRector::class, + AddParamTypeSplFixedArrayRector::class, + AddReturnTypeDeclarationBasedOnParentClassMethodRector::class, + AddReturnTypeDeclarationFromYieldsRector::class, + AddVoidReturnTypeWhereNoReturnRector::class, + AndAssignsToSeparateLinesRector::class, + ArrayKeyExistsTernaryThenValueToCoalescingRector::class, + ArrayKeysAndInArrayToArrayKeyExistsRector::class, + ArrayMergeOfNonArraysToSimpleArrayRector::class, + ArrayShapeFromConstantArrayReturnRector::class, + BinarySwitchToIfElseRector::class, + BooleanNotIdenticalToNotIdenticalRector::class, + BoolvalToTypeCastRector::class, + CallableThisArrayToAnonymousFunctionRector::class, + CallUserFuncArrayToVariadicRector::class, + CallUserFuncToMethodCallRector::class, + CallUserFuncToMethodCallRector::class, + CallUserFuncWithArrowFunctionToInlineRector::class, + CatchExceptionNameMatchingTypeRector::class, + ChangeArrayPushToArrayAssignRector::class, + ChangeGlobalVariablesToPropertiesRector::class, + ChangeIfElseValueAssignToEarlyReturnRector::class, + ChangeNestedForeachIfsToEarlyContinueRector::class, + ChangeNestedIfsToEarlyReturnRector::class, + ChangeOrIfContinueToMultiContinueRector::class, + ChangeSwitchToMatchRector::class, + ClassOnObjectRector::class, + ClassOnThisVariableObjectRector::class, + ClassPropertyAssignToConstructorPromotionRector::class, + CombinedAssignRector::class, + CombineIfRector::class, + CommonNotEqualRector::class, + CompactToVariablesRector::class, + CompleteDynamicPropertiesRector::class, + ConsecutiveNullCompareReturnsToNullCoalesceQueueRector::class, + ConsistentImplodeRector::class, + CountArrayToEmptyArrayComparisonRector::class, + CountArrayToEmptyArrayComparisonRector::class, + EmptyOnNullableObjectToInstanceOfRector::class, + EncapsedStringsToSprintfRector::class, + ExplicitBoolCompareRector::class, + FinalizeClassesWithoutChildrenRector::class, + FinalPrivateToPrivateVisibilityRector::class, + FlipTypeControlToUseExclusiveTypeRector::class, + FloatvalToTypeCastRector::class, + ForeachItemsAssignToEmptyArrayToAssignRector::class, + ForeachToInArrayRector::class, + ForRepeatedCountToOwnVariableRector::class, + FuncGetArgsToVariadicParamRector::class, + FuncGetArgsToVariadicParamRector::class, + GetClassToInstanceOfRector::class, + GetDebugTypeRector::class, + InlineArrayReturnAssignRector::class, + InlineConstructorDefaultToPropertyRector::class, + InlineIfToExplicitIfRector::class, + InlineIsAInstanceOfRector::class, + IntvalToTypeCastRector::class, + IsAWithStringWithThirdArgumentRector::class, + IssetOnPropertyObjectToPropertyExistsRector::class, + JoinStringConcatRector::class, + LogicalToBooleanRector::class, + MakeInheritedMethodVisibilitySameAsParentRector::class, + MultipleClassFileToPsr4ClassesRector::class, + NewlineBeforeNewAssignSetRector::class, + NewStaticToNewSelfRector::class, + NormalizeNamespaceByPSR4ComposerAutoloadRector::class, + NullableCompareToNullRector::class, + OptionalParametersAfterRequiredRector::class, + OptionalParametersAfterRequiredRector::class, + ParamTypeByMethodCallTypeRector::class, + ParamTypeByParentCallTypeRector::class, + ParamTypeFromStrictTypedPropertyRector::class, + Php8ResourceReturnToObjectRector::class, + PostIncDecToPreIncDecRector::class, + PreparedValueToEarlyReturnRector::class, + PrivatizeFinalClassMethodRector::class, + PrivatizeFinalClassPropertyRector::class, + PropertyTypeFromStrictSetterGetterRector::class, + RemoveAlwaysElseRector::class, + RemoveAlwaysTrueConditionSetInConstructorRector::class, + RemoveAndTrueRector::class, + RemoveDeadConditionAboveReturnRector::class, + RemoveDeadContinueRector::class, + RemoveDeadIfForeachForRector::class, + RemoveDeadLoopRector::class, + RemoveDeadReturnRector::class, + RemoveDeadStmtRector::class, + RemoveDeadTryCatchRector::class, + RemoveDeadZeroAndOneOperationRector::class, + RemoveDelegatingParentCallRector::class, + RemoveDoubleAssignRector::class, + RemoveDuplicatedArrayKeyRector::class, + RemoveDuplicatedCaseInSwitchRector::class, + RemoveEmptyClassMethodRector::class, + RemoveEmptyTestMethodRector::class, + RemoveExtraParametersRector::class, + RemoveFinalFromConstRector::class, + RemoveJustPropertyFetchForAssignRector::class, + RemoveJustVariableAssignRector::class, + RemoveLastReturnRector::class, + RemoveNonExistingVarAnnotationRector::class, + RemoveNullPropertyInitializationRector::class, + RemoveParentCallWithoutParentRector::class, + RemoveParentCallWithoutParentRector::class, + RemoveSoleValueSprintfRector::class, + RemoveUnreachableStatementRector::class, + RemoveUnusedConstructorParamRector::class, + RemoveUnusedForeachKeyRector::class, + RemoveUnusedNonEmptyArrayBeforeForeachRector::class, + RemoveUnusedPrivateClassConstantRector::class, + RemoveUnusedPrivateMethodParameterRector::class, + RemoveUnusedPrivatePropertyRector::class, + RemoveUnusedPromotedPropertyRector::class, + RemoveUnusedVariableAssignRector::class, + RemoveUnusedVariableInCatchRector::class, + RemoveUselessReturnTagRector::class, + RemoveUselessVarTagRector::class, + RenameForeachValueVariableToMatchExprVariableRector::class, + RenameForeachValueVariableToMatchMethodCallReturnTypeRector::class, + ReplaceMultipleBooleanNotRector::class, + ReturnAnnotationIncorrectNullableRector::class, + ReturnBinaryAndToEarlyReturnRector::class, + ReturnBinaryOrToEarlyReturnRector::class, + ReturnEarlyIfVariableRector::class, + ReturnNeverTypeRector::class, + ReturnTypeFromReturnDirectArrayRector::class, + ReturnTypeFromReturnNewRector::class, + ReturnTypeFromStrictBoolReturnExprRector::class, + ReturnTypeFromStrictConstantReturnRector::class, + ReturnTypeFromStrictNativeCallRector::class, + ReturnTypeFromStrictNewArrayRector::class, + ReturnTypeFromStrictScalarReturnExprRector::class, + ReturnTypeFromStrictScalarReturnExprRector::class, + ReturnTypeFromStrictTernaryRector::class, + ReturnTypeFromStrictTypedCallRector::class, + ReturnTypeFromStrictTypedPropertyRector::class, + SeparateMultiUseImportsRector::class, + SetStateToStaticRector::class, + SetTypeToCastRector::class, + ShortenElseIfRector::class, + SimplifyArraySearchRector::class, + SimplifyBoolIdenticalTrueRector::class, + SimplifyConditionsRector::class, + SimplifyDeMorganBinaryRector::class, + SimplifyEmptyArrayCheckRector::class, + SimplifyEmptyCheckOnEmptyArrayRector::class, + SimplifyForeachToArrayFilterRector::class, + SimplifyForeachToCoalescingRector::class, + SimplifyFuncGetArgsCountRector::class, + SimplifyIfElseToTernaryRector::class, + SimplifyIfElseWithSameContentRector::class, + SimplifyIfNotNullReturnRector::class, + SimplifyIfNullableReturnRector::class, + SimplifyIfReturnBoolRector::class, + SimplifyInArrayValuesRector::class, + SimplifyMirrorAssignRector::class, + SimplifyRegexPatternRector::class, + SimplifyStrposLowerRector::class, + SimplifyTautologyTernaryRector::class, + SimplifyUselessVariableRector::class, + SingleInArrayToCompareRector::class, + SingularSwitchToIfRector::class, + SplitDoubleAssignRector::class, + SplitGroupedClassConstantsRector::class, + SplitGroupedPropertiesRector::class, + StaticArrowFunctionRector::class, + StaticClosureRector::class, + StrContainsRector::class, + StrEndsWithRector::class, + StrictArraySearchRector::class, + StringableForToStringRector::class, + StrlenZeroToIdenticalEmptyStringRector::class, + StrStartsWithRector::class, + StrvalToTypeCastRector::class, + SwitchNegatedTernaryRector::class, + SymplifyQuoteEscapeRector::class, + TernaryConditionVariableAssignmentRector::class, + TernaryEmptyArrayArrayDimFetchToCoalesceRector::class, + TernaryFalseExpressionToIfRector::class, + TernaryToBooleanOrFalseToBooleanAndRector::class, + ThrowWithPreviousExceptionRector::class, + TypedPropertyFromAssignsRector::class, + TypedPropertyFromStrictConstructorRector::class, + TypedPropertyFromStrictGetterMethodReturnTypeRector::class, + TypedPropertyFromStrictSetUpRector::class, + UnnecessaryTernaryExpressionRector::class, + UnSpreadOperatorRector::class, + UnusedForeachValueToArrayKeysRector::class, + UnwrapFutureCompatibleIfPhpVersionRector::class, + UnwrapSprintfOneArgumentRector::class, + UseClassKeywordForClassNameResolutionRector::class, + UseIdenticalOverEqualWithSameTypeRector::class, + UseIncrementAssignRector::class, + VarAnnotationIncorrectNullableRector::class, + VarConstantCommentRector::class, + VarToPublicPropertyRector::class, + VersionCompareFuncCallToConstantRector::class, + WrapEncapsedVariableInCurlyBracesRector::class, + IfIssetToCoalescingRector::class, + CleanupUnneededNullsafeOperatorRector::class, + BoolReturnTypeFromStrictScalarReturnsRector::class, + ]); +}; From befe076293593aefc1cb472cf80d1be2486759e1 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Mon, 26 Jun 2023 00:56:42 -0500 Subject: [PATCH 2/2] Apply updated code linting rules --- .github/dependabot.yml | 10 --- .github/stale.yml | 20 ----- .gitignore | 1 + app/phpunit.xml.dist | 26 ------ app/src/Application.php | 132 +++++++++++++--------------- app/src/ApplicationErrorHandler.php | 12 ++- app/src/ApplicationRouter.php | 116 ++++++++++++------------ app/src/ApplicationTemplates.php | 88 ++++++++++--------- app/tests/Feature/ExampleTest.php | 5 ++ app/tests/Pest.php | 45 ++++++++++ app/tests/TestCase.php | 10 +++ app/tests/Unit/ExampleTest.php | 5 ++ 12 files changed, 242 insertions(+), 228 deletions(-) delete mode 100644 .github/dependabot.yml delete mode 100644 .github/stale.yml delete mode 100644 app/phpunit.xml.dist create mode 100644 app/tests/Feature/ExampleTest.php create mode 100644 app/tests/Pest.php create mode 100644 app/tests/TestCase.php create mode 100644 app/tests/Unit/ExampleTest.php diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index a327b35..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,10 +0,0 @@ -version: 2 -updates: - - - package-ecosystem: "composer" - directory: "/app" - schedule: - interval: "daily" - ignore: - - dependency-name: "*" - update-types: ["version-update:semver-major", "version-update:semver-patch"] diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index b2e13fc..0000000 --- a/.github/stale.yml +++ /dev/null @@ -1,20 +0,0 @@ -# Configuration for probot-stale - https://github.com/probot/stale - -# Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 90 - -# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. -daysUntilClose: 7 - -# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable -exemptLabels: [] - -# Set to true to ignore issues with an assignee (defaults to false) -exemptAssignees: true - -# Label to use when marking as stale -staleLabel: closed:stale - -# Comment to post when marking as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you have not received a response for our team (apologies for the delay) and this is still a blocker, please reply with additional information or just a ping. Thank you for your contribution! 🙇‍♂️ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 11d0ed3..7315fab 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ vendor .env composer.lock .DS_Store +app/.php-cs-fixer.cache diff --git a/app/phpunit.xml.dist b/app/phpunit.xml.dist deleted file mode 100644 index a52f9a8..0000000 --- a/app/phpunit.xml.dist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - tests/Unit - - - - - - - - ./src/ - - - ./src/Events/ - ./src/Exceptions/ - - - diff --git a/app/src/Application.php b/app/src/Application.php index 5b0072c..4525424 100644 --- a/app/src/Application.php +++ b/app/src/Application.php @@ -4,7 +4,6 @@ namespace Auth0\Quickstart; -use Auth0\Quickstart\Contract\QuickstartExample; use Auth0\SDK\Auth0; use Auth0\SDK\Configuration\SdkConfiguration; use Auth0\SDK\Exception\InvalidTokenException; @@ -16,16 +15,6 @@ final class Application */ private SdkConfiguration $configuration; - /** - * An instance of the Auth0 SDK. - */ - private Auth0 $sdk; - - /** - * An instance of our application's template rendering helper class, for sending responses. - */ - private ApplicationTemplates $templates; - /** * An instance of our application's error handling class, for gracefully reporting exceptions. */ @@ -37,16 +26,14 @@ final class Application private ApplicationRouter $router; /** - * An instance of a QuickstartExample class, specified from the AUTH0_USE_EXAMPLE env. + * An instance of the Auth0 SDK. */ - private ?QuickstartExample $example = null; + private Auth0 $sdk; /** - * An array of hooks with callback functions for examples to override default behavior. - * - * @var array + * An instance of our application's template rendering helper class, for sending responses. */ - private array $exampleHooks = []; + private ApplicationTemplates $templates; /** * Setup our Quickstart application. @@ -54,7 +41,7 @@ final class Application * @param array $env Auth0 configuration imported from .env file. */ public function __construct( - array $env + array $env, ) { // Configure the SDK using our .env configuration. $this->setupAuth0($env); @@ -63,41 +50,6 @@ public function __construct( $this->templates = new ApplicationTemplates($this); $this->errorHandler = new ApplicationErrorHandler($this); $this->router = new ApplicationRouter($this); - $this->example = null; - } - - /** - * Configure the Auth0 SDK using the .env configuration. - * - * @param array $env Auth0 configuration imported from .env file. - */ - public function setupAuth0( - array $env - ): void { - // Build our SdkConfiguration. - $this->configuration = new SdkConfiguration([ - 'domain' => $env['AUTH0_DOMAIN'] ?? null, - 'clientId' => $env['AUTH0_CLIENT_ID'] ?? null, - 'clientSecret' => $env['AUTH0_CLIENT_SECRET'] ?? null, - 'audience' => ($env['AUTH0_AUDIENCE'] ?? null) !== null ? [trim($env['AUTH0_AUDIENCE'])] : null, - 'organization' => ($env['AUTH0_ORGANIZATION'] ?? null) !== null ? [trim($env['AUTH0_ORGANIZATION'])] : null, - 'strategy' => SdkConfiguration::STRATEGY_API, - ]); - - // Setup the Auth0 SDK. - $this->sdk = new Auth0($this->configuration); - } - - /** - * "Run" our application, responding to end-user requests. - */ - public function run(): void - { - // Intercept exceptions to gracefully report them. - $this->errorHandler->hook(); - - // Handle incoming requests through the router. - $this->router->run(); } /** @@ -141,40 +93,80 @@ public function &getRouter(): ApplicationRouter } /** - * Called from the ApplicationRouter when end user loads '/'. + * Called from the ApplicationRouter when end user loads '/api'. + * + * @param ApplicationRouter $router */ - public function onIndexRoute( - ApplicationRouter $router + public function onApiRoute( + ApplicationRouter $router, ): void { + $session = $this->getToken(); + // Send response to browser. - $this->templates->render('spa', [ - 'config' => $this->getConfiguration(), + $this->templates->render('logged-' . ($session instanceof \Auth0\SDK\Contract\TokenInterface ? 'in' : 'out'), [ + 'session' => $session, 'router' => $router, ]); } /** - * Called from the ApplicationRouter when end user loads '/api'. + * Called from the ApplicationRouter when end user loads an unknown route. + * + * @param ApplicationRouter $router */ - public function onApiRoute( - ApplicationRouter $router + public function onError404( + ApplicationRouter $router, ): void { - $session = $this->getToken(); + $router->setHttpStatus(404); + } + /** + * Called from the ApplicationRouter when end user loads '/'. + * + * @param ApplicationRouter $router + */ + public function onIndexRoute( + ApplicationRouter $router, + ): void { // Send response to browser. - $this->templates->render('logged-' . ($session === null ? 'out' : 'in'), [ - 'session' => $session, + $this->templates->render('spa', [ + 'config' => $this->getConfiguration(), 'router' => $router, ]); } /** - * Called from the ApplicationRouter when end user loads an unknown route. + * "Run" our application, responding to end-user requests. */ - public function onError404( - ApplicationRouter $router + public function run(): void + { + // Intercept exceptions to gracefully report them. + $this->errorHandler->hook(); + + // Handle incoming requests through the router. + $this->router->run(); + } + + /** + * Configure the Auth0 SDK using the .env configuration. + * + * @param array $env Auth0 configuration imported from .env file. + */ + public function setupAuth0( + array $env, ): void { - $router->setHttpStatus(404); + // Build our SdkConfiguration. + $this->configuration = new SdkConfiguration([ + 'domain' => $env['AUTH0_DOMAIN'] ?? null, + 'clientId' => $env['AUTH0_CLIENT_ID'] ?? null, + 'clientSecret' => $env['AUTH0_CLIENT_SECRET'] ?? null, + 'audience' => ($env['AUTH0_AUDIENCE'] ?? null) !== null ? [trim($env['AUTH0_AUDIENCE'])] : null, + 'organization' => ($env['AUTH0_ORGANIZATION'] ?? null) !== null ? [trim($env['AUTH0_ORGANIZATION'])] : null, + 'strategy' => SdkConfiguration::STRATEGY_API, + ]); + + // Setup the Auth0 SDK. + $this->sdk = new Auth0($this->configuration); } /** @@ -188,7 +180,7 @@ private function getToken(): ?\Auth0\SDK\Contract\TokenInterface $token = $_GET['token'] ?? $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['Authorization'] ?? null; // If no token was present, abort processing. - if ($token === null) { + if (null === $token) { return null; } @@ -196,7 +188,7 @@ private function getToken(): ?\Auth0\SDK\Contract\TokenInterface $token = trim($token); // Remove the 'Bearer ' prefix, if present, in the event we're using an Authorization header that's using it. - if (substr($token, 0, 7) === 'Bearer ') { + if (str_starts_with($token, 'Bearer ')) { $token = substr($token, 7); } diff --git a/app/src/ApplicationErrorHandler.php b/app/src/ApplicationErrorHandler.php index c972e27..6bcd52c 100644 --- a/app/src/ApplicationErrorHandler.php +++ b/app/src/ApplicationErrorHandler.php @@ -7,6 +7,8 @@ use Auth0\SDK\Exception\Auth0Exception; use Throwable; +use function array_key_exists; + final class ApplicationErrorHandler { /** @@ -20,9 +22,9 @@ final class ApplicationErrorHandler * @param Application $app An instance of our Quickstart Application. */ public function __construct( - Application &$app + Application &$app, ) { - $this->app = & $app; + $this->app = &$app; } /** @@ -30,7 +32,9 @@ public function __construct( */ public function hook(): void { - set_exception_handler([$this, 'onException']); + set_exception_handler(function (Throwable $throwable): void { + $this->onException($throwable); + }); } /** @@ -39,7 +43,7 @@ public function hook(): void * @param Throwable $throwable The throwable to report. */ public function onException( - \Throwable $throwable + Throwable $throwable, ): void { $exception = $throwable; diff --git a/app/src/ApplicationRouter.php b/app/src/ApplicationRouter.php index 7408a8d..b0a291a 100644 --- a/app/src/ApplicationRouter.php +++ b/app/src/ApplicationRouter.php @@ -4,6 +4,8 @@ namespace Auth0\Quickstart; +use function array_key_exists; + final class ApplicationRouter { /** @@ -17,93 +19,63 @@ final class ApplicationRouter * @param Application $app An instance of our Quickstart Application. */ public function __construct( - Application &$app + Application &$app, ) { - $this->app = & $app; - } - - /** - * Process the current request and route it to the class handler. - * - * @param string $uri The new uri to redirect the end user to. - */ - public function redirect( - string $uri - ): void { - header('Location: ' . $uri, true, 303); - exit; + $this->app = &$app; } /** - * Process the current request and route it to the class handler. + * Return the request method (GET, POST, etc.). */ - public function run(): void + public function getMethod(): string { - $requestUri = parse_url($this->getUri(), PHP_URL_PATH); - - $routed = false; - - if ($requestUri === '/') { - $this->app->onIndexRoute($this); - $routed = true; - } - - if ($requestUri === '/api') { - $this->setHeaders(); - $this->app->onApiRoute($this); - $routed = true; - } - - if ($routed === false) { - $this->setHeaders(); - $this->app->onError404($this); - } - - exit; + return $_SERVER['REQUEST_METHOD'] ?? 'GET'; } /** * Return (and optionally manipulate) the currently requested uri. * - * @param string|null $path Unless null, manipulates the resulting path to match the value. - * @param string|null $query Unless, manipulates the resulting query to match the value. + * @param null|string $path Unless null, manipulates the resulting path to match the value. + * @param null|string $query Unless, manipulates the resulting query to match the value. */ public function getUri( ?string $path = null, - ?string $query = null + ?string $query = null, ): string { $httpScheme = $_SERVER['HTTPS'] ?? ''; - $httpScheme = $httpScheme === 'on' ? 'https' : 'http'; + $httpScheme = 'on' === $httpScheme ? 'https' : 'http'; + $httpPort = (int) $_SERVER['SERVER_PORT']; $httpHost = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME']; $httpHost = preg_replace('/\:' . $httpPort . '$/', '', $httpHost); + $httpRequest = (string) $_SERVER['REQUEST_URI']; - $httpUri = $httpScheme . '://' . $httpHost . ($httpPort !== 80 ? ':' . $httpPort : '') . $httpRequest; + $httpUri = $httpScheme . '://' . $httpHost . (80 !== $httpPort ? ':' . $httpPort : '') . $httpRequest; // If we aren't making changes, simply return the uri. - if ($path === null && $query === null) { + if (null === $path && null === $query) { return $httpUri; } // Parse a url into it's components so we can manipulate them more easily. $parsedUri = parse_url($httpUri); - if ($parsedUri === false) { + if (false === $parsedUri) { return $httpUri; } - $parsedUri['scheme'] = $parsedUri['scheme'] ?? 'http'; - $parsedUri['host'] = $parsedUri['host'] ?? $httpHost; - $parsedUri['path'] = $parsedUri['path'] ?? ''; + $parsedUri['scheme'] ??= 'http'; + $parsedUri['host'] ??= $httpHost; + $parsedUri['path'] ??= ''; $parsedUri['query'] = '?' . ($parsedUri['query'] ?? ''); // Manipulate the /path portion of the uri. - if ($path !== null) { + if (null !== $path) { $parsedUri['path'] = $path; } // Manipulate the ?query portion of the uri. - if ($query !== null) { + if (null !== $query) { $parsedUri['query'] = $query; } @@ -111,24 +83,56 @@ public function getUri( $parsedUri['port'] = 80; } - if ($parsedUri['query'] === '?') { + if ('?' === $parsedUri['query']) { $parsedUri['query'] = ''; } - if ($parsedUri['query'] !== '') { + if ('' !== $parsedUri['query']) { $parsedUri['query'] = '?' . $parsedUri['query']; } // Reconstruct the manipulated uri and return it. - return $parsedUri['scheme'] . '://' . $parsedUri['host'] . ($parsedUri['port'] !== 80 ? ':' . $parsedUri['port'] : '') . $parsedUri['path'] . $parsedUri['query']; + return $parsedUri['scheme'] . '://' . $parsedUri['host'] . (80 !== $parsedUri['port'] ? ':' . $parsedUri['port'] : '') . $parsedUri['path'] . $parsedUri['query']; } /** - * Return the request method (GET, POST, etc.) + * Process the current request and route it to the class handler. + * + * @param string $uri The new uri to redirect the end user to. */ - public function getMethod(): string + public function redirect( + string $uri, + ): void { + header('Location: ' . $uri, true, 303); + exit; + } + + /** + * Process the current request and route it to the class handler. + */ + public function run(): void { - return $_SERVER['REQUEST_METHOD'] ?? 'GET'; + $requestUri = parse_url($this->getUri(), PHP_URL_PATH); + + $routed = false; + + if ('/' === $requestUri) { + $this->app->onIndexRoute($this); + $routed = true; + } + + if ('/api' === $requestUri) { + $this->setHeaders(); + $this->app->onApiRoute($this); + $routed = true; + } + + if (false === $routed) { + $this->setHeaders(); + $this->app->onError404($this); + } + + exit; } /** @@ -151,7 +155,7 @@ public function setHeaders(): void * @param int $status The HTTP status code to send. */ public function setHttpStatus( - int $status + int $status, ): void { http_response_code($status); } diff --git a/app/src/ApplicationTemplates.php b/app/src/ApplicationTemplates.php index dec2cd8..1c17c13 100644 --- a/app/src/ApplicationTemplates.php +++ b/app/src/ApplicationTemplates.php @@ -4,6 +4,12 @@ namespace Auth0\Quickstart; +use Exception; +use LogicException; +use Throwable; + +use function array_key_exists; + final class ApplicationTemplates { /** @@ -14,9 +20,13 @@ final class ApplicationTemplates /** * State machine of the template being rendered. * - * @var array{section: string|null, sections: array, layout: array{name: string, variables: array}|null} + * @var array{section: null|string, sections: array, layout: null|array{name: string, variables: array}} */ - private array $state; + private array $state = [ + 'sections' => [], + 'section' => null, + 'layout' => null, + ]; /** * ApplicationTemplates constructor. @@ -24,26 +34,20 @@ final class ApplicationTemplates * @param Application $app An instance of our Quickstart Application. */ public function __construct( - Application &$app + Application &$app, ) { - $this->app = & $app; - - $this->state = [ - 'sections' => [], - 'section' => null, - 'layout' => null, - ]; + $this->app = &$app; } /** * Render a template as the browser response, then exit. * - * @param string $template The name of the template to use. + * @param string $template The name of the template to use. * @param array $variables Any variables the template should have access to use. */ public function render( string $template, - array $variables = [] + array $variables = [], ): void { $this->state = [ 'sections' => [], @@ -59,6 +63,22 @@ public function render( exit; } + /** + * Define a container layout in which to render a template. + * + * @param string $name The name of the layout template to use. + * @param array $variables Any additional variables the layout template should have access to use. + */ + private function layout( + string $name, + array $variables = [], + ): void { + $this->state['layout'] = [ + 'name' => $name, + 'variables' => $variables, + ]; + } + /** * Render a template, and return the content as a string. * @@ -67,19 +87,19 @@ public function render( */ private function renderTemplate( string $template, - array $variables = [] + array $variables = [], ): string { // Keep track of the output buffering 'level'. $level = 0; // Resolve the requested template to it's file path: - $templatePath = join(DIRECTORY_SEPARATOR, [APP_ROOT, 'templates', $template . '.php']); + $templatePath = implode(DIRECTORY_SEPARATOR, [APP_ROOT, 'templates', $template . '.php']); // Extract $variables into current scope, for use in template. extract($variables); - if (file_exists($templatePath) === false) { - throw new \Exception("Template file not found: {$template}"); + if (! file_exists($templatePath)) { + throw new Exception(sprintf('Template file not found: %s', $template)); } try { @@ -90,7 +110,7 @@ private function renderTemplate( $content = ob_get_clean(); - if ($this->state['layout'] !== null) { + if (null !== $this->state['layout']) { $layoutTemplate = $this->state['layout']['name']; $layoutVariables = array_merge($variables, $this->state['layout']['variables']); @@ -100,17 +120,17 @@ private function renderTemplate( $content = $this->renderTemplate($layoutTemplate, $layoutVariables); } - if ($content !== false) { + if (false !== $content) { return trim($content); } return ''; - } catch (\Throwable $e) { + } catch (Throwable $throwable) { while (ob_get_level() > $level) { ob_end_clean(); } - throw $e; + throw $throwable; } } @@ -120,7 +140,7 @@ private function renderTemplate( * @param string $sectionName Name of the section to render into the template. */ private function section( - string $sectionName + string $sectionName, ): string { return $this->state['sections'][$sectionName] ?? ''; } @@ -131,10 +151,10 @@ private function section( * @param string $sectionName Name of the section to begin capturing. */ private function start( - string $sectionName + string $sectionName, ): void { - if ($this->state['section'] !== null) { - throw new \LogicException('Nested sections are not supported.'); + if (null !== $this->state['section']) { + throw new LogicException('Nested sections are not supported.'); } $this->state['section'] = $sectionName; @@ -147,8 +167,8 @@ private function start( */ private function stop(): void { - if ($this->state['section'] === null) { - throw new \LogicException('You must start a section before stopping it.'); + if (null === $this->state['section']) { + throw new LogicException('You must start a section before stopping it.'); } if (array_key_exists($this->state['section'], $this->state['sections'])) { @@ -158,20 +178,4 @@ private function stop(): void $this->state['sections'][$this->state['section']] = ob_get_clean(); $this->state['section'] = null; } - - /** - * Define a container layout in which to render a template. - * - * @param string $name The name of the layout template to use. - * @param array $variables Any additional variables the layout template should have access to use. - */ - private function layout( - string $name, - array $variables = [] - ): void { - $this->state['layout'] = [ - 'name' => $name, - 'variables' => $variables, - ]; - } } diff --git a/app/tests/Feature/ExampleTest.php b/app/tests/Feature/ExampleTest.php new file mode 100644 index 0000000..61cd84c --- /dev/null +++ b/app/tests/Feature/ExampleTest.php @@ -0,0 +1,5 @@ +toBeTrue(); +}); diff --git a/app/tests/Pest.php b/app/tests/Pest.php new file mode 100644 index 0000000..5949c61 --- /dev/null +++ b/app/tests/Pest.php @@ -0,0 +1,45 @@ +in('Feature'); + +/* +|-------------------------------------------------------------------------- +| Expectations +|-------------------------------------------------------------------------- +| +| When you're writing tests, you often need to check that values meet certain conditions. The +| "expect()" function gives you access to a set of "expectations" methods that you can use +| to assert different things. Of course, you may extend the Expectation API at any time. +| +*/ + +expect()->extend('toBeOne', function () { + return $this->toBe(1); +}); + +/* +|-------------------------------------------------------------------------- +| Functions +|-------------------------------------------------------------------------- +| +| While Pest is very powerful out-of-the-box, you may have some testing code specific to your +| project that you don't want to repeat in every file. Here you can also expose helpers as +| global functions to help you to reduce the number of lines of code in your test files. +| +*/ + +function something() +{ + // .. +} diff --git a/app/tests/TestCase.php b/app/tests/TestCase.php new file mode 100644 index 0000000..cfb05b6 --- /dev/null +++ b/app/tests/TestCase.php @@ -0,0 +1,10 @@ +toBeTrue(); +});